mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
feat(labs): OpenAPI contract, i18n, and module-gate inventory
Add the Biomarker + restore routes to the OpenAPI registry and carry biomarkerId on the LabResult schema (additive; iOS resolves the unit + range from the linked marker, never recomputes). Regenerate the YAML. Add the labs.* / labs.catalog.* keys across en/de/es/fr/it/pl. List the biomarker catalog tree + restore route on the module-gate inventory's data-layer exemptions (the module gates the surfaces, not the row store).
This commit is contained in:
1 parent
04c95e5ba1
commit
90e0f2191c
11 files changed
+1357
-28
No files matched your search
+410
-4
@@ -5690,6 +5690,12 @@ paths:
|
||||
date-range filters. Each row carries a server-computed neutral `rangeStatus` and a `hasNote` flag (the encrypted
|
||||
note itself is not echoed).
|
||||
parameters:
|
||||
- in: query
|
||||
name: biomarkerId
|
||||
schema:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 64
|
||||
- in: query
|
||||
name: analyte
|
||||
schema:
|
||||
@@ -5759,6 +5765,10 @@ paths:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
biomarkerId:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 64
|
||||
panel:
|
||||
anyOf:
|
||||
- type: string
|
||||
@@ -5791,9 +5801,7 @@ paths:
|
||||
- type: string
|
||||
const: ""
|
||||
required:
|
||||
- analyte
|
||||
- value
|
||||
- unit
|
||||
- takenAt
|
||||
responses:
|
||||
"201":
|
||||
@@ -5924,6 +5932,212 @@ paths:
|
||||
"404": *a8
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/labs/restore:
|
||||
post:
|
||||
tags:
|
||||
- Labs
|
||||
summary: Restore soft-deleted lab results
|
||||
description: Un-tombstones 1..200 of the caller's soft-deleted lab results in one pass (the delete-Undo affordance). A
|
||||
foreign / live id is a silent no-op. Audits via the wide-event ledger.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RestoreLabResultsRequest"
|
||||
responses:
|
||||
"200":
|
||||
description: Restore count.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RestoreLabResultsResponse"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/biomarkers:
|
||||
get:
|
||||
tags:
|
||||
- Labs
|
||||
summary: List the caller's biomarker catalog
|
||||
description: Returns every biomarker the caller has defined, name-ordered. Each carries its unit, reference bounds, and
|
||||
decrypted context.
|
||||
responses:
|
||||
"200":
|
||||
description: Biomarker catalog.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ListBiomarkersEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
post:
|
||||
tags:
|
||||
- Labs
|
||||
summary: Define a biomarker
|
||||
description: Creates a user-scoped biomarker. The optional context note is AES-256-GCM encrypted before write. Audits as
|
||||
`biomarker.create`.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 120
|
||||
unit:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 40
|
||||
lowerBound:
|
||||
type: number
|
||||
upperBound:
|
||||
type: number
|
||||
context:
|
||||
type: string
|
||||
maxLength: 2000
|
||||
panel:
|
||||
anyOf:
|
||||
- type: string
|
||||
minLength: 1
|
||||
maxLength: 120
|
||||
- type: string
|
||||
const: ""
|
||||
required:
|
||||
- name
|
||||
- unit
|
||||
responses:
|
||||
"201":
|
||||
description: Created biomarker.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CreateBiomarkerResponse"
|
||||
"401": *a1
|
||||
"409": &a10
|
||||
description: A biomarker with this name already exists for the caller.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/biomarkers/{id}:
|
||||
get:
|
||||
tags:
|
||||
- Labs
|
||||
summary: Fetch a single biomarker
|
||||
description: Returns the biomarker including its decrypted `context`. Cross-user rows surface as 404.
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
responses:
|
||||
"200":
|
||||
description: Biomarker detail.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/GetBiomarkerResponse"
|
||||
"401": *a1
|
||||
"404": &a9
|
||||
description: Biomarker not found (or owned by another user).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
put:
|
||||
tags:
|
||||
- Labs
|
||||
summary: Edit a biomarker
|
||||
description: Partial edit; omitted fields are untouched, an explicit null clears `context` / `panel` / a bound. Audits
|
||||
as `biomarker.update`.
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 120
|
||||
unit:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 40
|
||||
lowerBound:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
upperBound:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
context:
|
||||
anyOf:
|
||||
- type: string
|
||||
maxLength: 2000
|
||||
- type: "null"
|
||||
panel:
|
||||
anyOf:
|
||||
- anyOf:
|
||||
- type: string
|
||||
minLength: 1
|
||||
maxLength: 120
|
||||
- type: string
|
||||
const: ""
|
||||
- type: "null"
|
||||
responses:
|
||||
"200":
|
||||
description: Updated biomarker.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UpdateBiomarkerResponse"
|
||||
"401": *a1
|
||||
"404": *a9
|
||||
"409": *a10
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
delete:
|
||||
tags:
|
||||
- Labs
|
||||
summary: Delete a biomarker
|
||||
description: "Hard-deletes the catalog definition. The `onDelete: SetNull` FK unlinks every reading (they keep their
|
||||
legacy `analyte` / `unit`), so the history survives. Audits as `biomarker.delete`."
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
responses:
|
||||
"200":
|
||||
description: Deletion succeeded.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DeleteBiomarkerResponse"
|
||||
"401": *a1
|
||||
"404": *a9
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
components:
|
||||
schemas:
|
||||
PasskeyLoginVerifyRequest:
|
||||
@@ -7230,6 +7444,17 @@ components:
|
||||
type: boolean
|
||||
description: Partial edit of a Vorsorge reminder. Omitted fields are left untouched; nextDueAt is recomputed server-side
|
||||
after the patch applies.
|
||||
RestoreLabResultsRequest:
|
||||
type: object
|
||||
properties:
|
||||
ids:
|
||||
minItems: 1
|
||||
maxItems: 200
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- ids
|
||||
MedicationTreatmentClass:
|
||||
type: string
|
||||
enum:
|
||||
@@ -17117,6 +17342,10 @@ components:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
biomarkerId:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
panel:
|
||||
anyOf:
|
||||
- type: string
|
||||
@@ -17149,6 +17378,7 @@ components:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- biomarkerId
|
||||
- panel
|
||||
- analyte
|
||||
- value
|
||||
@@ -17162,8 +17392,10 @@ components:
|
||||
- createdAt
|
||||
- updatedAt
|
||||
additionalProperties: false
|
||||
description: A stored lab result. The encrypted note is never echoed in list rows; `hasNote` flags its presence and the
|
||||
single-resource GET returns the decrypted `note`.
|
||||
description: A stored lab result. `biomarkerId` links the user-scoped catalog marker (null for legacy free-text rows);
|
||||
when linked, `analyte` / `unit` / `referenceLow` / `referenceHigh` are RESOLVED server-side from the Biomarker —
|
||||
the client renders them and never recomputes the range. The encrypted note is never echoed in list rows;
|
||||
`hasNote` flags its presence and the single-resource GET returns the decrypted `note`.
|
||||
LabReferenceRangeStatus:
|
||||
type: string
|
||||
enum:
|
||||
@@ -17213,6 +17445,10 @@ components:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
biomarkerId:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
panel:
|
||||
anyOf:
|
||||
- type: string
|
||||
@@ -17247,6 +17483,7 @@ components:
|
||||
- type: "null"
|
||||
required:
|
||||
- id
|
||||
- biomarkerId
|
||||
- panel
|
||||
- analyte
|
||||
- value
|
||||
@@ -17301,6 +17538,175 @@ components:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
RestoreLabResultsResponse:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
restored:
|
||||
type: number
|
||||
required:
|
||||
- restored
|
||||
additionalProperties: false
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
ListBiomarkersEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/ListBiomarkersResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
ListBiomarkersResponse:
|
||||
type: object
|
||||
properties:
|
||||
biomarkers:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Biomarker"
|
||||
required:
|
||||
- biomarkers
|
||||
additionalProperties: false
|
||||
Biomarker:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
unit:
|
||||
type: string
|
||||
lowerBound:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
upperBound:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
panel:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
hasContext:
|
||||
type: boolean
|
||||
context:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
createdAt:
|
||||
type: string
|
||||
updatedAt:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- unit
|
||||
- lowerBound
|
||||
- upperBound
|
||||
- panel
|
||||
- hasContext
|
||||
- context
|
||||
- createdAt
|
||||
- updatedAt
|
||||
additionalProperties: false
|
||||
description: A user-scoped catalog marker. `context` is the decrypted per-marker note (or null); `hasContext` flags its
|
||||
presence. A `LabResult` linking this marker resolves its unit + reference bounds from here.
|
||||
CreateBiomarkerResponse:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/Biomarker"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
GetBiomarkerResponse:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/Biomarker"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
UpdateBiomarkerResponse:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/Biomarker"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
DeleteBiomarkerResponse:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
deleted:
|
||||
type: boolean
|
||||
required:
|
||||
- deleted
|
||||
additionalProperties: false
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
MedicationScheduleOutput:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
+120
-4
@@ -6504,13 +6504,13 @@
|
||||
"labs": {
|
||||
"title": "Laborwerte",
|
||||
"subtitle": "Blutwerte und Biomarker",
|
||||
"addResult": "Hinzufügen",
|
||||
"addResult": "Wert hinzufügen",
|
||||
"addDescription": "Erfasse einen Biomarker aus deinem Laborbefund.",
|
||||
"addFirst": "Ersten Wert erfassen",
|
||||
"emptyTitle": "Noch keine Laborwerte",
|
||||
"emptyDescription": "Erfasse Blutwerte wie HbA1c, LDL oder Ferritin, um sie über die Zeit zu verfolgen und mit deiner Ärztin oder deinem Arzt zu teilen.",
|
||||
"referenceLabel": "Referenz",
|
||||
"readingsCount": "{count} Werte",
|
||||
"readingsCount": "{count} Messungen",
|
||||
"deleteLatest": "Letzten Wert löschen",
|
||||
"deleteConfirmTitle": "Laborwert löschen?",
|
||||
"deleteConfirmDescription": "Dieser Laborwert wird dauerhaft gelöscht.",
|
||||
@@ -6539,11 +6539,127 @@
|
||||
"referenceHint": "Übernimm den Referenzbereich aus deinem Laborbefund. Beide Grenzen sind optional.",
|
||||
"note": "Notiz",
|
||||
"notePlaceholder": "Optionaler Kontext zu diesem Wert",
|
||||
"requiredError": "Biomarker, Wert und Einheit sind erforderlich.",
|
||||
"requiredError": "Bitte einen Wert eingeben.",
|
||||
"rangeOrderError": "Die untere Referenz darf nicht über der oberen liegen.",
|
||||
"save": "Speichern",
|
||||
"savedToast": "Wert gespeichert",
|
||||
"saveError": "Wert konnte nicht gespeichert werden"
|
||||
"saveError": "Wert konnte nicht gespeichert werden",
|
||||
"biomarker": "Biomarker",
|
||||
"biomarkerPlaceholder": "Biomarker auswählen",
|
||||
"defineNew": "+ Neuen Biomarker anlegen",
|
||||
"noBiomarkersHint": "Lege zuerst einen Biomarker an, um einen Wert zu erfassen.",
|
||||
"pickBiomarkerError": "Bitte zuerst einen Biomarker auswählen."
|
||||
},
|
||||
"backToLabs": "Zurück zu den Laborwerten",
|
||||
"hasNote": "Notiz",
|
||||
"editTitle": "Wert bearbeiten",
|
||||
"editDescription": "Wert, Datum oder Notiz dieser Messung korrigieren.",
|
||||
"editedToast": "Wert aktualisiert",
|
||||
"editError": "Wert konnte nicht aktualisiert werden",
|
||||
"editTimestampError": "Bitte ein gültiges Datum eingeben.",
|
||||
"editReading": "Wert bearbeiten",
|
||||
"deleteReading": "Wert löschen",
|
||||
"restoredToast": "Wert wiederhergestellt",
|
||||
"restoreError": "Wert konnte nicht wiederhergestellt werden",
|
||||
"detail": {
|
||||
"history": "Verlauf",
|
||||
"emptyTitle": "Noch keine Werte",
|
||||
"emptyDescription": "Füge einen Wert hinzu, um diesen Biomarker über die Zeit zu verfolgen."
|
||||
},
|
||||
"chart": {
|
||||
"empty": "Keine Werte in diesem Zeitraum.",
|
||||
"valueLabel": "Wert",
|
||||
"range": {
|
||||
"30": "30 T",
|
||||
"90": "90 T",
|
||||
"365": "1 J",
|
||||
"all": "Alle"
|
||||
}
|
||||
},
|
||||
"biomarker": {
|
||||
"manage": "Biomarker",
|
||||
"manageTitle": "Biomarker",
|
||||
"manageSheetDescription": "Lege die Marker an, die du verfolgst. Jeder wird einmal mit Einheit und Referenzbereich eingerichtet.",
|
||||
"managerDescription": "Marker einmal definieren — Einheit und Bereich gelten für jede Messung.",
|
||||
"define": "Anlegen",
|
||||
"defineFirst": "Ersten Biomarker anlegen",
|
||||
"defineTitle": "Biomarker anlegen",
|
||||
"defineDescription": "Name, Einheit und Referenzbereich einmalig festlegen. Beim Erfassen wählst du nur den Marker.",
|
||||
"editTitle": "Biomarker bearbeiten",
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"deletedToast": "Biomarker gelöscht",
|
||||
"deleteError": "Biomarker konnte nicht gelöscht werden",
|
||||
"deleteConfirmTitle": "Biomarker löschen?",
|
||||
"deleteConfirmDescription": "Die Definition wird entfernt. Bestehende Werte bleiben erhalten, aber ohne Verknüpfung.",
|
||||
"emptyTitle": "Noch keine Biomarker",
|
||||
"emptyDescription": "Lege Marker wie LDL oder HbA1c an, um Werte zu erfassen.",
|
||||
"loadError": "Biomarker konnten nicht geladen werden",
|
||||
"noRange": "Kein Referenzbereich",
|
||||
"form": {
|
||||
"seedLabel": "Von einem gängigen Marker starten",
|
||||
"seedPlaceholder": "Marker wählen (optional)",
|
||||
"seedHint": "Eine Auswahl füllt die Felder unten — passe den Bereich an dein Labor an.",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "z. B. LDL-Cholesterin",
|
||||
"unit": "Einheit",
|
||||
"unitPlaceholder": "z. B. mg/dl",
|
||||
"panel": "Panel",
|
||||
"panelPlaceholder": "z. B. Lipide",
|
||||
"lowerBound": "Referenz unten",
|
||||
"upperBound": "Referenz oben",
|
||||
"lowerPlaceholder": "Untere Grenze",
|
||||
"upperPlaceholder": "Obere Grenze",
|
||||
"context": "Kontext",
|
||||
"contextPlaceholder": "Optionale Notiz zu diesem Marker",
|
||||
"rangeHint": "Beide Grenzen sind optional — eine leer lassen für einen offenen Bereich (z. B. LDL < 116).",
|
||||
"requiredError": "Name und Einheit sind erforderlich.",
|
||||
"rangeOrderError": "Die untere Grenze darf die obere nicht überschreiten.",
|
||||
"save": "Biomarker speichern",
|
||||
"savedToast": "Biomarker gespeichert",
|
||||
"updatedToast": "Biomarker aktualisiert",
|
||||
"saveError": "Biomarker konnte nicht gespeichert werden"
|
||||
}
|
||||
},
|
||||
"catalog": {
|
||||
"total-cholesterol": "Gesamtcholesterin",
|
||||
"ldl": "LDL-Cholesterin",
|
||||
"hdl": "HDL-Cholesterin",
|
||||
"triglycerides": "Triglyceride",
|
||||
"fasting-glucose": "Nüchternglukose",
|
||||
"hba1c": "HbA1c",
|
||||
"tsh": "TSH",
|
||||
"ft3": "Freies T3",
|
||||
"ft4": "Freies T4",
|
||||
"ferritin": "Ferritin",
|
||||
"transferrin-saturation": "Transferrinsättigung",
|
||||
"vitamin-d": "Vitamin D (25-OH)",
|
||||
"vitamin-b12": "Vitamin B12",
|
||||
"folate": "Folsäure",
|
||||
"hs-crp": "hs-CRP",
|
||||
"creatinine": "Kreatinin",
|
||||
"egfr": "eGFR",
|
||||
"alt": "ALT (GPT)",
|
||||
"ast": "AST (GOT)",
|
||||
"ggt": "GGT",
|
||||
"sodium": "Natrium",
|
||||
"potassium": "Kalium",
|
||||
"hemoglobin": "Hämoglobin",
|
||||
"hematocrit": "Hämatokrit",
|
||||
"wbc": "Leukozyten",
|
||||
"platelets": "Thrombozyten",
|
||||
"panel": {
|
||||
"lipids": "Lipide",
|
||||
"glucose": "Glukosestoffwechsel",
|
||||
"thyroid": "Schilddrüse",
|
||||
"iron": "Eisen",
|
||||
"vitamins": "Vitamine",
|
||||
"inflammation": "Entzündung",
|
||||
"renal": "Niere",
|
||||
"liver": "Leber",
|
||||
"electrolytes": "Elektrolyte",
|
||||
"bloodCount": "Blutbild"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
|
||||
+119
-3
@@ -6504,7 +6504,7 @@
|
||||
"labs": {
|
||||
"title": "Lab results",
|
||||
"subtitle": "Bloodwork and biomarker readings",
|
||||
"addResult": "Add",
|
||||
"addResult": "Add value",
|
||||
"addDescription": "Record a biomarker reading from a lab report.",
|
||||
"addFirst": "Add your first result",
|
||||
"emptyTitle": "No lab results yet",
|
||||
@@ -6539,11 +6539,127 @@
|
||||
"referenceHint": "Copy the reference range from your lab report. Either bound is optional.",
|
||||
"note": "Note",
|
||||
"notePlaceholder": "Optional context for this result",
|
||||
"requiredError": "Biomarker, value and unit are required.",
|
||||
"requiredError": "Enter a value.",
|
||||
"rangeOrderError": "The reference low must not exceed the reference high.",
|
||||
"save": "Save",
|
||||
"savedToast": "Result saved",
|
||||
"saveError": "Could not save the result"
|
||||
"saveError": "Could not save the result",
|
||||
"biomarker": "Biomarker",
|
||||
"biomarkerPlaceholder": "Pick a biomarker",
|
||||
"defineNew": "+ Define new biomarker",
|
||||
"noBiomarkersHint": "Define a biomarker first to record a value against it.",
|
||||
"pickBiomarkerError": "Pick a biomarker first."
|
||||
},
|
||||
"backToLabs": "Back to lab results",
|
||||
"hasNote": "Note",
|
||||
"editTitle": "Edit reading",
|
||||
"editDescription": "Correct the value, date or note of this reading.",
|
||||
"editedToast": "Reading updated",
|
||||
"editError": "Could not update the reading",
|
||||
"editTimestampError": "Enter a valid date.",
|
||||
"editReading": "Edit reading",
|
||||
"deleteReading": "Delete reading",
|
||||
"restoredToast": "Reading restored",
|
||||
"restoreError": "Could not restore the reading",
|
||||
"detail": {
|
||||
"history": "History",
|
||||
"emptyTitle": "No readings yet",
|
||||
"emptyDescription": "Add a value to start tracking this biomarker over time."
|
||||
},
|
||||
"chart": {
|
||||
"empty": "No readings in this period.",
|
||||
"valueLabel": "Value",
|
||||
"range": {
|
||||
"30": "30d",
|
||||
"90": "90d",
|
||||
"365": "1y",
|
||||
"all": "All"
|
||||
}
|
||||
},
|
||||
"biomarker": {
|
||||
"manage": "Biomarkers",
|
||||
"manageTitle": "Biomarkers",
|
||||
"manageSheetDescription": "Define the markers you track. Each is set up once with its unit and reference range.",
|
||||
"managerDescription": "Define a marker once — its unit and range are reused for every reading.",
|
||||
"define": "Define",
|
||||
"defineFirst": "Define your first biomarker",
|
||||
"defineTitle": "Define biomarker",
|
||||
"defineDescription": "Set the name, unit and reference range once. You pick the marker when adding a value.",
|
||||
"editTitle": "Edit biomarker",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deletedToast": "Biomarker deleted",
|
||||
"deleteError": "Could not delete the biomarker",
|
||||
"deleteConfirmTitle": "Delete biomarker?",
|
||||
"deleteConfirmDescription": "The definition is removed. Existing readings are kept but unlinked.",
|
||||
"emptyTitle": "No biomarkers yet",
|
||||
"emptyDescription": "Define markers like LDL or HbA1c to record values against them.",
|
||||
"loadError": "Could not load biomarkers",
|
||||
"noRange": "No reference range",
|
||||
"form": {
|
||||
"seedLabel": "Start from a common marker",
|
||||
"seedPlaceholder": "Choose a marker (optional)",
|
||||
"seedHint": "Picking one fills the fields below — adjust the range to match your lab.",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "e.g. LDL cholesterol",
|
||||
"unit": "Unit",
|
||||
"unitPlaceholder": "e.g. mg/dL",
|
||||
"panel": "Panel",
|
||||
"panelPlaceholder": "e.g. Lipids",
|
||||
"lowerBound": "Reference low",
|
||||
"upperBound": "Reference high",
|
||||
"lowerPlaceholder": "Lower bound",
|
||||
"upperPlaceholder": "Upper bound",
|
||||
"context": "Context",
|
||||
"contextPlaceholder": "Optional note about this marker",
|
||||
"rangeHint": "Either bound is optional — leave one blank for an open-ended range (e.g. LDL < 116).",
|
||||
"requiredError": "Name and unit are required.",
|
||||
"rangeOrderError": "The lower bound must not exceed the upper bound.",
|
||||
"save": "Save biomarker",
|
||||
"savedToast": "Biomarker saved",
|
||||
"updatedToast": "Biomarker updated",
|
||||
"saveError": "Could not save the biomarker"
|
||||
}
|
||||
},
|
||||
"catalog": {
|
||||
"total-cholesterol": "Total cholesterol",
|
||||
"ldl": "LDL cholesterol",
|
||||
"hdl": "HDL cholesterol",
|
||||
"triglycerides": "Triglycerides",
|
||||
"fasting-glucose": "Fasting glucose",
|
||||
"hba1c": "HbA1c",
|
||||
"tsh": "TSH",
|
||||
"ft3": "Free T3",
|
||||
"ft4": "Free T4",
|
||||
"ferritin": "Ferritin",
|
||||
"transferrin-saturation": "Transferrin saturation",
|
||||
"vitamin-d": "Vitamin D (25-OH)",
|
||||
"vitamin-b12": "Vitamin B12",
|
||||
"folate": "Folate",
|
||||
"hs-crp": "hs-CRP",
|
||||
"creatinine": "Creatinine",
|
||||
"egfr": "eGFR",
|
||||
"alt": "ALT",
|
||||
"ast": "AST",
|
||||
"ggt": "GGT",
|
||||
"sodium": "Sodium",
|
||||
"potassium": "Potassium",
|
||||
"hemoglobin": "Hemoglobin",
|
||||
"hematocrit": "Hematocrit",
|
||||
"wbc": "White blood cells",
|
||||
"platelets": "Platelets",
|
||||
"panel": {
|
||||
"lipids": "Lipids",
|
||||
"glucose": "Glucose metabolism",
|
||||
"thyroid": "Thyroid",
|
||||
"iron": "Iron",
|
||||
"vitamins": "Vitamins",
|
||||
"inflammation": "Inflammation",
|
||||
"renal": "Kidney",
|
||||
"liver": "Liver",
|
||||
"electrolytes": "Electrolytes",
|
||||
"bloodCount": "Blood count"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
|
||||
+120
-4
@@ -6504,13 +6504,13 @@
|
||||
"labs": {
|
||||
"title": "Resultados de análisis",
|
||||
"subtitle": "Analíticas de sangre y biomarcadores",
|
||||
"addResult": "Añadir",
|
||||
"addResult": "Añadir valor",
|
||||
"addDescription": "Registra un biomarcador a partir de un informe de laboratorio.",
|
||||
"addFirst": "Añade tu primer resultado",
|
||||
"emptyTitle": "Aún no hay resultados",
|
||||
"emptyDescription": "Registra analíticas como HbA1c, LDL o ferritina para seguirlas a lo largo del tiempo y compartirlas con tu médico.",
|
||||
"referenceLabel": "Referencia",
|
||||
"readingsCount": "{count} valores",
|
||||
"readingsCount": "{count} mediciones",
|
||||
"deleteLatest": "Eliminar el último valor",
|
||||
"deleteConfirmTitle": "¿Eliminar resultado de laboratorio?",
|
||||
"deleteConfirmDescription": "Este resultado de laboratorio se eliminará de forma permanente.",
|
||||
@@ -6539,11 +6539,127 @@
|
||||
"referenceHint": "Copia el rango de referencia de tu informe de laboratorio. Cualquiera de los límites es opcional.",
|
||||
"note": "Nota",
|
||||
"notePlaceholder": "Contexto opcional para este resultado",
|
||||
"requiredError": "El biomarcador, el valor y la unidad son obligatorios.",
|
||||
"requiredError": "Introduce un valor.",
|
||||
"rangeOrderError": "La referencia mínima no puede superar la máxima.",
|
||||
"save": "Guardar",
|
||||
"savedToast": "Resultado guardado",
|
||||
"saveError": "No se pudo guardar el resultado"
|
||||
"saveError": "No se pudo guardar el resultado",
|
||||
"biomarker": "Biomarcador",
|
||||
"biomarkerPlaceholder": "Elige un biomarcador",
|
||||
"defineNew": "+ Definir nuevo biomarcador",
|
||||
"noBiomarkersHint": "Define primero un biomarcador para registrar un valor.",
|
||||
"pickBiomarkerError": "Elige primero un biomarcador."
|
||||
},
|
||||
"backToLabs": "Volver a los resultados",
|
||||
"hasNote": "Nota",
|
||||
"editTitle": "Editar medición",
|
||||
"editDescription": "Corrige el valor, la fecha o la nota de esta medición.",
|
||||
"editedToast": "Medición actualizada",
|
||||
"editError": "No se pudo actualizar la medición",
|
||||
"editTimestampError": "Introduce una fecha válida.",
|
||||
"editReading": "Editar medición",
|
||||
"deleteReading": "Eliminar medición",
|
||||
"restoredToast": "Medición restaurada",
|
||||
"restoreError": "No se pudo restaurar la medición",
|
||||
"detail": {
|
||||
"history": "Historial",
|
||||
"emptyTitle": "Aún no hay mediciones",
|
||||
"emptyDescription": "Añade un valor para seguir este biomarcador con el tiempo."
|
||||
},
|
||||
"chart": {
|
||||
"empty": "Sin mediciones en este periodo.",
|
||||
"valueLabel": "Valor",
|
||||
"range": {
|
||||
"30": "30 d",
|
||||
"90": "90 d",
|
||||
"365": "1 a",
|
||||
"all": "Todo"
|
||||
}
|
||||
},
|
||||
"biomarker": {
|
||||
"manage": "Biomarcadores",
|
||||
"manageTitle": "Biomarcadores",
|
||||
"manageSheetDescription": "Define los marcadores que sigues. Cada uno se configura una vez con su unidad y rango de referencia.",
|
||||
"managerDescription": "Define un marcador una vez: su unidad y rango se reutilizan en cada medición.",
|
||||
"define": "Definir",
|
||||
"defineFirst": "Define tu primer biomarcador",
|
||||
"defineTitle": "Definir biomarcador",
|
||||
"defineDescription": "Configura el nombre, la unidad y el rango una vez. Eliges el marcador al añadir un valor.",
|
||||
"editTitle": "Editar biomarcador",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"deletedToast": "Biomarcador eliminado",
|
||||
"deleteError": "No se pudo eliminar el biomarcador",
|
||||
"deleteConfirmTitle": "¿Eliminar biomarcador?",
|
||||
"deleteConfirmDescription": "Se elimina la definición. Las mediciones existentes se conservan pero se desvinculan.",
|
||||
"emptyTitle": "Aún no hay biomarcadores",
|
||||
"emptyDescription": "Define marcadores como LDL o HbA1c para registrar valores.",
|
||||
"loadError": "No se pudieron cargar los biomarcadores",
|
||||
"noRange": "Sin rango de referencia",
|
||||
"form": {
|
||||
"seedLabel": "Partir de un marcador común",
|
||||
"seedPlaceholder": "Elige un marcador (opcional)",
|
||||
"seedHint": "Al elegir uno se rellenan los campos: ajusta el rango a tu laboratorio.",
|
||||
"name": "Nombre",
|
||||
"namePlaceholder": "p. ej. Colesterol LDL",
|
||||
"unit": "Unidad",
|
||||
"unitPlaceholder": "p. ej. mg/dL",
|
||||
"panel": "Panel",
|
||||
"panelPlaceholder": "p. ej. Lípidos",
|
||||
"lowerBound": "Referencia mínima",
|
||||
"upperBound": "Referencia máxima",
|
||||
"lowerPlaceholder": "Límite inferior",
|
||||
"upperPlaceholder": "Límite superior",
|
||||
"context": "Contexto",
|
||||
"contextPlaceholder": "Nota opcional sobre este marcador",
|
||||
"rangeHint": "Ambos límites son opcionales: deja uno en blanco para un rango abierto (p. ej. LDL < 116).",
|
||||
"requiredError": "El nombre y la unidad son obligatorios.",
|
||||
"rangeOrderError": "El límite inferior no debe superar al superior.",
|
||||
"save": "Guardar biomarcador",
|
||||
"savedToast": "Biomarcador guardado",
|
||||
"updatedToast": "Biomarcador actualizado",
|
||||
"saveError": "No se pudo guardar el biomarcador"
|
||||
}
|
||||
},
|
||||
"catalog": {
|
||||
"total-cholesterol": "Colesterol total",
|
||||
"ldl": "Colesterol LDL",
|
||||
"hdl": "Colesterol HDL",
|
||||
"triglycerides": "Triglicéridos",
|
||||
"fasting-glucose": "Glucosa en ayunas",
|
||||
"hba1c": "HbA1c",
|
||||
"tsh": "TSH",
|
||||
"ft3": "T3 libre",
|
||||
"ft4": "T4 libre",
|
||||
"ferritin": "Ferritina",
|
||||
"transferrin-saturation": "Saturación de transferrina",
|
||||
"vitamin-d": "Vitamina D (25-OH)",
|
||||
"vitamin-b12": "Vitamina B12",
|
||||
"folate": "Folato",
|
||||
"hs-crp": "PCR ultrasensible",
|
||||
"creatinine": "Creatinina",
|
||||
"egfr": "TFGe",
|
||||
"alt": "ALT",
|
||||
"ast": "AST",
|
||||
"ggt": "GGT",
|
||||
"sodium": "Sodio",
|
||||
"potassium": "Potasio",
|
||||
"hemoglobin": "Hemoglobina",
|
||||
"hematocrit": "Hematocrito",
|
||||
"wbc": "Leucocitos",
|
||||
"platelets": "Plaquetas",
|
||||
"panel": {
|
||||
"lipids": "Lípidos",
|
||||
"glucose": "Metabolismo de la glucosa",
|
||||
"thyroid": "Tiroides",
|
||||
"iron": "Hierro",
|
||||
"vitamins": "Vitaminas",
|
||||
"inflammation": "Inflamación",
|
||||
"renal": "Riñón",
|
||||
"liver": "Hígado",
|
||||
"electrolytes": "Electrolitos",
|
||||
"bloodCount": "Hemograma"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
|
||||
+120
-4
@@ -6504,13 +6504,13 @@
|
||||
"labs": {
|
||||
"title": "Résultats d'analyses",
|
||||
"subtitle": "Analyses sanguines et biomarqueurs",
|
||||
"addResult": "Ajouter",
|
||||
"addResult": "Ajouter une valeur",
|
||||
"addDescription": "Enregistrez un biomarqueur à partir d'un compte rendu de laboratoire.",
|
||||
"addFirst": "Ajouter votre premier résultat",
|
||||
"emptyTitle": "Aucun résultat pour l'instant",
|
||||
"emptyDescription": "Enregistrez des analyses comme l'HbA1c, le LDL ou la ferritine pour les suivre dans le temps et les partager avec votre médecin.",
|
||||
"referenceLabel": "Référence",
|
||||
"readingsCount": "{count} valeurs",
|
||||
"readingsCount": "{count} mesures",
|
||||
"deleteLatest": "Supprimer la dernière valeur",
|
||||
"deleteConfirmTitle": "Supprimer le résultat d'analyse ?",
|
||||
"deleteConfirmDescription": "Ce résultat d'analyse sera définitivement supprimé.",
|
||||
@@ -6539,11 +6539,127 @@
|
||||
"referenceHint": "Reprenez l'intervalle de référence de votre compte rendu. Chaque borne est facultative.",
|
||||
"note": "Note",
|
||||
"notePlaceholder": "Contexte facultatif pour ce résultat",
|
||||
"requiredError": "Le biomarqueur, la valeur et l'unité sont obligatoires.",
|
||||
"requiredError": "Saisissez une valeur.",
|
||||
"rangeOrderError": "La référence basse ne doit pas dépasser la référence haute.",
|
||||
"save": "Enregistrer",
|
||||
"savedToast": "Résultat enregistré",
|
||||
"saveError": "Impossible d'enregistrer le résultat"
|
||||
"saveError": "Impossible d'enregistrer le résultat",
|
||||
"biomarker": "Biomarqueur",
|
||||
"biomarkerPlaceholder": "Choisir un biomarqueur",
|
||||
"defineNew": "+ Définir un nouveau biomarqueur",
|
||||
"noBiomarkersHint": "Définissez d'abord un biomarqueur pour enregistrer une valeur.",
|
||||
"pickBiomarkerError": "Choisissez d'abord un biomarqueur."
|
||||
},
|
||||
"backToLabs": "Retour aux résultats",
|
||||
"hasNote": "Note",
|
||||
"editTitle": "Modifier la mesure",
|
||||
"editDescription": "Corrigez la valeur, la date ou la note de cette mesure.",
|
||||
"editedToast": "Mesure mise à jour",
|
||||
"editError": "Impossible de mettre à jour la mesure",
|
||||
"editTimestampError": "Saisissez une date valide.",
|
||||
"editReading": "Modifier la mesure",
|
||||
"deleteReading": "Supprimer la mesure",
|
||||
"restoredToast": "Mesure restaurée",
|
||||
"restoreError": "Impossible de restaurer la mesure",
|
||||
"detail": {
|
||||
"history": "Historique",
|
||||
"emptyTitle": "Aucune mesure pour l'instant",
|
||||
"emptyDescription": "Ajoutez une valeur pour suivre ce biomarqueur dans le temps."
|
||||
},
|
||||
"chart": {
|
||||
"empty": "Aucune mesure sur cette période.",
|
||||
"valueLabel": "Valeur",
|
||||
"range": {
|
||||
"30": "30 j",
|
||||
"90": "90 j",
|
||||
"365": "1 a",
|
||||
"all": "Tout"
|
||||
}
|
||||
},
|
||||
"biomarker": {
|
||||
"manage": "Biomarqueurs",
|
||||
"manageTitle": "Biomarqueurs",
|
||||
"manageSheetDescription": "Définissez les marqueurs que vous suivez. Chacun est configuré une fois avec son unité et sa plage de référence.",
|
||||
"managerDescription": "Définissez un marqueur une fois — son unité et sa plage sont réutilisées pour chaque mesure.",
|
||||
"define": "Définir",
|
||||
"defineFirst": "Définir votre premier biomarqueur",
|
||||
"defineTitle": "Définir un biomarqueur",
|
||||
"defineDescription": "Réglez le nom, l'unité et la plage une fois. Vous choisissez le marqueur à l'ajout d'une valeur.",
|
||||
"editTitle": "Modifier le biomarqueur",
|
||||
"edit": "Modifier",
|
||||
"delete": "Supprimer",
|
||||
"deletedToast": "Biomarqueur supprimé",
|
||||
"deleteError": "Impossible de supprimer le biomarqueur",
|
||||
"deleteConfirmTitle": "Supprimer le biomarqueur ?",
|
||||
"deleteConfirmDescription": "La définition est supprimée. Les mesures existantes sont conservées mais dissociées.",
|
||||
"emptyTitle": "Aucun biomarqueur pour l'instant",
|
||||
"emptyDescription": "Définissez des marqueurs comme LDL ou HbA1c pour enregistrer des valeurs.",
|
||||
"loadError": "Impossible de charger les biomarqueurs",
|
||||
"noRange": "Pas de plage de référence",
|
||||
"form": {
|
||||
"seedLabel": "Partir d'un marqueur courant",
|
||||
"seedPlaceholder": "Choisir un marqueur (facultatif)",
|
||||
"seedHint": "En choisir un remplit les champs — ajustez la plage à votre laboratoire.",
|
||||
"name": "Nom",
|
||||
"namePlaceholder": "ex. Cholestérol LDL",
|
||||
"unit": "Unité",
|
||||
"unitPlaceholder": "ex. mg/dL",
|
||||
"panel": "Panel",
|
||||
"panelPlaceholder": "ex. Lipides",
|
||||
"lowerBound": "Référence basse",
|
||||
"upperBound": "Référence haute",
|
||||
"lowerPlaceholder": "Borne inférieure",
|
||||
"upperPlaceholder": "Borne supérieure",
|
||||
"context": "Contexte",
|
||||
"contextPlaceholder": "Note facultative sur ce marqueur",
|
||||
"rangeHint": "Les deux bornes sont facultatives — laissez-en une vide pour une plage ouverte (ex. LDL < 116).",
|
||||
"requiredError": "Le nom et l'unité sont requis.",
|
||||
"rangeOrderError": "La borne inférieure ne doit pas dépasser la borne supérieure.",
|
||||
"save": "Enregistrer le biomarqueur",
|
||||
"savedToast": "Biomarqueur enregistré",
|
||||
"updatedToast": "Biomarqueur mis à jour",
|
||||
"saveError": "Impossible d'enregistrer le biomarqueur"
|
||||
}
|
||||
},
|
||||
"catalog": {
|
||||
"total-cholesterol": "Cholestérol total",
|
||||
"ldl": "Cholestérol LDL",
|
||||
"hdl": "Cholestérol HDL",
|
||||
"triglycerides": "Triglycérides",
|
||||
"fasting-glucose": "Glycémie à jeun",
|
||||
"hba1c": "HbA1c",
|
||||
"tsh": "TSH",
|
||||
"ft3": "T3 libre",
|
||||
"ft4": "T4 libre",
|
||||
"ferritin": "Ferritine",
|
||||
"transferrin-saturation": "Saturation de la transferrine",
|
||||
"vitamin-d": "Vitamine D (25-OH)",
|
||||
"vitamin-b12": "Vitamine B12",
|
||||
"folate": "Folate",
|
||||
"hs-crp": "CRP us",
|
||||
"creatinine": "Créatinine",
|
||||
"egfr": "DFGe",
|
||||
"alt": "ALAT",
|
||||
"ast": "ASAT",
|
||||
"ggt": "GGT",
|
||||
"sodium": "Sodium",
|
||||
"potassium": "Potassium",
|
||||
"hemoglobin": "Hémoglobine",
|
||||
"hematocrit": "Hématocrite",
|
||||
"wbc": "Leucocytes",
|
||||
"platelets": "Plaquettes",
|
||||
"panel": {
|
||||
"lipids": "Lipides",
|
||||
"glucose": "Métabolisme du glucose",
|
||||
"thyroid": "Thyroïde",
|
||||
"iron": "Fer",
|
||||
"vitamins": "Vitamines",
|
||||
"inflammation": "Inflammation",
|
||||
"renal": "Rein",
|
||||
"liver": "Foie",
|
||||
"electrolytes": "Électrolytes",
|
||||
"bloodCount": "Numération sanguine"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
|
||||
+120
-4
@@ -6504,13 +6504,13 @@
|
||||
"labs": {
|
||||
"title": "Risultati degli esami",
|
||||
"subtitle": "Analisi del sangue e biomarcatori",
|
||||
"addResult": "Aggiungi",
|
||||
"addResult": "Aggiungi valore",
|
||||
"addDescription": "Registra un biomarcatore da un referto di laboratorio.",
|
||||
"addFirst": "Aggiungi il primo risultato",
|
||||
"emptyTitle": "Ancora nessun risultato",
|
||||
"emptyDescription": "Registra esami come HbA1c, LDL o ferritina per seguirli nel tempo e condividerli con il tuo medico.",
|
||||
"referenceLabel": "Riferimento",
|
||||
"readingsCount": "{count} valori",
|
||||
"readingsCount": "{count} misurazioni",
|
||||
"deleteLatest": "Elimina l'ultimo valore",
|
||||
"deleteConfirmTitle": "Eliminare il risultato di laboratorio?",
|
||||
"deleteConfirmDescription": "Questo risultato di laboratorio verrà eliminato definitivamente.",
|
||||
@@ -6539,11 +6539,127 @@
|
||||
"referenceHint": "Copia l'intervallo di riferimento dal referto. Ciascun limite è facoltativo.",
|
||||
"note": "Nota",
|
||||
"notePlaceholder": "Contesto facoltativo per questo risultato",
|
||||
"requiredError": "Biomarcatore, valore e unità sono obbligatori.",
|
||||
"requiredError": "Inserisci un valore.",
|
||||
"rangeOrderError": "Il riferimento minimo non può superare quello massimo.",
|
||||
"save": "Salva",
|
||||
"savedToast": "Risultato salvato",
|
||||
"saveError": "Impossibile salvare il risultato"
|
||||
"saveError": "Impossibile salvare il risultato",
|
||||
"biomarker": "Biomarcatore",
|
||||
"biomarkerPlaceholder": "Scegli un biomarcatore",
|
||||
"defineNew": "+ Definisci nuovo biomarcatore",
|
||||
"noBiomarkersHint": "Definisci prima un biomarcatore per registrare un valore.",
|
||||
"pickBiomarkerError": "Scegli prima un biomarcatore."
|
||||
},
|
||||
"backToLabs": "Torna ai risultati",
|
||||
"hasNote": "Nota",
|
||||
"editTitle": "Modifica misurazione",
|
||||
"editDescription": "Correggi valore, data o nota di questa misurazione.",
|
||||
"editedToast": "Misurazione aggiornata",
|
||||
"editError": "Impossibile aggiornare la misurazione",
|
||||
"editTimestampError": "Inserisci una data valida.",
|
||||
"editReading": "Modifica misurazione",
|
||||
"deleteReading": "Elimina misurazione",
|
||||
"restoredToast": "Misurazione ripristinata",
|
||||
"restoreError": "Impossibile ripristinare la misurazione",
|
||||
"detail": {
|
||||
"history": "Cronologia",
|
||||
"emptyTitle": "Ancora nessuna misurazione",
|
||||
"emptyDescription": "Aggiungi un valore per seguire questo biomarcatore nel tempo."
|
||||
},
|
||||
"chart": {
|
||||
"empty": "Nessuna misurazione in questo periodo.",
|
||||
"valueLabel": "Valore",
|
||||
"range": {
|
||||
"30": "30 g",
|
||||
"90": "90 g",
|
||||
"365": "1 a",
|
||||
"all": "Tutto"
|
||||
}
|
||||
},
|
||||
"biomarker": {
|
||||
"manage": "Biomarcatori",
|
||||
"manageTitle": "Biomarcatori",
|
||||
"manageSheetDescription": "Definisci i marcatori che segui. Ognuno si imposta una volta con la sua unità e l'intervallo di riferimento.",
|
||||
"managerDescription": "Definisci un marcatore una volta — unità e intervallo valgono per ogni misurazione.",
|
||||
"define": "Definisci",
|
||||
"defineFirst": "Definisci il tuo primo biomarcatore",
|
||||
"defineTitle": "Definisci biomarcatore",
|
||||
"defineDescription": "Imposta nome, unità e intervallo una volta. Scegli il marcatore quando aggiungi un valore.",
|
||||
"editTitle": "Modifica biomarcatore",
|
||||
"edit": "Modifica",
|
||||
"delete": "Elimina",
|
||||
"deletedToast": "Biomarcatore eliminato",
|
||||
"deleteError": "Impossibile eliminare il biomarcatore",
|
||||
"deleteConfirmTitle": "Eliminare il biomarcatore?",
|
||||
"deleteConfirmDescription": "La definizione viene rimossa. Le misurazioni esistenti restano ma vengono scollegate.",
|
||||
"emptyTitle": "Ancora nessun biomarcatore",
|
||||
"emptyDescription": "Definisci marcatori come LDL o HbA1c per registrare i valori.",
|
||||
"loadError": "Impossibile caricare i biomarcatori",
|
||||
"noRange": "Nessun intervallo di riferimento",
|
||||
"form": {
|
||||
"seedLabel": "Parti da un marcatore comune",
|
||||
"seedPlaceholder": "Scegli un marcatore (facoltativo)",
|
||||
"seedHint": "Sceglierne uno compila i campi — adatta l'intervallo al tuo laboratorio.",
|
||||
"name": "Nome",
|
||||
"namePlaceholder": "es. Colesterolo LDL",
|
||||
"unit": "Unità",
|
||||
"unitPlaceholder": "es. mg/dL",
|
||||
"panel": "Panel",
|
||||
"panelPlaceholder": "es. Lipidi",
|
||||
"lowerBound": "Riferimento minimo",
|
||||
"upperBound": "Riferimento massimo",
|
||||
"lowerPlaceholder": "Limite inferiore",
|
||||
"upperPlaceholder": "Limite superiore",
|
||||
"context": "Contesto",
|
||||
"contextPlaceholder": "Nota facoltativa su questo marcatore",
|
||||
"rangeHint": "Entrambi i limiti sono facoltativi — lasciane uno vuoto per un intervallo aperto (es. LDL < 116).",
|
||||
"requiredError": "Nome e unità sono obbligatori.",
|
||||
"rangeOrderError": "Il limite inferiore non deve superare quello superiore.",
|
||||
"save": "Salva biomarcatore",
|
||||
"savedToast": "Biomarcatore salvato",
|
||||
"updatedToast": "Biomarcatore aggiornato",
|
||||
"saveError": "Impossibile salvare il biomarcatore"
|
||||
}
|
||||
},
|
||||
"catalog": {
|
||||
"total-cholesterol": "Colesterolo totale",
|
||||
"ldl": "Colesterolo LDL",
|
||||
"hdl": "Colesterolo HDL",
|
||||
"triglycerides": "Trigliceridi",
|
||||
"fasting-glucose": "Glicemia a digiuno",
|
||||
"hba1c": "HbA1c",
|
||||
"tsh": "TSH",
|
||||
"ft3": "T3 libero",
|
||||
"ft4": "T4 libero",
|
||||
"ferritin": "Ferritina",
|
||||
"transferrin-saturation": "Saturazione della transferrina",
|
||||
"vitamin-d": "Vitamina D (25-OH)",
|
||||
"vitamin-b12": "Vitamina B12",
|
||||
"folate": "Folati",
|
||||
"hs-crp": "PCR ad alta sensibilità",
|
||||
"creatinine": "Creatinina",
|
||||
"egfr": "eGFR",
|
||||
"alt": "ALT",
|
||||
"ast": "AST",
|
||||
"ggt": "GGT",
|
||||
"sodium": "Sodio",
|
||||
"potassium": "Potassio",
|
||||
"hemoglobin": "Emoglobina",
|
||||
"hematocrit": "Ematocrito",
|
||||
"wbc": "Leucociti",
|
||||
"platelets": "Piastrine",
|
||||
"panel": {
|
||||
"lipids": "Lipidi",
|
||||
"glucose": "Metabolismo del glucosio",
|
||||
"thyroid": "Tiroide",
|
||||
"iron": "Ferro",
|
||||
"vitamins": "Vitamine",
|
||||
"inflammation": "Infiammazione",
|
||||
"renal": "Reni",
|
||||
"liver": "Fegato",
|
||||
"electrolytes": "Elettroliti",
|
||||
"bloodCount": "Emocromo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
|
||||
+120
-4
@@ -6504,13 +6504,13 @@
|
||||
"labs": {
|
||||
"title": "Wyniki badań",
|
||||
"subtitle": "Badania krwi i biomarkery",
|
||||
"addResult": "Dodaj",
|
||||
"addResult": "Dodaj wynik",
|
||||
"addDescription": "Zapisz biomarker z wyniku laboratoryjnego.",
|
||||
"addFirst": "Dodaj pierwszy wynik",
|
||||
"emptyTitle": "Brak wyników badań",
|
||||
"emptyDescription": "Zapisuj badania takie jak HbA1c, LDL czy ferrytyna, aby śledzić je w czasie i udostępniać lekarzowi.",
|
||||
"referenceLabel": "Norma",
|
||||
"readingsCount": "{count} wyników",
|
||||
"readingsCount": "{count} pomiarów",
|
||||
"deleteLatest": "Usuń ostatni wynik",
|
||||
"deleteConfirmTitle": "Usunąć wynik laboratoryjny?",
|
||||
"deleteConfirmDescription": "Ten wynik laboratoryjny zostanie trwale usunięty.",
|
||||
@@ -6539,11 +6539,127 @@
|
||||
"referenceHint": "Przepisz zakres referencyjny z wyniku laboratoryjnego. Każda z granic jest opcjonalna.",
|
||||
"note": "Notatka",
|
||||
"notePlaceholder": "Opcjonalny kontekst tego wyniku",
|
||||
"requiredError": "Biomarker, wartość i jednostka są wymagane.",
|
||||
"requiredError": "Podaj wartość.",
|
||||
"rangeOrderError": "Norma dolna nie może przekraczać górnej.",
|
||||
"save": "Zapisz",
|
||||
"savedToast": "Wynik zapisany",
|
||||
"saveError": "Nie udało się zapisać wyniku"
|
||||
"saveError": "Nie udało się zapisać wyniku",
|
||||
"biomarker": "Biomarker",
|
||||
"biomarkerPlaceholder": "Wybierz biomarker",
|
||||
"defineNew": "+ Zdefiniuj nowy biomarker",
|
||||
"noBiomarkersHint": "Najpierw zdefiniuj biomarker, aby zapisać wartość.",
|
||||
"pickBiomarkerError": "Najpierw wybierz biomarker."
|
||||
},
|
||||
"backToLabs": "Wróć do wyników",
|
||||
"hasNote": "Notatka",
|
||||
"editTitle": "Edytuj pomiar",
|
||||
"editDescription": "Popraw wartość, datę lub notatkę tego pomiaru.",
|
||||
"editedToast": "Pomiar zaktualizowany",
|
||||
"editError": "Nie udało się zaktualizować pomiaru",
|
||||
"editTimestampError": "Podaj prawidłową datę.",
|
||||
"editReading": "Edytuj pomiar",
|
||||
"deleteReading": "Usuń pomiar",
|
||||
"restoredToast": "Pomiar przywrócony",
|
||||
"restoreError": "Nie udało się przywrócić pomiaru",
|
||||
"detail": {
|
||||
"history": "Historia",
|
||||
"emptyTitle": "Brak pomiarów",
|
||||
"emptyDescription": "Dodaj wartość, aby śledzić ten biomarker w czasie."
|
||||
},
|
||||
"chart": {
|
||||
"empty": "Brak pomiarów w tym okresie.",
|
||||
"valueLabel": "Wartość",
|
||||
"range": {
|
||||
"30": "30 d",
|
||||
"90": "90 d",
|
||||
"365": "1 r",
|
||||
"all": "Wszystko"
|
||||
}
|
||||
},
|
||||
"biomarker": {
|
||||
"manage": "Biomarkery",
|
||||
"manageTitle": "Biomarkery",
|
||||
"manageSheetDescription": "Zdefiniuj śledzone markery. Każdy ustawiasz raz wraz z jednostką i zakresem referencyjnym.",
|
||||
"managerDescription": "Zdefiniuj marker raz — jednostka i zakres są używane dla każdego pomiaru.",
|
||||
"define": "Zdefiniuj",
|
||||
"defineFirst": "Zdefiniuj pierwszy biomarker",
|
||||
"defineTitle": "Zdefiniuj biomarker",
|
||||
"defineDescription": "Ustaw nazwę, jednostkę i zakres raz. Marker wybierasz przy dodawaniu wartości.",
|
||||
"editTitle": "Edytuj biomarker",
|
||||
"edit": "Edytuj",
|
||||
"delete": "Usuń",
|
||||
"deletedToast": "Biomarker usunięty",
|
||||
"deleteError": "Nie udało się usunąć biomarkera",
|
||||
"deleteConfirmTitle": "Usunąć biomarker?",
|
||||
"deleteConfirmDescription": "Definicja zostanie usunięta. Istniejące pomiary pozostaną, ale bez powiązania.",
|
||||
"emptyTitle": "Brak biomarkerów",
|
||||
"emptyDescription": "Zdefiniuj markery, takie jak LDL lub HbA1c, aby zapisywać wartości.",
|
||||
"loadError": "Nie udało się wczytać biomarkerów",
|
||||
"noRange": "Brak zakresu referencyjnego",
|
||||
"form": {
|
||||
"seedLabel": "Zacznij od popularnego markera",
|
||||
"seedPlaceholder": "Wybierz marker (opcjonalnie)",
|
||||
"seedHint": "Wybór wypełnia pola poniżej — dopasuj zakres do swojego laboratorium.",
|
||||
"name": "Nazwa",
|
||||
"namePlaceholder": "np. Cholesterol LDL",
|
||||
"unit": "Jednostka",
|
||||
"unitPlaceholder": "np. mg/dl",
|
||||
"panel": "Panel",
|
||||
"panelPlaceholder": "np. Lipidy",
|
||||
"lowerBound": "Dolna granica",
|
||||
"upperBound": "Górna granica",
|
||||
"lowerPlaceholder": "Dolna granica",
|
||||
"upperPlaceholder": "Górna granica",
|
||||
"context": "Kontekst",
|
||||
"contextPlaceholder": "Opcjonalna notatka o tym markerze",
|
||||
"rangeHint": "Obie granice są opcjonalne — zostaw jedną pustą dla zakresu otwartego (np. LDL < 116).",
|
||||
"requiredError": "Nazwa i jednostka są wymagane.",
|
||||
"rangeOrderError": "Dolna granica nie może przekraczać górnej.",
|
||||
"save": "Zapisz biomarker",
|
||||
"savedToast": "Biomarker zapisany",
|
||||
"updatedToast": "Biomarker zaktualizowany",
|
||||
"saveError": "Nie udało się zapisać biomarkera"
|
||||
}
|
||||
},
|
||||
"catalog": {
|
||||
"total-cholesterol": "Cholesterol całkowity",
|
||||
"ldl": "Cholesterol LDL",
|
||||
"hdl": "Cholesterol HDL",
|
||||
"triglycerides": "Trójglicerydy",
|
||||
"fasting-glucose": "Glukoza na czczo",
|
||||
"hba1c": "HbA1c",
|
||||
"tsh": "TSH",
|
||||
"ft3": "Wolne T3",
|
||||
"ft4": "Wolne T4",
|
||||
"ferritin": "Ferrytyna",
|
||||
"transferrin-saturation": "Wysycenie transferyny",
|
||||
"vitamin-d": "Witamina D (25-OH)",
|
||||
"vitamin-b12": "Witamina B12",
|
||||
"folate": "Kwas foliowy",
|
||||
"hs-crp": "hs-CRP",
|
||||
"creatinine": "Kreatynina",
|
||||
"egfr": "eGFR",
|
||||
"alt": "ALT",
|
||||
"ast": "AST",
|
||||
"ggt": "GGTP",
|
||||
"sodium": "Sód",
|
||||
"potassium": "Potas",
|
||||
"hemoglobin": "Hemoglobina",
|
||||
"hematocrit": "Hematokryt",
|
||||
"wbc": "Leukocyty",
|
||||
"platelets": "Płytki krwi",
|
||||
"panel": {
|
||||
"lipids": "Lipidy",
|
||||
"glucose": "Metabolizm glukozy",
|
||||
"thyroid": "Tarczyca",
|
||||
"iron": "Żelazo",
|
||||
"vitamins": "Witaminy",
|
||||
"inflammation": "Stan zapalny",
|
||||
"renal": "Nerki",
|
||||
"liver": "Wątroba",
|
||||
"electrolytes": "Elektrolity",
|
||||
"bloodCount": "Morfologia krwi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
|
||||
@@ -74,6 +74,8 @@ const MODULE_ROUTE_TREES: ReadonlyArray<string> = [
|
||||
"src/app/api/sleep",
|
||||
"src/app/api/workouts",
|
||||
"src/app/api/labs",
|
||||
// v1.18.1 — the user-scoped Biomarker catalog backs the Labs feature.
|
||||
"src/app/api/biomarkers",
|
||||
"src/app/api/cycle",
|
||||
"src/app/api/gamification",
|
||||
// v1.18.0 B3 — the legacy `/api/doctor-report` tree (JSON + server-PDF +
|
||||
@@ -129,6 +131,14 @@ const EXEMPT_ROUTES: ReadonlyArray<string> = [
|
||||
"src/app/api/workouts/batch/route.ts",
|
||||
"src/app/api/labs/route.ts",
|
||||
"src/app/api/labs/[id]/route.ts",
|
||||
// v1.18.1 — the lab-result delete-Undo restore endpoint and the
|
||||
// user-scoped Biomarker catalog CRUD share the LabResult data-layer
|
||||
// reasoning: the labs module gates the SURFACES (the Labs page), not the
|
||||
// row store. A synced / pre-existing reading and its catalog definition
|
||||
// must survive a disabled module so re-enabling reveals a complete history.
|
||||
"src/app/api/labs/restore/route.ts",
|
||||
"src/app/api/biomarkers/route.ts",
|
||||
"src/app/api/biomarkers/[id]/route.ts",
|
||||
// ── INFRA / UI-ONLY ───────────────────────────────────────────────
|
||||
// Static FHIR CapabilityStatement — server metadata, no user data.
|
||||
"src/app/api/fhir/metadata/route.ts",
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* OpenAPI route table for the user-scoped Biomarker catalog (`/api/biomarkers`).
|
||||
*
|
||||
* Part of the OpenAPI route table; aggregated in `./index.ts`. The request
|
||||
* bodies reuse the runtime Zod schemas from `@/lib/validations/biomarkers`
|
||||
* so the wire contract stays single-source. The response shape is declared
|
||||
* here (the route serialises a derived `hasContext` flag the input schema
|
||||
* doesn't carry, and returns the decrypted `context`).
|
||||
*
|
||||
* The iOS client consumes the biomarker DTO + the `biomarkerId` field on
|
||||
* `LabResult` to render the resolved unit + reference range without
|
||||
* recomputing — server-authoritative parity for Labs.
|
||||
*/
|
||||
import type { ZodOpenApiObject } from "zod-openapi";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import {
|
||||
createBiomarkerSchema,
|
||||
updateBiomarkerSchema,
|
||||
} from "@/lib/validations/biomarkers";
|
||||
|
||||
import { dataEnvelope, errorEnvelope, stdResponses } from "./shared";
|
||||
|
||||
createBiomarkerSchema.meta({
|
||||
id: "CreateBiomarkerRequest",
|
||||
description:
|
||||
"Define a user-scoped biomarker ONCE: canonical `name`, `unit`, optional reference bounds (`lowerBound` / `upperBound`; when both present `lowerBound` must not exceed `upperBound`), an optional encrypted `context` note, and an optional `panel` grouping. The name is unique per user (no second 'LDL'). Recording a value later just picks this marker — its unit + range are never re-entered.",
|
||||
});
|
||||
|
||||
updateBiomarkerSchema.meta({
|
||||
id: "UpdateBiomarkerRequest",
|
||||
description:
|
||||
"Partial edit of a biomarker. An omitted key leaves the column untouched; an explicit `null` on `context` / `panel` / a bound clears it. A rename that collides with another of the caller's markers is rejected 409.",
|
||||
});
|
||||
|
||||
const biomarkerRow = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
unit: z.string(),
|
||||
lowerBound: z.number().nullable(),
|
||||
upperBound: z.number().nullable(),
|
||||
panel: z.string().nullable(),
|
||||
hasContext: z.boolean(),
|
||||
context: z.string().nullable(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
})
|
||||
.meta({
|
||||
id: "Biomarker",
|
||||
description:
|
||||
"A user-scoped catalog marker. `context` is the decrypted per-marker note (or null); `hasContext` flags its presence. A `LabResult` linking this marker resolves its unit + reference bounds from here.",
|
||||
});
|
||||
|
||||
const listResponse = z
|
||||
.object({ biomarkers: z.array(biomarkerRow) })
|
||||
.meta({ id: "ListBiomarkersResponse" });
|
||||
|
||||
const conflict = {
|
||||
"409": {
|
||||
description: "A biomarker with this name already exists for the caller.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
};
|
||||
|
||||
const notFound = {
|
||||
"404": {
|
||||
description: "Biomarker not found (or owned by another user).",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
};
|
||||
|
||||
export const biomarkerPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
"/api/biomarkers": {
|
||||
get: {
|
||||
tags: ["Labs"],
|
||||
summary: "List the caller's biomarker catalog",
|
||||
description:
|
||||
"Returns every biomarker the caller has defined, name-ordered. Each carries its unit, reference bounds, and decrypted context.",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Biomarker catalog.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(listResponse, "ListBiomarkersEnvelope"),
|
||||
},
|
||||
},
|
||||
},
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
post: {
|
||||
tags: ["Labs"],
|
||||
summary: "Define a biomarker",
|
||||
description:
|
||||
"Creates a user-scoped biomarker. The optional context note is AES-256-GCM encrypted before write. Audits as `biomarker.create`.",
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: { "application/json": { schema: createBiomarkerSchema } },
|
||||
},
|
||||
responses: {
|
||||
"201": {
|
||||
description: "Created biomarker.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(biomarkerRow, "CreateBiomarkerResponse"),
|
||||
},
|
||||
},
|
||||
},
|
||||
...conflict,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/biomarkers/{id}": {
|
||||
get: {
|
||||
tags: ["Labs"],
|
||||
summary: "Fetch a single biomarker",
|
||||
description:
|
||||
"Returns the biomarker including its decrypted `context`. Cross-user rows surface as 404.",
|
||||
requestParams: { path: z.object({ id: z.string() }) },
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Biomarker detail.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(biomarkerRow, "GetBiomarkerResponse"),
|
||||
},
|
||||
},
|
||||
},
|
||||
...notFound,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
put: {
|
||||
tags: ["Labs"],
|
||||
summary: "Edit a biomarker",
|
||||
description:
|
||||
"Partial edit; omitted fields are untouched, an explicit null clears `context` / `panel` / a bound. Audits as `biomarker.update`.",
|
||||
requestParams: { path: z.object({ id: z.string() }) },
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: { "application/json": { schema: updateBiomarkerSchema } },
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Updated biomarker.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(biomarkerRow, "UpdateBiomarkerResponse"),
|
||||
},
|
||||
},
|
||||
},
|
||||
...conflict,
|
||||
...notFound,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
tags: ["Labs"],
|
||||
summary: "Delete a biomarker",
|
||||
description:
|
||||
"Hard-deletes the catalog definition. The `onDelete: SetNull` FK unlinks every reading (they keep their legacy `analyte` / `unit`), so the history survives. Audits as `biomarker.delete`.",
|
||||
requestParams: { path: z.object({ id: z.string() }) },
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Deletion succeeded.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(
|
||||
z.object({ deleted: z.boolean() }),
|
||||
"DeleteBiomarkerResponse",
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
...notFound,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -26,6 +26,7 @@ import { authPaths } from "./auth";
|
||||
import { coachFeedbackPaths, coachPaths } from "./coach";
|
||||
import { consentPaths } from "./consent";
|
||||
import { cyclePaths } from "./cycle";
|
||||
import { biomarkerPaths } from "./biomarkers";
|
||||
import { devicePaths } from "./devices";
|
||||
import { healthRecordPaths } from "./health-record";
|
||||
import { importPaths } from "./import";
|
||||
@@ -63,6 +64,7 @@ export const openApiPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
...importPaths,
|
||||
...measurementReminderPaths,
|
||||
...labsPaths,
|
||||
...biomarkerPaths,
|
||||
};
|
||||
|
||||
export const openApiComponents: NonNullable<ZodOpenApiObject["components"]> = {
|
||||
|
||||
@@ -47,6 +47,7 @@ const rangeStatusEnum = z
|
||||
const labResultRow = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
biomarkerId: z.string().nullable(),
|
||||
panel: z.string().nullable(),
|
||||
analyte: z.string(),
|
||||
value: z.number(),
|
||||
@@ -63,7 +64,7 @@ const labResultRow = z
|
||||
.meta({
|
||||
id: "LabResult",
|
||||
description:
|
||||
"A stored lab result. The encrypted note is never echoed in list rows; `hasNote` flags its presence and the single-resource GET returns the decrypted `note`.",
|
||||
"A stored lab result. `biomarkerId` links the user-scoped catalog marker (null for legacy free-text rows); when linked, `analyte` / `unit` / `referenceLow` / `referenceHigh` are RESOLVED server-side from the Biomarker — the client renders them and never recomputes the range. The encrypted note is never echoed in list rows; `hasNote` flags its presence and the single-resource GET returns the decrypted `note`.",
|
||||
});
|
||||
|
||||
const labResultDetail = labResultRow
|
||||
@@ -201,4 +202,36 @@ export const labsPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/labs/restore": {
|
||||
post: {
|
||||
tags: ["Labs"],
|
||||
summary: "Restore soft-deleted lab results",
|
||||
description:
|
||||
"Un-tombstones 1..200 of the caller's soft-deleted lab results in one pass (the delete-Undo affordance). A foreign / live id is a silent no-op. Audits via the wide-event ledger.",
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z
|
||||
.object({ ids: z.array(z.string()).min(1).max(200) })
|
||||
.meta({ id: "RestoreLabResultsRequest" }),
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Restore count.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(
|
||||
z.object({ restored: z.number() }),
|
||||
"RestoreLabResultsResponse",
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user