mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
feat(coach): carry discovered drivers in the no-tools snapshot
The FDR cross-metric driver pairs were reachable only through the get_correlations tool, so a local/no-tools provider got the coincident flag but zero discovered drivers and the cross-metric layer silently degraded. The system prompt's fallback to a snapshot driver field pointed at a field that did not exist on that path. Attach a bounded top-3 slice of the same gated, ranked driver output the tool serves to the no-tools snapshot floor, gated on the derived layer and shed first under prompt-budget pressure. Local users now reach parity on the flagship cross-metric capability.
This commit is contained in:
1 parent
99a6163343
commit
b0be36c55b
3 files changed
+183
No files matched your search
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* RECON1 (D5-5) — the no-tools snapshot driver block surfaces the SAME gated,
|
||||
* ranked drivers the get_correlations tool serves, bounded to the top N, and
|
||||
* attaches nothing when no driver survives the quality gates.
|
||||
*/
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
|
||||
const readCoachCorrelations = vi.fn();
|
||||
vi.mock("@/lib/ai/coach/tools/correlations-read", () => ({
|
||||
readCoachCorrelations: (userId: string) => readCoachCorrelations(userId),
|
||||
}));
|
||||
|
||||
import {
|
||||
buildCorrelationsSnapshotBlock,
|
||||
SNAPSHOT_DRIVER_CAP,
|
||||
} from "@/lib/ai/coach/correlations-snapshot";
|
||||
import type { CoachCorrelationDriver } from "@/lib/ai/coach/tools/correlations-read";
|
||||
|
||||
function driver(
|
||||
behaviour: string,
|
||||
outcome: string,
|
||||
r: number,
|
||||
): CoachCorrelationDriver {
|
||||
return {
|
||||
behaviour,
|
||||
outcome,
|
||||
direction: r >= 0 ? "higher" : "lower",
|
||||
lagDays: 1,
|
||||
n: 42,
|
||||
r,
|
||||
note: `Higher ${behaviour} tends to go with ${outcome} — not a cause.`,
|
||||
};
|
||||
}
|
||||
|
||||
describe("buildCorrelationsSnapshotBlock (D5-5)", () => {
|
||||
beforeEach(() => {
|
||||
readCoachCorrelations.mockReset();
|
||||
});
|
||||
|
||||
it("carries driver pairs when the engine surfaces them", async () => {
|
||||
readCoachCorrelations.mockResolvedValue({
|
||||
present: true,
|
||||
drivers: [
|
||||
driver("time in daylight", "sleep duration", 0.41),
|
||||
driver("mood", "resting heart rate", -0.33),
|
||||
],
|
||||
pairsTested: 16,
|
||||
windowDays: 180,
|
||||
});
|
||||
const block = await buildCorrelationsSnapshotBlock("user-1");
|
||||
expect(block).not.toBeNull();
|
||||
expect(block!.drivers).toHaveLength(2);
|
||||
expect(block!.drivers[0].behaviour).toBe("time in daylight");
|
||||
expect(block!.pairsTested).toBe(16);
|
||||
expect(block!.windowDays).toBe(180);
|
||||
});
|
||||
|
||||
it("bounds the block to the top N ranked drivers", async () => {
|
||||
const many = Array.from({ length: SNAPSHOT_DRIVER_CAP + 4 }, (_, i) =>
|
||||
driver(`behaviour ${i}`, "sleep duration", 0.5 - i * 0.02),
|
||||
);
|
||||
readCoachCorrelations.mockResolvedValue({
|
||||
present: true,
|
||||
drivers: many,
|
||||
pairsTested: 30,
|
||||
windowDays: 180,
|
||||
});
|
||||
const block = await buildCorrelationsSnapshotBlock("user-1");
|
||||
expect(block!.drivers).toHaveLength(SNAPSHOT_DRIVER_CAP);
|
||||
// Order preserved from the already-ranked tool output (highest effect first).
|
||||
expect(block!.drivers[0].behaviour).toBe("behaviour 0");
|
||||
});
|
||||
|
||||
it("attaches nothing when no driver survives the quality gates", async () => {
|
||||
readCoachCorrelations.mockResolvedValue({
|
||||
present: false,
|
||||
reason: "no_significant_pattern",
|
||||
});
|
||||
expect(await buildCorrelationsSnapshotBlock("user-1")).toBeNull();
|
||||
});
|
||||
|
||||
it("attaches nothing when present but the driver list is empty (coincident-only)", async () => {
|
||||
// The reader can be present purely for the coincident flag with zero
|
||||
// discovered drivers — the snapshot floor adds the driver layer only.
|
||||
readCoachCorrelations.mockResolvedValue({
|
||||
present: true,
|
||||
drivers: [],
|
||||
coincident: { fired: true, contributing: [], day: "2026-06-26" },
|
||||
pairsTested: 12,
|
||||
windowDays: 180,
|
||||
});
|
||||
expect(await buildCorrelationsSnapshotBlock("user-1")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* RECON1 (D5-5) — discovered-driver block for the no-tools Coach snapshot.
|
||||
*
|
||||
* The FDR-controlled cross-metric driver pairs were reachable ONLY via the
|
||||
* `get_correlations` tool, so a local / no-tools provider (Ollama) got the
|
||||
* coincident-deviation flag but ZERO discovered drivers — the differentiating
|
||||
* cross-metric layer silently degraded, and system-prompt rule 14's fallback
|
||||
* clause ("any correlation/driver field the SNAPSHOT carries") pointed at a
|
||||
* field that did not exist on the no-tools path.
|
||||
*
|
||||
* This block closes that gap by attaching a BOUNDED slice of the SAME gated,
|
||||
* ranked output the tool serves — `readCoachCorrelations` runs the identical
|
||||
* discovery scan + quality gates (effect-size floor, family-tautology
|
||||
* exclusion, sample-size shrinkage, confidence tiering), so a local user sees
|
||||
* exactly the quality-filtered drivers a cloud user's tool would. We keep only
|
||||
* the top few by ranked effect so the no-tools prompt stays compact.
|
||||
*
|
||||
* Server-only — `readCoachCorrelations` reads `@/lib/db`. Fail-soft: a null
|
||||
* return (no surviving driver / read hiccup) attaches nothing.
|
||||
*/
|
||||
import {
|
||||
readCoachCorrelations,
|
||||
type CoachCorrelationDriver,
|
||||
} from "./tools/correlations-read";
|
||||
|
||||
/** How many ranked drivers reach the no-tools snapshot floor. */
|
||||
export const SNAPSHOT_DRIVER_CAP = 3;
|
||||
|
||||
/** One compact driver line for the snapshot — descriptive, never causal. */
|
||||
export interface CorrelationsSnapshotBlock {
|
||||
/** Top discovered drivers by ranked effect, capped + descriptive. */
|
||||
drivers: CoachCorrelationDriver[];
|
||||
/** How many behaviour×outcome pairs were tested (honest footer). */
|
||||
pairsTested: number;
|
||||
/** Trailing-day window the discovery scanned. */
|
||||
windowDays: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the bounded discovered-driver block for the no-tools snapshot floor, or
|
||||
* `null` when no driver survives the quality gates (or the read fails). The
|
||||
* coincident flag is intentionally NOT duplicated here — the derived block
|
||||
* already carries it; this block adds only the discovered-driver layer the
|
||||
* no-tools path was missing.
|
||||
*/
|
||||
export async function buildCorrelationsSnapshotBlock(
|
||||
userId: string,
|
||||
): Promise<CorrelationsSnapshotBlock | null> {
|
||||
const result = await readCoachCorrelations(userId);
|
||||
if (!result.present || !result.drivers || result.drivers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// `result.drivers` arrives already ranked by the discovery engine (shrunk
|
||||
// effect magnitude, q as tie-break) and already gated (below-floor pairs
|
||||
// dropped, tautologies excluded) — so the top-N slice is the highest-signal
|
||||
// quality-filtered drivers, identical to what the tool path would surface.
|
||||
return {
|
||||
drivers: result.drivers.slice(0, SNAPSHOT_DRIVER_CAP),
|
||||
pairsTested: result.pairsTested ?? 0,
|
||||
windowDays: result.windowDays ?? 0,
|
||||
};
|
||||
}
|
||||
@@ -46,6 +46,7 @@ import { annotate } from "@/lib/logging/context";
|
||||
import { memoizePerRequest } from "@/lib/request-cache";
|
||||
import { buildGlp1SnapshotBlock } from "./glp1-snapshot";
|
||||
import { buildDerivedSnapshotBlock } from "./derived-snapshot";
|
||||
import { buildCorrelationsSnapshotBlock } from "./correlations-snapshot";
|
||||
import { buildCoachMemoryBlock } from "./memory-snapshot";
|
||||
import { buildTrajectorySnapshotBlock } from "./trajectory-snapshot";
|
||||
import { buildCycleSnapshotBlock } from "./cycle-snapshot";
|
||||
@@ -1259,6 +1260,16 @@ async function buildCoachSnapshotImpl(
|
||||
const trajectoryBlockPromise = derivedActive
|
||||
? buildTrajectorySnapshotBlock(userId, derivedProfile, now)
|
||||
: null;
|
||||
// RECON1 (D5-5) — discovered cross-metric driver pairs for the no-tools
|
||||
// snapshot floor. Reuses the SAME gated/ranked output the get_correlations
|
||||
// tool serves (effect-size floor + family-tautology exclusion + shrinkage +
|
||||
// confidence tiering already applied inside the discovery engine), so a
|
||||
// local/no-tools provider reaches parity on the flagship cross-metric layer
|
||||
// instead of getting only the coincident flag. Gated on `derivedActive` (it
|
||||
// is the recovery / cross-metric layer) and fail-soft to null.
|
||||
const correlationsBlockPromise = derivedActive
|
||||
? buildCorrelationsSnapshotBlock(userId)
|
||||
: null;
|
||||
const memoryBlockPromise = buildCoachMemoryBlock(
|
||||
userId,
|
||||
derivedProfile,
|
||||
@@ -1305,6 +1316,7 @@ async function buildCoachSnapshotImpl(
|
||||
derivedBlock,
|
||||
dayStrainRows,
|
||||
trajectoryBlock,
|
||||
correlationsBlock,
|
||||
memoryBlock,
|
||||
cycleBlock,
|
||||
illnessBlock,
|
||||
@@ -1320,6 +1332,7 @@ async function buildCoachSnapshotImpl(
|
||||
derivedBlockPromise,
|
||||
dayStrainRowsPromise,
|
||||
trajectoryBlockPromise,
|
||||
correlationsBlockPromise,
|
||||
memoryBlockPromise,
|
||||
cycleBlockPromise,
|
||||
illnessBlockPromise,
|
||||
@@ -2269,6 +2282,20 @@ async function buildCoachSnapshotImpl(
|
||||
snapshot.trajectory = trajectoryBlock;
|
||||
registerBlock("trajectory", "skin_temp");
|
||||
}
|
||||
|
||||
// ── RECON1 (D5-5) — discovered cross-metric drivers ──────────────────
|
||||
// The bounded top-N driver pairs (post quality-gate, post-rank) from the
|
||||
// SAME engine the get_correlations tool reads, attached so the no-tools /
|
||||
// local-provider path narrates the cross-metric layer it was missing —
|
||||
// closing the parity gap with the tool path and making system-prompt rule
|
||||
// 14's "any driver field the SNAPSHOT carries" fallback real. Descriptive,
|
||||
// never causal. Registered against the lowest-priority `skin_temp`
|
||||
// (environment) cluster so the budget degrader sheds it before any
|
||||
// clinical block under prompt-budget pressure.
|
||||
if (correlationsBlock) {
|
||||
snapshot.correlations = correlationsBlock;
|
||||
registerBlock("correlations", "skin_temp");
|
||||
}
|
||||
}
|
||||
|
||||
// ── v1.11.0 W5a — rolling-profile memory (Pillar P2 2a) ──────────────
|
||||
|
||||
Reference in new issue
Block a user