Merge branch 'feat/v139-targets-and-links' into release/v1.39.0

This commit is contained in:
Marc-André Bombeck committed 2026-09-24 09:53:41 +02:00
commit e89f41710a
58 files changed
+2785 -214

No files matched your search

+1
View File
@@ -128,6 +128,7 @@ export const ERROR_CODE_CATALOGUE: Readonly<Record<string, readonly string[]>> =
"documents.inbound.restoreGone",
"documents.inbound.uploadBusy",
"documents.inbound.uploadTimeout",
"documents.inbound.vaccinationNotFound",
],
encounter: [
"encounter.invalid",
+18 -1
View File
@@ -98,7 +98,7 @@ const inboundFactEditRequest = inboundFactEditSchema.meta({
const documentUpdateRequest = documentUpdateSchema.meta({
id: "DocumentUpdateRequest",
description:
"Metadata edit for a stored document: `title` (user label; null clears it), `kind` (category), `documentDate` (user filing date, YYYY-MM-DD; null clears it), `episodeIds` (REPLACE-SET of condition links — the document's links become exactly this set; an empty array unlinks everything; every id must be a live episode of the caller or the whole request answers 404), `encounterIds` (the same replace-set semantics over the caller's visits). At least one field required. No `userId` field — narrowed from the session and fed to the Prisma `where` with the row id.",
"Metadata edit for a stored document: `title` (user label; null clears it), `kind` (category), `documentDate` (user filing date, YYYY-MM-DD; null clears it), `episodeIds` (REPLACE-SET of condition links — the document's links become exactly this set; an empty array unlinks everything; every id must be a live episode of the caller or the whole request answers 404), `encounterIds` (the same replace-set semantics over the caller's visits), `vaccinationIds` (the same replace-set semantics over the caller's vaccination doses, up to 100 so one childhood record can be filed against a whole primary schedule). At least one field required. No `userId` field — narrowed from the session and fed to the Prisma `where` with the row id.",
});
const documentBulkRequest = documentBulkSchema.meta({
@@ -156,6 +156,19 @@ const encounterLink = z
"A link to one of the caller's visits — \"this letter belongs to that appointment\". `kind` is the visit-kind ENUM CONSTANT rather than a rendered name: unlike a condition label, which is the person's own words, a visit kind is one of eight closed values whose human name the reader's own bundle owns, so publishing the constant is what lets a German reader and an English one each see it named correctly.",
});
const vaccinationLink = z
.object({
vaccinationId: z.string(),
occurredAt: z.string().nullable(),
catalogSlug: z.string().nullable(),
vaccineName: z.string().nullable(),
})
.meta({
id: "DocumentVaccinationLink",
description:
"A link to one of the caller's vaccination doses — \"this page records that dose\". `catalogSlug` is the catalogue entry when this release still knows the dose's slug, for the reader's own bundle to name; null is the signal to show `vaccineName`, the person's own wording. The same table the dose's own `/api/vaccinations/{id}/links` writes from the other end.",
});
const inboundDocument = z
.object({
id: z.string(),
@@ -203,6 +216,10 @@ const inboundDocument = z
const inboundDocumentDetail = inboundDocument
.extend({
facts: z.array(extractedFact),
vaccinationLinks: z.array(vaccinationLink).nullable().meta({
description:
"Vaccination doses this document is filed against. Null when the caller acts inside somebody else's record under a grant that does not cover the health background the doses live in: the page is shared, the doses are not, and an empty list would claim the page records none.",
}),
summary: z.string().nullable(),
summaryGeneratedAt: z.string().nullable(),
summaryState: z.enum(DOCUMENT_SUMMARY_STATES),
@@ -1201,6 +1201,24 @@ export const dashboardSnapshotResponse = z
.describe(
"Non-null when two writer buckets reported clearly different asleep totals for the latest night's main session (> 45 min apart and > 20% of the larger total). Observational only — the served summary stays the winning writer's totals; clients may show a discreet 'sources disagree' hint next to the sleep tile's headline. Null when the writers agree or the sleep module is off; optional for older cached snapshots.",
),
weightTrend: z
.object({
direction: z
.enum(["up-good", "up-bad", "hold"])
.describe(
"How the weight tile colours a change. `up-good`: a rise is progress (below the target band). `up-bad`: a fall is progress (above the band, and the reading when no target is stored). `hold`: the weight is inside the band, so a flat change is progress and a move either way is neutral, never a setback.",
),
targetPosition: z
.enum(["below", "inside", "above"])
.nullable()
.describe(
"Where the 7-day average (else the latest reading) sits against the person's own stored weight target. Null when no target is stored or there is no reading.",
),
})
.optional()
.describe(
"Which way a weight change counts as progress, judged against the person's own stored target rather than assuming a fall is good. Render the arrow, 7-day delta and comparison caption from `direction`; do not recompute it. Optional for older cached snapshots; when absent, treat it as `up-bad`.",
),
}),
extras: z
.object({
+27 -5
View File
@@ -175,11 +175,33 @@ const vaccination = z
"One administered dose. `occurredAt` is a day at UTC midnight — the source record carries dates, never times. `series` carries one entry per component antigen, so a combined preparation reports several at once and each may be at a different position: a dose can be a booster for one antigen and a first dose for another in the same act. An empty `series` means the dose has no resolvable antigen (free text only, or a slug this release does not know) and nothing is guessed from the name. `note` is the decrypted free text, or null on a key-rotation gap — fail-soft, never a 500. `reminderId` names the booster reminder this dose settled, when one matched. `documents` is present on the detail response and absent from the list.",
});
const vaccinationList = z.object({ vaccinations: z.array(vaccination) }).meta({
id: "VaccinationList",
description:
"The caller's immunization history, newest dose first, with every entry's series already resolved over the whole live set — never over the filtered page, which would report the oldest dose in a window as the first ever given.",
});
const vaccinationRenewal = z
.object({
antigen: z.string(),
reminderId: z.string(),
dueAt: z.string(),
daysUntil: z.number().int(),
state: z.enum(["current", "dueSoon", "overdue"]),
})
.meta({
id: "VaccinationRenewal",
description:
"One antigen's renewal state, read from the booster reminder the person confirmed for it (`dueAt` is that reminder's next due instant). `daysUntil` counts calendar days on the person's own clock and is negative once the date has passed. `state` is `overdue` once it has passed, `dueSoon` within 30 days, `current` otherwise. Render from `state`; do not recompute it. Nothing here is derived from age, history or the catalogue: an antigen without a confirmed booster has no entry.",
});
const vaccinationList = z
.object({
vaccinations: z.array(vaccination),
renewals: z.array(vaccinationRenewal).nullable().meta({
description:
"Each antigen's renewal state, earliest due first. Null when the caller acts under a grant that does not cover the measurements section the booster reminders live in.",
}),
})
.meta({
id: "VaccinationList",
description:
"The caller's immunization history, newest dose first, with every entry's series already resolved over the whole live set — never over the filtered page, which would report the oldest dose in a window as the first ever given.",
});
const vaccinationSuggestion = z
.object({