mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
Restore what each medication was supposed to move
The register said the drug comes back with no statement of what it was for, which is exactly what a pinned efficacy target is: a row exists ONLY where the person overrode the derived ATC / name resolution, so it is a decision and never a computation. Nothing recomputes it after a restore because there is nothing to recompute it from. The lab arm travels as a biomarker NAME rather than an id, the way a lab result's cross-reference already does. A portable restore mints a fresh catalogue id, and `Biomarker` is unique on `(userId, name)`, so the name is the only end of the reference that survives the trip. The obstacle was ordering, not machinery: the remap map already existed, but the drugs are rebuilt several hundred lines before the analytes are. The targets are collected against the id each drug actually got and written in a second pass once the biomarkers exist. The alternative was hoisting the biomarker restore above the medications, and it loses on two counts. `biomarkerByName` and `restoredBiomarkerIds` are read by the lab-result loop immediately below the block, so moving it would separate a map from its only other consumer and leave a later edit free to re-order them back with nothing noticing. And the dependency would read backwards: a pinned target is a leaf of the medication tree, and letting a leaf dictate where the clinical record gets rebuilt inverts what the file is about. The second pass states the constraint where it applies instead of hiding it in a line order. A named analyte the restore did not put back is dropped and reported as medicationTarget rather than written. `biomarkerId` is a real foreign key, so a dangling value would fail the constraint and cost the whole account over one override. Dropping beats nulling here because the override IS the reference: a row with neither arm set is what the resolver already reads as "no override", so keeping it would restore something that means nothing while still holding the primary slot. The round trip asserts the restored target points at the biomarker the restore itself wrote, resolved from the database rather than remembered from the fixture. Counting would have passed on a row whose biomarkerId came back null, and nothing else would have complained: the resolver falls back to deriving a target and the page still renders.
This commit is contained in:
1 parent
14638a6660
commit
768320f3d3
14 files changed
+246
-9
No files matched your search
@@ -566,6 +566,17 @@ const handler = apiHandler(
|
||||
|
||||
const medByName = new Map<string, string>();
|
||||
const restoredMedicationIds = new Set<string>();
|
||||
// The efficacy targets cannot ride inside the medication create
|
||||
// beside the schedules and the packs: `biomarkerId` is a foreign key
|
||||
// into a catalogue this transaction has not restored yet. They are
|
||||
// collected here against the id the drug ACTUALLY got and written in
|
||||
// a second pass once the biomarkers exist; the pass itself says why
|
||||
// hoisting the biomarker restore above the medications is the worse
|
||||
// of the two orderings.
|
||||
const pendingEfficacyTargets: Array<{
|
||||
medicationId: string;
|
||||
target: (typeof payload.medications)[number]["efficacyTargets"][number];
|
||||
}> = [];
|
||||
for (const m of payload.medications) {
|
||||
const created = await tx.medication.create({
|
||||
data: {
|
||||
@@ -771,6 +782,12 @@ const handler = apiHandler(
|
||||
});
|
||||
restoredMedicationIds.add(created.id);
|
||||
if (!medByName.has(m.name)) medByName.set(m.name, created.id);
|
||||
for (const target of m.efficacyTargets) {
|
||||
pendingEfficacyTargets.push({
|
||||
medicationId: created.id,
|
||||
target,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.intakeEvents.length > 0) {
|
||||
@@ -1230,6 +1247,65 @@ const handler = apiHandler(
|
||||
// `committedRecordId` names the lab result it was committed to, and
|
||||
// that reference is resolved against the rows this loop writes.
|
||||
const restoredLabResultIds = new Set<string>();
|
||||
// What each drug was supposed to move, written now that both ends
|
||||
// exist. The drugs came back several hundred lines up and the
|
||||
// analytes only just now, and one of those two had to move for a
|
||||
// target to reach the database at all.
|
||||
//
|
||||
// The second pass wins over hoisting the biomarker restore above the
|
||||
// medications, for two reasons. `biomarkerByName` and
|
||||
// `restoredBiomarkerIds` are read by the lab-result loop directly
|
||||
// below, so moving the block would separate a map from its only
|
||||
// other consumer and leave a future edit free to re-order them back
|
||||
// without anything noticing. And the direction of the dependency
|
||||
// would read backwards: a pinned target is a leaf of the medication
|
||||
// tree, and letting a leaf dictate where the clinical record is
|
||||
// rebuilt inverts what the file is about. A second pass states the
|
||||
// constraint where it applies instead of hiding it in a line order.
|
||||
//
|
||||
// A named analyte the restore did not put back cannot be written:
|
||||
// `biomarkerId` is a real foreign key, so a dangling value would
|
||||
// fail the constraint and roll the entire account back over one
|
||||
// override. The row is dropped and NAMED. Dropping rather than
|
||||
// nulling is deliberate here: the override IS the reference, and a
|
||||
// row with neither arm set is what the resolver already reads as "no
|
||||
// override", so keeping it would restore something that means
|
||||
// nothing while still claiming the primary slot. A target that
|
||||
// carried no name in the first place is a genuine live state (the
|
||||
// analyte was deleted before the export) and comes back as written.
|
||||
const unresolvedTargetAnalytes: string[] = [];
|
||||
const writableTargets = pendingEfficacyTargets.filter((pending) => {
|
||||
const name = pending.target.biomarkerName;
|
||||
if (!name || biomarkerByName.has(name)) return true;
|
||||
unresolvedTargetAnalytes.push(name);
|
||||
return false;
|
||||
});
|
||||
if (writableTargets.length > 0) {
|
||||
await tx.medicationEfficacyTarget.createMany({
|
||||
data: writableTargets.map(({ medicationId, target }) => ({
|
||||
...(target.id ? { id: target.id } : {}),
|
||||
medicationId,
|
||||
measurementType: target.measurementType ?? null,
|
||||
biomarkerId: target.biomarkerName
|
||||
? (biomarkerByName.get(target.biomarkerName) ?? null)
|
||||
: null,
|
||||
primary: target.primary ?? true,
|
||||
...(target.createdAt
|
||||
? { createdAt: new Date(target.createdAt) }
|
||||
: {}),
|
||||
...(target.updatedAt
|
||||
? { updatedAt: new Date(target.updatedAt) }
|
||||
: {}),
|
||||
})),
|
||||
});
|
||||
}
|
||||
recordUnknownKeys(
|
||||
skips,
|
||||
"medicationTarget",
|
||||
[...new Set(unresolvedTargetAnalytes)],
|
||||
unresolvedTargetAnalytes,
|
||||
);
|
||||
|
||||
for (const lab of payload.labResults) {
|
||||
const biomarkerId =
|
||||
lab.biomarkerId !== undefined
|
||||
|
||||
Reference in new issue
Block a user