Truth strip review fixes: all-offline is not Listening, hearing bounded (#232)
- Every configured node offline now degrades the Radio stage ("all N nodes
offline") so the wall no longer shows "Listening" with nothing up.
- nodes-hearing only counts configured nodes, so it can't exceed N/N.
- AI chip copy: "no incident writes in the recent window" instead of
"last incident write none in window".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f252e3eb61
commit
824b0f6f5a
@@ -182,14 +182,21 @@ export function derivePipelineHealth(
|
||||
// ── Radio ────────────────────────────────────────────────────────────────
|
||||
const newestCallAt = maxDate(calls.map((c) => ms(c.started_at)));
|
||||
const anyNodeUp = nodes.some((n) => n.status !== "offline");
|
||||
// Every configured node offline is not "Listening" — nothing can be heard.
|
||||
const allNodesOffline = nodes.length > 0 && !anyNodeUp;
|
||||
const radioDry = anyNodeUp && (!newestCallAt || now - newestCallAt.getTime() > absenceMs);
|
||||
const radio: PipelineStage = {
|
||||
key: "radio", label: "Radio", lastAt: newestCallAt,
|
||||
state: nodes.length === 0 ? "unknown" : radioDry ? "degraded" : "ok",
|
||||
detail: newestCallAt ? `last call heard ${agoText(newestCallAt, now)}` : "no calls in the recent window",
|
||||
state: nodes.length === 0 ? "unknown" : radioDry || allNodesOffline ? "degraded" : "ok",
|
||||
detail: allNodesOffline
|
||||
? `all ${nodes.length} node${nodes.length === 1 ? "" : "s"} offline`
|
||||
: newestCallAt ? `last call heard ${agoText(newestCallAt, now)}` : "no calls in the recent window",
|
||||
};
|
||||
// Bounded to configured nodes so a call from a since-removed node can't push
|
||||
// the KPI past N/N.
|
||||
const knownNodes = new Set(nodes.map((n) => n.node_id));
|
||||
const nodesHearing = new Set(
|
||||
calls.filter((c) => now - ms(c.started_at) <= staleMs).map((c) => c.node_id),
|
||||
calls.filter((c) => now - ms(c.started_at) <= staleMs && knownNodes.has(c.node_id)).map((c) => c.node_id),
|
||||
).size;
|
||||
|
||||
// ── STT: radio heard, nothing transcribed ────────────────────────────────
|
||||
@@ -225,7 +232,7 @@ export function derivePipelineHealth(
|
||||
state: aiDry || aiTiers.length ? "degraded" : reachable === false && !newestAiAt ? "unknown" : "ok",
|
||||
detail: aiDry
|
||||
? `transcribed, no incident written since ${newestAiAt ? clockTime(newestAiAt) : clockTime(new Date(oldestUncorrelated))}`
|
||||
: `last incident write ${agoText(newestAiAt, now)}${reachable === false ? " · /health/ai unreachable" : ""}`,
|
||||
: `${newestAiAt ? `last incident write ${agoText(newestAiAt, now)}` : "no incident writes in the recent window"}${reachable === false ? " · /health/ai unreachable" : ""}`,
|
||||
};
|
||||
|
||||
// ── Context feeds (chip only — a dead camera feed doesn't blind the pipeline) ──
|
||||
@@ -244,7 +251,8 @@ export function derivePipelineHealth(
|
||||
|
||||
// ── Verdict ─────────────────────────────────────────────────────────────
|
||||
const parts: string[] = [];
|
||||
if (radio.state === "degraded") parts.push(`nodes checking in, no radio heard since ${newestCallAt ? clockTime(newestCallAt) : "—"}`);
|
||||
if (allNodesOffline) parts.push(`${radio.detail}${newestCallAt ? `, last call heard ${clockTime(newestCallAt)}` : ""}`);
|
||||
else if (radioDry) parts.push(`nodes checking in, no radio heard since ${newestCallAt ? clockTime(newestCallAt) : "—"}`);
|
||||
if (sttDry) parts.push(stt.detail);
|
||||
if (aiDry) parts.push(ai_.detail);
|
||||
for (const t of degradedTiers) parts.push(tierText(t.tier, t.state));
|
||||
|
||||
Reference in New Issue
Block a user