Files
Jyotisha/frontend/tests/rectification-v9-engine-contract.test.ts
T
Jesse_Chen 397b6ef7c2
Staging Backend Quality Gate / validate (pull_request) Failing after 12m4s
Staging Backend Quality Gate / publish (pull_request) Has been skipped
fix(rectification): surface real activity context
2026-08-12 09:28:47 +08:00

265 lines
9.1 KiB
TypeScript

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();
}
});