Files
Cyrene_v2/frontend/web/src/admin/modules/backthinkUrgency.ts
T
AskaEth a0abf0199a
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(gateway)!: 后台思考档位精确化(+ 面板三态 + 契约 v0.69/v0.70)
- gateway:优先读落库的 rec.Urgency(精确档位,与安静时段窗口无关);仅空串才回落到既有下限推导,并打 urgencyLowerBound 标记(**只在推导值上为 true**)
- 面板三态:精确(例常/紧急(高)/紧急·安全域)|下限(一律加 ≥ 前缀)|未知;下限悬停说明写清「为什么旧行只能是下限」
- 实时帧零协议改动:Record 的 JSON 键就是 urgency ⇒ 帧自然带上;wsParse 早已防御性读取(只加一处 urgencyLowerBound 防御读)
- 契约:v0.69(directMsgPerMin 透传修复,逐字照抄附录 A)与 v0.70(精确化 + 更正 §4.10(b)「实时帧不带这两键」)一起登记
- 变异实测:旧行不标下限 ⇒ 3 条测试红(结构体对比);前端去掉 ≥ ⇒ 渲染测试红
- ⚠️ 诚实边界(§7.3 已登记):实时旧记录刷新前显示「档位未知」、刷新后显示「≥ 例常」——同一条记录两路下的已知差异
2026-10-05 14:15:55 +08:00

86 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { BackthinkItem } from "../../lib/types";
/**
* 后台思考行「紧急度 / 穿透安静时段」标记的**纯逻辑**(契约 v0.70 §4.10(b))。
*
* 三种态必须画得**不一样**,否则就是把猜当事实:
* - **精确档位**:后端 `urgency` 就是该轮实际生效的档位(随记录落库,`urgencyLowerBound` 缺席);
* - **≥ 下限**:`urgencyLowerBound:true` ⇒ 该行没有落库档位(旧行 / 未走到档位判定点的行),
* 此值由安静时段窗口规则推导:`≥ 高` = 至少 high、可能更高;`≥ 例常` = 只确定不低于最低档;
* - **未知**:`urgency` 键缺席 ⇒ 存档里没有这个事实(**不是**「例常」)。
* - `quietHoursBypassed` 只在 true 时渲染「穿透安静时段」。
*/
export type UrgencyTagTone = "ok" | "warn" | "err" | "neutral" | "accent";
export interface UrgencyTag {
label: string;
tone: UrgencyTagTone;
/** 悬停说明(为什么是这个档位 / 为什么只是下限 / 为什么未知),给人看的口径。 */
hint: string;
}
/** 档位未知时的标签(键缺席 = 后端没有这个事实)。 */
export const URGENCY_UNKNOWN_LABEL = "档位未知";
/** 下限标签的前缀:推导值一律带它,保证与精确档位**一眼可辨**。 */
export const URGENCY_LOWER_BOUND_PREFIX = "≥ ";
/** 穿透安静时段的标签。 */
export const QUIET_HOURS_BYPASS_LABEL = "穿透安静时段";
/** 精确档位的标签:后端 `urgency` 词汇 → 人话。 */
const EXACT_LABEL: Record<string, string> = {
routine: "例常",
warning: "紧急(高)",
critical: "紧急·安全域",
};
/** 精确档位的色调(安全域 err / 高 warn / 其余中性)。 */
function exactTone(level: string): UrgencyTagTone {
if (level === "critical") return "err";
if (level === "warning") return "warn";
return "neutral";
}
/** 下限的悬停说明:点明它是怎么推出来的、以及为什么它**不是**精确值。 */
const LOWER_BOUND_HINT =
"这一行没有存档档位(档位列出现之前的旧记录,或从未走到档位判定点的行)⇒ 精确档位已无从得知," +
"此值是安静时段窗口规则推导出的下限:窗口内被放行 ⇒ 至少 high(可能是安全域);" +
"被扣下 ⇒ 只确定不低于最低档。不是精确档位。";
/** urgencyTag 把后端 `urgency` + `urgencyLowerBound` 映射成标签。 */
export function urgencyTag(item: Pick<BackthinkItem, "urgency" | "urgencyLowerBound">): UrgencyTag {
const level = item.urgency;
if (level === undefined || level === "") {
return {
label: URGENCY_UNKNOWN_LABEL,
tone: "neutral",
hint: "这一轮的档位在存档里不存在(该行没有落库档位,且不在安静时段窗口内 —— 窗口之外判定不看档位)——不是「例常」",
};
}
if (item.urgencyLowerBound === true) {
return {
// 未知取值原样显示(后端加了本面板不认识的档位时不猜一个更好听的词),但下限前缀照加。
label: URGENCY_LOWER_BOUND_PREFIX + (EXACT_LABEL[level] ?? level),
tone: exactTone(level),
hint: LOWER_BOUND_HINT,
};
}
const exact = EXACT_LABEL[level];
if (exact === undefined) {
return { label: level, tone: "neutral", hint: "后端给了本面板不认识的档位,原样显示" };
}
return { label: exact, tone: exactTone(level), hint: `该轮实际生效的档位(精确值):${level}` };
}
/** quietHoursBypassTag 只在后端确实标记了穿透时返回标签。 */
export function quietHoursBypassTag(item: Pick<BackthinkItem, "quietHoursBypassed">): UrgencyTag | null {
if (item.quietHoursBypassed !== true) return null;
return {
label: QUIET_HOURS_BYPASS_LABEL,
tone: "accent",
hint: "这一轮落在安静时段窗口内、但仍被放行发出(档位 ≥ high)",
};
}