# Splitify Engine v3 — OpenAPI-spec
# Auto-gegenereerd door scripts/build-openapi.mjs
# Bron-van-waarheid: api/v3/_types.ts (TypeScript-interfaces)
# Niet handmatig editen; pas scripts/build-openapi.mjs aan en run opnieuw.
openapi: 3.1.0
info:
  title: Splitify Engine v3
  version: 3.0.0
  summary: Stabiel publiek contract voor de Splitify pand-context-engine
  description: |-
    Eén-shot pand-context voor heel Nederland. Per BAG-pand-id levert de engine een
    gestructureerde JSON-blob met (1) twaalf scenario-uitkomsten (kansrijk / voorwaardelijk
    / niet-kansrijk), (2) bron-tracking per veld voor audit-baar advies, (3) procedureel
    advies per scenario (route, doorlooptijd, kosten, kansrijkheid).
    
    Versioning: `/api/v3/` is een stabiel publiek contract. Breaking changes verhuizen
    naar `/api/v4/`. Tot dan: alleen additieve wijzigingen (nieuwe optionele velden,
    nieuwe scenarios).
    
    Auth: voor `?detail=full` vereisen we `X-API-Key` (tier-systeem).
    Voor de publieke MVP-context kun je zonder key aanvragen, met rate-limit per IP.
  contact:
    name: Splitify
    email: "info@splitify.nl"
    url: "https://splitify.nl"
  license:
    name: Proprietary — Splitify
    url: "https://splitify.nl"
servers:
  - url: "https://splitify.nl"
    description: Productie
  - url: "http://localhost:8000"
    description: Lokale dev (dev-server.py)
tags:
  - name: pand
    description: Pand-context per BAG-id
  - name: scenarios
    description: Scenario-catalogus
  - name: feedback
    description: Gebruikers-feedback (placeholder)
  - name: qa
    description: "Kwaliteits-indicatoren (regressie-runs, gouden-test)"
  - name: meta
    description: Spec en docs
  - name: vraag
    description: "Vraag-ingang: een team van agents beantwoordt een vraag over een pand uit eigen data, met een live-volgbare trace (fase A). Sinds Story 1: meerdere beurten binnen één gesprek, met hergebruik van eerder opgehaalde data en een cumulatief gespreksbudget naast het bestaande beurtbudget."
paths:
  "/api/v3/pand/{bag_id}":
    get:
      tags:
        - pand
      summary: Volledige pand-context
      description: "Levert de volledige `PandContextV3` voor een BAG-pand-id. In MVP-modus zijn niet alle velden ingevuld (zie `meta.ontbrekende_velden`)."
      operationId: getPandContextV3
      parameters:
        - name: bag_id
          in: path
          required: true
          description: "BAG-pand-id (16 cijfers), of een nummeraanduiding-id."
          schema:
            type: string
            pattern: "^[0-9A-Za-z_-]+$"
            minLength: 8
            maxLength: 32
          example: "0518100000123456"
        - name: detail
          in: query
          required: false
          description: "Detail-niveau van de response. `full` vereist `X-API-Key`."
          schema:
            type: string
            enum:
              - particulier
              - full
            default: particulier
        - name: refresh
          in: query
          required: false
          description: Forceer cache-bypass (alleen voor admin-tier keys).
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Pand-context succesvol opgehaald.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PandContextV3"
        "400":
          description: Verzoek mist verplichte parameter (bag_id).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "401":
          description: "Ongeldige X-API-Key (alleen bij `?detail=full`)."
        "404":
          description: Pand niet gevonden.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "429":
          description: Rate-limit overschreden.
        "500":
          description: Interne fout in engine.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  /api/v3/scenarios:
    get:
      tags:
        - scenarios
      summary: Scenario-catalogus
      description: Lijst van alle 12 scenarios met decision-tree-versie.
      operationId: getScenarios
      responses:
        "200":
          description: Lijst van scenarios.
          content:
            application/json:
              schema:
                type: object
                properties:
                  versie:
                    type: string
                    example: v3.0
                  scenarios:
                    type: array
                    items:
                      type: object
                      required:
                        - id
                        - naam
                        - versie
                      properties:
                        id:
                          type: string
                          example: splitsing_1_naar_2
                        naam:
                          type: string
                          example: Splitsen 1 woning naar 2
                        versie:
                          type: string
                          example: v3.0
                        beschikbaar:
                          type: boolean
  /api/v3/feedback:
    post:
      tags:
        - feedback
      summary: Gebruiker meldt fout (placeholder)
      description: "Placeholder-endpoint. Zal in latere fase schrijven naar `feedback_meldingen` (zie `100-procent-architectuur.md` sectie 6.5)."
      operationId: postFeedback
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - bag_id
              properties:
                bag_id:
                  type: string
                scenario:
                  type: string
                veld_pad:
                  type: string
                gemelde_waarde:
                  type: string
                correcte_waarde:
                  type: string
                toelichting:
                  type: string
                bron_url:
                  type: string
                  format: uri
                gebruiker_email:
                  type: string
                  format: email
      responses:
        "202":
          description: Feedback ontvangen.
        "400":
          description: Ongeldige feedback-payload.
        "501":
          description: Endpoint nog niet geïmplementeerd (placeholder).
  /api/v3/qa/regressie:
    get:
      tags:
        - qa
      summary: Laatste regressie-runs (gouden-testset CI)
      description: "Publieke kwaliteits-indicator: laatste N regressie-runs met match-percentage, verwarringsmatrix-summary en geslaagd-vlag. Iedere PR die de scenario-engine raakt draait een run; CI faalt bij <95% match. Zie `100-procent-architectuur.md` §6.2."
      operationId: getQaRegressie
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
        - name: scenario
          in: query
          schema:
            type: string
          description: Filter op scenario-id.
        - name: alleen_geslaagd
          in: query
          schema:
            type: string
            enum:
              - "1"
              - "true"
      responses:
        "200":
          description: Lijst regressie-runs.
          content:
            application/json:
              schema:
                type: object
                properties:
                  bron:
                    type: string
                    example: regressie_runs
                  limit:
                    type: integer
                    example: 10
                  laatste:
                    type: object
                    nullable: true
                  runs:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        scenario:
                          type: string
                          nullable: true
                        gemeente:
                          type: string
                          nullable: true
                        scenario_versie:
                          type: string
                          nullable: true
                        totaal:
                          type: integer
                        match:
                          type: integer
                        match_pct:
                          type: number
                        drempel:
                          type: number
                          example: 95
                        geslaagd:
                          type: boolean
                        duration_ms:
                          type: integer
                        pr_nummer:
                          type: integer
                          nullable: true
                        commit_sha:
                          type: string
                          nullable: true
                        created_at:
                          type: string
                          format: date-time
  /api/v3/vraag:
    post:
      tags:
        - vraag
      summary: "Stel een vraag over een pand, of vervolg een gesprek (vraag-ingang + Story 1 gesprekslaag)"
      description: "Een team van agents (Saar, Bas, optioneel Teun/taxateur en Roos/bouwkundige, Pim, Vera, Noor) beantwoordt een vraag in gewone taal uit eigen data. Teun en Roos spreken alleen als hun vak (resp. waarde, bouwkundige staat) een tool-treffer heeft; Pim, Vera en Noor spreken elke beurt. Zonder `gesprek_id` start dit een nieuw gesprek (`beurt: 1`); met een geldig `gesprek_id` vervolgt het hetzelfde gesprek en hergebruikt het team wat in eerdere beurten al is opgehaald en bevestigd. De run draait binnen deze POST -- er is geen wachtrij. `Accept: text/event-stream` levert een live SSE-trace (200); anders het afgeronde resultaat (202, `status` is dan terminaal: `beantwoord` / `onbeantwoord` / `fout`). Vereist `X-API-Key` (tier `pro`/`admin`), geen `publiek`-endpoint: elke aanroep kan modelkosten maken."
      operationId: postVraag
      security:
        - ApiKey: []
      parameters:
        - name: Accept
          in: header
          required: false
          description: "`text/event-stream` schakelt naar de live SSE-trace."
          schema:
            type: string
            enum:
              - application/json
              - text/event-stream
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - vraag
              properties:
                vraag:
                  type: string
                  minLength: 1
                  maxLength: 500
                  example: welke gegevens hebben jullie van dit pand
                gesprek_id:
                  type: string
                  format: uuid
                  nullable: true
                  description: Story 1. Afwezig of null = start een nieuw gesprek. Een geldig gesprek_id vervolgt dat gesprek met een nieuwe beurt.
                bag_pand_id:
                  type: string
                  pattern: "^[0-9]{16}$"
                  nullable: true
                  description: "Bij een vervolgbeurt (beurt >= 2) moet dit gelijk zijn aan het pand van het gesprek, of afwezig blijven -- één gesprek gaat over één pand (D6)."
                adres:
                  type: string
                  maxLength: 200
                  nullable: true
      responses:
        "200":
          description: "Live trace (SSE-pad, alleen bij `Accept: text/event-stream`). Headers `X-Vraag-Run-Id`, `X-Vraag-Gesprek-Id`, `X-Vraag-Beurt`; elk frame is een `VraagTraceEvent`; sluit altijd af met `run_eind` of `fout`."
          content:
            text/event-stream:
              schema:
                type: string
        "202":
          description: "Run afgerond (JSON-pad). `status` is terminaal, niet `\"loopt\"` -- afwijking van de vroege PoC-schets, expliciet in de Technische Eisen (de run draait binnen deze POST en is dan al klaar)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VraagRun"
        "400":
          description: "`ongeldige_vraag` / `ongeldig_pand_id` / `ongeldig_adres` / `ongeldig_gesprek_id` (Story 1, geen geldige uuid v4) / `ander_pand_in_gesprek` (Story 1, D6: het gesprek gaat al over een ander pand)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "401":
          description: "`geen_toegang` -- X-API-Key ontbreekt of onbekend."
        "403":
          description: "`tier_onvoldoende` -- geen pro/admin-tier, of (Story 1, besluit 6) een guard zonder key_hash."
        "404":
          description: "Story 1. `gesprek_onbekend` -- het gesprek bestaat niet, hoort bij een andere sleutel, of is verlopen. Geen onderscheid tussen die drie."
        "409":
          description: "Story 1. `gesprek_beurtplafond` (het gesprek heeft zijn maximale aantal beurten gehad) of `gesprek_gesloten` (het gesprek is al gesloten, of het cumulatieve gespreksbudget is op)."
        "413":
          description: Body groter dan 4 KB.
        "415":
          description: Content-Type is geen application/json.
        "429":
          description: "`vraag_dagbudget` -- deze sleutel is over zijn runs-per-dag heen (een beurt telt als run)."
        "503":
          description: "`llm_niet_geconfigureerd` (ANTHROPIC_API_KEY ontbreekt, R1) of `persistentie_niet_beschikbaar` (ops.vraag_run/ops.vraag_gesprek bestaan nog niet)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  "/api/v3/gesprek/{gesprek_id}":
    get:
      tags:
        - vraag
      summary: "Haal een heel gesprek terug (Story 1, gesprekslaag)"
      description: "Levert alle beurten van een gesprek op volgorde -- per beurt vraag, status, reden, antwoord, bronnen en (tenzij `?zonder_trace=1`) de trace-events op `seq`-volgorde. Gescopet op de sleutel die het gesprek begon: een andere sleutel krijgt dezelfde `404 gesprek_onbekend` als een onbekend gesprek."
      operationId: getGesprek
      security:
        - ApiKey: []
      parameters:
        - name: gesprek_id
          in: path
          required: true
          description: UUID v4.
          schema:
            type: string
            format: uuid
        - name: zonder_trace
          in: query
          required: false
          description: "`1` laat `runs[].trace` weg -- een lichte poll voor een chat-UI die alleen de status van elke beurt wil zien."
          schema:
            type: string
            enum:
              - "1"
      responses:
        "200":
          description: Het volledige gesprek.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VraagGesprek"
        "400":
          description: "`ongeldig_gesprek_id` -- geen geldige uuid, geen DB-call gedaan."
        "401":
          description: "`geen_toegang` -- X-API-Key ontbreekt of onbekend."
        "403":
          description: "`tier_onvoldoende` -- guard zonder key_hash (besluit 6)."
        "404":
          description: "`gesprek_onbekend`, zonder detail en zonder onderscheid tussen \"bestaat niet\" en \"van iemand anders\"."
        "503":
          description: "`persistentie_niet_beschikbaar` -- ops.vraag_gesprek bestaat nog niet."
  "/api/v3/vraag/{run_id}":
    get:
      tags:
        - vraag
      summary: Haal een vraag-run terug (trace + eindantwoord)
      description: "Levert de volledige run: vraag, status, eindantwoord, bronnen, `gesprek_id`/`beurt` (Story 1) en alle trace-events op `seq`-volgorde -- ook nadat de stream gesloten is en ook na een koude start. De terugvalweg als streaming tegenvalt. Gescopet op de sleutel die de run claimde (Story 1, B5): een andere sleutel krijgt dezelfde `404 run_onbekend`."
      operationId: getVraagRun
      security:
        - ApiKey: []
      parameters:
        - name: run_id
          in: path
          required: true
          description: UUID v4.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: De volledige run.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VraagRun"
        "400":
          description: "`ongeldig_run_id` -- geen geldige uuid, geen DB-call gedaan."
        "401":
          description: "`geen_toegang` -- X-API-Key ontbreekt of onbekend."
        "403":
          description: "`tier_onvoldoende` -- guard zonder key_hash (Story 1, besluit 6)."
        "404":
          description: "`run_onbekend`, zonder detail en zonder onderscheid tussen \"bestaat niet\" en \"van iemand anders\"."
        "503":
          description: "`persistentie_niet_beschikbaar` -- ops.vraag_run bestaat nog niet. Bewust geen 404: dat zou een andere bewering zijn."
  /api/v3/openapi.yaml:
    get:
      tags:
        - meta
      summary: OpenAPI-spec (YAML)
      description: Levert deze OpenAPI-spec als YAML.
      operationId: getOpenApiYaml
      responses:
        "200":
          description: OpenAPI-spec
          content:
            application/yaml:
              schema:
                type: string
            text/yaml:
              schema:
                type: string
  /api/v3/openapi.json:
    get:
      tags:
        - meta
      summary: OpenAPI-spec (JSON)
      description: Levert deze OpenAPI-spec als JSON.
      operationId: getOpenApiJson
      responses:
        "200":
          description: OpenAPI-spec
          content:
            application/json:
              schema:
                type: object
  /api/v3/docs:
    get:
      tags:
        - meta
      summary: Interactieve API-docs (Swagger UI)
      description: "HTML-pagina met Swagger UI die `openapi.yaml` rendert."
      operationId: getDocsPage
      responses:
        "200":
          description: HTML-pagina
          content:
            text/html:
              schema:
                type: string
components:
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: "API-key voor `?detail=full` en hogere rate-limits."
  schemas:
    RDCoordinate:
      type: array
      minItems: 2
      maxItems: 2
      items:
        type: number
      description: "RD-coördinaat [x, y] in EPSG:28992."
      example:
        - 121394
        - 487383
    WGS84Coordinate:
      type: array
      minItems: 2
      maxItems: 2
      items:
        type: number
      description: "WGS84 [lon, lat] in EPSG:4326 (GeoJSON-volgorde)."
      example:
        - 4.881
        - 52.367
    GeoJSONPolygon:
      type: object
      required:
        - type
        - coordinates
      properties:
        type:
          type: string
          enum:
            - Polygon
        coordinates:
          type: array
          items:
            type: array
            items:
              type: array
              items:
                type: number
    BronPad:
      type: object
      required:
        - api
        - document_id
        - deeplink
        - peildatum
        - extractie_methode
        - confidence
      properties:
        api:
          type: string
          example: rp_v4
        document_id:
          type: string
          example: NL.IMRO.0518.BP0123-VG01
        artikel_pad:
          type:
            - string
            - "null"
          example: 4.2.1
        deeplink:
          type: string
          format: uri
        peildatum:
          type: string
          format: date
        plan_versie:
          type:
            - string
            - "null"
        extractie_methode:
          type: string
          enum:
            - native
            - regex
            - llm-opus
            - llm-sonnet
            - manual
        confidence:
          type: number
          minimum: 0
          maximum: 1
        hash_payload:
          type:
            - string
            - "null"
    Identificatie:
      type: object
      properties:
        pand_id:
          type:
            - string
            - "null"
          example: "0518100000123456"
        vbo_id:
          type:
            - string
            - "null"
        num_id:
          type:
            - string
            - "null"
        coordinaten:
          type: object
          properties:
            rd:
              oneOf:
                - $ref: "#/components/schemas/RDCoordinate"
                - type: "null"
            wgs84:
              oneOf:
                - $ref: "#/components/schemas/WGS84Coordinate"
                - type: "null"
        gemeente_code:
          type:
            - string
            - "null"
          example: "0518"
        gemeente_naam:
          type:
            - string
            - "null"
        buurtcode:
          type:
            - string
            - "null"
        wijkcode:
          type:
            - string
            - "null"
    Energielabel:
      type: object
      required:
        - klasse
      properties:
        klasse:
          type: string
          example: C
        geldig_tot:
          type:
            - string
            - "null"
          format: date
    PandFeiten:
      type: object
      properties:
        oppervlakte_m2:
          type:
            - number
            - "null"
        bouwjaar:
          type:
            - integer
            - "null"
        gebruiksdoel:
          type: array
          items:
            type: string
        aantal_vbos_in_pand:
          type:
            - integer
            - "null"
        bouwlagen:
          type:
            - integer
            - "null"
        nokhoogte_m:
          type:
            - number
            - "null"
        daktype:
          type:
            - string
            - "null"
        energielabel:
          oneOf:
            - $ref: "#/components/schemas/Energielabel"
            - type: "null"
        woz_waarde:
          type:
            - number
            - "null"
        fundering_type:
          type:
            - string
            - "null"
        monument_status:
          type: string
          enum:
            - rijksmonument
            - geen
            - onbekend
          description: "'rijksmonument' = de bron noemt het pand zo; 'geen' = de bron zegt expliciet van niet; 'onbekend' = de bron zwijgt over dit pand (geldt voor het overgrote deel)."
    LeidendPlan:
      type: object
      properties:
        identificatie:
          type: string
        type:
          type: string
          example: bestemmingsplan
        naam:
          type:
            - string
            - "null"
        vastgesteld_op:
          type:
            - string
            - "null"
          format: date
        status:
          type:
            - string
            - "null"
    Bestemming:
      type: object
      properties:
        hoofdgroep:
          type: string
          example: Wonen
        naam:
          type: string
          example: Wonen-1
        bron_artikel:
          type:
            - string
            - "null"
    Dubbelbestemming:
      type: object
      properties:
        hoofdgroep:
          type: string
          enum:
            - Waarde
            - Leiding
            - Waterstaat
        naam:
          type: string
        regime:
          type: string
        bron_artikel:
          type:
            - string
            - "null"
    Bouwvlak:
      type: object
      properties:
        aanwezig:
          type:
            - boolean
            - "null"
        pand_binnen_bouwvlak:
          type:
            - boolean
            - "null"
        polygoon:
          oneOf:
            - $ref: "#/components/schemas/GeoJSONPolygon"
            - type: "null"
    Maatvoeringen:
      type: object
      properties:
        max_bouwhoogte_m:
          type:
            - number
            - "null"
        max_goothoogte_m:
          type:
            - number
            - "null"
        max_aantal_wooneenheden:
          type:
            - integer
            - "null"
        max_bebouwingspercentage:
          type:
            - number
            - "null"
    Functieaanduiding:
      type: object
      properties:
        type:
          type: string
        kwalificatie:
          type: string
          enum:
            - toelatend
            - beperkend
        brontekst:
          type:
            - string
            - "null"
    Gebiedsaanduiding:
      type: object
      properties:
        hoofdgroep:
          type: string
        naam:
          type: string
        regime_samenvatting:
          type:
            - string
            - "null"
    BruidsschatRegel:
      type: object
      properties:
        akn_uri:
          type: string
          example: /akn/nl/act/mnre1034/2020/bruidsschat
        onderwerp:
          type: string
        brontekst_excerpt:
          type:
            - string
            - "null"
    Juridisch:
      type: object
      properties:
        regime:
          type: string
          enum:
            - wro
            - ow
            - hybride
            - ow_omgevingsplan
            - onbekend
        leidend_plan:
          oneOf:
            - $ref: "#/components/schemas/LeidendPlan"
            - type: "null"
        bestemming:
          oneOf:
            - $ref: "#/components/schemas/Bestemming"
            - type: "null"
        dubbelbestemmingen:
          type: array
          items:
            $ref: "#/components/schemas/Dubbelbestemming"
        bouwvlak:
          $ref: "#/components/schemas/Bouwvlak"
        maatvoeringen:
          $ref: "#/components/schemas/Maatvoeringen"
        functieaanduidingen:
          type: array
          items:
            $ref: "#/components/schemas/Functieaanduiding"
        gebiedsaanduidingen:
          type: array
          items:
            $ref: "#/components/schemas/Gebiedsaanduiding"
        bruidsschat_regels:
          type: array
          items:
            $ref: "#/components/schemas/BruidsschatRegel"
    VerordeningStatus:
      type: string
      enum:
        - toegestaan
        - voorwaardelijk
        - verboden
        - onbekend
    Huisvestingsverordening:
      type: object
      properties:
        van_toepassing:
          type:
            - boolean
            - "null"
        cvdr_id:
          type:
            - string
            - "null"
        splitsing_status:
          $ref: "#/components/schemas/VerordeningStatus"
        kamerverhuur_status:
          $ref: "#/components/schemas/VerordeningStatus"
        min_opp_per_woning_m2:
          type:
            - number
            - "null"
        min_opp_pand_m2:
          type:
            - number
            - "null"
        min_opp_artikel:
          type:
            - string
            - "null"
        min_opp_bron_url:
          type:
            - string
            - "null"
        min_opp_confidence:
          type:
            - string
            - "null"
        gemeente:
          type:
            - string
            - "null"
        verordening_titel:
          type:
            - string
            - "null"
        voorwaarden:
          type: array
          items:
            type: string
    Parkeerverordening:
      type: object
      properties:
        van_toepassing:
          type:
            - boolean
            - "null"
        norm_per_woning:
          type:
            - number
            - "null"
        afkoop_mogelijk:
          type:
            - boolean
            - "null"
        afkoop_bedrag_per_plaats:
          type:
            - number
            - "null"
    Welstandsnota:
      type: object
      properties:
        van_toepassing:
          type:
            - boolean
            - "null"
        gebiedsregime:
          type:
            - string
            - "null"
        brontekst_excerpt:
          type:
            - string
            - "null"
    MonumentenVerordening:
      type: object
      properties:
        van_toepassing:
          type:
            - boolean
            - "null"
    ErfgoedVerordening:
      type: object
      properties:
        van_toepassing:
          type:
            - boolean
            - "null"
        bouwhistorische_waardering:
          type:
            - string
            - "null"
    SplitsZone:
      type: object
      properties:
        gemeente_code:
          type: string
        regio_naam:
          type: string
        zone_type:
          type: string
          enum:
            - verboden
            - voorwaardelijk
            - toegestaan
            - onbekend
        bron_url:
          type:
            - string
            - "null"
          format: uri
      additionalProperties: true
    Beleid:
      type: object
      properties:
        huisvestingsverordening:
          $ref: "#/components/schemas/Huisvestingsverordening"
        parkeerverordening:
          $ref: "#/components/schemas/Parkeerverordening"
        welstandsnota:
          $ref: "#/components/schemas/Welstandsnota"
        monumenten_verordening:
          $ref: "#/components/schemas/MonumentenVerordening"
        erfgoed_verordening:
          $ref: "#/components/schemas/ErfgoedVerordening"
        splits_zone:
          oneOf:
            - $ref: "#/components/schemas/SplitsZone"
            - type: "null"
    Geluidzone:
      type: object
      properties:
        modaliteit:
          type: string
        db_klasse:
          type: string
        vereist_hwbe:
          type: boolean
    ExterneVeiligheid:
      type: object
      properties:
        binnen_pr10_6:
          type:
            - boolean
            - "null"
        binnen_invloedsgebied:
          type:
            - boolean
            - "null"
        risicobronnen_nabij:
          type: array
          items:
            type: string
    Natura2000:
      type: object
      properties:
        binnen_natura2000:
          type:
            - boolean
            - "null"
        stikstofgevoelig_in_omgeving:
          type:
            - boolean
            - "null"
        aerius_verplicht:
          type:
            - boolean
            - "null"
    Bodem:
      type: object
      properties:
        verdacht_locatie:
          type:
            - boolean
            - "null"
        sanering_uitgevoerd:
          type:
            - boolean
            - "null"
        onderzoek_vereist_bij_functiewijziging:
          type:
            - boolean
            - "null"
    Fundering:
      type: object
      properties:
        kwetsbaarheid_klasse:
          type:
            - string
            - "null"
        score:
          type:
            - number
            - "null"
        perc_voor_1970:
          type:
            - number
            - "null"
        fgr:
          type:
            - string
            - "null"
    ExterneFactoren:
      type: object
      properties:
        geluidzones:
          type: array
          items:
            $ref: "#/components/schemas/Geluidzone"
        externe_veiligheid:
          $ref: "#/components/schemas/ExterneVeiligheid"
        natura2000:
          $ref: "#/components/schemas/Natura2000"
        bodem:
          $ref: "#/components/schemas/Bodem"
        fundering:
          oneOf:
            - $ref: "#/components/schemas/Fundering"
            - type: "null"
    StillePotentie:
      type: object
      properties:
        extra_lagen:
          type:
            - integer
            - "null"
        extra_hoogte_m:
          type:
            - number
            - "null"
        nieuwe_functies:
          type: array
          items:
            type: string
        headroom_score:
          type:
            - number
            - "null"
          minimum: 0
          maximum: 1
    ScenarioKlasse:
      type: string
      enum:
        - kansrijk
        - voorwaardelijk
        - niet_kansrijk
        - onbekend
    ScenarioOnderbouwingItem:
      type: object
      required:
        - aspect
        - bevinding
        - invloed
      properties:
        aspect:
          type: string
        bevinding:
          type: string
        invloed:
          type: string
          enum:
            - positief
            - voorwaarde
            - blokkade
            - neutraal
        bron_pad:
          type:
            - string
            - "null"
    ProcedurelRoute:
      type: object
      required:
        - type
        - kansrijkheid
        - doorlooptijd_weken
        - kosten_orde
      properties:
        type:
          type: string
          enum:
            - bij_recht
            - binnenplans
            - bopa_regulier
            - bopa_uitgebreid
            - wijziging_omgevingsplan
            - buitenplans_omgevingsvergunning
        kansrijkheid:
          type: string
          enum:
            - hoog
            - middel-hoog
            - middel
            - middel-laag
            - laag
        doorlooptijd_weken:
          type: array
          minItems: 2
          maxItems: 2
          items:
            type: integer
            minimum: 0
        kosten_orde:
          type: array
          minItems: 2
          maxItems: 2
          items:
            type: number
            minimum: 0
        onderbouwing_vereist:
          type: array
          items:
            type: string
    BevoegdGezag:
      type: object
      properties:
        type:
          type: string
          example: gemeente
        naam:
          type: string
        oin:
          type:
            - string
            - "null"
    ScenarioProcedureel:
      type: object
      properties:
        routes:
          type: array
          items:
            $ref: "#/components/schemas/ProcedurelRoute"
        bevoegd_gezag:
          oneOf:
            - $ref: "#/components/schemas/BevoegdGezag"
            - type: "null"
        indieningsvereisten:
          type: array
          items:
            type: string
    ScenarioPresentatie:
      type: object
      required:
        - samenvatting
        - detail
        - cta
      properties:
        samenvatting:
          type: string
        detail:
          type: string
        cta:
          type: string
    ScenarioOutcome:
      type: object
      required:
        - klasse
        - vertrouwen
        - onderbouwing
        - voorwaarden
        - blokkades
        - procedureel
        - presentatie
        - versie
      properties:
        klasse:
          $ref: "#/components/schemas/ScenarioKlasse"
        vertrouwen:
          type: number
          minimum: 0
          maximum: 1
        onderbouwing:
          type: array
          items:
            $ref: "#/components/schemas/ScenarioOnderbouwingItem"
        voorwaarden:
          type: array
          items:
            type: string
        blokkades:
          type: array
          items:
            type: string
        procedureel:
          $ref: "#/components/schemas/ScenarioProcedureel"
        presentatie:
          $ref: "#/components/schemas/ScenarioPresentatie"
        versie:
          type: string
          example: v3.0
    ScenariosV3:
      type: object
      required:
        - splitsing_1_naar_2
        - splitsing_1_naar_3plus
        - kamerverhuur
        - optoppen_dakopbouw
        - bijbehorend_bouwwerk
        - dakkapel
        - transformatie_kantoor_naar_wonen
        - transformatie_winkel_naar_wonen
        - transformatie_bedrijf_naar_wonen
        - bed_and_breakfast
        - beroep_aan_huis
        - mantelzorgwoning
      properties:
        splitsing_1_naar_2:
          $ref: "#/components/schemas/ScenarioOutcome"
        splitsing_1_naar_3plus:
          $ref: "#/components/schemas/ScenarioOutcome"
        kamerverhuur:
          $ref: "#/components/schemas/ScenarioOutcome"
        optoppen_dakopbouw:
          $ref: "#/components/schemas/ScenarioOutcome"
        bijbehorend_bouwwerk:
          $ref: "#/components/schemas/ScenarioOutcome"
        dakkapel:
          $ref: "#/components/schemas/ScenarioOutcome"
        transformatie_kantoor_naar_wonen:
          $ref: "#/components/schemas/ScenarioOutcome"
        transformatie_winkel_naar_wonen:
          $ref: "#/components/schemas/ScenarioOutcome"
        transformatie_bedrijf_naar_wonen:
          $ref: "#/components/schemas/ScenarioOutcome"
        bed_and_breakfast:
          $ref: "#/components/schemas/ScenarioOutcome"
        beroep_aan_huis:
          $ref: "#/components/schemas/ScenarioOutcome"
        mantelzorgwoning:
          $ref: "#/components/schemas/ScenarioOutcome"
    Meta:
      type: object
      required:
        - versie
        - engine_versie
        - gegenereerd_op
        - cache_hit
        - refresh_strategy
        - qa_status
        - privacy_check
      properties:
        versie:
          type: string
          example: v3.0
        engine_versie:
          type: string
          example: v3.0-mvp
        gegenereerd_op:
          type: string
          format: date-time
        cache_hit:
          type: boolean
        refresh_strategy:
          type: string
          enum:
            - pre_compute
            - on_demand
            - hybrid
        qa_status:
          type: string
          enum:
            - green
            - amber
            - red
        privacy_check:
          type: string
          enum:
            - passed
            - flagged
        ontbrekende_velden:
          type: array
          items:
            type: string
        schema_versie:
          type: string
          example: v3.0
        schema_validation:
          type: object
          properties:
            ok:
              type: boolean
            violations:
              type: integer
              minimum: 0
            warnings:
              type: integer
              minimum: 0
    BronPerVeld:
      type: object
      additionalProperties:
        oneOf:
          - $ref: "#/components/schemas/BronPad"
          - type: "null"
    PandContextV3:
      type: object
      required:
        - identificatie
        - pand_feiten
        - juridisch
        - beleid
        - externe_factoren
        - stille_potentie
        - scenarios
        - bron_per_veld
        - meta
      properties:
        identificatie:
          $ref: "#/components/schemas/Identificatie"
        pand_feiten:
          $ref: "#/components/schemas/PandFeiten"
        juridisch:
          $ref: "#/components/schemas/Juridisch"
        bp_bestemming:
          type:
            - string
            - "null"
        beleid:
          $ref: "#/components/schemas/Beleid"
        externe_factoren:
          $ref: "#/components/schemas/ExterneFactoren"
        stille_potentie:
          $ref: "#/components/schemas/StillePotentie"
        scenarios:
          $ref: "#/components/schemas/ScenariosV3"
        bron_per_veld:
          $ref: "#/components/schemas/BronPerVeld"
        meta:
          $ref: "#/components/schemas/Meta"
    ApiError:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          example: pand_niet_gevonden
        bag_id:
          type: string
        hint:
          type: string
        detail:
          type: string
    VraagBron:
      type: object
      description: "Letterlijk uit TOOL_CATALOGUS (api/v3/tool-detail.js): welke tool, welk systeem, welke deeplink."
      required:
        - tool
        - systeem
        - url
        - niveau
        - bekende_beperkingen
      properties:
        tool:
          type: string
          example: bag-feiten
        systeem:
          type: string
          example: BAG (Basisregistratie Adressen en Gebouwen) via Kadaster
        url:
          type: string
          nullable: true
        niveau:
          type: integer
        bekende_beperkingen:
          type: array
          items:
            type: string
    VraagTraceEvent:
      type: object
      description: "Eén event uit de trace. `schema` versioneert de envelope: `vraag-trace/v1` (fase A, geen gesprek) of `vraag-trace/v2` (Story 1 -- `gesprek_id`/`beurt` verplicht op de envelope; Story 3+4 breidt binnen v2 de `agent`-lijst uit met `taxateur`/`bouwkundige` en levert `overleg` daadwerkelijk uit, zonder een nieuwe schemaversie). De elf `type`-waarden zijn een gesloten lijst (AC3)."
      required:
        - schema
        - run_id
        - seq
        - ts
        - agent
        - type
        - data
      properties:
        schema:
          type: string
          enum:
            - vraag-trace/v1
            - vraag-trace/v2
        run_id:
          type: string
          format: uuid
        gesprek_id:
          type: string
          format: uuid
          description: "Story 1. Verplicht op een `vraag-trace/v2`-event, afwezig op v1."
        beurt:
          type: integer
          minimum: 1
          description: "Story 1. Verplicht op een `vraag-trace/v2`-event, afwezig op v1."
        seq:
          type: integer
          minimum: 1
          description: "Per run oplopend, geen gaten -- een eigenschap van de schrijver, niet van de aanroeper."
        ts:
          type: string
          format: date-time
        agent:
          type: string
          enum:
            - systeem
            - intake
            - data
            - taxateur
            - bouwkundige
            - regels
            - verificatie
            - woordvoerder
          description: "`systeem` is de loop zelf, geen persona -- run_start/run_eind/fout zijn geen handeling van een agent, en `systeem` mag ook geen `overleg` sturen. `taxateur` en `bouwkundige` (Story 3+4) claimen alleen als hun vak een tool-treffer heeft deze beurt; zonder treffer verschijnen ze niet in de trace."
        type:
          type: string
          enum:
            - run_start
            - agent_start
            - tool_call
            - tool_resultaat
            - claim
            - verificatie
            - afkeuring
            - antwoord
            - fout
            - run_eind
            - overleg
        data:
          type: object
          description: "Vorm hangt af van `type`; zie de eventtabel in de Technische Eisen van het werkitem. Bevat NOOIT de vraagtekst -- alleen `vraag_lengte` op `run_start`. Op `tool_resultaat` (Story 1): `hergebruikt_uit_beurt` (integer >= 1 of null) -- null/afwezig betekent deze beurt echt gelezen. Op `afkeuring` (Story 3+4, optioneel): `rol` -- de rol wiens claim afviel. Op `run_eind` (Story 3+4, optioneel): `specialisten_actief` -- array van rol-ids die deze beurt hebben gesproken."
    VraagRun:
      type: object
      required:
        - run_id
        - status
        - bronnen
        - trace
      properties:
        run_id:
          type: string
          format: uuid
        gesprek_id:
          type: string
          format: uuid
          nullable: true
          description: Story 1. Het gesprek waar deze beurt bij hoort.
        beurt:
          type: integer
          minimum: 1
          nullable: true
          description: Story 1. Het beurtnummer binnen het gesprek.
        vraag:
          type: string
          description: "Alleen aanwezig op GET /api/v3/vraag/{run_id}; de POST-response draagt de vraagtekst niet terug."
        vraagtype:
          type: string
          nullable: true
          enum:
            - objectvraag
            - mogelijkheidsvraag
            - null
        status:
          type: string
          enum:
            - beantwoord
            - onbeantwoord
            - fout
        reden:
          type: string
          nullable: true
          enum:
            - geen_bron
            - budget
            - gespreksbudget
            - tijdslimiet
            - geen_pand
            - antwoord_niet_gedekt
            - verificatie_onbeschikbaar
            - llm_fout
            - persistentie_niet_beschikbaar
            - wedervraag
            - voorstel
            - null
        antwoord:
          type: string
          nullable: true
        voorstellen:
          type: array
          description: "#1485: vervolgstappen die de tafel aanbiedt, uit de vaste actiecatalogus (lib/vraag/voorstellen.js). Leeg bij runs zonder voorstellen."
          items:
            type: object
            required:
              - actie
            properties:
              actie:
                type: string
                enum:
                  - rapport
                  - bewaren
                  - monitoren
                  - zoeken
                  - huurwaarde
              argumenten:
                type: object
        bronnen:
          type: array
          items:
            $ref: "#/components/schemas/VraagBron"
          description: "Nooit gevuld bij status \"beantwoord\" zonder ten minste één bron -- het model mag niet gokken (AC6)."
        verbruik:
          type: object
          properties:
            toolcalls:
              type: integer
            llm_calls:
              type: integer
            input_tokens:
              type: integer
            output_tokens:
              type: integer
            duur_ms:
              type: integer
              nullable: true
        beurten_resterend:
          type: integer
          minimum: 0
          description: "Story 1. Alleen aanwezig op POST /api/v3/vraag: VRAAG_MAX_BEURTEN_PER_GESPREK min `beurt`."
        gesprek_status:
          type: string
          enum:
            - open
            - gesloten
          description: Story 1. Alleen aanwezig op POST /api/v3/vraag.
        trace_url:
          type: string
          description: Alleen aanwezig op POST /api/v3/vraag.
        gesprek_url:
          type: string
          description: "Story 1. Alleen aanwezig op POST /api/v3/vraag: /api/v3/gesprek/{gesprek_id}."
        trace:
          type: array
          items:
            $ref: "#/components/schemas/VraagTraceEvent"
          description: "Alleen aanwezig op GET /api/v3/vraag/{run_id}, op seq-volgorde."
    VraagGesprek:
      type: object
      description: "Story 1. Het hele gesprek: gesprekstoestand plus alle beurten op volgorde (GET /api/v3/gesprek/{gesprek_id})."
      required:
        - gesprek_id
        - status
        - beurten
        - runs
      properties:
        gesprek_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - open
            - gesloten
        sluit_reden:
          type: string
          nullable: true
          enum:
            - gespreksbudget
            - beurtplafond
            - null
        bag_pand_id:
          type: string
          pattern: "^[0-9]{16}$"
          nullable: true
        adres:
          type: string
          nullable: true
          description: "Het adres bij bag_pand_id (#1433), of null bij een losse vraag of een adresloos pand."
        gestart_op:
          type: string
          format: date-time
        laatste_beurt_op:
          type: string
          format: date-time
        beurten:
          type: integer
          minimum: 0
        beurten_max:
          type: integer
          minimum: 1
        verbruik:
          type: object
          description: Som van het verbruik over alle beurten.
          properties:
            toolcalls:
              type: integer
            llm_calls:
              type: integer
            input_tokens:
              type: integer
            output_tokens:
              type: integer
            duur_ms:
              type: integer
        runs:
          type: array
          description: Op beurt oplopend.
          items:
            $ref: "#/components/schemas/VraagRun"
