fix(coach): do not count the freshness stamp as a figure in a series block

A block trimmed to its unit and freshness stamp passed the figure check,
because asOf.daysAgo is a number. The check now skips the asOf and scope
fields, so that shape answers retrieval_failed like any other block
without a reading.
This commit is contained in:
Marc-André Bombeck committed 2026-10-05 21:50:48 +02:00
1 parent 28517affd0
commit af79f4fb5c
2 files changed
+19 -2

No files matched your search

@@ -244,6 +244,12 @@ describe("get_metric_series inside a chat turn", () => {
for (const empty of [
{ unit: "kg" },
{ omitted: "trimmed for prompt budget" },
// The shape the budget pass really leaves: the unit and the freshness
// stamp, whose `daysAgo` is a number but not a reading.
{
unit: "kg",
asOf: { daysAgo: 3, isToday: false, currentForTodayClaims: false },
},
]) {
buildCoachSnapshot.mockReset();
buildCoachSnapshot.mockResolvedValue(snapshot({ fatMass: empty }));
+13 -2
View File
@@ -619,11 +619,22 @@ function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
/** Whether a payload holds at least one finite number anywhere. */
/**
* Fields that describe a block rather than measure anything: the freshness
* stamp (`asOf.daysAgo` is a number, but no reading), and the scope echo. A
* block trimmed to `{ unit, asOf }` holds no figure.
*/
const METADATA_FIELDS: ReadonlySet<string> = new Set(["asOf", "scope"]);
/** Whether a payload holds at least one finite measured number anywhere. */
function carriesFigures(value: unknown): boolean {
if (typeof value === "number") return Number.isFinite(value);
if (Array.isArray(value)) return value.some(carriesFigures);
if (isRecord(value)) return Object.values(value).some(carriesFigures);
if (isRecord(value)) {
return Object.entries(value).some(
([key, child]) => !METADATA_FIELDS.has(key) && carriesFigures(child),
);
}
return false;
}