/** * 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 = { 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 & 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; 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 : 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( value: unknown, parse: (item: unknown) => T, ): Readonly> { const row = asRecord(value); if (!row) return {}; const out: Record = {}; 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> { return parseClockKeyedMap(value, (item) => parseEventDashaLedger(item)); } export function parseProspectiveWindowsByTime( value: unknown, ): Readonly> { 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(); 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(EVENT_PROBE_DOMAINS); const EVENT_PROBE_SOURCES = new Set([ "dasha_activation", "dasha_boundary", "age_band", "known_event_quality", "oos_blind", ]); const PROBE_PHASES = new Set([ "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>, 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> | 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>; prospective_windows_by_time: Readonly>; } { 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), }; }