Skip to content

feat(financial): financial quantification on the redesign layout (#904) - #932

Merged
alex-dembele merged 4 commits into
masterfrom
feat/904-financial
Oct 12, 2026
Merged

alex-dembele merged 4 commits into
masterfrom
feat/904-financial

Conversation

@alex-dembele

Copy link
Copy Markdown
Member

Fifth screen of the redesign epic (#899). Stacked on #928 (executive view): the base is feat/903-executive, so the diff shows only this issue.

What changes for the user

The financial page now reads like the mockup:

  • Four headline figures: inherent ALE, targeted residual ALE, plan cost, 3-year ROSI.
  • The loss-exceedance curve, inherent and after plan, with the risk appetite as a dashed threshold. The legend reads both probabilities at the appetite ("Inhérent : 46 % de dépasser 128 M · Après plan : 0 %"). Both axes are labelled.
  • "Exposition par risque" with inherent, residual and the reduction bar.
  • "Rentabilité des mitigations" sorted by payback. Each row opens that risk's mitigations.

Backend

  • pkg/crq: PortfolioQuantiles returns the total annual loss at every percentile from the same Monte Carlo as the P10–P90 band. ScaleLoss gives the residual run, which uses the same draws and the same rule as ALE-after. SimulatePortfolio shares the sampling loop and its output is unchanged (existing tests pass).
  • New summary fields:
    • loss_exceedance: the two percentile curves.
    • treatments: risk plans with a cost and a reduction, shortest payback first.
    • portfolio_rosi_3y: (3 × annual reduction − plan cost) / plan cost.
    • treated_risks and risk_appetite_xaf.
  • PUT /analytics/financial/appetite (admin, XAF) stores the appetite in the organization settings, beside the display currency. No migration. A use case validates it and returns typed errors.
  • OpenAPI updated (with ale_after from feat(frontend): executive view on the redesign layout #903, which was missing) and TS types regenerated.

Frontend

  • features/financial/ split into FinancialDashboard, LossExceedanceCard, MethodologyModal (design-system modal), SimulatorCard, and pure lec.ts / methodology.ts. All copy moved to fr/en keys; the old page used inline FR/EN pairs.
  • Saving the appetite:
    • It saves 600 ms after the slider stops moving.
    • It updates the page at once and rolls back if refused.
    • It never retries.
    • Members without admin rights see the slider read-only, and an unset appetite reads "non fixée".

Verified

  • go test ./...: all packages pass. New tests:
    • TestPortfolioQuantiles and TestScaleLoss.
    • TestFinancialSummaryPage_* (curve, payback order, 3-year ROSI, empty register, failing appetite read).
    • TestSetRiskAppetite_* (Success / NotFound / Unauthorized / Validation).
    • TestSetAppetiteHandler_*.
    • TestOrgRiskAppetite_RoundTrip: the currency is kept and other organizations are untouched.
  • npx tsc -b --noEmit clean, eslint and prettier clean on features/financial, npx vitest run 114 files / 1030 tests.
  • Live on Postgres with the parity dataset, at 1440×900 in dark and light, side by side with the design:
    • Moving the appetite to 80 M sent one PUT {appetite_xaf: 80000000}, showed "Appétence enregistrée" and survived a reload.
    • The simulator returned its sentence ("… amorti en ~2 mois").
    • No console errors.

Deliberate differences from the mockup

  • The curve is an S, not a decay. FAIR-lite 1.0 samples loss size, not how often losses happen, so a quiet year has probability 0. Raised as D-068 / decision(crq): sample event frequency in the Monte Carlo #931, with a recommendation; not changed here because every band would move.
  • The payback rows are per risk plan, not per mitigation. Mitigations carry no money (only a legacy 1–3 scale), and the cost and effectiveness live on the risk. Follow-up feat(crq): cost and expected reduction on each mitigation #929.
  • The ALE sub-line says "FCFA / an, 14 risques", not "risques non clos". The totals currently include closed risks. Follow-up bug(crq): financial totals count closed risks #930.
  • The inherent amount is red for critical risks. The mockup turns it red above a quarter of the appetite, which is an arbitrary threshold.
  • Kept from the old page: the methodology (opened from the subtitle), the display currency (header, admins) and the investment simulator (below the design's blocks). The P10/P50/P90 band, the 5-year projection and the by-criticality card are dropped; the curve supersedes them.

Not done here

Closes #904

…#904)

The redesigned financial page draws the probability that a year's loss
exceeds an amount, before and after the treatment plans, and lists the
plans by payback. The summary now carries:

- loss_exceedance: total annual loss at every percentile 0..100 from the
  same shared Monte Carlo as the P10/P50/P90 band, inherent and residual.
  The residual run scales each risk's loss by (1 - effectiveness), the
  rule ALE-after already uses, with the same draws. The model is
  unchanged; SimulatePortfolio shares its sampling loop and returns the
  same band.
- treatments: each risk plan with a cost and a measurable reduction,
  its annual loss avoided and payback in months, shortest first.
- portfolio_rosi_3y and treated_risks for the KPI tiles.
- risk_appetite_xaf, read through an optional port (stored next commit).

An empty register returns no curve and no ROSI instead of zeros.

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
The appetite slider on the loss-exceedance curve was a local value in
the mockup. It is the board's accepted annual loss, so it is now a
tenant setting: PUT /analytics/financial/appetite (admin, XAF) stores it
in the organization settings beside the display currency, and the
financial summary returns it. No migration: settings is jsonb.

The use case rejects zero, negative, non-finite and absurd amounts, and
a session without an organization; an unknown organization is not
found. Tests cover the use case, the handler and a round trip on the
organization table that keeps the currency and leaves other
organizations alone. OpenAPI updated with the new fields of the summary
(ale_after from #903 was missing too) and the TS types regenerated.

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
…ign (#904)

The register in money, laid out as the mockup: inherent ALE, targeted
residual ALE, plan cost and 3-year ROSI; the loss-exceedance curve,
inherent and after plan, with both exceedance probabilities read at the
risk appetite; exposure by risk with its reduction bar; the treatment
plans ranked by payback, each opening the risk's mitigations.

The curve is drawn from the summary's percentiles and names both axes,
which the mockup left out. The appetite slider saves the tenant's
appetite after the pointer rests (optimistic, rolled back on refusal,
no retry); members without admin rights see it read-only, and an unset
appetite says so. The risk column of the exposure table gets the room
the mockup's wrapped titles lacked.

Kept from the previous page outside the mockup's layout: the methodology
opens from the subtitle, the display currency sits in the header for
admins, and the investment simulator follows the design's blocks. All
copy moves to fr/en locale keys; the dialog uses the design-system modal.

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
)

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
Base automatically changed from feat/903-executive to master October 9, 2026 09:03
@alex-dembele alex-dembele reopened this Oct 12, 2026
@alex-dembele
alex-dembele merged commit e5eff4c into master Oct 12, 2026
17 of 31 checks passed
@alex-dembele
alex-dembele deleted the feat/904-financial branch October 12, 2026 07:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(frontend): financial quantification on the redesign layout

1 participant