DATEYE

Messungen importieren

POST /api/v1/import

Authentifiziert. Der zentrale Endpunkt. Ein Request lädt einen Batch von 1–50 Messungen für einen Patienten hoch. Der mode-Diskriminator bestimmt, wie der Patient identifiziert wird.

Die vier Modi

Das Top-Level-Objekt ist eine discriminated union auf mode. Jede Variante ist .strict()unbekannte Felder führen zu 400 (bewusst, um Adapter-Bugs früh zu erkennen).

a) follow_up — bekannter Patient (bereits verknüpft)

{ "mode": "follow_up", "mkClientId": "<uuid>", "measurements": [  ] }

Der Client kennt die interne Client-UUID (aus dem Mapping-Sync). Messungen werden dem Patienten direkt zugeordnet. Voraussetzung: gültige Einwilligung (Behandlungseinwilligung) für den Patienten — andernfalls 403 CONSENT_MISSING / 403 CONSENT_WITHDRAWN.

b) first_visit — unbekannter Patient (erster Upload)

{
  "mode": "first_visit",
  "devicePseudonym": "<32 hex>",
  "initialsHint": "AB",
  "dobMonthYearHint": "1985-03",
  "measurements": [  ]
}

Landet in der Pending-Queue zur manuellen Verknüpfung im Portal.

  • devicePseudonym: genau 32 Hex-Zeichen (^[0-9a-f]{32}$, Groß-/Kleinschreibung egal, wird kleingeschrieben). Siehe devicePseudonym unten.
  • initialsHint: 1–3 Großbuchstaben ^[A-ZÄÖÜ]+$ (nur visueller Hinweis im Portal).
  • dobMonthYearHint: ^\d{4}-(0[1-9]|1[0-2])$ (Geburtsmonat/-jahr, nur visueller Hinweis).

c) pseudonym_only — Pseudonym ohne Hinweise (Fallback)

{ "mode": "pseudonym_only", "devicePseudonym": "<32 hex>", "measurements": [  ] }

Messungen werden nur akzeptiert, wenn das Pseudonym bereits serverseitig verknüpft ist. Andernfalls kein Puffern → Response mit Warnung resend_with_hints (der Client soll mit first_visit + Hinweisen erneut senden).

d) token — persistenter Re-Export (bevorzugt für Folgesendungen)

{ "mode": "token", "persistentToken": "tk_<32 hex>", "devicePseudonym": "<32 hex>?", "measurements": [  ] }

persistentToken (^tk_[0-9a-f]{32}$) identifiziert stabil einen bereits verknüpften Patienten. Ein optionales devicePseudonym wird nur zur Stale-Erkennung verwendet (weicht es vom Patienten des Tokens ab, gewinnt der Token; Warnung stale_pseudonym). Ein fremder Token (andere Praxis) → 403 TOKEN_FOREIGN.

Messobjekt (identisch in allen Modi)

Pflichtfelder je Messung: eye (OD|OS), measuredAt (ISO-8601), sourceAdapterId (1–64), sourceDevice (1–128). Optional dateOnly: true (Datum ohne Uhrzeit → Mitternacht UTC, Portal zeigt nur das Datum). Alle klinischen Felder sind optional/nullable; jedes hat ein Plausibilitäts-min/max (Bereichsverletzung → 400).

GruppeFelder (Einheit)
QuellesourceAdapterId, sourceSerial?, sourceDataId?, sourceDevice, sourceDeviceName?
Refraktionsphere (−30…30 D), cylinder (−15…15 D), axis (0…180°), visualAcuitySC/visualAcuityCC (0…2), cycloplegia (bool), method (≤32), addition (0…5 D), vertex (5…25 mm), prism (0…40), prismBase (0…360°), sphericalEquivalent (−30…30 D)
BiometrieaxialLength (10…45 mm), anteriorChamberDepth (1…7 mm), lensThickness (1…8 mm), vitreousChamberDepth (5…35 mm), axialSnr (≥0), axialMeasurementCount (int ≥0), axialStandardDeviation (≥0), cornealThickness (100…1500 µm), choroidalThickness (10…1000 µm), pupilDiameter (1…12 mm), pupilLighting (≤32), pupilCenterX/pupilCenterY, cornealDiameter (5…18 mm), bodyHeight (30…250 cm)
KeratometriecornealAstigmatism (0…20 D), cornealAxis1/cornealAxis2 (0…180°), cornealRadius1/cornealRadius2 (4…12 mm)
Patientgender, ethnicity (feste Enums)

sourceSerial + sourceDataId sind die Bausteine der Duplikatserkennung.

Erfolgsresponse (200)

Der Import antwortet immer mit einem Zählerobjekt (auch bei Teilfehlern):

{
  "success": true,
  "data": {
    "total": 4,
    "uploaded": 3,
    "duplicate": 1,
    "assigned": 0,
    "unmatched": 3,
    "errors": 0,
    "results": [ { "eye": "OD", "status": "stored" }, { "eye": "OS", "status": "duplicate" } ],
    "persistent_token": null,
    "skip_pseudonyms": [ "a1b2…", "f5e6…" ],
    "warnings": [ { "code": "rejected_pseudonym", "message": "…" } ]
  }
}
FeldBedeutung
totalAnzahl gesendeter Messungen
uploadedNeu gespeichert
duplicateAls Duplikat erkannt (nicht erneut gespeichert)
assignedDirekt einem Patienten zugeordnet (nur follow_up/token)
unmatchedIn die Pending-Queue eingestellt (nur first_visit)
errorsMessungen ohne verwertbare Werte u.ä.
results[]Pro Messung: eye + status (stored|duplicate|rejected) + optionaler errorCode
persistent_tokenBei verknüpftem Patienten das stabile tk_…-Token für künftige token-Uploads, sonst null
skip_pseudonyms[]Immer vorhanden — alle aktuell abgelehnten Pseudonyme der Praxis; der Client cacht sie und überspringt sie beim nächsten Export (identisch mit GET /rejected-clients)
warnings[]z.B. rejected_pseudonym, resend_with_hints, stale_pseudonym

Duplikat-Semantik

Ein Import ist ein Duplikat, wenn dieselbe logische Messung bereits existiert. Serverseitig via INSERT … ON CONFLICT DO NOTHING/UPDATE auf dem Natural Key (account_id, device_pseudonym, eye, measured_at in der Pending-Queue, oder Mess-Uniques beim verknüpften Patienten). duplicate: true bedeutet nur „bereits vorhanden” — der Status der vorhandenen Zeile (auch rejected) bleibt unverändert.

devicePseudonym — der Patientenbezeichner

  • Ein stabiler, clientseitig berechneter Hash pro Patient (in der Referenzimplementierung: 128-Bit-gekürztes HMAC-SHA256 → 32 Hex-Zeichen). Der Server berechnet ihn nicht und prüft nur das Format (^[0-9a-f]{32}$).
  • Für die OEM-Integration ist er ein opaker, stabiler Schlüssel: gleicher Patient + gleicher Client ⇒ gleiches Pseudonym. Das Backend behandelt ihn pro Praxis (account_id) als eindeutigen Patientenschlüssel — mehr muss es nicht darüber wissen.
  • Er ist pro Praxis eindeutig (tenant-scoped), nicht global.