mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
Merge branch 'main' into feat/mood-level-a
This commit is contained in:
86 files changed
+4346
-206
No files matched your search
@@ -717,6 +717,11 @@ const labResultBackupSchema = z
|
||||
unit: z.string().min(1),
|
||||
referenceLow: z.number().nullable().optional(),
|
||||
referenceHigh: z.number().nullable().optional(),
|
||||
// Optional so a backup written before these columns existed still
|
||||
// restores; absent reads as "this reading had no source window on file".
|
||||
sourceReferenceLow: z.number().nullable().optional(),
|
||||
sourceReferenceHigh: z.number().nullable().optional(),
|
||||
sourceReferenceText: z.string().nullable().optional(),
|
||||
takenAt: isoDateTime,
|
||||
source: z.string().min(1),
|
||||
biomarkerName: z.string().nullable().optional(),
|
||||
|
||||
@@ -167,6 +167,15 @@ export const extractedFactRawSchema = z.object({
|
||||
unit: nullableText(80),
|
||||
referenceLow: z.number().finite().nullable().catch(null),
|
||||
referenceHigh: z.number().finite().nullable().catch(null),
|
||||
/**
|
||||
* Observation: the reference range EXACTLY as printed, verbatim. A report
|
||||
* writes its window in prose ("3,5 - 5,0", "< 5", "bis 5,0", "negativ"), and
|
||||
* `referenceLow` / `referenceHigh` above can only carry the cases that
|
||||
* reduce to two numbers. This field is what stops every other case from
|
||||
* being dropped on the floor — the string is kept whether or not the parser
|
||||
* gets bounds out of it.
|
||||
*/
|
||||
referenceText: nullableText(120),
|
||||
/** MedicationStatement: dose + stated status (verbatim). */
|
||||
dose: nullableText(120),
|
||||
medicationStatus: nullableText(64),
|
||||
@@ -237,6 +246,14 @@ export interface ObservationFactData {
|
||||
unit: string | null;
|
||||
referenceLow: number | null;
|
||||
referenceHigh: number | null;
|
||||
/**
|
||||
* The reference range as printed on the report, verbatim.
|
||||
*
|
||||
* Optional because it genuinely is: a fact staged before this field existed
|
||||
* carries no key for it, and that is an honest absence rather than a shim
|
||||
* for a caller we do not ship. Read it as `?? null`.
|
||||
*/
|
||||
referenceText?: string | null;
|
||||
effectiveDate: string | null;
|
||||
}
|
||||
|
||||
@@ -379,6 +396,7 @@ const observationEditSchema = z
|
||||
unit: optText(80),
|
||||
referenceLow: z.number().finite().nullable().optional(),
|
||||
referenceHigh: z.number().finite().nullable().optional(),
|
||||
referenceText: optText(120),
|
||||
effectiveDate: isoDate,
|
||||
})
|
||||
// Numeric XOR qualitative, mirroring the lab-result discipline. A null/absent
|
||||
|
||||
@@ -78,6 +78,13 @@ export const extractedRowSchema = z.object({
|
||||
unit: z.string().trim().max(80).nullable().catch(null),
|
||||
referenceLow: z.number().finite().nullable().catch(null),
|
||||
referenceHigh: z.number().finite().nullable().catch(null),
|
||||
/**
|
||||
* The reference range EXACTLY as printed, verbatim. The two numeric bounds
|
||||
* above can only carry a window that reduces to two numbers; a report that
|
||||
* prints "bis 5,0" or "negativ" states a window all the same, and this is
|
||||
* what keeps it instead of dropping it.
|
||||
*/
|
||||
referenceText: z.string().trim().max(120).nullable().catch(null),
|
||||
takenAt: z.string().nullable().catch(null),
|
||||
confidence: extractedConfidenceSchema,
|
||||
});
|
||||
@@ -107,6 +114,8 @@ export interface OcrExtractedRowDto {
|
||||
unit: string | null;
|
||||
referenceLow: number | null;
|
||||
referenceHigh: number | null;
|
||||
/** The reference range as printed on the report, verbatim. */
|
||||
referenceText: string | null;
|
||||
takenAt: string | null;
|
||||
confidence: {
|
||||
analyte: number;
|
||||
@@ -150,6 +159,17 @@ export const ocrCommitRowSchema = z
|
||||
unit: z.string().trim().min(1).max(40).optional(),
|
||||
referenceLow,
|
||||
referenceHigh,
|
||||
/**
|
||||
* The window the scanned report printed for this row. Confirmed by the
|
||||
* human on the review screen alongside everything else, and written onto
|
||||
* the reading rather than only used to seed a new catalog marker.
|
||||
*/
|
||||
referenceText: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(120)
|
||||
.optional()
|
||||
.or(z.literal("").transform(() => undefined)),
|
||||
takenAt: takenAtField,
|
||||
})
|
||||
// Numeric XOR qualitative: exactly one of value / valueText.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { SOURCE_REFERENCE_TEXT_MAX } from "@/lib/labs/parse-reference-range";
|
||||
import { validateEntryInstant } from "@/lib/validations/entry-instant";
|
||||
|
||||
/**
|
||||
@@ -63,6 +64,29 @@ const optionalNote = z
|
||||
const referenceLow = z.number().finite().optional();
|
||||
const referenceHigh = z.number().finite().optional();
|
||||
|
||||
/**
|
||||
* The reference range the SOURCE REPORT printed for this one reading.
|
||||
*
|
||||
* Distinct from `referenceLow` / `referenceHigh` above, which seed (and are
|
||||
* stamped from) the catalog marker's band. A lab runs its own method on its
|
||||
* own device, so the window a physician evaluates against is the one on that
|
||||
* report; these fields carry it, and `resolveEffectiveReferenceRange` lets it
|
||||
* win for that reading alone.
|
||||
*
|
||||
* `sourceReferenceText` is what was printed, verbatim. It is accepted with or
|
||||
* without derivable bounds: a window stated as "negativ" or "siehe Befund"
|
||||
* still belongs on the record, and dropping it because no number could be read
|
||||
* out of it is exactly the loss this field exists to stop.
|
||||
*/
|
||||
const sourceReferenceLow = z.number().finite().optional();
|
||||
const sourceReferenceHigh = z.number().finite().optional();
|
||||
const sourceReferenceText = z
|
||||
.string()
|
||||
.trim()
|
||||
.max(SOURCE_REFERENCE_TEXT_MAX)
|
||||
.optional()
|
||||
.or(z.literal("").transform(() => undefined));
|
||||
|
||||
/**
|
||||
* v1.18.1 — optional link to a user-scoped `Biomarker` (the catalog row).
|
||||
* When set, the structured-entry path resolves the unit + reference range
|
||||
@@ -101,6 +125,9 @@ export const createLabResultSchema = z
|
||||
unit: requiredText(40).optional(),
|
||||
referenceLow,
|
||||
referenceHigh,
|
||||
sourceReferenceLow,
|
||||
sourceReferenceHigh,
|
||||
sourceReferenceText,
|
||||
takenAt: takenAtField,
|
||||
note: optionalNote,
|
||||
source,
|
||||
@@ -142,6 +169,19 @@ export const createLabResultSchema = z
|
||||
message: "referenceLow must not exceed referenceHigh",
|
||||
path: ["referenceLow"],
|
||||
},
|
||||
)
|
||||
// Same guard on the source window. A transposed window is a client error
|
||||
// here for the same reason: nothing downstream can tell it from a real one.
|
||||
.refine(
|
||||
(d) =>
|
||||
d.valueText !== undefined ||
|
||||
d.sourceReferenceLow === undefined ||
|
||||
d.sourceReferenceHigh === undefined ||
|
||||
d.sourceReferenceLow <= d.sourceReferenceHigh,
|
||||
{
|
||||
message: "sourceReferenceLow must not exceed sourceReferenceHigh",
|
||||
path: ["sourceReferenceLow"],
|
||||
},
|
||||
);
|
||||
|
||||
export type CreateLabResultInput = z.infer<typeof createLabResultSchema>;
|
||||
@@ -164,6 +204,9 @@ export const updateLabResultSchema = z
|
||||
unit: requiredText(40).optional(),
|
||||
referenceLow: referenceLow.or(z.null()),
|
||||
referenceHigh: referenceHigh.or(z.null()),
|
||||
sourceReferenceLow: sourceReferenceLow.or(z.null()),
|
||||
sourceReferenceHigh: sourceReferenceHigh.or(z.null()),
|
||||
sourceReferenceText: sourceReferenceText.or(z.null()),
|
||||
takenAt: takenAtField.optional(),
|
||||
note: optionalNote.or(z.null()),
|
||||
})
|
||||
@@ -183,6 +226,18 @@ export const updateLabResultSchema = z
|
||||
message: "referenceLow must not exceed referenceHigh",
|
||||
path: ["referenceLow"],
|
||||
},
|
||||
)
|
||||
.refine(
|
||||
(d) =>
|
||||
d.sourceReferenceLow === undefined ||
|
||||
d.sourceReferenceLow === null ||
|
||||
d.sourceReferenceHigh === undefined ||
|
||||
d.sourceReferenceHigh === null ||
|
||||
d.sourceReferenceLow <= d.sourceReferenceHigh,
|
||||
{
|
||||
message: "sourceReferenceLow must not exceed sourceReferenceHigh",
|
||||
path: ["sourceReferenceLow"],
|
||||
},
|
||||
);
|
||||
|
||||
export type UpdateLabResultInput = z.infer<typeof updateLabResultSchema>;
|
||||
@@ -246,7 +301,12 @@ export type ListLabResultsInput = z.infer<typeof listLabResultsSchema>;
|
||||
* implementation. Re-exported here for the existing import sites.
|
||||
*/
|
||||
export {
|
||||
classifyAgainstEffectiveRange,
|
||||
classifyReferenceRange,
|
||||
formatReferenceRange,
|
||||
resolveEffectiveReferenceRange,
|
||||
type EffectiveReferenceRange,
|
||||
type ReferenceRangeOrigin,
|
||||
type ReferenceRangeStatus,
|
||||
type SourceReferenceRange,
|
||||
} from "@/lib/labs/reference-range";
|
||||
Reference in new issue
Block a user