import assert from "node:assert/strict"; import test from "node:test"; import { RectificationEngineError, runV9CandidateScore, runV9Diagnostics, toEngineEvents, toEngineScoreableEvent, V9EngineScoreResult, } from "../src/lib/rectification-agentic/v9/engine-client.ts"; const RANGE = { start_time: "04:50", end_time: "05:10" }; /** * Real /api/rectification/v5/score response captured from * scripts/rectification/api_service.score_candidates (run on Python 3 with * the repository's engine). The engine never returns rank, * tied_minute_count, representative_time, confidence, margin_percent, * selection_allowed or confirmation_allowed -- the client must derive them. */ const REAL_ENGINE_SCORE_RESPONSE = { success: true, endpoint: "rectification_v5_score", result_id: "e4fbf2e0-85dc-5b42-a5a3-34e5dd4b7e62", algorithm_version: "rectification-v5-matrix-scoring-2", calculation_spec_hash: "f05fe0f56ef9ba2b18ec3c6c54f1649f06f1ae5a926491a5c5f676d718d92865", candidate_scores: [ { time: "04:50", score: 3.85, supporting_event_ids: ["00000000-0000-4000-8000-000000000001"], conflicting_event_ids: [] }, { time: "04:51", score: 2.9, supporting_event_ids: [], conflicting_event_ids: [] }, { time: "04:52", score: 2.9, supporting_event_ids: [], conflicting_event_ids: [] }, { time: "04:53", score: 1.2, supporting_event_ids: [], conflicting_event_ids: [] }, ], event_contribution_matrix: { "00000000-0000-4000-8000-000000000001": { "04:50": { points: 3.85, rule_ids: ["vim_md_domain_house", "controlled_transit_jupiter_domain_house"], technique_layers: [ "vim_md_domain_house", "controlled_transit_jupiter_domain_house", "ashtakavarga_target_house_support_auxiliary", ], }, }, }, diagnostics: { primary_cluster_retention_rate: 0.86, leave_one_event_out_retention_rate: 0.81, leave_one_domain_out_retention_rate: 0.9, date_sensitivity_retention_rate: 0.72, neighbor_support_minutes: 2, primary_secondary_margin_percent: 42.5, unstable_event_ids: [], most_discriminating_layers: ["vimsottari_dasha"], candidate_splits: [{ time: "05:02", width: 6 }], }, missing_layers: ["KP_cusps"], can_confirm_exact_minute: false, }; const REAL_ENGINE_DIAGNOSTICS_RESPONSE = { success: true, endpoint: "rectification_v5_diagnostics", result_id: "e4fbf2e0-85dc-5b42-a5a3-34e5dd4b7e62", algorithm_version: "rectification-v5-matrix-scoring-2", calculation_spec_hash: "f05fe0f56ef9ba2b18ec3c6c54f1649f06f1ae5a926491a5c5f676d718d92865", diagnostics: { primary_cluster_retention_rate: 0.86, leave_one_event_out_retention_rate: 0.81, leave_one_domain_out_retention_rate: 0.9, date_sensitivity_retention_rate: 0.72, neighbor_support_minutes: 2, primary_secondary_margin_percent: 42.5, unstable_event_ids: [], most_discriminating_layers: ["vimsottari_dasha"], candidate_splits: [{ time: "05:02", width: 6 }], }, missing_layers: ["KP_cusps"], can_confirm_exact_minute: false, }; function stubEngine(response: unknown, status = 200) { const previous = globalThis.fetch; globalThis.fetch = (async () => ({ ok: status >= 200 && status < 300, status, json: async () => response, })) as unknown as typeof fetch; return () => { globalThis.fetch = previous; }; } const SNAPSHOT = { birth_date: "1997-08-08", latitude: 36.420487, longitude: 114.209936, timezone_offset: 8, birth_time_source: "family_exact", }; const EVIDENCE = [ { id: "00000000-0000-4000-8000-000000000001", eventKind: "education_start", domain: "education", occurredFrom: "2016-09-01", occurredTo: "2016-09-30", datePrecision: "month", summary: "大学入学", }, ]; test("toEngineEvents maps V9 evidence kinds onto the engine scoreable vocabulary", () => { const events = toEngineEvents(EVIDENCE); assert.equal(events.length, 1); assert.equal(events[0]!.domain, "education"); assert.equal(events[0]!.event_kind, "education_milestone"); assert.equal(events[0]!.date_start, "2016-09-01"); assert.equal(events[0]!.precision, "month"); }); test("toEngineScoreableEvent keeps relationship start/change distinct and drops background kinds", () => { assert.deepEqual( toEngineScoreableEvent({ domain: "relationship", eventKind: "relationship_start" }), { domain: "relationship", event_kind: "relationship_start" }, ); assert.deepEqual( toEngineScoreableEvent({ domain: "relationship", eventKind: "relationship_separation" }), { domain: "relationship", event_kind: "relationship_change" }, ); assert.deepEqual( toEngineScoreableEvent({ domain: "career", eventKind: "promotion" }), { domain: "career", event_kind: "career_change" }, ); assert.deepEqual( toEngineScoreableEvent({ domain: "health", eventKind: "self_health_event" }), { domain: "health_pressure", event_kind: "self_health_event" }, ); // Background evidence never reaches the engine scoring path. assert.equal(toEngineScoreableEvent({ domain: "family", eventKind: "family_event" }), null); assert.equal(toEngineScoreableEvent({ domain: "other", eventKind: "other" }), null); }); test("runV9CandidateScore derives rank/support/gating from the real engine response shape", async () => { const restore = stubEngine(REAL_ENGINE_SCORE_RESPONSE); try { const result: V9EngineScoreResult = await runV9CandidateScore({ baselineBirthSnapshot: SNAPSHOT, candidateRange: RANGE, events: toEngineEvents(EVIDENCE), }); assert.equal(result.candidates.length, 3); assert.deepEqual( result.candidates.map((candidate) => candidate.time), ["04:50", "04:51", "04:52"], ); assert.deepEqual(result.candidates.map((candidate) => candidate.rank), [1, 2, 3]); assert.equal(result.candidates[0]!.relative_support, 40, "3.85/(3.85+2.9+2.9)"); assert.equal(result.candidates[1]!.tied_minute_count, 2, "equal scores share a tie count"); assert.equal(result.representativeTime, "04:50"); assert.equal(result.selectionAllowed, true); assert.equal(result.confirmationAllowed, false, "engine gate is the only confirm source"); assert.equal(result.overallConfidence, "high", "margin>=40 and retention>=0.8"); assert.equal(result.marginPercent, 42.5); assert.equal(result.algorithmVersion, "rectification-v5-matrix-scoring-2"); assert.deepEqual(result.executedMethods, [ "d1-rashi", "vimshottari-dasha", "narayana-dasha", "d24-chaturvimshamsha", "gochara", "ashtakavarga", ]); } finally { restore(); } }); test("runV9CandidateScore fails closed when the engine returns no usable candidates", async () => { const restore = stubEngine({ ...REAL_ENGINE_SCORE_RESPONSE, candidate_scores: [] }); try { await assert.rejects( runV9CandidateScore({ baselineBirthSnapshot: SNAPSHOT, candidateRange: RANGE, events: toEngineEvents(EVIDENCE), }), (error: unknown) => error instanceof RectificationEngineError && error.code === "engine_no_candidates", ); } finally { restore(); } }); test("runV9CandidateScore fails closed when no evidence maps to the engine vocabulary", async () => { const backgroundOnly = [ { id: "00000000-0000-4000-8000-000000000002", eventKind: "family_event", domain: "family", occurredFrom: "2018-05-01", occurredTo: null, datePrecision: "year", summary: "家庭事件", }, ]; const restore = stubEngine(REAL_ENGINE_SCORE_RESPONSE); try { await assert.rejects( runV9CandidateScore({ baselineBirthSnapshot: SNAPSHOT, candidateRange: RANGE, events: toEngineEvents(backgroundOnly), }), (error: unknown) => error instanceof RectificationEngineError && error.code === "no_scorable_evidence", ); } finally { restore(); } }); test("runV9Diagnostics maps the real diagnostics response keys", async () => { const restore = stubEngine(REAL_ENGINE_DIAGNOSTICS_RESPONSE); try { const result = await runV9Diagnostics({ baselineBirthSnapshot: SNAPSHOT, candidateRange: RANGE, events: toEngineEvents(EVIDENCE), }); assert.equal(result.canConfirmExactMinute, false); assert.equal(result.missingLayers.join(","), "KP_cusps"); assert.equal(result.diagnostics.primary_secondary_margin_percent, 42.5); assert.equal(result.diagnostics.leave_one_event_out_retention_rate, 0.81); assert.deepEqual(result.diagnostics.most_discriminating_layers, ["vimsottari_dasha"]); assert.deepEqual(result.executedMethods, [ "d1-rashi", "vimshottari-dasha", "narayana-dasha", "d24-chaturvimshamsha", ]); } finally { restore(); } }); test("engine http failures surface as safe engine errors, never raw stack traces", async () => { const restore = stubEngine({ error: "invalid event kind" }, 400); try { await assert.rejects( runV9CandidateScore({ baselineBirthSnapshot: SNAPSHOT, candidateRange: RANGE, events: toEngineEvents(EVIDENCE), }), (error: unknown) => error instanceof Error && error.message.includes("invalid event kind"), ); } finally { restore(); } });