Files
Jyotisha/frontend/src/lib/rectification-agentic/v9/divergence-panel.ts
T
Jesse_ChenandCursor 6c9a089620
Independent Staging Quality Gate / validate (push) Successful in 13m54s
Independent Staging Quality Gate / publish (push) Successful in 10m50s
fix(rectification): refresh remaining probes and targeted collect before delivering range (BUG-653/654)
Dated-choice exhaustion is not convergence. Refresh probes from remaining
active candidates, then ask a targeted collect, then deliver. Skill 10.0.24.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-11 18:28:39 +08:00

643 lines
23 KiB
TypeScript

/**
* Range-delivery projection: up to three compare columns, ranked by posterior.
* Traits come only from D9 / D10 type tables and nakshatra_boundary options.
*/
import { rankActive } from "../core/convergence-evaluator.ts";
import { signFromTransitions as signFromTransitionLookup } from "../core/sign-from-transitions.ts";
import type {
ConflictProbe,
InferenceCandidate,
InferenceState,
} from "../core/types.ts";
import {
RANGE_DELIVERY_MORE_MINUTES,
REPRESENTATIVE_MINUTE_DISCLAIMER,
rangeDeliveryFitLine,
rangeDeliveryWindowLine,
sharedTraitLine,
} from "../user-copy.ts";
import { previousInferenceFromReceipt } from "./inference-adapter.ts";
import {
rangeNarrowHint,
remainingSplitLayers,
remainingSplitTimes,
} from "./collection-question-pool.ts";
import {
parseEventDashaLedgerByTime,
parseProspectiveWindowsByTime,
refinementFromDecisionReceipt,
type EventDashaLedgerRow,
type NakshatraBoundary,
type ProspectiveWindow,
} from "./refinement-packet.ts";
import { parseWindowScan, type WindowScan } from "./varga-observations.ts";
import {
D10_TYPE_TABLE,
D9_TYPE_TABLE,
signKey,
} from "./varga-type-tables.ts";
const CLOCK = /^(?:[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 COLUMN_LIMIT = 3;
const MATCH_RANK: Readonly<Record<EventDashaLedgerRow["match"], number>> = {
none: 0,
weak: 1,
medium: 2,
strong: 3,
};
export type RangeDeliveryFit = Readonly<{
strong: number;
medium: number;
weak: number;
total: number;
worst: Readonly<{ year_month: string; domain: string }> | null;
}>;
export type RangeDeliveryColumn = Readonly<{
time: string;
candidate_id: string;
probability_percent: number;
traits: Readonly<{ d9: string; d10: string; nakshatra: readonly string[] }>;
fit: RangeDeliveryFit | null;
windows: readonly ProspectiveWindow[] | null;
fit_line: string;
window_line: string;
}>;
export type RangeDeliveryProjection = Readonly<{
range: readonly [string, string] | null;
representative_time: string | null;
representative_candidate_id: string | null;
event_count: number;
fit_percent: number | null;
boundary: string;
shared_traits: readonly string[];
columns: readonly RangeDeliveryColumn[];
more_count: number;
more_label: string | null;
verification_markdown: string | null;
narrow_hint: string | null;
}>;
export type PublicCandidateClock = Readonly<{
candidateId: string;
time: string;
relativeSupport?: number;
}>;
function clock(value: unknown): string | null {
if (typeof value !== "string") return null;
const normalized = value.slice(0, 5);
return CLOCK.test(normalized) ? normalized : null;
}
function signFromProbe(
probes: readonly ConflictProbe[],
layer: "d9" | "d10",
candidateId: string,
): string | null {
const prefix = layer === "d9" ? "varga.d9" : "varga.d10";
for (const probe of probes) {
if (!probe.semantic_key.startsWith(prefix)) continue;
const options = probe.style_options;
if (!options?.length) continue;
for (const outcome of probe.expected_outcomes) {
if (!outcome.supports.includes(candidateId)) continue;
const option = options.find((item) => item.answer_class === outcome.answer_class);
const sign = option?.sign?.trim();
if (!sign) continue;
const keyed = signKey(sign);
if (layer === "d9" && D9_TYPE_TABLE[keyed]) return keyed;
if (layer === "d10" && D10_TYPE_TABLE[keyed]) return keyed;
}
}
return null;
}
export function signFromTransitions(
transitions: readonly { layer: string; at: string; from_sign?: string; to_sign?: string }[],
layer: "d9" | "d10",
time: string,
): string | null {
const sign = signFromTransitionLookup(transitions, layer, time);
const keyed = sign ? signKey(sign) : "";
if (layer === "d9" && D9_TYPE_TABLE[keyed]) return keyed;
if (layer === "d10" && D10_TYPE_TABLE[keyed]) return keyed;
return null;
}
function signForCandidate(
probes: readonly ConflictProbe[],
windowScan: WindowScan | null,
layer: "d9" | "d10",
candidate: InferenceCandidate,
): string | null {
return signFromProbe(probes, layer, candidate.id)
?? signFromTransitions(windowScan?.transitions ?? [], layer, candidate.time);
}
export function d9DivergenceLabel(sign: string): string | null {
const keyed = signKey(sign);
const row = D9_TYPE_TABLE[keyed];
if (!row) return null;
return `关系盘落在${keyed},通常表现为${row.userChoice}`;
}
export function d10DivergenceLabel(sign: string): string | null {
const keyed = signKey(sign);
const row = D10_TYPE_TABLE[keyed];
if (!row) return null;
return `事业盘落在${keyed},通常表现为${row.userChoice}`;
}
function publicIdForTime(
publicCandidates: readonly PublicCandidateClock[],
time: string,
fallbackId: string,
): string {
const match = publicCandidates.find((item) => item.time === time);
if (match && UUID.test(match.candidateId)) return match.candidateId;
return fallbackId;
}
export function datedEventCount(inference: InferenceState | null): number {
if (!inference) return 0;
return inference.events.filter((item) => item.year != null && item.usage !== "unused").length;
}
function inRange(
time: string,
range: readonly [string, string] | null,
): boolean {
if (!range) return true;
return time >= range[0] && time <= range[1];
}
function probabilityPercent(value: number | null | undefined, fallback = 0): number {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return fallback;
const raw = value <= 1 ? value * 100 : value;
return Math.min(100, Math.round(raw));
}
function fitFromLedger(rows: readonly EventDashaLedgerRow[]): RangeDeliveryFit {
let strong = 0;
let medium = 0;
let weak = 0;
let worst: RangeDeliveryFit["worst"] = null;
let worstRank = 99;
for (const row of rows) {
if (row.match === "strong") strong += 1;
else if (row.match === "medium") medium += 1;
else if (row.match === "weak") weak += 1;
const rank = MATCH_RANK[row.match] ?? 99;
if (rank < worstRank && row.year_month && row.domain) {
worstRank = rank;
worst = { year_month: row.year_month, domain: row.domain };
}
}
return { strong, medium, weak, total: rows.length, worst };
}
function lookupByTime<T>(
map: Readonly<Record<string, T>> | undefined,
time: string,
): T | undefined {
if (!map || !Object.prototype.hasOwnProperty.call(map, time)) return undefined;
return map[time];
}
function traitLines(traits: RangeDeliveryColumn["traits"]): string[] {
return [traits.d9, traits.d10, ...traits.nakshatra].filter(Boolean);
}
function splitSharedTraits(
columns: readonly Omit<RangeDeliveryColumn, "fit_line" | "window_line">[],
): { shared: string[]; columns: typeof columns } {
if (columns.length < 2) return { shared: [], columns };
const lineSets = columns.map((column) => traitLines(column.traits));
const sharedRaw = lineSets[0]?.filter((line) => lineSets.every((set) => set.includes(line))) ?? [];
const sharedSet = new Set(sharedRaw);
const shared = sharedRaw.map((line) => sharedTraitLine(line, columns.length));
return {
shared,
columns: columns.map((column) => ({
...column,
traits: {
d9: sharedSet.has(column.traits.d9) ? "" : column.traits.d9,
d10: sharedSet.has(column.traits.d10) ? "" : column.traits.d10,
nakshatra: column.traits.nakshatra.filter((line) => !sharedSet.has(line)),
},
})),
};
}
function nakshatraTraitsForTime(
boundary: NakshatraBoundary | null,
time: string,
representativeTime: string | null,
): readonly string[] {
if (!boundary?.near_boundary) return [];
const earlier = boundary.options.find((item) => item.time_bias === "earlier")?.traits ?? [];
const later = boundary.options.find((item) => item.time_bias === "later")?.traits ?? [];
if (!representativeTime || time === representativeTime) return earlier.length ? earlier : later;
return time < representativeTime ? earlier : later;
}
type ColumnClock = Readonly<{
time: string;
id: string;
candidate: InferenceCandidate | null;
probability: number;
}>;
function columnClocks(input: {
inference: InferenceState | null;
publicCandidates: readonly PublicCandidateClock[];
range: readonly [string, string] | null;
}): { clocks: ColumnClock[]; moreCount: number } {
const fromInference = input.inference
? rankActive(input.inference.candidates).filter((item) => inRange(item.time, input.range))
: [];
const seen = new Set<string>();
const ranked: ColumnClock[] = [];
for (const item of fromInference) {
if (seen.has(item.time)) continue;
seen.add(item.time);
ranked.push({
time: item.time,
id: item.id,
candidate: item,
probability: probabilityPercent(item.probability),
});
}
if (ranked.length === 0) {
const publicRanked = [...input.publicCandidates]
.filter((item) => inRange(item.time, input.range))
.sort((left, right) => (right.relativeSupport ?? 0) - (left.relativeSupport ?? 0)
|| left.time.localeCompare(right.time));
for (const item of publicRanked) {
if (seen.has(item.time)) continue;
seen.add(item.time);
ranked.push({
time: item.time,
id: item.candidateId,
candidate: null,
probability: probabilityPercent(item.relativeSupport, 0),
});
}
}
return {
clocks: ranked.slice(0, COLUMN_LIMIT),
moreCount: Math.max(0, ranked.length - COLUMN_LIMIT),
};
}
export function verificationMarkdownFromUnknown(value: unknown): string | null {
if (typeof value === "string" && value.trim()) return value.trim();
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const markdown = (value as { markdown?: unknown }).markdown;
return typeof markdown === "string" && markdown.trim() ? markdown : null;
}
function fitPercentFromUnknown(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const percent = (value as { percent?: unknown }).percent;
return typeof percent === "number" && Number.isFinite(percent) ? percent : null;
}
export function buildRangeDelivery(input: {
inference: InferenceState | null;
windowScan?: WindowScan | null;
publicCandidates?: readonly PublicCandidateClock[];
credibleRange?: readonly [string, string] | null;
representativeTime?: string | null;
fitPercent?: number | null;
verificationMarkdown?: string | null;
nakshatraBoundary?: NakshatraBoundary | null;
eventDashaLedgerByTime?: Readonly<Record<string, readonly EventDashaLedgerRow[]>>;
prospectiveWindowsByTime?: Readonly<Record<string, readonly ProspectiveWindow[]>>;
eventDashaLedger?: readonly EventDashaLedgerRow[];
evidence?: readonly Readonly<{
status: string;
domain: string;
datePrecision: string;
occurredFrom: string | null;
occurredTo: string | null;
eventKind?: string | null;
summary?: string | null;
}>[];
declinedTopics?: readonly Readonly<Record<string, unknown>>[];
}): RangeDeliveryProjection {
const inference = input.inference;
const range = input.credibleRange
?? inference?.credible_range
?? (inference ? [inference.range_start, inference.range_end] as const : null);
const representativeTime = clock(input.representativeTime)
?? clock(inference?.representative_time)
?? null;
const publicCandidates = input.publicCandidates ?? [];
const representativeId = representativeTime
? publicIdForTime(
publicCandidates,
representativeTime,
inference?.candidates.find((item) => item.time === representativeTime)?.id ?? representativeTime,
)
: publicCandidates[0]?.candidateId ?? null;
const normalizedRange = range && clock(range[0]) && clock(range[1])
? [clock(range[0])!, clock(range[1])!] as const
: null;
const { clocks, moreCount } = columnClocks({
inference,
publicCandidates,
range: normalizedRange,
});
const probes = inference?.probes ?? [];
const windowScan = input.windowScan ?? null;
const rawColumns = clocks.map((item) => {
const d9 = item.candidate
? signForCandidate(probes, windowScan, "d9", item.candidate)
: signFromTransitions(windowScan?.transitions ?? [], "d9", item.time);
const d10 = item.candidate
? signForCandidate(probes, windowScan, "d10", item.candidate)
: signFromTransitions(windowScan?.transitions ?? [], "d10", item.time);
const timedLedger = lookupByTime(input.eventDashaLedgerByTime, item.time);
const ledger = timedLedger
?? (item.time === representativeTime ? input.eventDashaLedger : undefined);
const windows = lookupByTime(input.prospectiveWindowsByTime, item.time);
const fit = ledger ? fitFromLedger(ledger) : null;
return {
time: item.time,
candidate_id: publicIdForTime(publicCandidates, item.time, item.id),
probability_percent: item.probability,
traits: {
d9: d9 ? (d9DivergenceLabel(d9) ?? "") : "",
d10: d10 ? (d10DivergenceLabel(d10) ?? "") : "",
nakshatra: nakshatraTraitsForTime(input.nakshatraBoundary ?? null, item.time, representativeTime),
},
fit,
windows: windows === undefined ? null : windows,
};
});
const split = splitSharedTraits(rawColumns);
const columns = split.columns.map((column) => ({
...column,
fit_line: rangeDeliveryFitLine(column.fit),
window_line: rangeDeliveryWindowLine(column.windows),
}));
return {
range: normalizedRange,
representative_time: representativeTime,
representative_candidate_id: representativeId,
event_count: datedEventCount(inference),
fit_percent: input.fitPercent ?? null,
boundary: REPRESENTATIVE_MINUTE_DISCLAIMER,
shared_traits: split.shared,
columns,
more_count: moreCount,
more_label: moreCount > 0 ? RANGE_DELIVERY_MORE_MINUTES(moreCount) : null,
verification_markdown: input.verificationMarkdown ?? null,
narrow_hint: rangeNarrowHint(
remainingSplitLayers({
transitions: inference?.transitions ?? windowScan?.transitions ?? [],
scanFlags: windowScan,
activeTimes: inference?.candidates
.filter((item) => item.status !== "eliminated")
.map((item) => item.time) ?? clocks.map((item) => item.time),
}),
input.evidence ?? [],
input.declinedTopics ?? [],
remainingSplitTimes(
inference?.candidates
.filter((item) => item.status !== "eliminated")
.map((item) => item.time) ?? clocks.map((item) => item.time),
),
),
};
}
function publicCandidatesFromUnknown(value: unknown): PublicCandidateClock[] {
if (!Array.isArray(value)) return [];
const out: PublicCandidateClock[] = [];
for (const item of value) {
if (!item || typeof item !== "object") continue;
const row = item as Record<string, unknown>;
const time = clock(row.time);
const id = typeof row.candidateId === "string"
? row.candidateId
: typeof row.candidate_id === "string"
? row.candidate_id
: "";
if (!time || !id) continue;
const relativeSupport = typeof row.relativeSupport === "number"
? row.relativeSupport
: typeof row.relative_support === "number"
? row.relative_support
: undefined;
out.push({ candidateId: id, time, ...(relativeSupport != null ? { relativeSupport } : {}) });
}
return out;
}
function rangeFromUnknown(value: unknown): readonly [string, string] | null {
if (!Array.isArray(value) || value.length !== 2) return null;
const start = clock(value[0]);
const end = clock(value[1]);
return start && end ? [start, end] : null;
}
export function rangeDeliveryForSnapshot(snapshot: {
decisionReceipt?: Readonly<Record<string, unknown>> | null;
decision_receipt?: Readonly<Record<string, unknown>> | null;
candidates?: unknown;
representativeTime?: string | null;
representative_time?: string | null;
credibleRange?: unknown;
credible_range?: unknown;
skill_verification_report?: unknown;
skillVerificationReport?: unknown;
event_fit_rate?: unknown;
eventFitRate?: unknown;
evidence?: readonly Readonly<{
status: string;
domain: string;
datePrecision: string;
occurredFrom: string | null;
occurredTo: string | null;
eventKind?: string | null;
summary?: string | null;
}>[];
declinedTopics?: readonly Readonly<Record<string, unknown>>[];
} | null | undefined): RangeDeliveryProjection {
const receipt = snapshot?.decisionReceipt ?? snapshot?.decision_receipt ?? null;
const inference = previousInferenceFromReceipt(receipt);
const windowScan = parseWindowScan(receipt?.window_scan) ?? parseWindowScan(
snapshot && "window_scan" in (snapshot as object)
? (snapshot as { window_scan?: unknown }).window_scan
: null,
);
const refinement = refinementFromDecisionReceipt(receipt);
return buildRangeDelivery({
inference,
windowScan,
publicCandidates: publicCandidatesFromUnknown(snapshot?.candidates),
credibleRange: rangeFromUnknown(snapshot?.credibleRange) ?? rangeFromUnknown(snapshot?.credible_range),
representativeTime: clock(snapshot?.representativeTime) ?? clock(snapshot?.representative_time),
fitPercent: refinement.event_fit_rate?.percent
?? fitPercentFromUnknown(snapshot?.event_fit_rate)
?? fitPercentFromUnknown(snapshot?.eventFitRate),
verificationMarkdown: verificationMarkdownFromUnknown(snapshot?.skill_verification_report)
?? verificationMarkdownFromUnknown(snapshot?.skillVerificationReport),
nakshatraBoundary: refinement.nakshatra_boundary,
eventDashaLedgerByTime: refinement.event_dasha_ledger_by_time,
prospectiveWindowsByTime: refinement.prospective_windows_by_time,
eventDashaLedger: refinement.event_dasha_ledger,
evidence: snapshot?.evidence,
declinedTopics: snapshot?.declinedTopics,
});
}
function parseFit(value: unknown): RangeDeliveryFit | null {
if (value == null) return null;
if (!value || typeof value !== "object" || Array.isArray(value)) {
return { strong: 0, medium: 0, weak: 0, total: 0, worst: null };
}
const row = value as Record<string, unknown>;
const count = (key: string) => (
typeof row[key] === "number" && Number.isInteger(row[key]) && (row[key] as number) >= 0
? row[key] as number
: 0
);
const worstRaw = row.worst && typeof row.worst === "object" && !Array.isArray(row.worst)
? row.worst as Record<string, unknown>
: null;
const yearMonth = typeof worstRaw?.year_month === "string" ? worstRaw.year_month : "";
const domain = typeof worstRaw?.domain === "string" ? worstRaw.domain : "";
const strong = count("strong");
const medium = count("medium");
const weak = count("weak");
const total = count("total") || strong + medium + weak;
return {
strong,
medium,
weak,
total,
worst: yearMonth && domain ? { year_month: yearMonth, domain } : null,
};
}
function parseWindows(value: unknown): ProspectiveWindow[] {
if (!Array.isArray(value)) return [];
const out: ProspectiveWindow[] = [];
for (const item of value) {
if (!item || typeof item !== "object") continue;
const row = item as Record<string, unknown>;
const domain = typeof row.domain === "string" ? row.domain : "";
const from = typeof row.from === "string" ? row.from : "";
const to = typeof row.to === "string" ? row.to : from;
if (!domain || !from) continue;
out.push({ domain, from, to });
}
return out;
}
function parseColumns(value: unknown): RangeDeliveryColumn[] {
if (!Array.isArray(value)) return [];
const out: RangeDeliveryColumn[] = [];
for (const item of value) {
if (!item || typeof item !== "object") continue;
const row = item as Record<string, unknown>;
const time = clock(row.time);
const id = typeof row.candidate_id === "string"
? row.candidate_id
: typeof row.candidateId === "string"
? row.candidateId
: "";
if (!time || !id) continue;
const traits = row.traits && typeof row.traits === "object" && !Array.isArray(row.traits)
? row.traits as Record<string, unknown>
: {};
const nakshatra = Array.isArray(traits.nakshatra)
? traits.nakshatra.filter((entry): entry is string => typeof entry === "string")
: [];
const fit = "fit" in row && row.fit === null ? null : parseFit(row.fit);
const windows = !("windows" in row) || row.windows == null ? null : parseWindows(row.windows);
out.push({
time,
candidate_id: id,
probability_percent: probabilityPercent(
typeof row.probability_percent === "number" ? row.probability_percent : 0,
0,
),
traits: {
d9: typeof traits.d9 === "string" ? traits.d9 : "",
d10: typeof traits.d10 === "string" ? traits.d10 : "",
nakshatra,
},
fit,
windows,
fit_line: typeof row.fit_line === "string" && row.fit_line.trim()
? row.fit_line.trim()
: rangeDeliveryFitLine(fit),
window_line: typeof row.window_line === "string" && row.window_line.trim()
? row.window_line.trim()
: rangeDeliveryWindowLine(windows),
});
}
return out;
}
export function parseRangeDelivery(value: unknown): RangeDeliveryProjection | null {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const row = value as Record<string, unknown>;
const columns = parseColumns(row.columns);
if (columns.length === 0) return null;
const range = rangeFromUnknown(row.range);
const representativeTime = clock(row.representative_time) ?? clock(row.representativeTime);
const representativeId = typeof row.representative_candidate_id === "string"
? row.representative_candidate_id
: typeof row.representativeCandidateId === "string"
? row.representativeCandidateId
: null;
const eventCount = typeof row.event_count === "number" && Number.isInteger(row.event_count) && row.event_count >= 0
? row.event_count
: typeof row.eventCount === "number" && Number.isInteger(row.eventCount) && row.eventCount >= 0
? row.eventCount
: 0;
const moreCount = typeof row.more_count === "number" && Number.isInteger(row.more_count) && row.more_count >= 0
? row.more_count
: 0;
const boundary = typeof row.boundary === "string" && row.boundary.trim()
? row.boundary.trim()
: REPRESENTATIVE_MINUTE_DISCLAIMER;
const sharedTraits = Array.isArray(row.shared_traits)
? row.shared_traits.filter((item): item is string => typeof item === "string" && item.trim().length > 0)
: Array.isArray(row.sharedTraits)
? row.sharedTraits.filter((item): item is string => typeof item === "string" && item.trim().length > 0)
: [];
return {
range,
representative_time: representativeTime,
representative_candidate_id: representativeId,
event_count: eventCount,
fit_percent: fitPercentFromUnknown(row.fit_percent) ?? fitPercentFromUnknown(row.fitPercent),
boundary,
shared_traits: sharedTraits,
columns,
more_count: moreCount,
more_label: typeof row.more_label === "string" && row.more_label.trim()
? row.more_label.trim()
: moreCount > 0 ? RANGE_DELIVERY_MORE_MINUTES(moreCount) : null,
verification_markdown: verificationMarkdownFromUnknown(row.verification_markdown)
?? verificationMarkdownFromUnknown(row.verificationMarkdown),
narrow_hint: typeof row.narrow_hint === "string" && row.narrow_hint.trim()
? row.narrow_hint.trim()
: typeof row.narrowHint === "string" && row.narrowHint.trim()
? row.narrowHint.trim()
: null,
};
}