mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
fix(labs): store the catalog slug as a marker's analyte key
The matcher stored the registry entry's key, so LDL and HDL markers were recorded as total-cholesterol. The key now names what the marker is (the catalog slug, or a registry-only key for an analyte with no seed), and the registry resolves ldl and hdl to the cholesterol entry for the conversion as before. A key already written in the old form is rewritten by the same fill step that brings a stale key in line with its name, the first time a reading needs it. The OpenAPI descriptions say what the key is and that it follows the marker's name and unit.
This commit is contained in:
1 parent
cd1449e63e
commit
07f09e1b61
11 files changed
+91
-33
No files matched your search
@@ -30,7 +30,7 @@ export interface OcrReviewRow {
|
||||
* Save when it can be (see `readingConversion`) and cannot be saved when not.
|
||||
*/
|
||||
markerUnit: string | null;
|
||||
/** Which analyte the matched marker is (conversion registry key), or null. */
|
||||
/** Which analyte the matched marker is (catalog slug or registry-only key), or null. */
|
||||
markerAnalyteKey: string | null;
|
||||
/** Non-null when the server flagged a likely duplicate of a live reading. */
|
||||
duplicateOf: string | null;
|
||||
|
||||
@@ -75,7 +75,7 @@ export interface BiomarkerDto {
|
||||
panel: string | null;
|
||||
/**
|
||||
* Which analyte the marker is, for converting a reading in another unit
|
||||
* (the unit-conversion registry's key), or null when it is none that is known.
|
||||
* (the catalog slug, e.g. `ldl`, or a registry-only key such as `urea`), or null when it is none that is known.
|
||||
*/
|
||||
analyteKey: string | null;
|
||||
hasContext: boolean;
|
||||
|
||||
@@ -26,15 +26,22 @@ describe("the catalog's own names, in every language", () => {
|
||||
["Gesamtcholesterin", "mg/dL", "total-cholesterol"],
|
||||
["Cholestérol total", "mmol/L", "total-cholesterol"],
|
||||
["LDL cholesterol", "mg/dL", "ldl"],
|
||||
["LDL-Cholesterin", "mg/dL", "total-cholesterol"],
|
||||
["Colesterolo HDL", "mg/dL", "total-cholesterol"],
|
||||
["LDL-Cholesterin", "mg/dL", "ldl"],
|
||||
["Colesterolo HDL", "mg/dL", "hdl"],
|
||||
["Vitamin D (25-OH)", "ng/mL", "vitamin-d"],
|
||||
["Créatinine", "mg/dL", "creatinine"],
|
||||
["Kreatynina", "µmol/L", "creatinine"],
|
||||
])("%s (%s) is %s", (name, unit, key) => {
|
||||
// LDL and HDL are aliases of cholesterol: the registry reports one key.
|
||||
const expected = analyteConversionFor(key)!.key;
|
||||
expect(match(name, unit)).toBe(expected);
|
||||
// The key is the catalog slug the name belongs to, so LDL and HDL keep
|
||||
// their own identity even though they convert with the cholesterol entry.
|
||||
expect(match(name, unit)).toBe(key);
|
||||
});
|
||||
|
||||
it("resolves an LDL or HDL key to the cholesterol entry for the conversion", () => {
|
||||
expect(match("LDL cholesterol", "mg/dL")).toBe("ldl");
|
||||
expect(analyteConversionFor("ldl")?.key).toBe("total-cholesterol");
|
||||
expect(analyteConversionFor("hdl")?.key).toBe("total-cholesterol");
|
||||
expect(analyteAcceptsUnit("ldl", "mmol/L")).toBe(true);
|
||||
});
|
||||
|
||||
it("recognises a seed typed without the app's gloss", () => {
|
||||
@@ -65,7 +72,7 @@ describe("typed names", () => {
|
||||
|
||||
it("folds case, accents, hyphens and spacing the same on both sides", () => {
|
||||
expect(match(" HARNSÄURE ", "mg/dL")).toBe("uric-acid");
|
||||
expect(match("ldl-c", "mg/dL")).toBe(analyteConversionFor("ldl")!.key);
|
||||
expect(match("ldl-c", "mg/dL")).toBe("ldl");
|
||||
expect(match("blood urea-nitrogen", "mg/dL")).toBe("blood-urea-nitrogen");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,8 +12,12 @@
|
||||
* - the marker's own unit must be one the analyte reaches, so a marker called
|
||||
* "Glucose" in a unit the registry cannot convert is left unlinked.
|
||||
*
|
||||
* Returns the registry key, or null. Pure apart from reading the message
|
||||
* bundles once.
|
||||
* Returns the catalog slug the name was recognised as (`ldl`, `hdl`,
|
||||
* `fasting-glucose`), or the registry-only key for an analyte with no seed
|
||||
* (`urea`, `uric-acid`), or null. LDL stays `ldl` even though the registry
|
||||
* converts it with the cholesterol entry: the key says which marker this is,
|
||||
* and `analyteConversionFor` resolves it to its entry. Pure apart from reading
|
||||
* the message bundles once.
|
||||
*/
|
||||
import { foldForMatch } from "@/lib/i18n/fold-for-match";
|
||||
import { localisedValues } from "@/lib/i18n/shared-resolve";
|
||||
@@ -30,7 +34,7 @@ function withoutGloss(folded: string): string {
|
||||
let cache: ReadonlyMap<string, string> | null = null;
|
||||
|
||||
/**
|
||||
* folded name -> registry key, with every name two analytes claim removed.
|
||||
* folded name -> analyte key, with every name two keys claim removed.
|
||||
* Exported for the guard test, which asserts nothing was dropped silently.
|
||||
*/
|
||||
export function analyteNameIndex(): {
|
||||
@@ -47,18 +51,16 @@ export function analyteNameIndex(): {
|
||||
};
|
||||
|
||||
for (const seed of BIOMARKER_CATALOG) {
|
||||
const entry = analyteConversionFor(seed.slug);
|
||||
if (!entry) continue;
|
||||
if (!analyteConversionFor(seed.slug)) continue;
|
||||
for (const name of localisedValues(`labs.catalog.${seed.slug}`)) {
|
||||
claim(name, entry.key);
|
||||
claim(name, seed.slug);
|
||||
// The bare name too, for a marker typed without the app's gloss.
|
||||
claim(withoutGloss(foldForMatch(name)), entry.key);
|
||||
claim(withoutGloss(foldForMatch(name)), seed.slug);
|
||||
}
|
||||
}
|
||||
for (const { key, names } of ANALYTE_ALIASES) {
|
||||
const entry = analyteConversionFor(key);
|
||||
if (!entry) continue;
|
||||
for (const name of names) claim(name, entry.key);
|
||||
if (!analyteConversionFor(key)) continue;
|
||||
for (const name of names) claim(name, key);
|
||||
}
|
||||
|
||||
const index = new Map<string, string>();
|
||||
|
||||
@@ -279,7 +279,9 @@ export const BIOMARKER_PANELS = [
|
||||
|
||||
/**
|
||||
* Names a marker may carry for an analyte the unit-conversion registry knows
|
||||
* (`src/lib/labs/unit-conversion/registry.ts`), keyed by the registry's key.
|
||||
* (`src/lib/labs/unit-conversion/registry.ts`), keyed by the analyte key a
|
||||
* marker stores: the catalog slug (`ldl`, `hdl`, `fasting-glucose`), or a
|
||||
* registry-only key for an analyte with no seed.
|
||||
*
|
||||
* A `Biomarker` row keeps only its name, not the seed it came from, so this is
|
||||
* how a marker is told which analyte it is: its folded name equals one of these,
|
||||
@@ -296,7 +298,7 @@ export const BIOMARKER_PANELS = [
|
||||
* names live only here.
|
||||
*/
|
||||
export interface AnalyteAliases {
|
||||
/** The registry key of the analyte. */
|
||||
/** The analyte key: a catalog slug, or a registry-only key. */
|
||||
key: string;
|
||||
names: readonly string[];
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export interface ResolvedBiomarker {
|
||||
lowerBound: number | null;
|
||||
upperBound: number | null;
|
||||
panel: string | null;
|
||||
/** Which analyte the marker is (unit-conversion registry key), if known. */
|
||||
/** Which analyte the marker is (catalog slug or registry-only key), if known. */
|
||||
analyteKey?: string | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ const biomarkerRow = z
|
||||
.meta({
|
||||
id: "Biomarker",
|
||||
description:
|
||||
"A user-scoped catalog marker. `context` is the decrypted per-marker note (or null); `hasContext` flags its presence. `analyteKey` is the analyte the marker was recognised as (the unit-conversion registry's key, e.g. `fasting-glucose`), or null when its name matches none; a reading in another unit is converted only for a marker that has one. `hidden` drops the marker from the active catalog list and the lab-entry pickers without deleting it (its readings and unit/range survive). A `LabResult` linking this marker resolves its unit + reference bounds from here.",
|
||||
"A user-scoped catalog marker. `context` is the decrypted per-marker note (or null); `hasContext` flags its presence. `analyteKey` is the analyte the marker was recognised as: the catalog slug its name belongs to (e.g. `fasting-glucose`, `ldl`, `hdl`), or a key of the unit-conversion registry for an analyte with no catalog seed (e.g. `urea`, `uric-acid`), or null when its name matches none. It is derived from `name` and `unit`, never chosen: it is set on create and re-derived (or cleared) when an update changes either. A reading in another unit is converted only for a marker that has one. `hidden` drops the marker from the active catalog list and the lab-entry pickers without deleting it (its readings and unit/range survive). A `LabResult` linking this marker resolves its unit + reference bounds from here.",
|
||||
});
|
||||
|
||||
const listResponse = z
|
||||
|
||||
@@ -79,7 +79,7 @@ const extractedRow = z
|
||||
}),
|
||||
markerAnalyteKey: z.string().nullable().meta({
|
||||
description:
|
||||
"The analyte the matched marker is (the unit-conversion registry's key, e.g. `fasting-glucose`), or null when the marker does not exist yet or its name matches none. With `markerUnit` it tells a client whether a row in another unit can be converted on commit.",
|
||||
"The analyte the matched marker is (the catalog slug, e.g. `fasting-glucose` or `ldl`, or a registry-only key such as `urea`), or null when the marker does not exist yet or its name matches none. With `markerUnit` it tells a client whether a row in another unit can be converted on commit.",
|
||||
}),
|
||||
duplicateOf: z.string().nullable(),
|
||||
})
|
||||
|
||||
@@ -132,7 +132,7 @@ export interface OcrExtractedRowDto {
|
||||
*/
|
||||
markerUnit: string | null;
|
||||
/**
|
||||
* Which analyte the matched marker is (unit-conversion registry key), or null
|
||||
* Which analyte the matched marker is (catalog slug or registry-only key), or null
|
||||
* when the marker is new or none is known. With `markerUnit` it tells the
|
||||
* review screen whether a row in another unit can be converted on Save.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user