# Financial / insurance comparison chart pitfalls

Use when turning insurance, pension, or investment analyses into one-page charts/PDFs.

## Core rule: do not mix value semantics silently

Label each curve as one of:

- **Liquid/account value today** — money actually available at that date.
- **Projected portfolio value** — invested assets grown with assumptions.
- **Guaranteed future benefit / end-value entitlement** — a contractual benefit payable at a future date, not liquid today.
- **Benchmark target** — a reference line, not a tradable or withdrawable value.

If a future insurance benefit is shown before its payout date, explicitly label it as an **end-value entitlement / Anwartschaft**, not a current cash value.

## Common pitfalls

1. **Artificial terminal jumps**
   - Avoid drawing a curve that stays low and then jumps at maturity unless the chart is explicitly a cashflow timeline.
   - For comparison charts, either:
     - show the maturity benefit as a horizontal/dashed target benchmark, or
     - show a clearly labelled “future-benefit entitlement + separate portfolio” curve.

2. **Ignoring realized surrender loss**
   - When modelling surrender/rückkauf, show the reset from cumulative premiums paid to surrender value.
   - Example: cumulative paid CHF 38’400 → surrender value CHF 31’091 = realized loss CHF 7’309.
   - Annotate this as a one-time realized loss/rücksetzer before the investment projection starts.

3. **Benchmark vs projection confusion**
   - Guaranteed insurance payout in 2050 should often be shown as a dashed target line with text like “2050 guarantee; not liquid intermediate value.”
   - Do not imply it compounds annually unless the contract actually specifies annual accessible values.

4. **Label clipping and overlap**
   - After generating PDF/SVG, render a raster preview and inspect it visually.
   - Check title clipping, endpoint labels, legend overlap, break-even marker overlap, and table legibility.
   - Move endpoint labels inside the plot area or use right-aligned labels if they extend beyond the page.

## Recommended one-page layout

- A4 landscape for 3–4 variants.
- Large chart left, explanatory legend panel right, compact comparison table at bottom.
- Use different visual semantics:
  - solid lines = portfolio projections,
  - dashed horizontal line = guaranteed maturity benchmark,
  - dotted vertical marker = break-even year,
  - red/grey annotation = realized loss at surrender.

## Implementation pattern for polished one-page outputs

When hand-drawn ReportLab charts become crowded or label placement is fragile, switch to a plotting library and keep ReportLab only for document assembly if needed.

Recommended workflow:

1. Use a task-local venv if system Python is PEP-668/external-managed:
   ```bash
   uv venv /tmp/<task>/.venv --python python3.12
   uv pip install --python /tmp/<task>/.venv/bin/python matplotlib plotly kaleido pandas
   ```
2. Build the printable artifact with Matplotlib for reliable A4/PDF layout:
   - `fig = plt.figure(figsize=(11.69, 8.27), dpi=220)` for A4 landscape.
   - Save without `bbox_inches='tight'` when exact A4 size matters; tight bounding boxes can shrink the page.
   - Put endpoint labels in white boxed annotations inside the plot area or a dedicated label strip.
   - Put realized surrender-loss details in a separate loss box below the chart, not as tiny labels floating outside the axes.
3. Optionally build a Plotly HTML sibling for interactive inspection; do not rely on Plotly static export unless Kaleido/Chrome is verified.
4. Render the generated PDF/PNG and use vision to inspect the actual pixels, not just source code.

## Plotly static export pitfalls from insurance-chart work

Use Plotly for polished static PNG/PDF when Matplotlib/ReportLab label placement becomes brittle, but do **not** assume Plotly's built-in or annotation legends will stay out of the way.

Lessons:

- For iPhone delivery, prefer **static PNG/PDF** over interactive HTML. Telegram/iOS may not open locally delivered HTML reliably, while PNG previews work immediately.
- Verify Kaleido/Chrome export before relying on Plotly static output:
  ```bash
  /path/to/venv/bin/python - <<'PY'
  import plotly, kaleido
  print(plotly.__version__)
  PY
  ```
- If using Plotly `legend`, reserve top margin and inspect the raster: horizontal legends can collide with title/subtitle even when technically outside the plot.
- If a legend collides twice, **remove the legend** and use direct endpoint labels plus an explanatory table. Do not keep nudging a floating legend over the figure.
- Avoid paper-coordinate annotation panels in the chart area unless the chart domain is explicitly shrunk; otherwise panels can cover curves or endpoint labels in the final raster.
- Put surrender-loss values (e.g. paid premiums → surrender value → realized loss) in their **own row/table** below the chart. Do not place tiny floating `38’400` / `31’091` labels near or outside the plot; they are easy to clip or misread.
- For exact A4 Plotly export, use an A4-ratio canvas, e.g. `width=1123, height=794`, then verify with `pdfinfo` (`841.92 x 595.92 pts (A4)`).

## Interactive HTML sibling for scenario testing

When the user wants to “play with the rates,” produce a **single-file interactive HTML sibling** to the printable report rather than trying to make the PDF interactive.

Recommended controls for life-insurance / Säule 3a comparisons:

- current surrender/transfer value including vested surplus;
- annual future contribution;
- securities return assumption;
- mortgage rate and whether interest savings are reinvested;
- guaranteed maturity benefit;
- paid-up guaranteed maturity benefit;
- start/end year;
- optional technical-interest reference rate.

Recommended curves:

- **A transfer + contributions**: projected securities portfolio;
- **B paid-up entitlement + contribution depot**: explicitly label the guarantee component as an end-value entitlement, not liquid cash;
- **C transfer value only**: isolates growth of current capital;
- **D existing-policy guarantee**: dashed maturity benchmark, not an accessible account curve;
- **E mortgage amortisation**: debt reduction + contribution depot + optional reinvested interest savings; label as economic balance-sheet value.

For a technical-interest sales claim, an optional dotted reference can compound every gross contribution at the claimed rate. Label it **theoretical gross-contribution reference only**, not contractual customer return. This makes the gap between tariff calculation rate and guaranteed maturity value understandable without presenting the reference as a real product outcome.

### Self-contained delivery

Google Drive often previews HTML as a file rather than executing it. Tell the user to download and open it in a browser. Make the artifact self-contained so it works offline:

```python
from plotly.offline import get_plotlyjs
html = html.replace(
    '<script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>',
    '<script>' + get_plotlyjs() + '</script>'
)
```

Embedding Plotly increases the file to several MB but avoids a blank chart when CDN access is unavailable.

### Verification

1. Serve locally with a temporary HTTP server and open in a real browser.
2. Confirm `typeof Plotly !== 'undefined'`, the chart DOM is populated, KPI/ranking rows render, and there are no current JS errors.
3. Change at least one numeric rate and verify that the chart, end-value cards, best-variant badge, and ranking all change.
4. Take a browser screenshot and inspect controls, legend, plot, KPI cards, ranking, mobile/desktop overflow, and explanatory text.
5. If controls are sticky with a viewport-height cap, ensure the panel itself scrolls; clipped controls without internal scrolling are not acceptable.
6. Optionally offer CSV export of annual model values.

## Verification checklist

- [ ] Curves start at economically correct values.
- [ ] Terminal benefits are not presented as liquid interim values.
- [ ] Surrender loss is visible if relevant and its labels stay inside a dedicated box/table/plot area.
- [ ] Assumptions state return, contribution timing, fees/taxes omitted/included.
- [ ] PDF has exactly the requested page size/count; if A4 is required, verify with `pdfinfo`.
- [ ] Raster preview has no clipped title/labels, no legend/KPI collisions, and no critical overlaps.
- [ ] If the user flags a specific label/value as outside the graphic, rebuild layout rather than nudging piecemeal; reserve a dedicated area for that semantic element.
- [ ] If the user reports “legend/text covers the graphic,” remove/relocate that element structurally; do not deliver another minor offset tweak without vision-checking the raster.
