Files
Jyotisha/frontend/src/lib/rectification-agentic/v9/refinement-packet.ts
T
jesse-uxandGrok (agent loop) d2296c2654 把前后端共用的请求契约和小工具收成一份,避免再各抄一份。
报告创建请求、校正入口和同名类型改从 contracts 推导;同义的对象判断与 sha256 改走共享函数。调用处用 import 别名保留原名,使 diff 低于 1500 行。行为、哈希和状态字符串不变。

Co-Authored-By: Grok (agent loop)
2026-10-15 22:22:44 +08:00

1061 lines
37 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.
/**
* Public-safe P0/P1 refinement fields from the decision receipt.
*
* Scores, weights and event IDs stay out of public copy. D9/D10 sign names
* may appear for the skill type-table verification report. D1 sign names may
* appear on lagna contrast, matching the natal house table.
*/
import { distinguishContractErrors } from "../core/distinguish-contract.ts";
import { parseCandidateIntervals } from "../core/candidate-window.ts";
import { hasDatedTransition, parseDatedTransition, validDatedTransitions, type DatedTransitionPosition } from "../core/sign-from-transitions.ts";
import { engineMeaningToDisplayCopy } from "../user-copy.ts";
import { questionContractVersionIsCompatible } from "./probe-question-contract.ts";
import { asRecord } from "@/lib/guards";
const TIME = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const DENIED = /lon|latitude|longitude|degree|score|weight|fingerprint|\bpoints\b/i;
const LAYER_LABEL: Record<WindowScanLayer, string> = {
d1: "本命上升",
d9: "D9",
d10: "D10",
d4: "D4",
d5: "D5",
d7: "D7",
d12: "D12",
d24: "D24",
d2: "D2",
d11: "D11",
d30: "D30",
pada: "Nakshatra pada",
hora: "Hora Lagna",
ghati: "Ghati Lagna",
bhava: "Bhava Lagna",
pranapada: "Pranapada Lagna",
kp1: "KP 1宫子主",
kp4: "KP 4宫子主",
kp7: "KP 7宫子主",
kp10: "KP 10宫子主",
};
export const WINDOW_SCAN_SCORING_LAYER_ORDER = [
"d1",
"d9",
"d10",
"d4",
"d5",
"d7",
"d12",
"d24",
"d2",
"d11",
"d30",
] as const satisfies readonly WindowScanLayer[];
export const WINDOW_SCAN_DISPLAY_LAYER_ORDER = [
"pada",
"hora",
"ghati",
"bhava",
"pranapada",
"kp1",
"kp4",
"kp7",
"kp10",
] as const satisfies readonly WindowScanLayer[];
export function windowScanLayerLabel(layer: WindowScanLayer): string {
return LAYER_LABEL[layer];
}
export function isWindowScanDisplayLayer(layer: WindowScanLayer): boolean {
return (WINDOW_SCAN_DISPLAY_LAYER_ORDER as readonly WindowScanLayer[]).includes(layer);
}
export type WindowScanLayer =
| "d1"
| "d9"
| "d10"
| "d4"
| "d5"
| "d7"
| "d12"
| "d24"
| "d2"
| "d11"
| "d30"
| "pada"
| "hora"
| "ghati"
| "bhava"
| "pranapada"
| "kp1"
| "kp4"
| "kp7"
| "kp10";
export type WindowScanTransition = Partial<DatedTransitionPosition> & Readonly<{
layer: WindowScanLayer;
at: string;
user_meaning: string;
from_sign?: string;
to_sign?: string;
}>;
export type EventDashaLedgerRow = Readonly<{
summary: string;
match: "strong" | "medium" | "weak" | "none";
match_label: string;
user_meaning: string;
gochara: "activated" | "not_seen" | null;
domain?: string;
year_month?: string;
}>;
export type DashaAgreement = Readonly<{
status: "agree" | "conflict" | "partial" | "unavailable";
vimshottari_top: string | null;
narayana_top: string | null;
user_meaning: string;
}>;
export type LagnaContrastInterval = Readonly<{
start: string;
end: string;
start_at?: string;
end_at?: string;
segment_index?: number;
lagna: string;
lords: Readonly<{ l1: string | null; l4: string | null; l7: string | null; l10: string | null }>;
}>;
export type LagnaContrast = Readonly<{
intervals: readonly LagnaContrastInterval[];
user_meaning: string;
unique_minute_claim: false;
}>;
export type NakshatraOption = Readonly<{
key: "A" | "B";
time_bias: "earlier" | "later";
traits: readonly string[];
}>;
export type NakshatraBoundary = Readonly<{
near_boundary: boolean;
user_meaning: string | null;
options: readonly NakshatraOption[];
}>;
export type PrecisionStageId =
| "collect_events"
| "lagna_frame"
| "d9_refine"
| "d10_refine"
| "d4_refine"
| "d5_refine"
| "theme_refine"
| "ready_to_adopt";
export type PrecisionStage = Readonly<{
current: PrecisionStageId;
can_stop: boolean;
user_meaning: string;
unique_minute_claim: false;
}>;
export type OosBlindPrompt = Readonly<{
domain: string;
user_meaning: string;
used_for_scoring: false;
}>;
export const EVENT_PROBE_DOMAINS = [
"education",
"relocation",
"relationship",
"career",
"family",
"finance",
"health_pressure",
] as const;
export type EventProbeDomain = (typeof EVENT_PROBE_DOMAINS)[number];
export type EventProbeSource =
| "dasha_activation"
| "dasha_boundary"
| "age_band"
| "known_event_quality"
| "oos_blind";
export type EventProbeRole = "distinguish" | "reverse_verify" | "clarify" | "collect" | "holdout";
export type ProbePhase =
| "evidence_collection"
| "event_clarification"
| "candidate_discriminator"
| "holdout_validation";
export type EventProbeChoiceKind = "existence" | "varga_style" | "event_quality";
export type EventProbeStyleOption = Readonly<{
label: string;
answer_class: string;
sign?: string;
}>;
export type ProbeExpectedOutcome = Readonly<{
answer_class: string;
supports: readonly string[];
conflicts: readonly string[];
}>;
export type DiscriminatingEventProbe = Readonly<{
year: number;
year_label: string;
month?: number;
domain: EventProbeDomain;
event_family: string;
source: EventProbeSource;
tracks: readonly ("vimshottari" | "narayana")[];
tracks_agree: boolean;
unique_minute_claim: false;
user_meaning: string;
role: EventProbeRole;
phase?: ProbePhase;
information_gain?: number;
semantic_key?: string;
candidate_split_hash?: string;
candidate_set_version?: string;
candidate_ids?: readonly string[];
expected_outcomes?: readonly ProbeExpectedOutcome[];
left_time?: string;
right_time?: string;
choice_kind?: EventProbeChoiceKind;
style_options?: readonly EventProbeStyleOption[];
target_evidence_id?: string;
display_date_label?: string;
}>;
export type GuidedCollectWindowRow = Readonly<{
year: number;
month_lo: number;
month_hi: number;
domain: string;
split: Readonly<{ left: number; right: number }>;
}>;
export function parseGuidedCollectWindows(value: unknown): readonly GuidedCollectWindowRow[] {
if (!Array.isArray(value)) return [];
const rows: GuidedCollectWindowRow[] = [];
for (const item of value) {
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
const row = item as Record<string, unknown>;
const year = typeof row.year === "number" ? row.year : Number.NaN;
const monthLo = typeof row.month_lo === "number" ? row.month_lo : Number.NaN;
const monthHi = typeof row.month_hi === "number" ? row.month_hi : Number.NaN;
const domain = typeof row.domain === "string" ? row.domain.trim() : "";
const split = row.split && typeof row.split === "object" && !Array.isArray(row.split)
? row.split as Record<string, unknown>
: null;
const left = typeof split?.left === "number" ? split.left : Number.NaN;
const right = typeof split?.right === "number" ? split.right : Number.NaN;
if (!Number.isInteger(year) || year < 1900 || year > 2100) continue;
if (!Number.isInteger(monthLo) || monthLo < 1 || monthLo > 12) continue;
if (!Number.isInteger(monthHi) || monthHi < 1 || monthHi > 12) continue;
if (!domain || !EVENT_PROBE_DOMAINS.includes(domain as EventProbeDomain)) continue;
if (!Number.isInteger(left) || !Number.isInteger(right)) continue;
if (left < 0 || right < 0) continue;
rows.push({
year,
month_lo: monthLo,
month_hi: monthHi,
domain,
split: { left, right },
});
}
return rows;
}
export type ProspectiveWindow = Readonly<{
domain: string;
from: string;
to: string;
}>;
export function parseProspectiveWindows(value: unknown): readonly ProspectiveWindow[] {
if (!Array.isArray(value)) return [];
const rows: ProspectiveWindow[] = [];
for (const item of value) {
const row = asRecord(item);
const domain = typeof row?.domain === "string" ? row.domain.trim() : "";
const from = typeof row?.from === "string" ? row.from.trim() : "";
const to = typeof row?.to === "string" ? row.to.trim() : from;
if (!row || !domain || !/^\d{4}-\d{2}$/.test(from) || !/^\d{4}-\d{2}$/.test(to)) continue;
rows.push({ domain, from, to });
if (rows.length >= 2) break;
}
return rows;
}
function parseClockKeyedMap<T>(
value: unknown,
parse: (item: unknown) => T,
): Readonly<Record<string, T>> {
const row = asRecord(value);
if (!row) return {};
const out: Record<string, T> = {};
for (const [key, item] of Object.entries(row)) {
const clock = asTime(key);
if (!clock) continue;
out[clock] = parse(item);
}
return out;
}
export function parseEventDashaLedgerByTime(
value: unknown,
): Readonly<Record<string, readonly EventDashaLedgerRow[]>> {
return parseClockKeyedMap(value, (item) => parseEventDashaLedger(item));
}
export function parseProspectiveWindowsByTime(
value: unknown,
): Readonly<Record<string, readonly ProspectiveWindow[]>> {
return parseClockKeyedMap(value, (item) => parseProspectiveWindows(item));
}
export type ProspectiveProbe = Readonly<{
candidate_label: string;
domain: string;
window_label: string;
user_meaning: string;
used_for_scoring: false;
}>;
export type DroppedPacketProbe = Readonly<{
semantic_key?: string;
reason: string;
domain?: string;
year?: number;
source?: string;
}>;
export type CandidateContrastOpportunity = Readonly<{
domain: string;
time_window: Readonly<{ year: number | null; year_label: string | null }>;
candidate_groups: readonly (readonly string[])[];
expected_outcomes: readonly ProbeExpectedOutcome[];
information_gain: number;
source_features: readonly Readonly<{ technique: string; layers?: readonly string[] }>[];
semantic_key?: string;
candidate_split_hash?: string;
candidate_set_version?: string;
event_family?: string;
phase: "candidate_discriminator";
}>;
function asTime(value: unknown): string | null {
if (typeof value !== "string") return null;
const normalized = value.slice(0, 5);
return TIME.test(normalized) ? normalized : null;
}
function asText(value: unknown, maximum = 160): string | null {
if (typeof value !== "string") return null;
const trimmed = value.trim();
if (!trimmed || trimmed.length > maximum || DENIED.test(trimmed) || UUID.test(trimmed)) return null;
return trimmed;
}
export function parseWindowScanTransitions(value: unknown): readonly WindowScanTransition[] {
if (!Array.isArray(value)) return [];
const rows: WindowScanTransition[] = [];
const seen = new Set<string>();
const dated = value.some((item) => { const row = asRecord(item); return row && hasDatedTransition(row); });
for (const item of value) {
const row = asRecord(item);
const layer = row?.layer;
const at = asTime(row?.at);
if (
!row
|| (
layer !== "d1"
&& layer !== "d9"
&& layer !== "d10"
&& layer !== "d4"
&& layer !== "d5"
&& layer !== "d7"
&& layer !== "d12"
&& layer !== "d24"
&& layer !== "d2"
&& layer !== "d11"
&& layer !== "d30"
&& layer !== "pada"
&& layer !== "hora"
&& layer !== "ghati"
&& layer !== "bhava"
&& layer !== "pranapada"
&& layer !== "kp1"
&& layer !== "kp4"
&& layer !== "kp7"
&& layer !== "kp10"
)
|| !at
) {
if (dated) return [];
continue;
}
const position = dated ? parseDatedTransition(row) : null;
if (dated && !position) return [];
const key = `${layer}:${at}`;
if (seen.has(key)) { if (dated) return []; else continue; }
seen.add(key);
rows.push({
...(position ?? {}),
layer,
at,
user_meaning: position?.segment_start ? `${LAYER_LABEL[layer]} 在这一段起点的状态` : `${LAYER_LABEL[layer]} 在 ${at} 发生变化`,
...(asText(row.from_sign, 12) ? { from_sign: asText(row.from_sign, 12)! } : {}),
...(asText(row.to_sign, 12) ? { to_sign: asText(row.to_sign, 12)! } : {}),
});
}
return dated && !validDatedTransitions(rows) ? [] : rows;
}
export function parseEventDashaLedger(value: unknown): readonly EventDashaLedgerRow[] {
if (!Array.isArray(value)) return [];
const rows: EventDashaLedgerRow[] = [];
for (const item of value) {
const row = asRecord(item);
const match = row?.match;
if (
!row
|| (match !== "strong" && match !== "medium" && match !== "weak" && match !== "none")
) continue;
const summary = asText(row.summary, 80);
if (!summary) continue;
const matchLabel = match === "strong" ? "强相关"
: match === "medium" ? "有关联"
: match === "weak" ? "弱关联"
: "未见对应";
const gochara = row.gochara === "activated" || row.gochara === "not_seen"
? row.gochara
: null;
const gocharaNote = gochara === "activated"
? ";Gochara 激活相关宫"
: gochara === "not_seen"
? ";Gochara 未见对应"
: "";
rows.push({
summary,
match,
match_label: matchLabel,
user_meaning: `${summary}:${matchLabel}${gocharaNote}`,
gochara,
...(typeof row.domain === "string" && row.domain.trim() ? { domain: row.domain.trim() } : {}),
...(typeof row.year_month === "string" && /^\d{4}(?:-\d{2})?$/.test(row.year_month)
? { year_month: row.year_month }
: {}),
});
}
return rows;
}
export function parseDashaAgreement(value: unknown): DashaAgreement | null {
const row = asRecord(value);
const status = row?.status;
if (
!row
|| (status !== "agree" && status !== "conflict" && status !== "partial" && status !== "unavailable")
) return null;
const meaning = asText(row.user_meaning, 220)
?? (status === "conflict"
? "主限和分盘大运偏向不同时间。两边看法还不一致,先不往更窄处收。"
: status === "agree"
? "主限和分盘大运都更支持同一段代表性时间。这仍不是唯一分钟确认。"
: "还没有足够的大运对照。");
return {
status,
vimshottari_top: asTime(row.vimshottari_top),
narayana_top: asTime(row.narayana_top),
user_meaning: meaning,
};
}
function dashaAgreementCopy(
status: DashaAgreement["status"],
vim: string | null,
nar: string | null,
): DashaAgreement {
if (status === "conflict" && vim && nar) {
return {
status,
vimshottari_top: vim,
narayana_top: nar,
user_meaning: `主限更偏向 ${vim},分盘大运更偏向 ${nar}。冲突时不能按更高把握收口。`,
};
}
if (status === "agree") {
return {
status,
vimshottari_top: vim,
narayana_top: nar,
user_meaning: "主限和分盘大运都更支持同一段代表性时间。这仍不是唯一分钟确认。",
};
}
if (status === "partial") {
return {
status,
vimshottari_top: vim,
narayana_top: nar,
user_meaning: "主限和分盘大运还不能做成完整对照,只作观察。",
};
}
return {
status: "unavailable",
vimshottari_top: null,
narayana_top: null,
user_meaning: "还没有足够的大运对照。",
};
}
export type DashaClusterHint = Readonly<{
time?: string;
cluster_range?: readonly [string, string];
cluster_start?: string;
cluster_end?: string;
clusterTimes?: readonly string[];
cluster_times?: readonly string[];
}>;
function clockMinutes(value: string): number | null {
if (!TIME.test(value)) return null;
return Number(value.slice(0, 2)) * 60 + Number(value.slice(3));
}
function clusterKeyForTime(time: string, clusters: readonly DashaClusterHint[]): string {
const point = clockMinutes(time);
for (const row of clusters) {
const start = row.cluster_start ?? row.cluster_range?.[0];
const end = row.cluster_end ?? row.cluster_range?.[1];
const times = row.cluster_times ?? row.clusterTimes ?? [];
const inTimes = times.includes(time) || row.time === time;
const startMinutes = start ? clockMinutes(start) : null;
const endMinutes = end ? clockMinutes(end) : null;
const inSpan = point !== null && startMinutes !== null && endMinutes !== null
&& point >= startMinutes && point <= endMinutes;
if (inTimes || inSpan) {
return start && end ? `${start}–${end}` : time;
}
}
return time;
}
function activeClusterKey(
time: string | null,
activeTimes: readonly string[],
clusters: readonly DashaClusterHint[],
): string | null {
if (!time) return null;
const key = clusterKeyForTime(time, clusters);
if (activeTimes.some((item) => item === time || clusterKeyForTime(item, clusters) === key)) {
return key;
}
return null;
}
/** Recompute Vimshottari / Narayana tops inside the still-active inference minutes. */
export function dashaAgreementAmongActive(
engine: DashaAgreement | null,
activeTimes: readonly string[],
clusters: readonly DashaClusterHint[] = [],
): DashaAgreement | null {
if (!engine) return null;
const active = new Set(activeTimes);
if (active.size === 0) return dashaAgreementCopy("unavailable", null, null);
const vimCluster = activeClusterKey(engine.vimshottari_top, activeTimes, clusters);
const narCluster = activeClusterKey(engine.narayana_top, activeTimes, clusters);
if (vimCluster && narCluster && vimCluster === narCluster) {
return dashaAgreementCopy("agree", engine.vimshottari_top, engine.narayana_top);
}
const vim = engine.vimshottari_top && active.has(engine.vimshottari_top)
? engine.vimshottari_top
: null;
const nar = engine.narayana_top && active.has(engine.narayana_top)
? engine.narayana_top
: null;
if (vim && nar && vim === nar) return dashaAgreementCopy("agree", vim, nar);
if (vim && nar) return dashaAgreementCopy("conflict", vim, nar);
if (!vim && !nar) return dashaAgreementCopy("agree", null, null);
return dashaAgreementCopy("partial", vim, nar);
}
function parseLords(value: unknown): LagnaContrastInterval["lords"] | null {
const row = asRecord(value);
if (!row) return null;
const read = (key: "l1" | "l4" | "l7" | "l10"): string | null => {
const label = asText(row[key], 12);
return label;
};
return { l1: read("l1"), l4: read("l4"), l7: read("l7"), l10: read("l10") };
}
export function lagnaContrastIntervalLabel(interval: LagnaContrastInterval): string {
return interval.start_at && interval.end_at
? `${interval.start_at.replace("T", " ")}–${interval.end_at.replace("T", " ")}`
: `${interval.start}-${interval.end}`;
}
export function parseLagnaContrast(value: unknown): LagnaContrast | null {
const row = asRecord(value);
if (!row || !Array.isArray(row.intervals) || row.intervals.length < 2) return null;
const intervals: LagnaContrastInterval[] = [];
const dated = row.intervals.some((item) => {
const part = asRecord(item);
return part && ["start_at", "end_at", "segment_index"].some((key) => key in part);
});
for (const item of row.intervals.slice(0, 3)) {
const interval = asRecord(item);
const start = asTime(interval?.start);
const end = asTime(interval?.end);
const lagna = asText(interval?.lagna, 12);
const lords = parseLords(interval?.lords);
if (!interval || !start || !end || !lagna || !lords) return null;
const absolute = dated ? parseCandidateIntervals([interval])?.[0] : null;
if (dated && (!absolute || !Number.isSafeInteger(interval.segment_index) || Number(interval.segment_index) < 0
|| absolute.start_at.slice(11) !== start || absolute.end_at.slice(11) !== end
|| (intervals.length > 0 && (intervals.at(-1)!.end_at! >= absolute.start_at
|| intervals.at(-1)!.segment_index! > Number(interval.segment_index))))) return null;
intervals.push({ start, end, lagna, lords, ...(absolute ? { ...absolute, segment_index: Number(interval.segment_index) } : {}) });
}
if (intervals.length < 2) return null;
const datedMeaning = `窗口里出现两段本命上升:${lagnaContrastIntervalLabel(intervals[0])} 为${intervals[0].lagna},${lagnaContrastIntervalLabel(intervals[1])} 为${intervals[1].lagna}。`;
const meaning = dated ? `${datedMeaning}不是命运承诺,也不能确认唯一分钟。`
: asText(row.user_meaning, 220) ?? datedMeaning;
return {
intervals,
user_meaning: meaning,
unique_minute_claim: false,
};
}
export function parseNakshatraBoundary(value: unknown): NakshatraBoundary | null {
const row = asRecord(value);
if (!row || typeof row.near_boundary !== "boolean") return null;
if (!row.near_boundary) {
return { near_boundary: false, user_meaning: null, options: [] };
}
const options: NakshatraOption[] = [];
if (Array.isArray(row.options)) {
for (const item of row.options) {
const option = asRecord(item);
const key = option?.key;
const bias = option?.time_bias;
if (
!option
|| (key !== "A" && key !== "B")
|| (bias !== "earlier" && bias !== "later")
|| !Array.isArray(option.traits)
) continue;
const traits = option.traits
.map((trait) => asText(trait, 40))
.filter((trait): trait is string => Boolean(trait))
.slice(0, 2);
if (traits.length === 0) continue;
options.push({ key, time_bias: bias, traits });
}
}
if (options.length < 2) return { near_boundary: false, user_meaning: null, options: [] };
return {
near_boundary: true,
user_meaning: asText(row.user_meaning, 400) ?? "升点靠近两段日常节奏的交界。哪一组更像你近年的处事方式?这只用来偏置时间窗,不能确认唯一分钟。",
options: options.slice(0, 2),
};
}
export function parsePrecisionStage(value: unknown): PrecisionStage | null {
const row = asRecord(value);
const current = row?.current;
if (
!row
|| (
current !== "collect_events"
&& current !== "lagna_frame"
&& current !== "d9_refine"
&& current !== "d10_refine"
&& current !== "d4_refine"
&& current !== "d5_refine"
&& current !== "theme_refine"
&& current !== "ready_to_adopt"
)
) return null;
const meaning = asText(row.user_meaning, 220) ?? "继续用带日期的经历缩小窗口。";
return {
current,
can_stop: row.can_stop === true || current === "ready_to_adopt",
user_meaning: meaning,
unique_minute_claim: false,
};
}
const EVENT_PROBE_DOMAIN_SET = new Set<string>(EVENT_PROBE_DOMAINS);
const EVENT_PROBE_SOURCES = new Set<string>([
"dasha_activation",
"dasha_boundary",
"age_band",
"known_event_quality",
"oos_blind",
]);
const PROBE_PHASES = new Set<string>([
"evidence_collection",
"event_clarification",
"candidate_discriminator",
"holdout_validation",
]);
const CLOCK_IN_COPY = /(?:[01]?\d|2[0-3]):[0-5]\d/;
function parseExpectedOutcomes(value: unknown): ProbeExpectedOutcome[] {
if (!Array.isArray(value)) return [];
return value.flatMap((item) => {
const outcome = asRecord(item);
const answer = typeof outcome?.answer_class === "string" ? outcome.answer_class : "";
if (!outcome || !answer) return [];
return [{
answer_class: answer,
supports: Array.isArray(outcome.supports)
? outcome.supports.filter((entry): entry is string => typeof entry === "string")
: [],
conflicts: Array.isArray(outcome.conflicts)
? outcome.conflicts.filter((entry): entry is string => typeof entry === "string")
: [],
}];
});
}
function parseProbeRole(row: Readonly<Record<string, unknown>>, source: string): EventProbeRole {
const anchored = typeof row.target_evidence_id === "string" && row.target_evidence_id.length > 0;
if (source === "known_event_quality" && row.role === "distinguish" && anchored) return "distinguish";
if (row.role === "clarify" || source === "known_event_quality") return "clarify";
if (row.role === "collect" || source === "age_band") return "collect";
if (row.role === "holdout" || source === "oos_blind") return "holdout";
if (row.role === "distinguish" || row.phase === "candidate_discriminator") return "distinguish";
return "reverse_verify";
}
export function parseEventProbes(
value: unknown,
options: { allowQuality?: boolean; requireDistinguishContract?: boolean } = {},
): readonly DiscriminatingEventProbe[] {
if (!Array.isArray(value)) return [];
const rows: DiscriminatingEventProbe[] = [];
for (const item of value) {
const row = asRecord(item);
if (!questionContractVersionIsCompatible(row?.question_contract_version ?? row?.question_contract)) continue;
const year = typeof row?.year === "number" && Number.isInteger(row.year) ? row.year : null;
const domain = typeof row?.domain === "string" ? row.domain : "";
const source = typeof row?.source === "string" ? row.source : "";
const family = asText(row?.event_family, 80);
const meaning = asText(row?.user_meaning, 160);
const label = asText(row?.year_label, 40);
const month = typeof row?.month === "number" && Number.isInteger(row.month) && row.month >= 1 && row.month <= 12
? row.month
: null;
if (
!row
|| year === null
|| year < 1900
|| year > 2100
|| !EVENT_PROBE_DOMAIN_SET.has(domain)
|| !EVENT_PROBE_SOURCES.has(source)
|| !family
|| !meaning
|| !label
|| CLOCK_IN_COPY.test(meaning)
|| CLOCK_IN_COPY.test(family)
|| row.unique_minute_claim === true
) continue;
const evidenceId = typeof row.target_evidence_id === "string" && UUID.test(row.target_evidence_id)
? row.target_evidence_id
: null;
const anchoredQuality = source === "known_event_quality"
&& Boolean(evidenceId)
&& (row.role === "distinguish" || row.phase === "candidate_discriminator");
if (!options.allowQuality && source === "known_event_quality" && !anchoredQuality) continue;
const tracks = Array.isArray(row.tracks)
? row.tracks.filter((track): track is "vimshottari" | "narayana" => track === "vimshottari" || track === "narayana")
: [];
const expectedOutcomes = parseExpectedOutcomes(row.expected_outcomes);
const candidateIds = Array.isArray(row.candidate_ids)
? row.candidate_ids.filter((entry): entry is string => typeof entry === "string" && TIME.test(entry.slice(0, 5)))
: [];
const probe: DiscriminatingEventProbe = {
year,
year_label: label,
...(month ? { month } : {}),
domain: domain as EventProbeDomain,
event_family: family,
source: source as EventProbeSource,
tracks,
tracks_agree: row.tracks_agree === true,
unique_minute_claim: false,
user_meaning: meaning,
role: parseProbeRole(row, source),
...(typeof row.phase === "string" && PROBE_PHASES.has(row.phase) ? { phase: row.phase as ProbePhase } : {}),
...(typeof row.information_gain === "number" && Number.isFinite(row.information_gain)
? { information_gain: row.information_gain }
: {}),
...(asText(row.semantic_key, 80) ? { semantic_key: asText(row.semantic_key, 80)! } : {}),
...(asText(row.candidate_split_hash, 120) ? { candidate_split_hash: asText(row.candidate_split_hash, 120)! } : {}),
...(asText(row.candidate_set_version, 40) ? { candidate_set_version: asText(row.candidate_set_version, 40)! } : {}),
...(candidateIds.length > 0 ? { candidate_ids: candidateIds } : {}),
...(asTime(row.left_time) ? { left_time: asTime(row.left_time)! } : {}),
...(asTime(row.right_time) ? { right_time: asTime(row.right_time)! } : {}),
...(row.choice_kind === "existence" || row.choice_kind === "varga_style" || row.choice_kind === "event_quality"
? { choice_kind: row.choice_kind }
: {}),
...(Array.isArray(row.style_options) ? {
style_options: row.style_options.flatMap((optionItem) => {
const option = asRecord(optionItem);
const optionLabel = asText(option?.label, 80);
const answer = typeof option?.answer_class === "string" ? option.answer_class : "";
if (!option || !optionLabel || !answer) return [];
return [{
label: optionLabel,
answer_class: answer,
...(asText(option.sign, 12) ? { sign: asText(option.sign, 12)! } : {}),
}];
}),
} : {}),
...(expectedOutcomes.length > 0 ? { expected_outcomes: expectedOutcomes } : {}),
...(evidenceId ? { target_evidence_id: evidenceId } : {}),
...(asText(row.display_date_label, 40) ? { display_date_label: asText(row.display_date_label, 40)! } : {}),
};
if (options.requireDistinguishContract && distinguishContractErrors({ ...probe, role: "distinguish" }).length > 0) continue;
rows.push(probe);
if (rows.length === 8) break;
}
return rows;
}
export function parseDiscriminatingEventProbes(value: unknown): readonly DiscriminatingEventProbe[] {
return parseEventProbes(value, { requireDistinguishContract: true });
}
export function parseClarificationProbes(value: unknown): readonly DiscriminatingEventProbe[] {
return parseEventProbes(value, { allowQuality: true }).filter((item) => (
item.source === "known_event_quality" || item.phase === "event_clarification" || item.role === "clarify"
));
}
export function parseCollectionProbes(value: unknown): readonly DiscriminatingEventProbe[] {
return parseEventProbes(value).filter((item) => (
item.phase === "evidence_collection" || item.role === "collect" || item.source === "age_band"
));
}
export function parseDroppedPacketProbes(value: unknown): readonly DroppedPacketProbe[] {
if (!Array.isArray(value)) return [];
const rows: DroppedPacketProbe[] = [];
for (const item of value) {
const row = asRecord(item);
const reason = asText(row?.reason, 80);
if (!row || !reason) continue;
rows.push({
reason,
...(asText(row.semantic_key, 80) ? { semantic_key: asText(row.semantic_key, 80)! } : {}),
...(typeof row.domain === "string" ? { domain: row.domain } : {}),
...(typeof row.year === "number" && Number.isInteger(row.year) ? { year: row.year } : {}),
...(typeof row.source === "string" ? { source: row.source } : {}),
});
if (rows.length === 16) break;
}
return rows;
}
export function parseProspectiveProbes(value: unknown): readonly ProspectiveProbe[] {
if (!Array.isArray(value)) return [];
const rows: ProspectiveProbe[] = [];
for (const item of value) {
const row = asRecord(item);
const label = asText(row?.candidate_label, 8);
const domain = typeof row?.domain === "string" ? row.domain : "";
const window = asText(row?.window_label, 40);
const meaning = asText(row?.user_meaning, 200);
if (
!row
|| !label
|| !EVENT_PROBE_DOMAIN_SET.has(domain)
|| !window
|| !meaning
|| row.used_for_scoring === true
|| CLOCK_IN_COPY.test(meaning)
|| CLOCK_IN_COPY.test(window)
) continue;
rows.push({
candidate_label: label,
domain,
window_label: window,
user_meaning: meaning,
used_for_scoring: false,
});
if (rows.length === 3) break;
}
return rows;
}
export function prospectiveWindowsNarration(probes: readonly ProspectiveProbe[]): string | null {
const meanings = probes
.filter((item) => item.used_for_scoring === false)
.map((item) => engineMeaningToDisplayCopy(item.user_meaning).trim())
.filter((item) => item.length > 0 && !CLOCK_IN_COPY.test(item));
if (meanings.length === 0) return null;
return meanings.join(" ");
}
export function parseCandidateContrastOpportunities(value: unknown): readonly CandidateContrastOpportunity[] {
if (!Array.isArray(value)) return [];
const rows: CandidateContrastOpportunity[] = [];
for (const item of value) {
const row = asRecord(item);
if (!row) continue;
const domain = typeof row.domain === "string" ? row.domain : "";
const gain = typeof row.information_gain === "number" && Number.isFinite(row.information_gain)
? row.information_gain
: 0;
const window = asRecord(row.time_window);
const groups = Array.isArray(row.candidate_groups)
? row.candidate_groups.flatMap((group) => (
Array.isArray(group)
? [group.filter((entry): entry is string => typeof entry === "string")]
: []
))
: [];
const outcomes = parseExpectedOutcomes(row.expected_outcomes);
if (!domain || gain <= 0 || groups.length < 2 || outcomes.length < 2) continue;
rows.push({
domain,
time_window: {
year: typeof window?.year === "number" && Number.isInteger(window.year) ? window.year : null,
year_label: asText(window?.year_label, 40),
},
candidate_groups: groups,
expected_outcomes: outcomes,
information_gain: gain,
source_features: Array.isArray(row.source_features)
? row.source_features.flatMap((feature) => {
const parsed = asRecord(feature);
const technique = asText(parsed?.technique, 80);
if (!parsed || !technique) return [];
return [{
technique,
...(Array.isArray(parsed.layers)
? { layers: parsed.layers.filter((layer): layer is string => typeof layer === "string") }
: {}),
}];
})
: [],
phase: "candidate_discriminator",
...(asText(row.semantic_key, 80) ? { semantic_key: asText(row.semantic_key, 80)! } : {}),
...(asText(row.candidate_split_hash, 120) ? { candidate_split_hash: asText(row.candidate_split_hash, 120)! } : {}),
...(asText(row.candidate_set_version, 40) ? { candidate_set_version: asText(row.candidate_set_version, 40)! } : {}),
...(asText(row.event_family, 80) ? { event_family: asText(row.event_family, 80)! } : {}),
});
if (rows.length === 8) break;
}
return rows;
}
export function parseOosBlindPrompts(value: unknown): readonly OosBlindPrompt[] {
if (!Array.isArray(value)) return [];
const rows: OosBlindPrompt[] = [];
const allowed = new Set(["relationship", "career", "family", "education", "finance", "health_pressure"]);
for (const item of value) {
const row = asRecord(item);
const domain = typeof row?.domain === "string" ? row.domain : "";
const meaning = asText(row?.user_meaning, 180);
if (!row || !allowed.has(domain) || !meaning) continue;
rows.push({ domain, user_meaning: meaning, used_for_scoring: false });
if (rows.length === 3) break;
}
return rows;
}
export type EventFitRate = Readonly<{
matched: number;
total: number;
percent: number | null;
band: "high" | "medium" | "low" | "insufficient" | null;
label: string;
user_meaning: string;
unique_minute_claim: false;
}>;
export function parseEventFitRate(value: unknown): EventFitRate | null {
const row = asRecord(value);
if (!row) return null;
const matched = typeof row.matched === "number" && Number.isInteger(row.matched) ? row.matched : null;
const total = typeof row.total === "number" && Number.isInteger(row.total) ? row.total : null;
if (matched === null || total === null) return null;
const percent = typeof row.percent === "number" && Number.isFinite(row.percent) ? row.percent : null;
const band = row.band === "high" || row.band === "medium" || row.band === "low" || row.band === "insufficient"
? row.band
: null;
return {
matched,
total,
percent,
band,
label: asText(row.label, 80) ?? "事件吻合率",
user_meaning: asText(row.user_meaning, 280)
?? "这是相对拟合,不是已确认唯一出生分钟。",
unique_minute_claim: false,
};
}
export function refinementFromDecisionReceipt(
receipt: Readonly<Record<string, unknown>> | null | undefined,
): {
event_dasha_ledger: readonly EventDashaLedgerRow[];
event_fit_rate: EventFitRate | null;
dasha_agreement: DashaAgreement | null;
lagna_contrast: LagnaContrast | null;
nakshatra_boundary: NakshatraBoundary | null;
precision_stage: PrecisionStage | null;
oos_blind_prompts: readonly OosBlindPrompt[];
discriminating_event_probes: readonly DiscriminatingEventProbe[];
guided_collect_windows: readonly GuidedCollectWindowRow[];
event_clarification_probes: readonly DiscriminatingEventProbe[];
evidence_collection_probes: readonly DiscriminatingEventProbe[];
candidate_contrast_opportunities: readonly CandidateContrastOpportunity[];
dropped_probes: readonly DroppedPacketProbe[];
prospective_probes: readonly ProspectiveProbe[];
event_dasha_ledger_by_time: Readonly<Record<string, readonly EventDashaLedgerRow[]>>;
prospective_windows_by_time: Readonly<Record<string, readonly ProspectiveWindow[]>>;
} {
return {
event_dasha_ledger: parseEventDashaLedger(receipt?.event_dasha_ledger),
event_fit_rate: parseEventFitRate(receipt?.event_fit_rate),
dasha_agreement: parseDashaAgreement(receipt?.dasha_agreement),
lagna_contrast: parseLagnaContrast(receipt?.lagna_contrast),
nakshatra_boundary: parseNakshatraBoundary(receipt?.nakshatra_boundary),
precision_stage: parsePrecisionStage(receipt?.precision_stage),
oos_blind_prompts: parseOosBlindPrompts(receipt?.oos_blind_prompts),
discriminating_event_probes: parseDiscriminatingEventProbes(receipt?.discriminating_event_probes),
guided_collect_windows: parseGuidedCollectWindows(receipt?.guided_collect_windows),
event_clarification_probes: parseClarificationProbes(
receipt?.event_clarification_probes ?? receipt?.discriminating_event_probes,
),
evidence_collection_probes: parseCollectionProbes(
receipt?.evidence_collection_probes ?? receipt?.discriminating_event_probes,
),
candidate_contrast_opportunities: parseCandidateContrastOpportunities(receipt?.candidate_contrast_opportunities),
dropped_probes: parseDroppedPacketProbes(receipt?.dropped_probes),
prospective_probes: parseProspectiveProbes(receipt?.prospective_probes),
event_dasha_ledger_by_time: parseEventDashaLedgerByTime(receipt?.event_dasha_ledger_by_time),
prospective_windows_by_time: parseProspectiveWindowsByTime(receipt?.prospective_windows_by_time),
};
}