import type { AnamnesisRecord, MedicationEntry, NumericParameterDef, Visit } from './anamnesis-types'; /** Anthropometric parameter keys map to AnthropometricSet fields; `bmi` is * computed. Kept as a lookup so the grid can treat anthropometrics + labs * uniformly. */ export declare const ANTHRO_FIELD_BY_KEY: Record; /** `Timestamp` options for the record's calendar dates (visit date, date of * birth, cycle start). Every one of them is a date-only ISO string, which * `Date` parses as UTC midnight — rendered with the viewer's zone that shows a * phantom time ("13 Apr 2026, 2:00" in CEST) and, west of UTC, the WRONG DAY. * Dropping the time fields and pinning the zone to UTC makes the calendar date * render as written, identically in every timezone (and so deterministically * in Chromatic). */ export declare const CALENDAR_DATE_FORMAT: Intl.DateTimeFormatOptions; /** `Timestamp` options for a visit stamped with a clock time * (`YYYY-MM-DDTHH:mm`, no zone suffix). `Date` parses that form as LOCAL * time, so the zone is deliberately NOT pinned here: the UTC pin above would * move a 00:30 visit onto the previous day everywhere east of Greenwich. */ export declare const CALENDAR_DATETIME_FORMAT: Intl.DateTimeFormatOptions; /** Whether a stored ISO string carries a clock time (`YYYY-MM-DDTHH:mm…`). * A visit is now an interview with a time stamp, and two interviews of one * module on the same day are two visits — the time is what tells them apart. */ export declare function hasClockTime(iso: string | null | undefined): boolean; /** The `Timestamp` options that render a stored date AS WRITTEN: the UTC pin * for a date-only string, local time for a datetime string. One helper, so a * header cannot pin a datetime to UTC and print the wrong day. */ export declare function calendarFormatFor(iso: string): Intl.DateTimeFormatOptions; /** BMI = kg / m². Thin `undefined`-tolerant wrapper over the kit's canonical * BMI maths — the anthropometric set leaves unmeasured fields `undefined`, * which `bmi-calculator/bmi` (a form-backed component) does not model. The * formula itself lives there and is not restated here. */ export declare function computeBmi(weightKg: number | null | undefined, heightCm: number | null | undefined): number | null; /** Whether a stored ISO string is a date `Date` can actually read. An archive * holds blanks and typos; nothing downstream may throw or print "NaN" on one. */ export declare function isReadableDate(iso: string | null | undefined): boolean; /** Whole-years age at a reference instant, or `null` when either date is * unreadable OR the reference predates the birth — an unknown age is absent, * never `NaN` and never negative. Reference is the record's * own visit date (deterministic for stories / Chromatic), not the wall clock. * Read in UTC because both inputs are date-only strings, which `Date` parses as * UTC midnight: local getters return the previous day west of UTC. */ export declare function ageAt(dobIso: string, referenceIso: string): number | null; /** Where a parameter's value actually lives on a `Visit`. */ export type ParameterStorage = 'computed' | 'anthropometric' | 'dietary' | 'lab'; /** Keys stored in `visit.dietaryIntake`. Explicit, like `ANTHRO_FIELD_BY_KEY`: * the kit's two shipped intake parameters have a drawer of their own, and * anything else a practice adds falls through to the open-ended one below. */ export declare const DIETARY_STORAGE_KEYS: ReadonlySet; /** Resolve a parameter's STORAGE HOME. Read and write branch on this, never on * `def.group`: `AnthropometricSet` is a fixed shape, so a practice-added * anthropometric has no field of its own and must round-trip through the * open-ended `labs` array. Branching on `group` made it read null and store * nothing. `bmi` keeps its explicit guard — `def.computed` is a bare boolean * with no formula registry to dispatch on. * * `labs` stays the FALLBACK, so a key with no drawer of its own still stores * and reads back. There is no such thing as a parameter with no storage home. */ export declare function parameterStorage(def: NumericParameterDef): ParameterStorage; /** The raw numeric value of a parameter at a visit (BMI computed on the fly, * the six shipped anthropometrics read from the set, everything else looked up * in `labs` by key). Non-finite stored values read as absent. */ export declare function parameterValueAt(visit: Visit, def: NumericParameterDef): number | null; /** The MOST RECENT recorded value of a parameter, and the visit it was * recorded at — walking backwards from the newest visit to the first one that * measured it. * * Not the same question as `parameterValueAt(latestVisit, def)`, and the * difference is what a goal tile lives on: a practice that weighs the patient * on the 1st and sees them again on the 10th has a weight on record, and a * tile reading only the newest visit called it "not yet measured". Every * surface that answers "where is this patient now" wants this one; the grid, * which is per-column by construction, wants the other. * * `null` when no visit ever measured it. */ export declare function latestParameterValue(visits: readonly Visit[], def: NumericParameterDef): { value: number; visit: Visit; } | null; /** Write a numeric value onto a visit at its storage home, returning a new * visit. Computed parameters are never written. Pure — the surfaces call it * through their own working-state setters. */ export declare function setNumericOnVisit(visit: Visit, def: NumericParameterDef, value: number | null): Visit; /** Set or clear a per-patient target, returning a new record. `null` clears * it. Pure — the surfaces call it through their own working-state setters, * exactly as they call `setNumericOnVisit`. A non-finite target is not a goal * and clears rather than storing "NaN". */ export declare function setTargetOnRecord(record: AnamnesisRecord, parameterKey: string, target: number | null): AnamnesisRecord; export type RangeFlag = 'low' | 'high' | null; /** Whether a value sits outside its reference range. */ export declare function outOfRange(value: number | null, range: NumericParameterDef['referenceRange']): RangeFlag; export type DeltaDirection = 'up' | 'down' | 'flat'; export type DeltaValence = 'good' | 'bad' | 'neutral'; export interface DeltaInfo { /** Signed change vs the previous visit (current − previous). */ abs: number; direction: DeltaDirection; valence: DeltaValence; } /** Direction-aware good/bad. Uses the parameter's `betterDirection` and, for * `toRange`, the reference range — never the sign of the delta alone. */ export declare function deltaInfo(current: number | null, previous: number | null, def: NumericParameterDef): DeltaInfo | null; export interface ParameterCell { visitId: string; value: number | null; outOfRange: RangeFlag; /** Change vs the immediately preceding visit; null for the first column. */ delta: DeltaInfo | null; } export interface ParameterRow { def: NumericParameterDef; /** One cell per record visit, aligned to `record.visits` order. */ cells: ParameterCell[]; /** ONE ENTRY PER RECORD VISIT, in visit order — the sparkline series. A * `null` is a visit where this parameter was not measured, and it holds its * place: dropping it would compress a four-times-measured lab across the * same width as a thirteen-times-measured weight, and an event annotation * would then land on a different date in each row. */ series: (number | null)[]; /** Per-patient target for this parameter, when one is configured. */ target: number | null; } /** Build the transposed grid: one row per parameter definition, one cell per * visit, with derived deltas / out-of-range flags / sparkline series. */ export declare function buildParameterRows(record: AnamnesisRecord, defs: NumericParameterDef[]): ParameterRow[]; export interface NumericChange { def: NumericParameterDef; from: number | null; to: number | null; delta: DeltaInfo | null; } export interface PathologyChange { code: string; change: 'added' | 'removed'; } export interface MedicationChange { entry: MedicationEntry; change: 'added' | 'removed' | 'changed'; previous?: MedicationEntry; } export type TextField = 'nutritionalProposal' | 'clinicalNotes'; /** The free-text fields a visit carries, in diff order. */ export declare const TEXT_FIELDS: readonly TextField[]; export interface TextChange { field: TextField; from?: string; to?: string; } export interface VisitDiff { from: Visit; to: Visit; numeric: NumericChange[]; pathologies: PathologyChange[]; medications: MedicationChange[]; texts: TextChange[]; } /** Everything that changed between two visits — numeric parameters plus the * non-numeric fields (pathologies added/removed, medications added/removed/ * changed, free-text notes edited). Only entries that actually differ are * returned, so compare mode can mark exactly the changed cells. */ export declare function compareVisits(from: Visit, to: Visit, defs: NumericParameterDef[]): VisitDiff; export interface NumericSpanChange { def: NumericParameterDef; /** One entry per compared visit, rounded to the parameter's precision. */ values: (number | null)[]; /** Net change, first compared visit → last. Null when either end is absent. */ delta: DeltaInfo | null; } export interface PathologySpanChange { code: string; /** Whether the code was assigned at each compared visit. */ present: boolean[]; } export interface MedicationSpanChange { id: string; /** The entry at each compared visit; `undefined` where it was not carried. */ entries: (MedicationEntry | undefined)[]; } export interface TextSpanChange { field: TextField; /** The field's text at each compared visit, trimmed; '' where absent. */ values: string[]; } export interface SpanDiff { /** The compared visits, in the order given (record order). Never below two. */ visits: Visit[]; numeric: NumericSpanChange[]; pathologies: PathologySpanChange[]; medications: MedicationSpanChange[]; texts: TextSpanChange[]; } /** Everything that is not constant across two or more visits. `null` below * two visits — there is no span to read, and an empty diff would claim there * was nothing to report. */ export declare function compareVisitSpan(visits: readonly Visit[], defs: NumericParameterDef[]): SpanDiff | null; /** The newest visit — the editable one in compile mode, and the record's one * reference instant for anything dated against "now": the patient's age, the * header's visit date. Every surface must read it from here. Intake used to * date itself to `visits[0]` while compile and read-only used the last visit, * which printed two different ages for the same patient. */ export declare function latestVisit(record: AnamnesisRecord): Visit | undefined; /** The one string every absent value renders as. Never a bare hyphen, never a * blank, never "NaN". */ export declare const ABSENT_VALUE = "\u2014"; /** Format a numeric value to its parameter precision in the app locale (a * comma decimal under `it`), or an em dash when absent. `0` formats as a * value; a non-finite number is absent, because `Intl` would print "NaN" and * that must never reach a cell. No grouping: "1234,5" is a lab value, not a * sum of money. Callers append the unit separately. */ export declare function formatValue(value: number | null, precision?: number, locale?: string): string; /** Localised medium date (e.g. "12 May 2025") from an ISO date string. * @deprecated Pins neither timezone nor locale. Use `visitDateLabel` * (anamnesis-format.ts) for anything date-only; it is the reason this has no * callers left. */ export declare function formatDateMedium(iso: string): string; /** Localised calendar date from a date-only ISO string, pinned to UTC through * `CALENDAR_DATE_FORMAT`. Prefer `visitDateLabel` (anamnesis-format.ts), which * resolves `locale` from the surface's `t` — omitting it falls back to the * BROWSER locale, which disagrees with `` on the same screen. * An unreadable date renders the em dash: `Intl` THROWS on one, which would * take down the whole evolution surface rather than blank one label. */ export declare function formatCalendarDate(iso: string, locale?: string): string; /** Localised calendar date + short time from a `YYYY-MM-DDTHH:mm` string * (e.g. "25 ago 2026, 15:27" under `it`). Parsed as LOCAL time and rendered * in the viewer's zone — never through the UTC pin, which is for date-only * strings. Same em-dash rule as `formatCalendarDate`. */ export declare function formatCalendarDateTime(iso: string, locale?: string): string; /** Signed, precision-formatted delta string (e.g. "+2.4", "−1.0"; "+2,4" * under `it`). Uses a real minus sign for the negative case. */ export declare function formatDelta(abs: number, precision?: number, locale?: string): string; //# sourceMappingURL=anamnesis-derive.d.ts.map