# ODIN open dental outcomes registry — pilot API (draft / заготовка, R-7).
# Human-readable companion: https://dentum.uz/ru/registry/developers/
openapi: 3.0.3
info:
  title: ODIN Registry API (pilot)
  version: 0.7.0
  description: >
    De-identified dental implant outcomes registry embedded in dentum.uz.
    Consent-first ("no consent, no record"), de-identification happens BEFORE
    anything is written to disk, every write is appended to a hash-chain audit
    (data passport v0). The CDSS prediction is a research prototype, NOT a
    medical device. Pilot tokens are issued manually.
servers:
  - url: https://dentum.uz
security:
  - RegistryToken: []
paths:
  /api/registry/case:
    post:
      summary: Create a baseline case (caseFormV0)
      description: >
        Required: consent_scopes (subset of [model_training, external_research,
        commercial_use]), consent_confirmed=true, age 18-100, sex m|f, smoking
        no|former|current, diabetes no|type1|type2, tooth_fdi 11-48,
        implant_system. Optional: hygiene, bone_type, implant_length_mm,
        implant_diameter_mm, immediate_placement, augmentation,
        surgeon_experience_years, prosthetics, clinic_ref.
      responses:
        '200': { description: '{ ok, study_uuid, chain }' }
        '400': { description: validation errors }
        '403': { description: missing/invalid token }
  /api/registry/upload:
    post:
      summary: Resumable de-identifying upload (tus-core subset)
      description: >
        create (POST with X-Upload-Length, X-Study-Uuid) -> HEAD returns
        Upload-Offset -> PATCH application/offset+octet-stream appends.
        JPEG APPn/COM stripped, PNG chunk whitelist, DICOM explicit-VR PII tags
        blanked in place. Implicit-VR DICOM is rejected (415).
      responses:
        '201': { description: upload created }
  /api/registry/predict:
    post:
      summary: CDSS prediction for a case
      description: >
        { study_uuid } -> { percentile, band, top_factors }. Percentile of the
        uncalibrated ensemble score vs the training cohort (n=747, AUROC ~0.77
        prototype). Never returns "risk N%" - calibrated probabilities are flat
        on this cohort and would be dishonest.
      responses:
        '200': { description: prediction + chain event }
  /api/registry/outcome:
    post:
      summary: Record a 6/12-month outcome (closes label y)
      description: >
        { study_uuid, outcome: success|periimplantitis|failure,
        marginal_bone_loss_mm?: 0-15, outcome_date?: YYYY-MM-DD,
        failure_reason? }. Merged into the case payload, chain event
        'outcome.recorded'.
      responses:
        '200': { description: outcome recorded }
        '404': { description: unknown study_uuid }
  /api/registry/chain:
    get:
      summary: Hash-chain audit (data passport v0)
      responses:
        '200': { description: '{ ok, length, head }' }
  /api/registry/health:
    get:
      summary: Liveness
      security: []
      responses:
        '200': { description: '{ ok, cases }' }
  /dicom-web/studies:
    post:
      summary: STOW-RS store (2D radiography + defaced CBCT/CT)
      security: [{ BasicAuth: [] }]
      description: >
        multipart/related; type="application/dicom". Contract: PatientID must
        equal the registry study_uuid (create the case first). Modalities:
        IO, DX, PX, CR pass tag de-identification; CT/CBCT are additionally
        DEFACED before anything hits disk - every voxel below 150 HU is
        replaced with air, so no skin isosurface exists (bone/teeth/implants
        survive; soft-tissue diagnostics are lost - declared honestly).
        CT requirements: uncompressed transfer syntax, MONOCHROME2, calibrated
        HU (RescaleIntercept < -500 or RescaleType=HU); otherwise rejected.
        De-identification (PS3.15 Basic subset + UID remap into 2.25 via
        salted SHA-256, dates retained, BurnedInAnnotation=YES rejected) runs
        BEFORE the instance reaches storage.
      responses:
        '200': { description: STOW response (application/dicom+json) }
        '401': { description: missing/invalid Basic credentials }
    get:
      summary: QIDO-RS search (e.g. ?PatientID={study_uuid})
      security: [{ BasicAuth: [] }]
      responses:
        '200': { description: study list (application/dicom+json) }
  /dicom-web/studies/{study}/metadata:
    get:
      summary: WADO-RS metadata
      security: [{ BasicAuth: [] }]
      parameters:
        - { name: study, in: path, required: true, schema: { type: string } }
      responses:
        '200': { description: instance metadata (application/dicom+json) }
components:
  securitySchemes:
    RegistryToken:
      type: apiKey
      in: header
      name: X-Registry-Token
    BasicAuth:
      type: http
      scheme: basic
