mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
The retrieval-tool DATA INVENTORY was built against the user's default
narration cluster (cardio/body/mood/medication), so sleep, glucose,
activity, body composition, vascular, SpO2, gait and environment series
reported absent even when the data existed — and the grounding rule then
told the model not to fetch them. Probe presence against the full source
set instead, and generate the metric-series inventory rows from the
complete source-to-section map so every series the user has rows for is
advertised. Per-tool reads still re-scope to their own domain, so the
wider probe never widens a figure read.
Add three retrieval tools whose snapshot blocks already existed but were
unreachable in tool mode:
- get_workouts: recent sessions + per-sport rollup
- get_cycle: phase / prediction / correlation (gated by cycle access)
- get_correlations: the FDR-controlled lagged cross-metric drivers plus
the coincident-deviation flag, surfaced descriptively
Also honour the caller's window in get_labs and get_illness_recovery so a
cross-metric answer no longer silently mixes horizons.
154 lines
5.9 KiB
TypeScript
154 lines
5.9 KiB
TypeScript
/**
|
|
* v1.20.0 (F1) — CoachScopeSource → snapshot section key map.
|
|
*
|
|
* `get_metric_series` fetches a single metric by re-running the snapshot
|
|
* builder scoped to that one source, then reads the matching section out of the
|
|
* structured `CoachSnapshotResult.sections`. The builder names each section by
|
|
* a camelCase key (e.g. `heartRateVariability`); this map mirrors the
|
|
* `valueBlocks` table in `snapshot.ts` plus the three core clinical blocks
|
|
* (bloodPressure / weight / pulse) and the mood block.
|
|
*
|
|
* `glucose` and `workouts` are intentionally absent: they have dedicated tools
|
|
* / branches (glucose carries the clinical panel; workouts is deferred). A
|
|
* `get_metric_series` call for one of them resolves to `null` here and the
|
|
* executor returns `{ present: false }` with a pointer to the right tool.
|
|
*
|
|
* The `source-keys.test.ts` guard asserts every `coachScopeSourceSchema` value
|
|
* either resolves here or is explicitly one of the dedicated-tool sources, so a
|
|
* future source addition cannot silently fall through to a fabricated answer.
|
|
*/
|
|
import {
|
|
coachScopeSourceSchema,
|
|
type CoachScopeSource,
|
|
} from "@/lib/ai/coach/types";
|
|
|
|
/** Sources that `get_metric_series` does NOT serve (they have own tools). */
|
|
export const METRIC_SERIES_EXCLUDED_SOURCES: ReadonlySet<CoachScopeSource> =
|
|
new Set<CoachScopeSource>(["glucose", "workouts", "compliance"]);
|
|
|
|
/** source → the snapshot section key its single-metric block lands under. */
|
|
export const COACH_SOURCE_SNAPSHOT_KEY: Readonly<
|
|
Partial<Record<CoachScopeSource, string>>
|
|
> = {
|
|
// ── core clinical ──
|
|
bp: "bloodPressure",
|
|
weight: "weight",
|
|
pulse: "pulse",
|
|
mood: "mood",
|
|
// ── cardio ──
|
|
hrv: "heartRateVariability",
|
|
resting_hr: "restingHeartRate",
|
|
walking_hr: "walkingHeartRateAverage",
|
|
respiratory_rate: "respiratoryRate",
|
|
spo2: "oxygenSaturation",
|
|
pulse_wave_velocity: "pulseWaveVelocity",
|
|
vascular_age: "vascularAge",
|
|
// ── body composition ──
|
|
body_fat: "bodyFat",
|
|
fat_mass: "fatMass",
|
|
fat_free_mass: "fatFreeMass",
|
|
muscle_mass: "muscleMass",
|
|
lean_body_mass: "leanBodyMass",
|
|
bone_mass: "boneMass",
|
|
total_body_water: "totalBodyWater",
|
|
bmi: "bodyMassIndex",
|
|
visceral_fat: "visceralFat",
|
|
// ── activity ──
|
|
steps: "steps",
|
|
active_energy: "activeEnergy",
|
|
flights: "flightsClimbed",
|
|
distance: "walkingRunningDistance",
|
|
vo2_max: "vo2Max",
|
|
// ── mobility & gait ──
|
|
walking_steadiness: "walkingSteadiness",
|
|
walking_asymmetry: "walkingAsymmetry",
|
|
walking_double_support: "walkingDoubleSupport",
|
|
walking_step_length: "walkingStepLength",
|
|
walking_speed: "walkingSpeed",
|
|
// ── environment / exposure ──
|
|
audio_env: "audioExposureEnvironment",
|
|
audio_headphone: "audioExposureHeadphone",
|
|
audio_event: "audioExposureEvent",
|
|
daylight: "timeInDaylight",
|
|
skin_temp: "skinTemperature",
|
|
// ── sleep (also has the dedicated get_sleep tool, but the raw duration
|
|
// series is reachable here too) ──
|
|
sleep: "sleep",
|
|
body_temp: "bodyTemperature",
|
|
};
|
|
|
|
/**
|
|
* v1.21.0 (C2-3) — human-readable domain label per metric source, for the DATA
|
|
* INVENTORY lines. Brand-free, lower-case, stable across turns so the cached
|
|
* prefix holds. Every key in `COACH_SOURCE_SNAPSHOT_KEY` plus the dedicated-tool
|
|
* sources has an entry; the inventory renders one line per series source the
|
|
* user actually has rows for.
|
|
*/
|
|
export const COACH_SOURCE_DOMAIN_LABEL: Readonly<
|
|
Partial<Record<CoachScopeSource, string>>
|
|
> = {
|
|
bp: "blood pressure",
|
|
weight: "weight",
|
|
pulse: "pulse",
|
|
mood: "mood",
|
|
hrv: "heart-rate variability",
|
|
resting_hr: "resting heart rate",
|
|
walking_hr: "walking heart rate",
|
|
respiratory_rate: "respiratory rate",
|
|
spo2: "blood oxygen (SpO2)",
|
|
pulse_wave_velocity: "pulse-wave velocity",
|
|
vascular_age: "vascular age",
|
|
body_fat: "body fat",
|
|
fat_mass: "fat mass",
|
|
fat_free_mass: "fat-free mass",
|
|
muscle_mass: "muscle mass",
|
|
lean_body_mass: "lean body mass",
|
|
bone_mass: "bone mass",
|
|
total_body_water: "total body water",
|
|
bmi: "BMI",
|
|
visceral_fat: "visceral fat",
|
|
steps: "steps",
|
|
active_energy: "active energy",
|
|
flights: "flights climbed",
|
|
distance: "walking/running distance",
|
|
vo2_max: "VO2 max",
|
|
walking_steadiness: "walking steadiness",
|
|
walking_asymmetry: "walking asymmetry",
|
|
walking_double_support: "walking double support",
|
|
walking_step_length: "walking step length",
|
|
walking_speed: "walking speed",
|
|
audio_env: "environmental audio exposure",
|
|
audio_headphone: "headphone audio exposure",
|
|
audio_event: "audio exposure events",
|
|
daylight: "time in daylight",
|
|
skin_temp: "skin temperature",
|
|
sleep: "sleep",
|
|
body_temp: "body temperature",
|
|
};
|
|
|
|
/**
|
|
* v1.21.0 (C2-2 / C2-3) — the FULL set of metric-series sources the inventory
|
|
* probes for presence: every source `get_metric_series` can serve (the
|
|
* `COACH_SOURCE_SNAPSHOT_KEY` keys), minus the few that ride a dedicated tool
|
|
* (sleep has `get_sleep`; glucose/compliance/workouts are excluded). The
|
|
* inventory is built against this full set so a domain with data is advertised
|
|
* regardless of the user's narration-cluster preference — closing the
|
|
* default-cluster reach gap. `sleep` is omitted here because `get_sleep` owns
|
|
* its inventory line.
|
|
*/
|
|
export const METRIC_SERIES_INVENTORY_SOURCES: ReadonlyArray<CoachScopeSource> =
|
|
(Object.keys(COACH_SOURCE_SNAPSHOT_KEY) as CoachScopeSource[]).filter(
|
|
(source) => source !== "sleep",
|
|
);
|
|
|
|
/**
|
|
* v1.21.0 (C2-2) — the full source set the inventory snapshot is built against,
|
|
* so every domain with stored rows reports `present` (not the default-cluster
|
|
* subset). The per-tool reads still re-scope to the exact domain, so widening
|
|
* the inventory probe never widens a figure read. This is the canonical enum
|
|
* (every `CoachScopeSource`) — the snapshot builder only emits a block when the
|
|
* matching source has rows, so probing the full set is presence-only.
|
|
*/
|
|
export const FULL_INVENTORY_SOURCE_SET: ReadonlyArray<CoachScopeSource> =
|
|
coachScopeSourceSchema.options;
|