@pablotech/akesi 0.1.32 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dates.ts CHANGED
@@ -27,7 +27,7 @@ export function formatDay(iso: string | undefined | null): string {
27
27
  return `${MONTHS[Number(m) - 1]} ${Number(d)}, ${y}`;
28
28
  }
29
29
 
30
- // M107 — a type="date" input fires `change` on every keystroke inside an already-plausible year
30
+ // A type="date" input fires `change` on every keystroke inside an already-plausible year
31
31
  // segment, zero-padded (e.g. typing just "2" of "2026" reports "0002-08-15") — not only once the
32
32
  // full year is typed. A plain truthiness/length check on `value` fires on that first padded digit.
33
33
  // Require a 4-digit year that isn't itself a padding artifact (<1000) before treating it as done.
@@ -22,7 +22,7 @@ export interface FindingAIResponse {
22
22
  };
23
23
  /** Leaf-owned: absent from a core response, present on an older stored finding. */
24
24
  studyResults?: { study: string; result: string; group: string }[];
25
- // M92 — no `noteId`/label field here: a note has no short verbatim-echoable label like Study's
25
+ // No `noteId`/label field here: a note has no short verbatim-echoable label like Study's
26
26
  // `focus`, so entries are paired back to `factors.noteEntries` by array position, not content.
27
27
  // See assembleFinding's `meta.noteIds` (the caller supplies the parallel id array).
28
28
  /** Leaf-owned: absent from a core response, present on an older stored finding. */
@@ -673,7 +673,7 @@ export function assembleFinding(
673
673
  inputsHash: string;
674
674
  nodeHashes: Record<string, string>;
675
675
  generatedBy: ClientFinding["generatedBy"];
676
- // M92 — the ordered ids of the Note entries the prompt presented (populatedNoteEntries), zipped
676
+ // The ordered ids of the Note entries the prompt presented (populatedNoteEntries), zipped
677
677
  // positionally against parsed.noteResults since a note has no label the LLM could echo back.
678
678
  noteIds: string[];
679
679
  // 06/Gap A — injected rather than looked up here, so this module carries no dependency on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pablotech/akesi",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "Schema-constrained clinical reasoning over lab and marker data, where every constraint stated to the model in prose is re-enforced in code on the response.",
5
5
  "repository": {
6
6
  "type": "git",
package/pinned-queries.ts CHANGED
@@ -61,7 +61,7 @@ export function pinnedQueries(client: Client): PinnedQuery[] {
61
61
  if (t) out.push({ section, label: clip(t) });
62
62
  };
63
63
 
64
- // M74 reversed, on the owner's instruction (2026-08-20): a ratio name used to be barred from the
64
+ // On the owner's instruction (2026-08-20): a ratio name used to be barred from the
65
65
  // prompt outright. As an area of query it is safe and useful — "look at the TG/HDL ratio" is a
66
66
  // topic, not a reading — and the block below is explicit that it is not data.
67
67
  for (const name of client.pinnedRatios ?? []) push("Marker ratios", name);
package/report-merge.ts CHANGED
@@ -297,7 +297,7 @@ export interface SourceEditPatch {
297
297
  date: string;
298
298
  }
299
299
 
300
- // M66 P3 — patch ONE source and its linked DiseaseEntry rows (by position, in sourceId order) in
300
+ // Patch ONE source and its linked DiseaseEntry rows (by position, in sourceId order) in
301
301
  // one mutation, keyed by sourceId — the patch counterpart to removeSource's drop. `diseasePatches`
302
302
  // must line up 1:1 with diseasesForSource(sourceId)'s current order; a short/mismatched array is
303
303
  // ignored past its own length (no add/remove of diagnoses here). Raw values in — normalizeClientDraft
@@ -12,7 +12,7 @@ import { formatDay } from "./dates";
12
12
 
13
13
  export type Bucket = "past" | "ongoing" | "planned";
14
14
 
15
- // M102 Phase 1 — the display label for a bucket badge (Treatment's Ungrouped view, which
15
+ // The display label for a bucket badge (Treatment's Ungrouped view, which
16
16
  // otherwise concatenates all three buckets with no other visual distinction).
17
17
  export const BUCKET_LABEL: Record<Bucket, string> = { ongoing: "Ongoing", planned: "Planned", past: "Past" };
18
18
 
@@ -73,7 +73,7 @@ export function collapseByName(items: TreatmentItem[]): TreatmentItem[] {
73
73
  id: latest.id,
74
74
  name: latest.name,
75
75
  dose: latest.dose,
76
- // M104 — carry the structured dose fields through too, same "latest row wins" rule as `dose`
76
+ // Carry the structured dose fields through too, same "latest row wins" rule as `dose`
77
77
  // itself; formatDose() prefers these over `dose`, so dropping them here would silently blank
78
78
  // out any collapsed-view display for a titration step entered through the new Amount/Unit form.
79
79
  doseAmount: latest.doseAmount,
@@ -110,16 +110,16 @@ export interface NamedTreatmentGroup {
110
110
  rows: TreatmentItem[];
111
111
  }
112
112
 
113
- // M108 — primary group order: Planned drugs first, then Ongoing, then Past — recency of use. A
113
+ // Primary group order: Planned drugs first, then Ongoing, then Past — recency of use. A
114
114
  // group's bucket is its NEWEST row's (rows[0], already sorted descending below), so a drug
115
115
  // currently mid-titration reads as Ongoing even if an older row of the same drug once looked
116
- // Planned. M109 — within one bucket, groups sort alphabetically (secondary key), not entry order.
116
+ // Planned. Within one bucket, groups sort alphabetically (secondary key), not entry order.
117
117
  const GROUP_BUCKET_ORDER: Record<Bucket, number> = { planned: 0, ongoing: 1, past: 2 };
118
118
 
119
- // M103 — the Medicine audit view's grouping: every raw row for a drug together, unlike
119
+ // The Medicine audit view's grouping: every raw row for a drug together, unlike
120
120
  // collapseByName which discards all but one representative row per name.
121
121
  //
122
- // M105 — rows sorted newest-first (start descending): the table must always read newest-to-oldest,
122
+ // Rows sorted newest-first (start descending): the table must always read newest-to-oldest,
123
123
  // top to bottom. dateGaps() below is written against this same descending order.
124
124
  export function groupByName(items: TreatmentItem[], today: string): NamedTreatmentGroup[] {
125
125
  const byName = new Map<string, TreatmentItem[]>();
@@ -140,31 +140,31 @@ export function groupByName(items: TreatmentItem[], today: string): NamedTreatme
140
140
 
141
141
  export type DateGapVerdict = "ok" | "gap" | "overlap";
142
142
 
143
- // M104 — whole-day distance between two full YYYY-MM-DD dates (nextStart - end), or null if
143
+ // Whole-day distance between two full YYYY-MM-DD dates (nextStart - end), or null if
144
144
  // either isn't full day precision (legacy month/year-only can't do calendar-day arithmetic).
145
145
  function daysBetween(a: string, b: string): number | null {
146
146
  if (!/^\d{4}-\d{2}-\d{2}$/.test(a) || !/^\d{4}-\d{2}-\d{2}$/.test(b)) return null;
147
147
  return Math.round((Date.parse(b) - Date.parse(a)) / 86400000);
148
148
  }
149
149
 
150
- // M103 — one verdict per consecutive pair in an already-sorted `rows` (see groupByName), comparing
150
+ // One verdict per consecutive pair in an already-sorted `rows` (see groupByName), comparing
151
151
  // the OLDER row's end against the NEWER row's start: no end (still ongoing) or an unknown start
152
152
  // can't be judged, so those read "ok" rather than a false positive. Length is rows.length - 1.
153
153
  //
154
- // M104 — a titration step ending 2026-06-30 with the next starting 2026-07-01 is back-to-back
154
+ // A titration step ending 2026-06-30 with the next starting 2026-07-01 is back-to-back
155
155
  // coverage, not a gap: prefer day-precision arithmetic (0 or 1 day apart both read "ok") over the
156
156
  // cmp() prefix comparison, which only recognized an exact same-day handoff as contiguous. Falls
157
157
  // back to cmp() when either date isn't full day precision.
158
158
  //
159
- // M105 — `rows` is newest-first (groupByName sorts descending), so for the pair at (i, i+1), i+1
159
+ // `rows` is newest-first (groupByName sorts descending), so for the pair at (i, i+1), i+1
160
160
  // is the OLDER row and i is the NEWER one — verdict[i] describes the gap/overlap between them.
161
161
  //
162
- // M106 — symmetric with the gap side: a 1-day overlap (the newer row starts the day before the
162
+ // Symmetric with the gap side: a 1-day overlap (the newer row starts the day before the
163
163
  // older one's recorded end — a rounding/entry-day wobble, not a real double-dosing period) also
164
164
  // reads "ok". Only a 2+ day overlap is worth flagging. This only applies to the day-precision
165
165
  // branch — the cmp() fallback (month/year-only dates) has no day-scale magnitude to be lenient about.
166
166
  //
167
- // M110 — an AM row and a PM row covering the same dates aren't double-dosing, they're a twice-daily
167
+ // An AM row and a PM row covering the same dates aren't double-dosing, they're a twice-daily
168
168
  // split — only downgrades a would-be "overlap" (a real gap between an AM and a PM step is still a
169
169
  // gap; this isn't a blanket "ignore timing" exemption).
170
170
  function splitByTiming(a: TreatmentItem, b: TreatmentItem): boolean {
@@ -198,7 +198,7 @@ export function dateGaps(rows: TreatmentItem[]): DateGapVerdict[] {
198
198
  // The verbatim label a PLANNED treatment presents to the Finding (Patient Plan Action, treatmentGroups
199
199
  // patient ref, planAssessmentRows key) — its name plus dose when present. Must be identical everywhere
200
200
  // the ref is matched (finding-generate input block, finding-assemble expected set, treatment-groups resolve).
201
- // M104 — the display string for a dose: structured amount/unit/frequency when the record has been
201
+ // The display string for a dose: structured amount/unit/frequency when the record has been
202
202
  // entered/edited through the new form, reproducing the pre-existing "6mg/week" convention so
203
203
  // treatmentLabel()'s output (and everything matched against it) doesn't change shape; falls back to
204
204
  // the legacy free-text `dose` string for anything a host has not yet migrated.
@@ -1,4 +1,4 @@
1
- // M54/4 — treatment add-flow intake: read what the user actually has about a product — photos of a
1
+ // Treatment add-flow intake: read what the user actually has about a product — photos of a
2
2
  // bottle/package/label, or the product sheet as pasted text — and propose a record. Pure of
3
3
  // Node/process/env, mirroring report-extract.ts's shape. The Anthropic client is INJECTED and the
4
4
  // model is a required arg (no default), so this module never touches process.env or the config.
package/types.ts CHANGED
@@ -128,7 +128,7 @@ export interface TreatmentItem {
128
128
  id: string;
129
129
  pinned?: boolean;
130
130
  name: string;
131
- // M104 — superseded by doseAmount/doseUnit/doseFrequency (below) for anything entered through the
131
+ // Superseded by doseAmount/doseUnit/doseFrequency (below) for anything entered through the
132
132
  // edit form; kept as the display/matching fallback for un-migrated records (see formatDose() in
133
133
  // treatment-bucket.ts, and whatever one-time conversion a host runs).
134
134
  dose?: string;
@@ -429,15 +429,15 @@ export interface ClientFinding {
429
429
  // findings without it still render (StudyResults falls back to a flat list).
430
430
  studyResults?: { study: string; result: string; group?: string }[];
431
431
  // One inference per populated Note row (factors.noteEntries); keyed by `noteId` rather than a
432
- // label (M92 — unlike Study's short hand-picked `focus` labels, a note's `text` is unbounded
433
- // free prose, unreliable for the LLM to echo back verbatim for pairing).
432
+ // label — unlike Study's short hand-picked `focus` labels, a note's `text` is unbounded
433
+ // free prose, unreliable for the LLM to echo back verbatim for pairing.
434
434
  noteResults?: { noteId: string; result: string; group?: string }[];
435
- // M94/M97 §C — one inference per known allergy (factors.allergies) / family history entry
435
+ // One inference per known allergy (factors.allergies) / family history entry
436
436
  // (factors.familyHistory), keyed by id like noteResults, paired by array position (no natural
437
437
  // unique label to echo back).
438
438
  allergyResults?: { allergyId: string; result: string; group?: string }[];
439
439
  familyResults?: { familyId: string; result: string; group?: string }[];
440
- // M102 — mirrors allergyResults/familyResults exactly, for Reports diagnoses (factors.diseases),
440
+ // Mirrors allergyResults/familyResults exactly, for Reports diagnoses (factors.diseases),
441
441
  // which previously had no AI-paired leaf at all.
442
442
  diseaseResults?: { diseaseId: string; result: string; group?: string }[];
443
443
  disease: { group: string; finding: string }[];
package/unit-systems.ts CHANGED
@@ -36,7 +36,7 @@ const IMPERIAL_TO_METRIC: Record<string, { unit: string; factor: number }> = {
36
36
  "in³": { unit: "cm³", factor: 16.387064 },
37
37
  };
38
38
 
39
- // M93 — markers where the metric unit IS the US-prevailing clinical convention, so the
39
+ // Markers where the metric unit IS the US-prevailing clinical convention, so the
40
40
  // generic PHYSICAL_TO_IMPERIAL unit-keyed table must NOT convert them (e.g. visceral adipose
41
41
  // tissue mass is reported in grams in US practice too, never ounces/lb — the owner's example,
42
42
  // extended here to the sibling DEXA body-composition markers sharing the same convention).
@@ -49,7 +49,7 @@ export const PHYSICAL_NO_CONVERT = new Set<string>([
49
49
  "Subcutaneous adipose tissue area",
50
50
  ]);
51
51
 
52
- // M93 — cosmetic unit-STRING variants for the same physical unit, straight from different lab
52
+ // Cosmetic unit-STRING variants for the same physical unit, straight from different lab
53
53
  // source formats (values stay as-reported per the module doc above; only the DISPLAYED label is
54
54
  // canonicalized, never the stored data). eGFR renders identically regardless of which lab
55
55
  // formatted its unit string.
@@ -61,7 +61,7 @@ export function canonicalUnit(unit: string): string {
61
61
  return UNIT_ALIAS[unit] ?? unit;
62
62
  }
63
63
 
64
- // M93 — compound clinical units that are, on research, reported identically in US and SI/
64
+ // Compound clinical units that are, on research, reported identically in US and SI/
65
65
  // international practice — no US-customary form exists in routine use, so no conversion ever
66
66
  // applies regardless of marker. NEEDS VERIFICATION against a clinical reference before treating
67
67
  // as final; each entry's marker(s) are noted so a reviewer can check the specific convention.
@@ -84,7 +84,7 @@ export interface AnalyteRule {
84
84
  }
85
85
  export const ANALYTE: Record<string, AnalyteRule> = {
86
86
  // Lipid/glucose panel — mirrors the in-repo ingest table (parsers/healthmatters.ts
87
- // NORMALIZE, in production since W1); 1/k equals that table's factor (cross-checked by test).
87
+ // NORMALIZE); 1/k equals that table's factor (cross-checked by test).
88
88
  Glucose: { us: "mg/dL", si: "mmol/L", k: 1 / 18.02 }, // glucose MW 180.16
89
89
  "Estimated Average Glucose (eAG)": { us: "mg/dL", si: "mmol/L", k: 1 / 18.02 },
90
90
  "Total Cholesterol": { us: "mg/dL", si: "mmol/L", k: 1 / 38.67 }, // chol MW 386.65
@@ -133,7 +133,7 @@ export const ANALYTE: Record<string, AnalyteRule> = {
133
133
  // Monovalent electrolytes: mEq/L (US) and mmol/L (SI) are numerically identical (valence 1).
134
134
  "Potassium, Serum (Kalium)": { us: "mEq/L", si: "mmol/L", k: 1 },
135
135
  "Sodium, Serum (Natrium)": { us: "mEq/L", si: "mmol/L", k: 1 },
136
- // M93 — urine albumin/creatinine ratio: US commonly reports mg/g creatinine; UK/Canada/
136
+ // Urine albumin/creatinine ratio: US commonly reports mg/g creatinine; UK/Canada/
137
137
  // Australia and KDIGO's international staging table use mg/mmol creatinine. Factor derived
138
138
  // from creatinine MW 113.12 g/mol (1 g creatinine = 8.84 mmol, so mg/mmol = mg/g × 0.1131).
139
139
  // NEEDS VERIFICATION against a clinical reference before relying on this — flagged per the
@@ -144,7 +144,7 @@ export const ANALYTE: Record<string, AnalyteRule> = {
144
144
 
145
145
  // The units that genuinely differ US↔SI, or need an explicit verified-non-convertible decision —
146
146
  // used by the coverage gate to flag any (marker, unit) pair carrying one of these WITHOUT a rule
147
- // (so a gap can never be silent). M93 extended this beyond molar-concentration units to the
147
+ // (so a gap can never be silent) extended beyond molar-concentration units to the
148
148
  // compound/physical units researched above.
149
149
  export const CONVERTIBLE_UNIT_CLASS = new Set([
150
150
  "mg/dL", "ng/dL", "pg/mL", "µg/dL", "ug/dL", "mcg/dL", "ng/mL",