8010245981
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>
214 lines
9.2 KiB
TypeScript
214 lines
9.2 KiB
TypeScript
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 D1–D60": "正式分盘 D1–D60",
|
||
"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());
|
||
}
|