Files
Jyotisha/frontend/src/lib/consultation-technique-audit.ts
T
Jesse_Chen 8010245981
Independent Staging Quality Gate / validate (push) Failing after 6m42s
Independent Staging Quality Gate / publish (push) Has been skipped
feat(product): present consult and rectification in local skill form
Default ayanamsa to Raman with true_pushya support, attach governed Raman packets, restore Path C questionnaires and eight-method verification copy, and keep unique-minute confirmation blocked.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 21:42:47 +08:00

214 lines
9.2 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 { TechniqueAuditRow, TechniqueAuditStatus } from "./consultation-agent-events.ts";
export type SpokenAnswerAndTechniqueAudit = Readonly<{
spoken: string;
rows: readonly TechniqueAuditRow[];
complete: boolean;
}>;
const AUDIT_HEADER = /^\|\s*技法\s*\|\s*状态\s*\|\s*说明\s*\|?\s*$/;
const AUDIT_SEPARATOR = /^\|?(?:\s*:?-{2,}:?\s*\|){2,}\s*:?-{2,}:?\s*\|?\s*$/;
const MACHINE_KEY = /^[a-z][a-z0-9_]{2,}$/i;
const STATUS_LABELS: Readonly<Record<TechniqueAuditStatus, string>> = {
executed: "已执行",
blocked: "阻塞",
not_applicable: "不适用",
};
const TECHNIQUE_LABELS: Readonly<Record<string, string>> = {
"VedAstro Cloud State": "VedAstro 云状态",
"VedAstro Raw Archive Manifest": "VedAstro 原始档案清单",
"External Engine Cross-Validation": "外部引擎交叉验证",
"Western Cross-Validation": "西洋交叉验证",
"Cross-System Arbitration": "跨系统仲裁",
"Evidence Packet": "证据包",
"Blind Technical Mode": "盲技术模式",
"MEVG / Global Web Evidence": "MEVG / 全网证据",
"Real Case Calibration": "真实案例校准",
"Timing Precision Gate": "时间精度门",
"Functional Benefic/Malefic": "功能吉凶星",
"Formal Vargas D1D60": "正式分盘 D1D60",
"Research D-N through D60": "研究型 D-N 至 D60",
"Extended Vargas D81/D108/D144": "扩展分盘 D81/D108/D144",
"Arudha / UL / A10": "Arudha / UL / A10",
"Narayana Dasha": "Narayana Dasha",
"Vimshottari sub-periods": "Vimshottari 小运",
Yogas: "格局 Yoga",
"Chara Dasha": "Chara Dasha",
"Transits / Sade Sati": "行运 / 土星过月",
Ashtakavarga: "Ashtakavarga",
"KP cusps": "KP 宫头",
"Gulika / Mandi": "Gulika / Mandi",
"Shadbala components": "Shadbala 分量",
"Ashtakavarga Kakshya": "Ashtakavarga Kakshya",
"Western natal (tropical)": "西洋本命(回归黄道)",
"Prashna chart": "问卦盘(Prashna",
"Ashtakoota matching": "合盘 Ashtakoota",
"Adhana / Niseka": "Adhana / Niseka",
"Muhurta election": "择时 Muhurta",
};
const WESTERN_LAYER_LABELS: Readonly<Record<string, string>> = {
transits: "行运",
solar_return: "太阳回归盘",
secondary_progressions: "次限",
solar_arc_directions: "太阳弧",
converse_secondary_progressions: "逆行次限",
converse_solar_arc_directions: "逆行太阳弧",
midpoints: "中点",
lunar_return: "月亮回归盘",
transit_duration_scan: "行运时长扫描",
parans: "parans",
};
const NOTE_LABELS: Readonly<Record<string, string>> = {
confidence_capped_without_verified_official_cloud: "未经核验的官方云证据,置信度封顶",
official_raw_archive_not_auditable_for_this_run: "官方原始档案本轮不可审计",
official_raw_archive_is_auditable: "官方原始档案可审计",
official_cloud_evidence_available: "官方云证据可用",
foreground_optional_evidence_timeout: "前台官方云证据超时,本轮未赶上",
gateway_invocation_error: "官方云网关调用失败",
claims_capped_until_pyjhora_jhora_jyotishganit_are_invoked_for_this_run: "结论上限受外部引擎调用限制",
three_engine_runtime_closure_available: "三引擎运行时已闭环",
complete_packet_required_for_high_confidence: "高置信度需完整证据包",
supports_high_confidence: "支持高置信度",
normal_runtime_mode: "常规运行模式",
prevents_conversation_feedback_leakage: "防止会话反馈泄漏",
caps_claims_until_global_web_evidence_runs: "全局网页证据未运行,声明受限",
partial_reference_only_until_outcome_replay: "仅部分参考,未做结果回放",
supports_calibration: "支持校准",
blocks_false_precision_until_control_date_rankings_pass: "控制日期排名未通过前禁止伪精度",
high_rigor_claims_blocked_until_functional_nature_layer_is_present: "功能属性与自然属性冲突时降置信",
"observation for A10/UL; commercial claims stay non-deterministic": "仅观察层,商业断语保持非确定",
"component parity remains partial": "分量对齐仍为部分",
"KP remains non-deterministic in this product": "本产品内 KP 非确定性",
observation_only: "仅观察层",
"Chesta/Sthana/Dig/Drik/Kala/Naisargika as components; absolute Virupa truth is not closed": "绝对 Virupa 真值未闭环",
"observation_only; not verified day/month timing": "仅观察层,不作已核验日期",
"observation windows, not guaranteed events": "观察窗口,不是保证事件",
"commercial claims stay non-deterministic; not a majority-vote truth": "商业断语保持非确定,不以多数引擎表决",
"this run is a natal consultation": "本轮为本命咨询",
horary_path: "问卦路径",
"requires a partner chart": "需要伴侣盘",
"research_only; not a natal consultation layer": "仅研究用途",
"not an electional request": "非择时请求",
layer_not_materialized: "本层未物化",
};
export function techniqueAuditStatusLabel(status: TechniqueAuditStatus): string {
return STATUS_LABELS[status];
}
export function displayTechniqueAuditLabel(technique: string): string {
const mapped = TECHNIQUE_LABELS[technique];
if (mapped) return mapped;
const western = /^Western (.+)$/i.exec(technique);
if (!western?.[1]) return technique;
const layer = western[1].trim();
const layerLabel = WESTERN_LAYER_LABELS[layer] ?? layer.replaceAll("_", " ");
return `西洋${layerLabel}`;
}
export function displayTechniqueAuditNote(note: string | undefined): string {
const trimmed = note?.trim() ?? "";
if (!trimmed) return "";
const mapped = NOTE_LABELS[trimmed];
if (mapped) return mapped;
const formal = /^(\d+)\/20 named traditional charts/i.exec(trimmed);
if (formal) return `${formal[1]}/20 传统命名分盘`;
const research = /^(\d+) generic D-N charts/i.exec(trimmed);
if (research) return `${research[1]} 张通用分盘,非传统命名`;
const extended = /^(\d+) super-divisional charts/i.exec(trimmed);
if (extended) return `${extended[1]} 张超细分盘`;
if (MACHINE_KEY.test(trimmed) || trimmed.includes("_")) return "";
return trimmed;
}
export function parseTechniqueAuditStatus(value: string): TechniqueAuditStatus {
const normalized = value.trim().toLowerCase();
if (
value.trim() === "已执行"
|| ["used", "executed", "complete", "official_verified", "pass", "ready", "ok", "partial"].includes(normalized)
) {
return "executed";
}
if (
value.trim() === "不适用"
|| ["not_applicable", "n/a", "na", "not applicable"].includes(normalized)
) {
return "not_applicable";
}
return "blocked";
}
export function normalizeTechniqueAuditRows(value: unknown): TechniqueAuditRow[] | undefined {
if (!Array.isArray(value)) return undefined;
const rows: TechniqueAuditRow[] = [];
for (const item of value.slice(0, 80)) {
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
const record = item as Record<string, unknown>;
const technique = String(record.technique ?? record.name ?? "").trim();
if (!technique) continue;
const noteValue = record.note ?? record.boundary ?? record.effect_on_confidence;
const note = typeof noteValue === "string" ? noteValue.trim().slice(0, 320) : "";
rows.push({
technique: technique.slice(0, 160),
status: parseTechniqueAuditStatus(String(record.status ?? "")),
...(note ? { note } : {}),
});
}
return rows.length > 0 ? rows : undefined;
}
export function splitSpokenAnswerAndTechniqueAudit(text: string): SpokenAnswerAndTechniqueAudit {
const lines = text.replace(/\r\n/g, "\n").split("\n");
let headerIndex = -1;
for (let index = 0; index < lines.length; index += 1) {
if (AUDIT_HEADER.test(lines[index].trim())) headerIndex = index;
}
if (headerIndex < 0) {
return { spoken: text.replace(/\s+$/, ""), rows: [], complete: true };
}
const prefix = lines.slice(0, headerIndex).join("\n").replace(/\s+$/, "");
const tableLines = lines.slice(headerIndex).map((line) => line.trim());
const separator = tableLines[1] && AUDIT_SEPARATOR.test(tableLines[1]) ? tableLines[1] : null;
if (!separator) {
return { spoken: prefix, rows: [], complete: false };
}
const rows: TechniqueAuditRow[] = [];
for (const line of tableLines.slice(2)) {
if (!line) break;
if (!line.startsWith("|")) break;
const cells = splitGfmRow(line);
if (cells.length < 3) return { spoken: prefix, rows, complete: false };
const technique = cells[0]?.trim() ?? "";
if (!technique) continue;
const note = cells.slice(2).join(" | ").trim().slice(0, 320);
rows.push({
technique: technique.slice(0, 160),
status: parseTechniqueAuditStatus(cells[1] ?? ""),
...(note ? { note } : {}),
});
}
if (rows.length === 0) {
return { spoken: prefix, rows: [], complete: false };
}
return { spoken: text.replace(/\s+$/, ""), rows, complete: true };
}
export function resolveTechniqueAuditRows(
split: SpokenAnswerAndTechniqueAudit,
delivered?: readonly TechniqueAuditRow[],
): readonly TechniqueAuditRow[] {
if (split.complete && split.rows.length > 0) return split.rows;
return delivered && delivered.length > 0 ? delivered : [];
}
function splitGfmRow(line: string): string[] {
return line.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
}