refactor: rebuild birth time rectification agent

This commit is contained in:
Jesse_Chen
2026-07-28 13:04:30 +08:00
parent ac5aef5f89
commit 8ade6ed5c8
59 changed files with 4422 additions and 1141 deletions
@@ -2,21 +2,68 @@ import { z } from "zod";
import type { CalculationSpec, CandidateMinute, LifeEventRevision } from "./contracts.ts";
import { rectificationV4AlgorithmVersion } from "./contracts.ts";
const responseSchema = z.object({
result_id: z.string().uuid(),
const uuid = z.string().uuid();
const hash = z.string().regex(/^[a-f0-9]{64}$/);
const dateSensitivitySchema = z.object({
event_id: uuid,
declared_date_range: z.object({ start: z.string(), end: z.string(), precision: z.string() }),
sample_dates: z.array(z.string()).min(1).max(12),
winner_retention_rate: z.number().min(0).max(1),
score_variance: z.number().nonnegative(),
candidate_cluster_retention_rate: z.number().min(0).max(1),
}).passthrough();
const diagnosticsSchema = z.object({
primary_cluster_retention_rate: z.number().min(0).max(1),
leave_one_event_out_retention_rate: z.number().min(0).max(1),
leave_one_domain_out_retention_rate: z.number().min(0).max(1),
date_sensitivity_retention_rate: z.number().min(0).max(1),
neighbor_support_minutes: z.number().int().nonnegative(),
primary_secondary_margin_percent: z.number().min(0).max(100),
cluster_mass_ratio: z.number().min(0).max(1),
unstable_event_ids: z.array(uuid),
most_discriminating_layers: z.array(z.string()),
event_date_sensitivity: z.array(dateSensitivitySchema),
candidate_splits: z.array(z.object({
left_cluster: z.object({ start: z.string(), end: z.string() }),
right_cluster: z.object({ start: z.string(), end: z.string() }),
technique_layers: z.array(z.string()),
event_ids: z.array(uuid),
}).passthrough()),
}).passthrough();
const featureSchema = z.object({
calculation_spec_hash: hash,
algorithm_version: z.literal(rectificationV4AlgorithmVersion),
calculation_spec_hash: z.string().regex(/^[a-f0-9]{64}$/),
candidate_count: z.number().int().positive(),
feature_hash: hash,
features: z.array(z.object({
time: z.string(),
ascendant_degree: z.number().nullable(),
ascendant_sign_index: z.number().int().min(0).max(11).nullable(),
varga_ascendants: z.record(z.string(), z.number().int().min(0).max(11)),
arudha_signs: z.object({ A7: z.number().int().min(0).max(11).nullable(), A10: z.number().int().min(0).max(11).nullable(), UL: z.number().int().min(0).max(11).nullable() }),
available_layers: z.array(z.string()), blocked_layers: z.array(z.string()),
fingerprints: z.record(z.string(), z.string()),
}).passthrough()),
}).passthrough();
const responseSchema = z.object({
result_id: uuid,
algorithm_version: z.literal(rectificationV4AlgorithmVersion),
calculation_spec_hash: hash,
candidate_scores: z.array(z.object({
time: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),
score: z.number().finite(),
supporting_event_ids: z.array(z.string().uuid()),
conflicting_event_ids: z.array(z.string().uuid()),
time: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/), score: z.number().finite(),
supporting_event_ids: z.array(uuid), conflicting_event_ids: z.array(uuid),
}).strict()).min(1).max(1_440),
robustness: z.object({
neighbor_support_minutes: z.number().int().nonnegative(),
leave_one_out_retention_rate: z.number().finite().min(0).max(1),
date_sensitivity_retention_rate: z.number().finite().min(0).max(1),
leave_one_out_retention_rate: z.number().min(0).max(1),
leave_one_domain_out_retention_rate: z.number().min(0).max(1),
date_sensitivity_retention_rate: z.number().min(0).max(1),
}).passthrough(),
diagnostics: diagnosticsSchema,
candidate_feature_snapshot: featureSchema,
event_contribution_matrix: z.record(z.string(), z.record(z.string(), z.object({
points: z.number(), rule_ids: z.array(z.string()), technique_layers: z.array(z.string()),
}).passthrough())),
missing_layers: z.array(z.string()),
can_confirm_exact_minute: z.literal(false),
}).passthrough();
@@ -25,11 +72,10 @@ export type CandidateEngineResult = Readonly<{
resultId: string;
calculationSpecHash: string;
candidates: readonly CandidateMinute[];
robustness: {
readonly neighborSupportMinutes: number;
readonly leaveOneOutRetentionRate: number;
readonly dateSensitivityRetentionRate: number;
};
robustness: { neighborSupportMinutes: number; leaveOneOutRetentionRate: number; leaveOneDomainOutRetentionRate: number; dateSensitivityRetentionRate: number };
diagnostics: z.infer<typeof diagnosticsSchema>;
featureSnapshot: z.infer<typeof featureSchema>;
contributionMatrix: z.infer<typeof responseSchema>["event_contribution_matrix"];
missingLayers: readonly string[];
}>;
@@ -37,54 +83,37 @@ export interface RectificationV4CandidateEngine {
score(input: { readonly calculationSpec: CalculationSpec; readonly events: readonly LifeEventRevision[] }): Promise<CandidateEngineResult>;
}
export function createRectificationV4CandidateEngine(options: {
readonly apiBase: string;
readonly fetchImpl?: typeof fetch;
}): RectificationV4CandidateEngine {
export function createRectificationV4CandidateEngine(options: { readonly apiBase: string; readonly fetchImpl?: typeof fetch }): RectificationV4CandidateEngine {
const fetchImpl = options.fetchImpl ?? fetch;
return {
async score({ calculationSpec, events }) {
const response = await fetchImpl(`${options.apiBase}/api/active_rectification_events_v4`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
birth_date: calculationSpec.birthDate,
start_time: calculationSpec.candidateRange.start,
end_time: calculationSpec.candidateRange.end,
lat: calculationSpec.latitude,
lon: calculationSpec.longitude,
tz: calculationSpec.timezoneOffsetHours,
events: events.map((event) => ({
id: event.eventId,
domain: event.domain,
event_kind: event.eventKind,
date_start: event.dateRange.start,
date_end: event.dateRange.end,
precision: event.dateRange.precision,
summary: event.summary,
})),
}),
signal: AbortSignal.timeout(5 * 60_000),
});
const payload: unknown = await response.json();
if (!response.ok) throw new Error(`rectification_v4_engine_${response.status}`);
const parsed = responseSchema.parse(payload);
return {
resultId: parsed.result_id,
calculationSpecHash: parsed.calculation_spec_hash,
candidates: parsed.candidate_scores.map((candidate) => ({
time: candidate.time,
score: candidate.score,
supportingEventIds: candidate.supporting_event_ids,
conflictingEventIds: candidate.conflicting_event_ids,
return { async score({ calculationSpec, events }) {
const response = await fetchImpl(`${options.apiBase}/api/rectification/v5/score`, {
method: "POST", headers: { "content-type": "application/json" }, signal: AbortSignal.timeout(5 * 60_000),
body: JSON.stringify({
birth_date: calculationSpec.birthDate, start_time: calculationSpec.candidateRange.start, end_time: calculationSpec.candidateRange.end,
lat: calculationSpec.latitude, lon: calculationSpec.longitude, tz: calculationSpec.timezoneOffsetHours,
events: events.map((event) => ({
id: event.eventId, domain: event.domain, event_kind: event.eventKind,
date_start: event.dateRange.start, date_end: event.dateRange.end, precision: event.dateRange.precision, summary: event.summary,
})),
robustness: {
neighborSupportMinutes: parsed.robustness.neighbor_support_minutes,
leaveOneOutRetentionRate: parsed.robustness.leave_one_out_retention_rate,
dateSensitivityRetentionRate: parsed.robustness.date_sensitivity_retention_rate,
},
missingLayers: parsed.missing_layers,
};
},
};
}),
});
const payload: unknown = await response.json();
if (!response.ok) throw new Error(`rectification_v5_engine_${response.status}`);
const parsed = responseSchema.parse(payload);
return {
resultId: parsed.result_id,
calculationSpecHash: parsed.calculation_spec_hash,
candidates: parsed.candidate_scores.map((candidate) => ({ time: candidate.time, score: candidate.score, supportingEventIds: candidate.supporting_event_ids, conflictingEventIds: candidate.conflicting_event_ids })),
robustness: {
neighborSupportMinutes: parsed.robustness.neighbor_support_minutes,
leaveOneOutRetentionRate: parsed.robustness.leave_one_out_retention_rate,
leaveOneDomainOutRetentionRate: parsed.robustness.leave_one_domain_out_retention_rate,
dateSensitivityRetentionRate: parsed.robustness.date_sensitivity_retention_rate,
},
diagnostics: parsed.diagnostics,
featureSnapshot: parsed.candidate_feature_snapshot,
contributionMatrix: parsed.event_contribution_matrix,
missingLayers: parsed.missing_layers,
};
}};
}