814c924e4a
Keep askable cards after exhaustion, explain each probe, read the adopted credible range in reports and chat, and compare declared periods before the minute grid when the clock is unknown. Co-authored-by: Cursor <cursoragent@cursor.com>
399 lines
15 KiB
TypeScript
399 lines
15 KiB
TypeScript
import type {
|
|
DashaAgreement,
|
|
EventDashaLedgerRow,
|
|
LagnaContrast,
|
|
NakshatraBoundary,
|
|
OosBlindPrompt,
|
|
PrecisionStage,
|
|
WindowScanTransition,
|
|
} from "./rectification-agentic/v9/refinement-packet";
|
|
import {
|
|
parseWindowScanTransitions,
|
|
refinementFromDecisionReceipt,
|
|
} from "./rectification-agentic/v9/refinement-packet";
|
|
import { parseWindowScan } from "./rectification-agentic/v9/varga-observations";
|
|
import type { TechniqueAuditRow } from "./consultation-agent-events";
|
|
import { normalizeTechniqueAuditRows } from "./consultation-technique-audit";
|
|
import {
|
|
buildConfirmationGate,
|
|
type ConfirmationGate,
|
|
} from "./rectification-agentic/v9/confirmation-gate";
|
|
import { MIN_SEPARATION_LEAD } from "./rectification-agentic/core/candidate-separation";
|
|
import {
|
|
engineCapabilityCeilingFromReceipt,
|
|
sessionOutcomeAllowsAdopt,
|
|
type DecisionSessionOutcome,
|
|
} from "./rectification-agentic/core/rectification-decision";
|
|
import {
|
|
parseRectificationStepState,
|
|
type RectificationStepState,
|
|
} from "./rectification-agentic/v9/step-state.ts";
|
|
|
|
export { parseRectificationStepState };
|
|
export type { RectificationStepState };
|
|
|
|
export type RectificationCandidate = Readonly<{
|
|
candidateId: string;
|
|
rank: number;
|
|
time: string;
|
|
relativeSupport: number;
|
|
tiedMinuteCount: number;
|
|
}>;
|
|
|
|
export type RectificationHouseRow = Readonly<{
|
|
house: number;
|
|
sign: string;
|
|
occupants: readonly string[];
|
|
}>;
|
|
|
|
export type RectificationHouseTable = Readonly<{
|
|
time: string;
|
|
lagna: string;
|
|
houses: readonly RectificationHouseRow[];
|
|
}>;
|
|
|
|
export type RectificationNatalRecast = Readonly<{
|
|
time: string;
|
|
lagna: string;
|
|
user_meaning: string;
|
|
unique_minute_claim: false;
|
|
confirmation_allowed: false;
|
|
}>;
|
|
|
|
export type RectificationCandidateResult = Readonly<{
|
|
resultId: string;
|
|
candidates: readonly RectificationCandidate[];
|
|
overallConfidence: "low" | "medium" | "high";
|
|
selectionAllowed: boolean;
|
|
canAdopt: boolean;
|
|
confirmationAllowed: boolean;
|
|
decisionReceipt: Readonly<Record<string, unknown>> | null;
|
|
representativeTime: string | null;
|
|
selectedTime: string | null;
|
|
selectionKind: string | null;
|
|
houseTable: RectificationHouseTable | null;
|
|
houseTablesByTime: Readonly<Record<string, RectificationHouseTable>>;
|
|
natalRecast: RectificationNatalRecast | null;
|
|
techniqueAudit: readonly TechniqueAuditRow[];
|
|
windowTransitions: readonly WindowScanTransition[];
|
|
eventDashaLedger: readonly EventDashaLedgerRow[];
|
|
dashaAgreement: DashaAgreement | null;
|
|
lagnaContrast: LagnaContrast | null;
|
|
nakshatraBoundary: NakshatraBoundary | null;
|
|
precisionStage: PrecisionStage | null;
|
|
oosBlindPrompts: readonly OosBlindPrompt[];
|
|
confirmationGate: ConfirmationGate;
|
|
validated: boolean;
|
|
completionStatus: "provisional_range_user_stopped" | "validated_range" | "exact_minute_confirmed" | null;
|
|
sessionOutcome: DecisionSessionOutcome | null;
|
|
credibleRange: readonly [string, string] | null;
|
|
}>;
|
|
|
|
function record(value: unknown): Record<string, unknown> | null {
|
|
return value && typeof value === "object" ? value as Record<string, unknown> : null;
|
|
}
|
|
|
|
function time(value: unknown): string | null {
|
|
if (typeof value !== "string") return null;
|
|
const normalized = value.slice(0, 5);
|
|
return /^(?:[01]\d|2[0-3]):[0-5]\d$/.test(normalized) ? normalized : null;
|
|
}
|
|
|
|
function text(value: unknown): string | null {
|
|
return typeof value === "string" ? value : null;
|
|
}
|
|
|
|
function finiteNumber(value: unknown): number | null {
|
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
}
|
|
|
|
const uuidPattern = /^[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 deniedHouseTable = /lon|latitude|longitude|degree|score|fingerprint/i;
|
|
|
|
export function parseRectificationHouseTable(value: unknown): RectificationHouseTable | null {
|
|
const row = record(value);
|
|
const tableTime = time(row?.time);
|
|
const lagna = text(row?.lagna)?.trim() ?? "";
|
|
if (!row || !tableTime || !lagna || !Array.isArray(row.houses) || row.houses.length !== 12) return null;
|
|
const houses: RectificationHouseRow[] = [];
|
|
for (let index = 0; index < 12; index += 1) {
|
|
const house = record(row.houses[index]);
|
|
const houseNumber = finiteNumber(house?.house);
|
|
const sign = text(house?.sign)?.trim() ?? "";
|
|
if (
|
|
!house
|
|
|| houseNumber !== index + 1
|
|
|| !sign
|
|
|| deniedHouseTable.test(sign)
|
|
|| !Array.isArray(house.occupants)
|
|
) return null;
|
|
const occupants: string[] = [];
|
|
for (const occupant of house.occupants) {
|
|
const label = text(occupant)?.trim() ?? "";
|
|
if (!label) continue;
|
|
if (deniedHouseTable.test(label)) return null;
|
|
occupants.push(label);
|
|
}
|
|
houses.push({ house: houseNumber, sign, occupants });
|
|
}
|
|
return { time: tableTime, lagna, houses };
|
|
}
|
|
|
|
function houseTablesByTimeFromSnapshot(
|
|
snapshot: Record<string, unknown>,
|
|
receipt: Record<string, unknown> | null,
|
|
): Record<string, RectificationHouseTable> {
|
|
const raw = record(snapshot.houseTablesByTime)
|
|
?? record(snapshot.house_tables_by_time)
|
|
?? record(receipt?.house_tables_by_time);
|
|
const tables: Record<string, RectificationHouseTable> = {};
|
|
if (!raw) return tables;
|
|
for (const [key, value] of Object.entries(raw)) {
|
|
const clock = time(key);
|
|
const table = parseRectificationHouseTable(value);
|
|
if (!clock || !table) continue;
|
|
tables[clock] = table;
|
|
}
|
|
return tables;
|
|
}
|
|
|
|
function natalRecastFromSnapshot(
|
|
snapshot: Record<string, unknown>,
|
|
receipt: Record<string, unknown> | null,
|
|
table: RectificationHouseTable | null,
|
|
wantedMinute: string | null,
|
|
): RectificationNatalRecast | null {
|
|
const raw = record(snapshot.natalRecast) ?? record(snapshot.natal_recast) ?? record(receipt?.natal_recast);
|
|
const recastTime = table?.time ?? time(raw?.time);
|
|
const lagna = table?.lagna || (typeof raw?.lagna === "string" ? raw.lagna.trim() : "") || "";
|
|
if (!recastTime || !lagna || deniedHouseTable.test(lagna)) return null;
|
|
if (wantedMinute && recastTime !== wantedMinute) return null;
|
|
const meaning = natalRecastMeaning({ time: recastTime, lagna })
|
|
?? (typeof raw?.user_meaning === "string" && raw.user_meaning.trim() && !deniedHouseTable.test(raw.user_meaning)
|
|
? raw.user_meaning.trim().slice(0, 220)
|
|
: null);
|
|
if (!meaning) return null;
|
|
return {
|
|
time: recastTime,
|
|
lagna,
|
|
user_meaning: meaning,
|
|
unique_minute_claim: false,
|
|
confirmation_allowed: false,
|
|
};
|
|
}
|
|
|
|
export function natalRecastMeaning(table: Pick<RectificationHouseTable, "time" | "lagna"> | null): string | null {
|
|
if (!table) return null;
|
|
return `本命宫位已按 ${table.time} 重算(上升 ${table.lagna})。`;
|
|
}
|
|
|
|
export function houseTableAlignedToMinute(
|
|
wanted: string | null,
|
|
tablesByTime: Readonly<Record<string, RectificationHouseTable>>,
|
|
fallback: RectificationHouseTable | null,
|
|
): RectificationHouseTable | null {
|
|
if (wanted && tablesByTime[wanted]?.time === wanted) return tablesByTime[wanted];
|
|
if (fallback && wanted && fallback.time === wanted) return fallback;
|
|
if (!wanted) return fallback;
|
|
return null;
|
|
}
|
|
|
|
export function publicChartForMinute(
|
|
wanted: string | null,
|
|
snapshot: Record<string, unknown>,
|
|
receipt: Record<string, unknown> | null,
|
|
): {
|
|
houseTable: RectificationHouseTable | null;
|
|
natalRecast: RectificationNatalRecast | null;
|
|
} {
|
|
const tablesByTime = houseTablesByTimeFromSnapshot(snapshot, receipt);
|
|
const fallback = parseRectificationHouseTable(snapshot.houseTable)
|
|
?? parseRectificationHouseTable(snapshot.house_table)
|
|
?? parseRectificationHouseTable(receipt?.house_table);
|
|
const houseTable = houseTableAlignedToMinute(wanted, tablesByTime, fallback);
|
|
return {
|
|
houseTable,
|
|
natalRecast: natalRecastFromSnapshot(snapshot, receipt, houseTable, wanted),
|
|
};
|
|
}
|
|
|
|
export function workingRectificationHouseTable(
|
|
result: Pick<RectificationCandidateResult, "selectedTime" | "representativeTime" | "houseTable" | "houseTablesByTime">,
|
|
): RectificationHouseTable | null {
|
|
const wanted = result.selectedTime ?? result.representativeTime;
|
|
return houseTableAlignedToMinute(wanted, result.houseTablesByTime, result.houseTable);
|
|
}
|
|
|
|
function techniqueAuditFromSnapshot(
|
|
snapshot: Record<string, unknown>,
|
|
receipt: Record<string, unknown> | null,
|
|
): readonly TechniqueAuditRow[] {
|
|
return normalizeTechniqueAuditRows(snapshot.techniqueAudit)
|
|
?? normalizeTechniqueAuditRows(snapshot.technique_audit_table)
|
|
?? normalizeTechniqueAuditRows(receipt?.technique_audit_table)
|
|
?? [];
|
|
}
|
|
|
|
function receiptFromSnapshot(snapshot: Record<string, unknown>): Record<string, unknown> | null {
|
|
return record(snapshot.decisionReceipt) ?? record(snapshot.decision_receipt);
|
|
}
|
|
|
|
function strictAliasedBoolean(primary: unknown, alias: unknown): boolean {
|
|
if (primary === undefined && alias === undefined) return false;
|
|
if (primary !== undefined && typeof primary !== "boolean") return false;
|
|
if (alias !== undefined && typeof alias !== "boolean") return false;
|
|
if (primary !== undefined && alias !== undefined && primary !== alias) return false;
|
|
return (primary ?? alias) === true;
|
|
}
|
|
|
|
export function rectificationDecisionReceiptAllowsAdoption(value: unknown): boolean {
|
|
const ceiling = engineCapabilityCeilingFromReceipt(value);
|
|
return ceiling.acceptanceAllowed && ceiling.selectionAllowed;
|
|
}
|
|
|
|
function parseSessionOutcome(value: unknown): DecisionSessionOutcome | null {
|
|
if (typeof value !== "string") return null;
|
|
if (
|
|
sessionOutcomeAllowsAdopt(value)
|
|
|| value === "collect_evidence"
|
|
|| value === "discriminate_candidates"
|
|
|| value === "validate_holdout"
|
|
|| value === "provisional_range"
|
|
|| value === "completed_with_range"
|
|
|| value === "validated_range"
|
|
|| value === "exact_minute_confirmed"
|
|
) {
|
|
return value as DecisionSessionOutcome;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function parseCredibleRange(value: unknown): readonly [string, string] | null {
|
|
if (!Array.isArray(value) || value.length !== 2) return null;
|
|
const start = time(value[0]);
|
|
const end = time(value[1]);
|
|
if (!start || !end) return null;
|
|
return [start, end];
|
|
}
|
|
|
|
export function parseRectificationCandidateResult(value: unknown): RectificationCandidateResult | null {
|
|
const snapshot = record(value);
|
|
if (!snapshot || typeof snapshot.resultId !== "string") return null;
|
|
if (!Array.isArray(snapshot.candidates) || snapshot.candidates.length === 0) return null;
|
|
|
|
const candidates: RectificationCandidate[] = [];
|
|
for (const value of snapshot.candidates) {
|
|
const candidate = record(value);
|
|
const candidateId = candidate?.candidateId;
|
|
const candidateTime = time(candidate?.time);
|
|
const rank = finiteNumber(candidate?.rank);
|
|
const relativeSupport = finiteNumber(candidate?.relativeSupport);
|
|
const tiedMinuteCount = finiteNumber(candidate?.tiedMinuteCount);
|
|
if (
|
|
!candidate || typeof candidateId !== "string" || !uuidPattern.test(candidateId)
|
|
|| !candidateTime
|
|
|| rank === null || !Number.isInteger(rank) || rank < 1
|
|
|| relativeSupport === null || !Number.isInteger(relativeSupport) || relativeSupport < 0 || relativeSupport > 100
|
|
|| tiedMinuteCount === null || !Number.isInteger(tiedMinuteCount) || tiedMinuteCount < 1
|
|
) return null;
|
|
candidates.push({ candidateId, rank, time: candidateTime, relativeSupport, tiedMinuteCount });
|
|
}
|
|
|
|
const receipt = receiptFromSnapshot(snapshot);
|
|
const receiptAllowsAdoption = rectificationDecisionReceiptAllowsAdoption(receipt);
|
|
const snapshotSelectionAllowed = strictAliasedBoolean(snapshot.selectionAllowed, snapshot.selection_allowed);
|
|
const snapshotCanAdopt = strictAliasedBoolean(snapshot.canAdopt, snapshot.can_adopt);
|
|
const refinement = refinementFromDecisionReceipt(receipt);
|
|
const windowScan = parseWindowScan(receipt?.window_scan) ?? parseWindowScan(snapshot.window_scan);
|
|
const selectedTime = time(snapshot.selectedTime);
|
|
const representativeTime = time(snapshot.representativeTime);
|
|
const houseTablesByTime = houseTablesByTimeFromSnapshot(snapshot, receipt);
|
|
const fallbackHouseTable = parseRectificationHouseTable(snapshot.houseTable)
|
|
?? parseRectificationHouseTable(snapshot.house_table)
|
|
?? parseRectificationHouseTable(receipt?.house_table);
|
|
const houseTable = houseTableAlignedToMinute(
|
|
selectedTime ?? representativeTime,
|
|
houseTablesByTime,
|
|
fallbackHouseTable,
|
|
);
|
|
|
|
return {
|
|
resultId: snapshot.resultId,
|
|
candidates,
|
|
overallConfidence: snapshot.overallConfidence === "high" || snapshot.overallConfidence === "medium"
|
|
? snapshot.overallConfidence
|
|
: "low",
|
|
selectionAllowed: snapshotSelectionAllowed && receiptAllowsAdoption,
|
|
canAdopt: snapshotCanAdopt && receiptAllowsAdoption,
|
|
confirmationAllowed: snapshot.confirmationAllowed === true,
|
|
decisionReceipt: receipt,
|
|
representativeTime,
|
|
selectedTime,
|
|
selectionKind: text(snapshot.selectionKind),
|
|
houseTable,
|
|
houseTablesByTime,
|
|
natalRecast: natalRecastFromSnapshot(snapshot, receipt, houseTable, selectedTime ?? representativeTime),
|
|
techniqueAudit: techniqueAuditFromSnapshot(snapshot, receipt),
|
|
windowTransitions: windowScan?.transitions ?? parseWindowScanTransitions(snapshot.window_scan),
|
|
eventDashaLedger: refinement.event_dasha_ledger,
|
|
dashaAgreement: refinement.dasha_agreement,
|
|
lagnaContrast: refinement.lagna_contrast,
|
|
nakshatraBoundary: refinement.nakshatra_boundary,
|
|
precisionStage: refinement.precision_stage,
|
|
oosBlindPrompts: refinement.oos_blind_prompts,
|
|
confirmationGate: buildConfirmationGate({
|
|
engineConfirmationAllowed: snapshot.confirmationAllowed === true,
|
|
candidates,
|
|
decisionReceipt: receipt,
|
|
}),
|
|
validated: snapshot.validated === true,
|
|
completionStatus: snapshot.completionStatus === "provisional_range_user_stopped"
|
|
|| snapshot.completionStatus === "validated_range"
|
|
|| snapshot.completionStatus === "exact_minute_confirmed"
|
|
|| snapshot.completion_status === "provisional_range_user_stopped"
|
|
|| snapshot.completion_status === "validated_range"
|
|
|| snapshot.completion_status === "exact_minute_confirmed"
|
|
? (snapshot.completionStatus ?? snapshot.completion_status) as RectificationCandidateResult["completionStatus"]
|
|
: null,
|
|
sessionOutcome: parseSessionOutcome(snapshot.sessionOutcome ?? snapshot.session_outcome),
|
|
credibleRange: parseCredibleRange(snapshot.credibleRange ?? snapshot.credible_range),
|
|
};
|
|
}
|
|
|
|
export function canRenderRectificationSelectionCards(
|
|
result: RectificationCandidateResult | null,
|
|
): boolean {
|
|
return Boolean(
|
|
result?.selectionAllowed
|
|
&& result.canAdopt
|
|
&& rectificationDecisionReceiptAllowsAdoption(result.decisionReceipt),
|
|
);
|
|
}
|
|
|
|
export function canShowRectificationSelectionCards(
|
|
result: RectificationCandidateResult | null,
|
|
): boolean {
|
|
return canRenderRectificationSelectionCards(result)
|
|
&& sessionOutcomeAllowsAdopt(result?.sessionOutcome);
|
|
}
|
|
|
|
export function canShowRectificationReadonlyRange(
|
|
result: RectificationCandidateResult | null,
|
|
): boolean {
|
|
if (!result?.credibleRange) return false;
|
|
const outcome = result.sessionOutcome;
|
|
return outcome === "collect_evidence" || outcome === "discriminate_candidates";
|
|
}
|
|
|
|
export function isRecommendedRectificationCandidate(
|
|
result: RectificationCandidateResult,
|
|
candidate: RectificationCandidate,
|
|
): boolean {
|
|
if (result.selectedTime || !result.selectionAllowed || result.representativeTime !== candidate.time) {
|
|
return false;
|
|
}
|
|
const ranked = [...result.candidates].sort((left, right) => right.relativeSupport - left.relativeSupport);
|
|
const lead = (ranked[0]?.relativeSupport ?? 0) - (ranked[1]?.relativeSupport ?? 0);
|
|
return lead >= MIN_SEPARATION_LEAD;
|
|
}
|