Files
Jyotisha/frontend/tests/birth-time-journey-adapters.test.ts
T
2026-07-27 11:29:12 +08:00

311 lines
9.0 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
parseBirthTimeProfile,
parseCandidateResult,
parseRectificationQuestionnaire,
parseRectificationScoring,
} from "../src/lib/birth-time-journey-adapters.ts";
const coordinates = {
latitude: 31.2304,
longitude: 121.4737,
timezone_offset: 8,
} as const;
test("birth time profile adapter parses an exact hospital declaration", () => {
const assessment = parseBirthTimeProfile({
birth_date: "1993-04-17",
reported_birth_time: "08:16:00",
birth_time_source: "hospital_record",
uncertainty_before_minutes: 2,
uncertainty_after_minutes: 2,
...coordinates,
});
assert.equal(assessment.source, "hospital_record");
if (assessment.source === "hospital_record") {
assert.equal(assessment.reportedTime, "08:16");
assert.equal(assessment.location.lon, 121.4737);
}
});
test("birth time profile adapter normalizes PostgreSQL date values", () => {
const assessment = parseBirthTimeProfile({
birth_date: new Date("1993-04-17T00:00:00.000Z"),
reported_birth_time: "08:16:00",
birth_time_source: "hospital_record",
uncertainty_before_minutes: 2,
uncertainty_after_minutes: 2,
...coordinates,
});
assert.equal(assessment.date, "1993-04-17");
});
test("birth time profile adapter parses a period without inventing a time", () => {
const assessment = parseBirthTimeProfile({
birth_date: "1993-04-17",
reported_birth_time: null,
birth_time_source: "period_only",
birth_time_period: "evening",
...coordinates,
});
assert.equal(assessment.source, "period_only");
assert.equal("reportedTime" in assessment, false);
});
test("birth time profile adapter rejects missing location coordinates", () => {
assert.throws(() => parseBirthTimeProfile({
birth_date: "1993-04-17",
reported_birth_time: "08:16:00",
birth_time_source: "hospital_record",
uncertainty_before_minutes: 2,
uncertainty_after_minutes: 2,
}));
});
test("rectification adapter normalizes Python questionnaire samples and options", () => {
const questionnaire = parseRectificationQuestionnaire({
questions: [{
id: "education_environment_shift",
prompt: "是否有明显学业变化?",
options: [
{ key: "A", label: "明确有" },
{ key: "D", label: "不记得" },
],
}],
candidate_scan: {
samples: [{
ascendant: { sign: "Cancer" },
varga_lagna: {
D9: { sign: "Leo" },
D10: { sign: "Virgo" },
},
}],
},
});
assert.deepEqual(questionnaire.questions[0]?.options, [
{ key: "A", label: "明确有" },
{ key: "D", label: "不记得" },
]);
assert.deepEqual(questionnaire.samples[0], {
ascendantSign: "Cancer",
d4Sign: null,
d9Sign: "Leo",
d10Sign: "Virgo",
d24Sign: null,
d30Sign: null,
a7Sign: null,
ulSign: null,
a10Sign: null,
});
});
test("rectification adapter preserves real engine sample times and named Varga keys", () => {
const questionnaire = parseRectificationQuestionnaire({
questions: [],
candidate_scan: {
samples: [{
time: "1997-08-08 06:00",
ascendant: { sign: "Cancer" },
varga_lagna: {
D2_Hora: { sign: "Cancer" },
D4_Turyamsa: { sign: "Aries" },
D9_Navamsa: { sign: "Aquarius" },
D10_Dasamsa: { sign: "Scorpio" },
D11_Rudramsa: { sign: "Libra" },
D24_Siddhamsa: { sign: "Pisces" },
D30_Trimsamsa: { sign: "Pisces" },
},
arudha: {
A7: { sign: "Scorpio" },
UL: { sign: "Virgo" },
A10: { sign: "Capricorn" },
},
}],
},
});
const rawScan = questionnaire.raw.candidate_scan as {
samples: Array<{ time?: unknown }>;
};
assert.equal(rawScan.samples[0]?.time, "1997-08-08 06:00");
assert.deepEqual(questionnaire.samples[0], {
ascendantSign: "Cancer",
d2Sign: "Cancer",
d4Sign: "Aries",
d9Sign: "Aquarius",
d10Sign: "Scorpio",
d11Sign: "Libra",
d24Sign: "Pisces",
d30Sign: "Pisces",
a7Sign: "Scorpio",
ulSign: "Virgo",
a10Sign: "Capricorn",
});
});
test("rectification adapter rejects a malformed Python questionnaire", () => {
assert.throws(() => parseRectificationQuestionnaire({
questions: [{ id: "missing_prompt" }],
candidate_scan: { samples: [] },
}));
});
test("rectification adapter normalizes all evidence-domain Varga signs", () => {
const questionnaire = parseRectificationQuestionnaire({
questions: [],
candidate_scan: {
samples: [{
ascendant: { sign: "Cancer" },
varga_lagna: {
D4: { sign: "Aries" },
D9: { sign: "Leo" },
D10: { sign: "Virgo" },
D24: { sign: "Gemini" },
D30: { sign: "Pisces" },
},
}],
},
});
assert.deepEqual(questionnaire.samples[0], {
ascendantSign: "Cancer",
d4Sign: "Aries",
d9Sign: "Leo",
d10Sign: "Virgo",
d24Sign: "Gemini",
d30Sign: "Pisces",
a7Sign: null,
ulSign: null,
a10Sign: null,
});
});
test("rectification adapter preserves scoring maps needed after the third answer", () => {
const questionnaire = parseRectificationQuestionnaire({
questions: [{
id: "education_environment_shift",
prompt: "是否有明显学业变化?",
round: 1,
options: [{ key: "A", label: "明确有" }],
scoring_map: {
A: { cluster: "early_candidate_cluster", points: 3 },
},
}],
candidate_scan: { samples: [] },
});
assert.deepEqual(questionnaire.raw.questions, [{
id: "education_environment_shift",
prompt: "是否有明显学业变化?",
round: 1,
options: [{ key: "A", label: "明确有" }],
scoring_map: {
A: { cluster: "early_candidate_cluster", points: 3 },
},
}]);
});
test("rectification adapter normalizes scoring without elevating confidence", () => {
const scoring = parseRectificationScoring({
answered_count: 3,
candidate_cluster_rankings: [
{ cluster: "middle_candidate_cluster", score: 5 },
],
next_round: 2,
next_round_questions: [{
id: "health_crisis_or_low_period",
prompt: "是否有明显健康或低谷阶段?",
options: [{ key: "A", label: "明确有" }],
}],
});
assert.equal(scoring.answeredCount, 3);
assert.deepEqual(scoring.candidateClusterRankings, [
{ cluster: "middle_candidate_cluster", score: 5 },
]);
assert.equal(scoring.nextRound, 2);
assert.deepEqual(scoring.nextRoundQuestions, [{
id: "health_crisis_or_low_period",
prompt: "是否有明显健康或低谷阶段?",
options: [{ key: "A", label: "明确有" }],
}]);
});
test("rectification adapter normalizes an event-scored candidate result", () => {
const result = parseCandidateResult({
result_id: "1d8ee348-61a3-433d-8907-ff6d281b9992",
confidence: "high",
can_apply: true,
winning_segment: {
start_time: "14:22",
end_time: "14:26",
representative_time: "14:24",
width_minutes: 5,
},
event_count: 4,
domain_count: 3,
top_score: 16,
second_score: 10,
margin_percent: 37.5,
reasons: [],
evidence: [{
event_id: "5cb071d6-6d99-46be-85dc-a9bf59ef6ac5",
domain: "career",
candidate_time: "14:24",
rule_ids: ["vim_md_domain_house"],
points: 4,
legacy_server_metadata: { source: "existing-engine" },
}],
algorithm_version: "birth-time-event-scoring-v1",
technique_contract: {
calculation_status: "evaluated",
used_divisional_charts: ["D10"],
used_arudha: [],
dasha_tracks: ["vimshottari"],
missing_layers: [],
auxiliary_layers: [],
hard_blockers: ["neighbor_stability"],
confirmation_allowed: false,
decision: "continue_rectification",
gates: {
neighbor_stability: {
status: "diagnostic_fail",
reason: "diagnostic_only_unique_lead_at_plus_minus_1_2_5_minutes",
},
},
},
});
assert.equal(result.resultId, "1d8ee348-61a3-433d-8907-ff6d281b9992");
assert.equal(result.winningSegment?.representativeTime, "14:24");
assert.equal(result.canApply, false, "an old or incomplete engine receipt cannot open minute confirmation");
assert.deepEqual(result.evidence[0]?.ruleIds, ["vim_md_domain_house"]);
assert.equal(result.techniqueReceipt?.gates?.neighbor_stability?.status, "fail");
assert.equal("legacy_server_metadata" in (result.evidence[0] ?? {}), false);
});
test("candidate compatibility result accepts event counts beyond the former safety cap", () => {
const lowCandidate = {
result_id: "1d8ee348-61a3-433d-8907-ff6d281b9992",
confidence: "low",
can_apply: false,
winning_segment: null,
event_count: 10,
domain_count: 5,
top_score: 0,
second_score: 0,
margin_percent: 0,
reasons: ["safety_cap"],
evidence: [],
algorithm_version: "birth-time-choice-scoring-v2",
} as const;
assert.equal(parseCandidateResult(lowCandidate).eventCount, 10);
assert.equal(parseCandidateResult({ ...lowCandidate, event_count: 11 }).eventCount, 11);
});