Skip to content

Expliciete reason-code toevoegen aan de analysis-productrespons wanneer geen risicoklasse kan worden geleverd #1002

Description

@DonZandbergen

Expliciete reason-code toevoegen aan de analysis-productrespons wanneer geen risicoklasse kan worden geleverd

Let op — repo: dit betreft de v4-webservice Laixer/FunderMapsWebservice (TypeScript/Bun + Hono), niet de .NET-repo Laixer/FunderMaps. De productie-endpoints (/v4/product/...) draaien hier en geven platte JSON-fouten terug ({"message": "..."}), geen RFC 7807 ProblemDetails.

Context

Voor de NWWI-koppeling (funderingsrisicorapport in het taxatieproces) moet de aanroepende partij per opvraag geautomatiseerd kunnen bepalen waarom er geen risicoklasse is, en welke vervolgstap daarbij hoort:

Oorzaak Gewenste vervolgstap aan afnemerskant
a. Objecttype (geen BAG-pand: ligplaats/woonboot/standplaats) geen actie — QuickScan niet zinvol
b. Onvoldoende broninformatie (pand bekend, geen klasse) QuickScan aanvragen
c. Onjuiste/ontbrekende adressering nieuwe aanvraag met correct adres
d. Technische storing opnieuw aanbieden (retry/queue)

Op dit moment kan de afnemer deze vier situaties niet uit elkaar houden op basis van de respons: elke "geen resultaat"-uitkomst geeft dezelfde 404 met body {"message": "Not found"}.

Huidig gedrag

Endpoint: GET /v4/product/analysis/:id in src/routes/product.ts.

De handler kent twee aparte 404-takken die vandaag al bestaan, maar dezelfde body teruggeven:

const externalId = await resolveBuildingExternalId(id);
if (!externalId) return c.json({ message: "Not found" }, 404);   // ① geen pand/adres herleidbaar → categorie a/c

const rows = await sql`... FROM data.model_risk_static WHERE building_id = ${externalId} LIMIT 1`;
if (rows.length === 0) return c.json({ message: "Not found" }, 404);   // ② pand herleid, geen risicorij → categorie b

Daarnaast bestaat een derde "geen klasse"-uitkomst die géén 404 is: als er wél een rij is maar niet geclassificeerd, komt er een 200 OK terug met foundationType/de *Risk-velden op null (de rij uit model_risk_static wordt verbatim teruggegeven; die velden zijn nullable).

Overige statussen (contract, voor de volledigheid):

  • 500 {"message": "Internal server error"} — globale app.onError in src/index.ts (echte storing / DB-fout).
  • 429 {"message": "Too Many Requests"}rateLimit()-middleware bij overschrijding van de billing-/quota-limiet (mét Retry-After + X-RateLimit-*). Dit is een quota-limiet, geen storing.

Belangrijk (afwijking t.o.v. eerdere aanname): v4 registreert de misser-gevallen op dit moment niet. De usage-tracker (trackerMiddleware + c.set("tracker", ...)) wordt alleen op de succes-tak gezet; beide 404-takken keren vroeg terug zonder tracking. Er is dus (anders dan in de oude .NET-service) geen product_tracker_mismatch-registratie — de frequentie per oorzaak is nu niet meetbaar. Dat moet daarom onderdeel van deze wijziging zijn.

Gewenst gedrag

Voeg aan de respons een machine-leesbare reason-code toe, zodat de afnemer zonder de body te interpreteren de juiste vervolgstap kan kiezen — en registreer de misser + reden server-side zodat de frequentie rapporteerbaar wordt.

Voorgestelde enum:

reason Situatie Techn. herkomst (v4) HTTP
NO_BUILDING Identifier geldig maar geen BAG-pand (ligplaats/woonboot/standplaats) of geen BAG-match tak ① (resolveBuildingExternalId → null) 404
ADDRESS_INVALID Identifier-formaat ongeldig / niet-parsebaar geocoder (zie open punt) 404
INSUFFICIENT_SOURCE Pand bekend, maar geen (volledige) risicoklasse tak ② (rows.length === 0) óf 200 met risicovelden null 404 óf 200
UPSTREAM_ERROR Interne/afhankelijkheidsstoring onError (500) 500

Waar de code moet landen

  1. Bij de 404/500-fouten: extra veld op de bestaande platte JSON-body, bijv.:

    { "message": "Not found", "reason": "NO_BUILDING" }

    Concreet in src/routes/product.ts: geef in tak ① NO_BUILDING mee en in tak ② INSUFFICIENT_SOURCE; in src/index.ts onErrorUPSTREAM_ERROR. De twee takken maken het onderscheid a/c ↔ b nu al — alleen de body deelt de reden nog niet.

  2. Bij een 200 zonder klasse: een veld op de analysis-response (bv. reason: "INSUFFICIENT_SOURCE" of classified: false), zodat "pand bekend maar geen klasse" óók expliciet is en niet uit null-inspectie hoeft te volgen.

  3. Misser-registratie (nieuw): leg de 404-missers server-side vast — óf door de tracker ook op de miss-tak te zetten met een reason-kolom, óf via een aparte application.product_tracker_mismatch-tabel. Zonder dit blijft de frequentie per oorzaak onmeetbaar.

Acceptatiecriteria

  • Elke respons zonder (volledige) risicoklasse bevat een reason uit de vaste enum — zowel bij 404/500 (in de {"message", "reason"}-body) als bij 200-zonder-klasse (veld op de response).
  • NO_BUILDING, INSUFFICIENT_SOURCE, ADDRESS_INVALID en UPSTREAM_ERROR zijn onderling te onderscheiden op basis van uitsluitend de reason-waarde.
  • 404-missers worden server-side geregistreerd (met reason), zodat frequentie per oorzaak rapporteerbaar wordt.
  • De reason-waarden zijn stabiel en gedocumenteerd in MIGRATION.md (net als de bestaande enum-referenties, met de sync-test-discipline).
  • Additief: bestaande afnemers die alleen op HTTP-status of op message kijken blijven werken (reason is een extra veld; message blijft ongewijzigd).
  • reason wordt consistent toegepast op alle product-endpoints die "not found" kunnen geven (analysis, risk, light, en de nieuwe quickscan/foundation-research), niet alleen analysis.

Open punten / afstemming

  • NO_BUILDING vs ADDRESS_INVALID: resolveBuildingExternalId(id) geeft nu alleen string | null terug — de reden van een null (ongeldig formaat vs. geldig maar geen pand vs. niet in BAG) is niet zichtbaar. Om deze twee codes te scheiden moet src/geocoder.ts een reden meegeven (bv. een discriminated result i.p.v. null). INSUFFICIENT_SOURCE en UPSTREAM_ERROR zijn wél direct beschikbaar zonder geocoder-wijziging.
  • 200 vs 404 bij INSUFFICIENT_SOURCE: willen we het onderscheid "geen rij" (404) en "rij met null-risico" (200) behouden, of beide onder één reason scharen?
  • Eventueel een aparte code voor de grootste subgroep binnen INSUFFICIENT_SOURCE (niet-onderheid + ontbrekende INSAR/grondwater — o.a. de Waddeneilanden), of gewoon onder INSUFFICIENT_SOURCE laten.

Codeverwijzingen (repo Laixer/FunderMapsWebservice)

  • Endpoint + de twee 404-takken: src/routes/product.ts (/analysis/:id; idem /risk/:id, /light/:id).
  • ID-resolutie: src/geocoder.ts (resolveBuildingExternalId).
  • Globale foutafhandeling (500) en 404-fallback: src/index.ts (app.onError, app.notFound).
  • Usage-tracking + 24u-dedup (relevant voor de miss-registratie): src/tracker.ts (application.product_tracker).
  • Overbelasting/quota (429, niet 503): src/rate-limit.ts.
  • Enum-documentatie + sync-discipline: src/enums.ts / src/enums.test.ts / MIGRATION.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions