diff --git a/src/app/api/insights/generate/route.ts b/src/app/api/insights/generate/route.ts index 40a00ebb8..52a89ea4c 100644 --- a/src/app/api/insights/generate/route.ts +++ b/src/app/api/insights/generate/route.ts @@ -671,7 +671,9 @@ export const POST = apiHandler((request: NextRequest) => const compactFeatures = compactSections( features as unknown as Record, ); - const featuresJson = JSON.stringify(compactFeatures, null, 2); + // Compact on the wire: indentation is about 40% of a pretty-printed + // payload, and the model reads the structure without it (#1217). + const featuresJson = JSON.stringify(compactFeatures); if (payloadDowngraded) { annotate({ meta: { diff --git a/src/lib/ai/coach/__tests__/snapshot.test.ts b/src/lib/ai/coach/__tests__/snapshot.test.ts index 7b9dfd64f..447dee86a 100644 --- a/src/lib/ai/coach/__tests__/snapshot.test.ts +++ b/src/lib/ai/coach/__tests__/snapshot.test.ts @@ -225,7 +225,10 @@ describe("buildCoachSnapshot", () => { ], }); - expect(out.snapshotJson).toBe(JSON.stringify(out.sections, null, 2)); + // The prompt carries the snapshot compact (#1217): the same content, + // without the indentation. + expect(out.snapshotJson).toBe(JSON.stringify(out.sections)); + expect(JSON.parse(out.snapshotJson)).toEqual(out.sections); expect(out).toMatchInlineSnapshot(` { "provenance": { @@ -468,199 +471,7 @@ describe("buildCoachSnapshot", () => { "unit": "kg", }, }, - "snapshotJson": "{ - "bloodPressure": { - "aggregate": { - "avgSys30": 125, - "avgDia30": 80, - "coverage": { - "count": 4 - } - }, - "timeline": { - "recent": [ - { - "date": "2026-07-10", - "weekday": "Fri", - "sys": 128, - "dia": 82 - } - ], - "weeklySys": [ - { - "weekISO": "2026-W27", - "mean": 122, - "count": 1 - } - ], - "weeklyDia": [ - { - "weekISO": "2026-W27", - "mean": 78, - "count": 1 - } - ] - } - }, - "weight": { - "unit": "kg", - "aggregate": { - "latest": 80.8, - "bmi": 24.7, - "coverage": { - "count": 2 - }, - "unit": "kg" - }, - "timeline": { - "recent": [ - { - "date": "2026-07-16", - "weekday": "Thu", - "value": 80.8 - } - ], - "weekly": [ - { - "weekISO": "2026-W27", - "mean": 81.4, - "count": 1 - } - ] - } - }, - "pulse": { - "aggregate": { - "avg7": 63, - "coverage": { - "count": 2 - } - }, - "timeline": { - "recent": [ - { - "date": "2026-07-17", - "weekday": "Fri", - "value": 63 - } - ], - "weekly": [ - { - "weekISO": "2026-W27", - "mean": 67, - "count": 1 - } - ] - } - }, - "mood": { - "aggregate": { - "avg30": 4, - "coverage": { - "count": 2 - } - }, - "timeline": { - "recent": [ - { - "date": "2026-07-18", - "weekday": "Sat", - "value": 5 - } - ], - "weekly": [ - { - "weekISO": "2026-W27", - "mean": 3, - "count": 1 - } - ] - } - }, - "bodyMassIndex": { - "unit": "kg/m²", - "timeline": { - "recent": [ - { - "date": "2026-07-08", - "weekday": "Wed", - "value": 24.8 - }, - { - "date": "2026-07-15", - "weekday": "Wed", - "value": 24.6 - } - ], - "weekly": [] - } - }, - "steps": { - "unit": "steps", - "timeline": { - "recent": [ - { - "date": "2026-07-18", - "weekday": "Sat", - "value": 8400 - } - ], - "weekly": [ - { - "weekISO": "2026-W27", - "mean": 7200, - "count": 1 - } - ] - } - }, - "skinTemperature": { - "unit": "°C", - "timeline": { - "recent": [ - { - "date": "2026-07-19", - "weekday": "Sun", - "value": 33.5 - } - ], - "weekly": [ - { - "weekISO": "2026-W27", - "mean": 33.2, - "count": 1 - } - ] - } - }, - "anthropometrics": { - "heightCm": 181, - "ageYears": 41, - "gender": "MALE" - }, - "dayStrain": { - "source": "WHOOP-native", - "scale": "0-21", - "latest": 13.9, - "recentMean": 12.6, - "days": 2, - "note": "Device-native day strain; prefer over derived.STRAIN_SCORE (computed 0-100 proxy)." - }, - "scope": { - "window": "last30days", - "sources": [ - "bp", - "weight", - "pulse", - "mood", - "steps", - "bmi", - "skin_temp", - "hrv" - ], - "timelineRecentDays": 14 - } - }", + "snapshotJson": "{"bloodPressure":{"aggregate":{"avgSys30":125,"avgDia30":80,"coverage":{"count":4}},"timeline":{"recent":[{"date":"2026-07-10","weekday":"Fri","sys":128,"dia":82}],"weeklySys":[{"weekISO":"2026-W27","mean":122,"count":1}],"weeklyDia":[{"weekISO":"2026-W27","mean":78,"count":1}]}},"weight":{"unit":"kg","aggregate":{"latest":80.8,"bmi":24.7,"coverage":{"count":2},"unit":"kg"},"timeline":{"recent":[{"date":"2026-07-16","weekday":"Thu","value":80.8}],"weekly":[{"weekISO":"2026-W27","mean":81.4,"count":1}]}},"pulse":{"aggregate":{"avg7":63,"coverage":{"count":2}},"timeline":{"recent":[{"date":"2026-07-17","weekday":"Fri","value":63}],"weekly":[{"weekISO":"2026-W27","mean":67,"count":1}]}},"mood":{"aggregate":{"avg30":4,"coverage":{"count":2}},"timeline":{"recent":[{"date":"2026-07-18","weekday":"Sat","value":5}],"weekly":[{"weekISO":"2026-W27","mean":3,"count":1}]}},"bodyMassIndex":{"unit":"kg/m²","timeline":{"recent":[{"date":"2026-07-08","weekday":"Wed","value":24.8},{"date":"2026-07-15","weekday":"Wed","value":24.6}],"weekly":[]}},"steps":{"unit":"steps","timeline":{"recent":[{"date":"2026-07-18","weekday":"Sat","value":8400}],"weekly":[{"weekISO":"2026-W27","mean":7200,"count":1}]}},"skinTemperature":{"unit":"°C","timeline":{"recent":[{"date":"2026-07-19","weekday":"Sun","value":33.5}],"weekly":[{"weekISO":"2026-W27","mean":33.2,"count":1}]}},"anthropometrics":{"heightCm":181,"ageYears":41,"gender":"MALE"},"dayStrain":{"source":"WHOOP-native","scale":"0-21","latest":13.9,"recentMean":12.6,"days":2,"note":"Device-native day strain; prefer over derived.STRAIN_SCORE (computed 0-100 proxy)."},"scope":{"window":"last30days","sources":["bp","weight","pulse","mood","steps","bmi","skin_temp","hrv"],"timelineRecentDays":14}}", "units": { "glucoseUnit": "mg/dL", "system": "metric", diff --git a/src/lib/ai/coach/snapshot-prelude.ts b/src/lib/ai/coach/snapshot-prelude.ts index 5acdedc8f..8a6b8ac30 100644 --- a/src/lib/ai/coach/snapshot-prelude.ts +++ b/src/lib/ai/coach/snapshot-prelude.ts @@ -36,16 +36,19 @@ import type { CoachScope, CoachScopeSource, CoachScopeWindow } from "./types"; export const DAILY_TIMELINE_DAYS = 14; /** - * v1.7.0 — assembled-snapshot soft char cap. After the snapshot is - * built we measure `JSON.stringify(snapshot).length` as a ~4-chars-per- - * token proxy and, if it exceeds this cap, progressively degrade the - * lowest-priority clusters (drop `timeline.recent`, then collapse the - * weekly buckets) until it fits. ~24 000 chars ≈ ~6 000 tokens, which - * sits comfortably inside every provider's context alongside the system - * prompt + history window. The daily token ledger (`budget.ts`) stays - * the per-day cost backstop; this is the per-prompt shape backstop. + * v1.7.0 — assembled-snapshot soft char cap. After the snapshot is built we + * measure its compact JSON (`JSON.stringify(snapshot).length`, the form the + * prompt and every tool result ship) and, if it exceeds this cap, + * progressively degrade the lowest-priority clusters (drop + * `timeline.recent`, then collapse the weekly buckets) until it fits. The + * daily token ledger (`budget.ts`) stays the per-day cost backstop; this is + * the per-prompt shape backstop. + * + * v1.43 (#1217) — 24 000 pretty-printed characters became 14 000 compact + * ones. Compact JSON is ~0.42 to ~0.5 of the pretty form for these blocks, so + * everything that fitted before still fits: the same data, fewer tokens. */ -export const MAX_SNAPSHOT_CHARS = 24_000; +export const MAX_SNAPSHOT_CHARS = 14_000; /** * v1.18.10 (P-2) — newest-first cap on the single multi-type measurement read diff --git a/src/lib/ai/coach/snapshot.ts b/src/lib/ai/coach/snapshot.ts index 2dd46bbc6..04e37f92b 100644 --- a/src/lib/ai/coach/snapshot.ts +++ b/src/lib/ai/coach/snapshot.ts @@ -1128,7 +1128,7 @@ async function buildCoachSnapshotImpl( } return { - snapshotJson: JSON.stringify(compactSnapshot, null, 2), + snapshotJson: JSON.stringify(compactSnapshot), // v1.20.0 (F1) — the structured, post-degrade snapshot record keyed by // domain block (`bloodPressure`, `glucose`, `labs`, …). The coach tool // executor slices a single domain block out of this so a retrieval tool @@ -1175,10 +1175,9 @@ export function degradeToBudget( cluster: CoachDataCluster; pass: number; }> = []; - // Measure against the SAME pretty-printed form the prompt ships - // (`JSON.stringify(snapshot, null, 2)`), not the compact form — - // otherwise the cap under-counts by ~2× and the prompt overflows. - const size = () => JSON.stringify(snapshot, null, 2).length; + // Measure against the SAME compact form the prompt ships + // (`JSON.stringify(snapshot)`), so the cap counts what the model reads. + const size = () => JSON.stringify(snapshot).length; if (size() <= MAX_SNAPSHOT_CHARS) return degraded; // Build a degrade order: blocks grouped by cluster, lowest priority diff --git a/src/lib/ai/coach/source-snapshot.ts b/src/lib/ai/coach/source-snapshot.ts index 723b94be5..ce9ae0bda 100644 --- a/src/lib/ai/coach/source-snapshot.ts +++ b/src/lib/ai/coach/source-snapshot.ts @@ -78,7 +78,7 @@ import type { } from "./types"; /** - * Above this pretty-printed size the section goes to the full builder, whose + * Above this compact size the section goes to the full builder, whose * budget pass may trim it (see the module comment). A 30-day section is a few * thousand characters; a year of blood pressure with its coarse tail is the * kind of section that crosses it. @@ -240,7 +240,7 @@ export async function buildCoachSourceSnapshot( const sections = compactSections(snapshot); annotateSnapshotFreshness(sections); - const size = JSON.stringify(sections, null, 2).length; + const size = JSON.stringify(sections).length; if (size > SOURCE_SNAPSHOT_FALLBACK_CHARS) { annotate({ action: { name: "coach.source_snapshot.full" }, diff --git a/src/lib/insights/__tests__/compact-snapshot.test.ts b/src/lib/insights/__tests__/compact-snapshot.test.ts new file mode 100644 index 000000000..3dbfd2c86 --- /dev/null +++ b/src/lib/insights/__tests__/compact-snapshot.test.ts @@ -0,0 +1,165 @@ +/** + * #1217 — the status snapshots travel as compact JSON, and the char cap was + * lowered with the switch so the model sees at least the same data for fewer + * tokens, never less. + * + * Two properties pin that: + * 1. compact JSON parses back to exactly the object the pretty form did, so + * dropping the indentation changes no content; + * 2. every snapshot (and every shed stage of one) that fitted the old cap, + * 24 000 pretty-printed characters, fits the new compact cap, so the + * shedding never starts earlier than it used to. + */ +import { describe, expect, it } from "vitest"; + +import { + MAX_SNAPSHOT_CHARS, + buildGradedSeriesFromPoints, + degradeStatusSnapshotToBudget, +} from "@/lib/insights/graded-series"; + +/** The cap before v1.43, measured on `JSON.stringify(snapshot, null, 2)`. */ +const PRETTY_CAP_BEFORE = 24_000; + +const NOW = new Date("2026-10-15T12:00:00Z"); + +function series(days: number, perDay: number, base: number, seed: number) { + const points: Array<{ measuredAt: Date; value: number }> = []; + let x = seed; + for (let d = 0; d < days; d++) { + for (let k = 0; k < perDay; k++) { + x = (x * 9301 + 49297) % 233280; + points.push({ + measuredAt: new Date(NOW.getTime() - d * 86_400_000 - k * 3_600_000), + value: Math.round((base + (x / 233280) * base * 0.2) * 10) / 10, + }); + } + } + return buildGradedSeriesFromPoints(points, NOW, "Europe/Berlin"); +} + +/** A general-status-shaped snapshot over `metrics` series of `days` days. */ +function snapshot(metrics: number, days: number): Record { + const measurementSeries: Record = {}; + for (let i = 0; i < metrics; i++) { + measurementSeries[`METRIC_${i}`] = { + unit: i % 2 === 0 ? "bpm" : "kg", + summary: { points: days * 2, mean: 61.2, min: 50, max: 80, latest: 63 }, + series: series(days, 2, 50 + i * 10, i + 1), + }; + } + return { + locale: "de", + generatedForDay: "2026-10-15", + interpretationHint: + "Use trend direction and deltas. Prioritize the newest data if trends conflict.", + dataCoverage: { + totalMeasurements: days * 2 * metrics, + totalSpanDays: days, + newestMeasurementDaysAgo: 0, + }, + measurementSeries, + correlations: Array.from({ length: 40 }, (_, i) => ({ + a: `METRIC_${i % metrics}`, + b: `METRIC_${(i + 1) % metrics}`, + r: Math.round(((i % 7) / 10 - 0.3) * 100) / 100, + n: 30 + i, + })), + }; +} + +const FIXTURES: Array<[number, number]> = []; +for (const metrics of [1, 2, 3, 4, 6, 8, 10, 16]) { + for (const days of [14, 30, 90, 200, 400, 1000]) { + FIXTURES.push([metrics, days]); + } +} + +/** The shed stages `degradeStatusSnapshotToBudget` walks, in order. */ +function stages(snap: Record): Record[] { + const out: Record[] = [structuredClone(snap)]; + const drop = (node: unknown, field: string): void => { + if (Array.isArray(node)) return node.forEach((n) => drop(n, field)); + if (node && typeof node === "object") { + const obj = node as Record; + for (const key of Object.keys(obj)) { + if (key === field && Array.isArray(obj[key])) delete obj[key]; + else drop(obj[key], field); + } + } + }; + const tails = (node: unknown): void => { + if (Array.isArray(node)) return node.forEach(tails); + if (node && typeof node === "object") { + const obj = node as Record; + for (const key of Object.keys(obj)) { + const value = obj[key]; + if (Array.isArray(value) && key !== "recent" && value.length > 30) { + obj[key] = value.slice(-30); + } + tails(obj[key]); + } + } + }; + let current = structuredClone(snap); + drop(current, "yearly"); + out.push(structuredClone(current)); + drop(current, "weekly"); + out.push(structuredClone(current)); + tails(current); + out.push(structuredClone(current)); + current = structuredClone(current); + drop(current, "recent"); + out.push(current); + return out; +} + +describe("compact status snapshots (#1217)", () => { + it.each(FIXTURES)( + "parses back to the same object (%i metrics, %i days)", + (metrics, days) => { + const snap = snapshot(metrics, days); + const compact = JSON.stringify(snap); + expect(JSON.parse(compact)).toEqual( + JSON.parse(JSON.stringify(snap, null, 2)), + ); + expect(compact.length).toBeLessThan(JSON.stringify(snap, null, 2).length); + }, + ); + + it("fits every stage that fitted the old pretty cap under the new compact cap", () => { + let checked = 0; + for (const [metrics, days] of FIXTURES) { + for (const stage of stages(snapshot(metrics, days))) { + if (JSON.stringify(stage, null, 2).length > PRETTY_CAP_BEFORE) continue; + checked += 1; + expect(JSON.stringify(stage).length).toBeLessThanOrEqual( + MAX_SNAPSHOT_CHARS, + ); + } + } + // The property is only worth something if it saw real cases near the cap. + expect(checked).toBeGreaterThan(40); + }); + + it("sheds nothing from a snapshot that fitted before", () => { + let fitted = 0; + for (const [metrics, days] of FIXTURES) { + const snap = snapshot(metrics, days); + if (JSON.stringify(snap, null, 2).length > PRETTY_CAP_BEFORE) continue; + fitted += 1; + const before = structuredClone(snap); + expect(degradeStatusSnapshotToBudget(snap)).toEqual([]); + expect(snap).toEqual(before); + } + expect(fitted).toBeGreaterThan(5); + }); + + it("still sheds a snapshot far over the cap, coarsest-last", () => { + const snap = snapshot(16, 1000); + const size = JSON.stringify(snap).length; + const shed = degradeStatusSnapshotToBudget(snap); + expect(shed[0]).toBe("yearly"); + expect(JSON.stringify(snap).length).toBeLessThan(size); + }); +}); diff --git a/src/lib/insights/__tests__/comprehensive-generate-force.test.ts b/src/lib/insights/__tests__/comprehensive-generate-force.test.ts index 5eb22ab81..e9accd527 100644 --- a/src/lib/insights/__tests__/comprehensive-generate-force.test.ts +++ b/src/lib/insights/__tests__/comprehensive-generate-force.test.ts @@ -350,7 +350,7 @@ describe("generateComprehensiveInsight — content-hash gate (v1.16.8)", () => { expect(outcome).toEqual({ status: "generated", providerType: "openai" }); const prompt = JSON.stringify(runRawCompletionWithFallback.mock.calls[0]); - expect(prompt).toContain('\\"unit\\": \\"lb\\"'); + expect(prompt).toContain('\\"unit\\":\\"lb\\"'); expect(prompt).toContain("179.5"); expect(prompt).not.toContain("81.4"); diff --git a/src/lib/insights/biomarker-status.ts b/src/lib/insights/biomarker-status.ts index 20b9e1887..17ca5636f 100644 --- a/src/lib/insights/biomarker-status.ts +++ b/src/lib/insights/biomarker-status.ts @@ -315,7 +315,7 @@ export async function generateBiomarkerStatus(args: { value: r.value, })), }; - const snapshotJson = JSON.stringify(snapshot, null, 2); + const snapshotJson = JSON.stringify(snapshot); annotate({ action: { name: cacheAction }, diff --git a/src/lib/insights/blood-pressure-status.ts b/src/lib/insights/blood-pressure-status.ts index 4057590a4..59e2c9c70 100644 --- a/src/lib/insights/blood-pressure-status.ts +++ b/src/lib/insights/blood-pressure-status.ts @@ -642,7 +642,7 @@ export async function prepareBloodPressureStatusForUser( const shed = degradeStatusSnapshotToBudget( snapshot as unknown as Record, ); - const snapshotJson = JSON.stringify(snapshot, null, 2); + const snapshotJson = JSON.stringify(snapshot); annotate({ action: { name: cacheAction }, diff --git a/src/lib/insights/bmi-status.ts b/src/lib/insights/bmi-status.ts index 9c9808185..7257869dd 100644 --- a/src/lib/insights/bmi-status.ts +++ b/src/lib/insights/bmi-status.ts @@ -335,7 +335,7 @@ export async function prepareBmiStatusForUser( const shed = degradeStatusSnapshotToBudget( snapshot as unknown as Record, ); - const snapshotJson = JSON.stringify(snapshot, null, 2); + const snapshotJson = JSON.stringify(snapshot); annotate({ action: { name: cacheAction }, diff --git a/src/lib/insights/comprehensive-generate.ts b/src/lib/insights/comprehensive-generate.ts index cd396bfc3..824681e30 100644 --- a/src/lib/insights/comprehensive-generate.ts +++ b/src/lib/insights/comprehensive-generate.ts @@ -965,7 +965,9 @@ export async function generateComprehensiveInsight( const compactFeatures = compactSections( features as unknown as Record, ); - const featuresJson = JSON.stringify(compactFeatures, null, 2); + // Compact on the wire: indentation is about 40% of a pretty-printed + // payload, and the model reads the structure without it (#1217). + const featuresJson = JSON.stringify(compactFeatures); // v1.16.8 — content-hash gate. When the compacted feature snapshot is // byte-equivalent (modulo clock-relative offsets, see snapshot-hash.ts) diff --git a/src/lib/insights/derived/derived-assessment-ai.ts b/src/lib/insights/derived/derived-assessment-ai.ts index 7d1acb045..bf29989e6 100644 --- a/src/lib/insights/derived/derived-assessment-ai.ts +++ b/src/lib/insights/derived/derived-assessment-ai.ts @@ -133,11 +133,12 @@ function scoreUserPrompt( /** v1.30.3 (QA F5) — the user's own IANA tz; was hardcoded "Europe/Berlin". */ tz: string, ): string { - const snapshot = JSON.stringify( - { promptVersion: PROMPT_VERSION, generatedForDay: todayKey, band, signal }, - null, - 2, - ); + const snapshot = JSON.stringify({ + promptVersion: PROMPT_VERSION, + generatedForDay: todayKey, + band, + signal, + }); if (instructionLocale(locale) === "en") { return `Date: ${todayKey} (${tz}) OPENER HINT: ${openerHint} diff --git a/src/lib/insights/general-status.ts b/src/lib/insights/general-status.ts index de8420700..837e0339a 100644 --- a/src/lib/insights/general-status.ts +++ b/src/lib/insights/general-status.ts @@ -419,7 +419,7 @@ export async function prepareGeneralStatusForUser( const shed = degradeStatusSnapshotToBudget( snapshot as unknown as Record, ); - const snapshotJson = JSON.stringify(snapshot, null, 2); + const snapshotJson = JSON.stringify(snapshot); annotate({ action: { name: cacheAction }, diff --git a/src/lib/insights/graded-series.ts b/src/lib/insights/graded-series.ts index ed50332bb..c322381bf 100644 --- a/src/lib/insights/graded-series.ts +++ b/src/lib/insights/graded-series.ts @@ -682,17 +682,25 @@ export function convertGradedSeries( } /** - * Assembled-snapshot soft char cap, mirroring the Coach's - * `MAX_SNAPSHOT_CHARS` (~24 000 chars ≈ ~6 000 tokens against the - * pretty-printed form). The status snapshots had no cap at all; a - * many-metric `general-status` could balloon past 100 K tokens. + * Assembled-snapshot soft char cap, measured against the COMPACT JSON the + * prompt ships (`JSON.stringify(snapshot)`). The status snapshots had no cap + * at all once; a many-metric `general-status` could balloon past 100 K + * tokens. + * + * Until v1.43 the cap was 24 000 characters of pretty-printed JSON. The + * prompt is compact now, and a graded series loses about 58% of its + * characters to indentation (compact is ~0.42 of pretty across one to sixteen + * metrics and thirty to a thousand days), so 24 000 pretty characters are + * ~10 100 compact ones. 14 000 keeps every snapshot that fitted before + * fitting now, with room for the string-heavier blocks whose ratio is higher: + * the model sees at least the same data for fewer tokens, never less. */ -export const MAX_SNAPSHOT_CHARS = 24_000; +export const MAX_SNAPSHOT_CHARS = 14_000; /** * Walk an arbitrary snapshot object and, while it exceeds - * `MAX_SNAPSHOT_CHARS` (measured against the pretty-printed form the - * prompt ships), shed the lowest-signal slices in order: + * `MAX_SNAPSHOT_CHARS` (measured against the compact form the prompt + * ships), shed the lowest-signal slices in order: * 1. drop every graded `yearly` array, * 2. drop every graded `weekly` array, * 3. truncate any other array to its tail `arrayTailCap` entries @@ -709,7 +717,7 @@ export function degradeStatusSnapshotToBudget( arrayTailCap = 30, ): string[] { const shed: string[] = []; - const size = () => JSON.stringify(snapshot, null, 2).length; + const size = () => JSON.stringify(snapshot).length; if (size() <= MAX_SNAPSHOT_CHARS) return shed; const walk = ( diff --git a/src/lib/insights/medication-compliance-status.ts b/src/lib/insights/medication-compliance-status.ts index 299b97a64..7b6846d3f 100644 --- a/src/lib/insights/medication-compliance-status.ts +++ b/src/lib/insights/medication-compliance-status.ts @@ -406,7 +406,7 @@ export async function prepareMedicationComplianceStatusForUser( const shed = degradeStatusSnapshotToBudget( snapshot as unknown as Record, ); - const snapshotJson = JSON.stringify(snapshot, null, 2); + const snapshotJson = JSON.stringify(snapshot); annotate({ action: { name: cacheAction }, diff --git a/src/lib/insights/metric-status.ts b/src/lib/insights/metric-status.ts index 8ee1c927c..26b5748ab 100644 --- a/src/lib/insights/metric-status.ts +++ b/src/lib/insights/metric-status.ts @@ -454,7 +454,7 @@ export async function generateMetricStatus(args: { const shed = degradeStatusSnapshotToBudget( snapshot as unknown as Record, ); - const snapshotJson = JSON.stringify(snapshot, null, 2); + const snapshotJson = JSON.stringify(snapshot); annotate({ action: { name: cacheAction }, diff --git a/src/lib/insights/mood-status.ts b/src/lib/insights/mood-status.ts index 4c787f040..53b4d06dd 100644 --- a/src/lib/insights/mood-status.ts +++ b/src/lib/insights/mood-status.ts @@ -665,7 +665,7 @@ export async function prepareMoodStatusForUser( const shed = degradeStatusSnapshotToBudget( snapshot as unknown as Record, ); - const snapshotJson = JSON.stringify(snapshot, null, 2); + const snapshotJson = JSON.stringify(snapshot); annotate({ action: { name: cacheAction }, diff --git a/src/lib/insights/pulse-status.ts b/src/lib/insights/pulse-status.ts index b6143d2e1..1d1901f91 100644 --- a/src/lib/insights/pulse-status.ts +++ b/src/lib/insights/pulse-status.ts @@ -392,7 +392,7 @@ export async function preparePulseStatusForUser( const shed = degradeStatusSnapshotToBudget( snapshot as unknown as Record, ); - const snapshotJson = JSON.stringify(snapshot, null, 2); + const snapshotJson = JSON.stringify(snapshot); annotate({ action: { name: cacheAction }, diff --git a/src/lib/insights/weight-status.ts b/src/lib/insights/weight-status.ts index 7bc94bef0..59d2d58e4 100644 --- a/src/lib/insights/weight-status.ts +++ b/src/lib/insights/weight-status.ts @@ -608,7 +608,7 @@ export async function prepareWeightStatusForUser( const shed = degradeStatusSnapshotToBudget( snapshot as unknown as Record, ); - const snapshotJson = JSON.stringify(snapshot, null, 2); + const snapshotJson = JSON.stringify(snapshot); annotate({ action: { name: cacheAction },