Skip to content

feat(product): add /v4/product/risk and /v4/product/light (Laixer/FunderMaps#985)#10

Merged
yorickdewid merged 1 commit into
mainfrom
feat/products-risk-and-light
Jun 1, 2026
Merged

feat(product): add /v4/product/risk and /v4/product/light (Laixer/FunderMaps#985)#10
yorickdewid merged 1 commit into
mainfrom
feat/products-risk-and-light

Conversation

@yorickdewid

Copy link
Copy Markdown
Contributor

Summary

Adds two new billable products on top of `data.model_risk_static` per Laixer/FunderMaps#985.

route shape tracker name
`GET /v4/product/risk/:id` subset of `analysis` for valuation chains and dashboards `risk3`
`GET /v4/product/light/:id` minimal output with a derived `overallRisk` for fast chain integrations `light3`

overallRisk derivation (light)

The three component risks — drystand, bio-infection, dewatering-depth — collapse into one `overallRisk` + `overallRiskReliability`. Priority is:

  1. Reliability tier first: `established` > `cluster` > `supercluster` > `indicative`.
  2. Risk letter as tiebreaker within the same tier: `E` > `D` > `C` > `B` > `A`.

So `C established` beats `D indicative` — the example called out in the issue. Components missing either field are skipped (null risk without a reliability label can't compete).

Recovery override. Any non-NULL `recovery_type` forces `overallRisk = "a"` with `overallRiskReliability = "established"`, regardless of model output. A recovered building is the lowest risk class by definition, and the recovery flag itself is hard evidence — confirmed in scoping that `unknown` (the 'known-recovered, type unclear' sentinel) also triggers the override.

The computation lives as a pure function in `src/risk.ts` with 12 table-driven tests in `risk.test.ts` covering each rule the issue pins down (issue example, in-tier letter ranking, cross-tier reliability, null-component skipping, all-null, the override, override on `unknown`, override over otherwise-worst data).

Naming note

Issue spells it `product_risc` (Dutch). Code stays English (`risk` / `light`) to match the rest of the surface — confirmed in scoping.

Test plan

  • `bun test` — 44 pass (12 new)
  • `bun run typecheck` clean
  • Live E2E against the local stack: `/v4/product/risk` and `/v4/product/light` return the documented shapes; `light` correctly derives `a / indicative` from a real model row whose components are (`a` indicative, null indicative, `a` indicative)
  • 404 for unknown BAG id, 401 without auth

Dev DB has no rows with `recovery_type` set, so the override branch is verified by unit tests rather than HTTP — the route passes `recovery_type` straight into `computeOverallRisk` with no extra logic, so the unit-test coverage is the relevant signal there.

🤖 Generated with Claude Code

Two new billable products on top of data.model_risk_static.

- /v4/product/risk — subset of analysis for valuation chains and
  dashboards. Same data source, fewer fields.

- /v4/product/light — minimal output for fast chain integrations. The
  three component risks (drystand, bio-infection, dewatering-depth)
  collapse into one overallRisk plus its overallRiskReliability.
  Priority is reliability tier first (established > cluster >
  supercluster > indicative), then risk letter (E > D > C > B > A)
  within the same tier — so 'C established' beats 'D indicative'.

Override: any non-NULL recovery_type forces overallRisk = A with
established reliability — a recovered building is the lowest risk
class by definition and the recovery flag itself is hard evidence.

Tracker product names: risk3, light3 (matching the analysis3 /
statistics3 versioning).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yorickdewid yorickdewid merged commit 0f46092 into main Jun 1, 2026
1 check passed
@yorickdewid yorickdewid deleted the feat/products-risk-and-light branch June 1, 2026 11:33
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.

1 participant