Files
Jyotisha/frontend/tests/rectification-birth-time-provenance-20260914.test.ts
T
jesse-ux c5fbee56a6
Independent Staging Quality Gate / validate (push) Failing after 8m55s
Independent Staging Quality Gate / publish (push) Skipped
fix(rectification): label declared birth time as record or estimate (BUG-690)
Hospital records, family estimates and period-only windows now keep distinct copy on the board and range card. Scoring still uses the reported clock as the search-window centre. Hospital minutes outside the current range are stated with the offset and no preference.
2026-09-14 23:20:15 +08:00

184 lines
6.8 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import {
FORBIDDEN_DECLARED_BIRTH_TIME_PHRASE,
boardDeclaredTimeLine,
normalizeBirthTimeSource,
reportedTimeOffsetCopy,
} from "../src/lib/rectification-agentic/birth-time-provenance.ts";
import { publicDecisionFields } from "../src/lib/rectification-agentic/core/rectification-decision.ts";
import { INFERENCE_ALGORITHM_VERSION, type InferenceCandidate, type InferenceState } from "../src/lib/rectification-agentic/core/types.ts";
import { decideFromDossier, type DecisionDossier } from "../src/lib/rectification-agentic/v9/decision-from-dossier.ts";
import { buildRangeDelivery } from "../src/lib/rectification-agentic/v9/divergence-panel.ts";
import { RectificationRangeDelivery } from "../src/components/rectification-range-delivery.tsx";
import type { RectificationCandidateResult } from "../src/lib/rectification-candidate-result.ts";
import { RECTIFICATION_USER_COPY } from "../src/lib/rectification-agentic/user-copy.ts";
function dossier(source?: string | null): DecisionDossier {
return {
evidence: [],
conversationSummary: { activeFocus: null, declinedSkippedTopics: [] },
latestResult: null,
case: { acceptedTime: null, ...(source === undefined ? {} : { birthTimeSource: source }) },
};
}
function cand(time: string, probability: number): InferenceCandidate {
return {
id: time,
time,
cluster_range: ["04:48", "05:07"],
prior_score: probability * 100,
posterior_score: probability * 100,
probability,
status: "active",
rank: 1,
strong_conflict_count: 0,
};
}
function inference(): InferenceState {
return {
algorithm_version: INFERENCE_ALGORITHM_VERSION,
candidate_set_id: "set",
revision: 1,
phase: "discrimination",
result_status: "credible_range",
range_start: "04:48",
range_end: "05:07",
candidates: [cand("04:53", 0.4), cand("04:50", 0.35)],
events: [],
probes: [],
answered_probes: [],
rounds: [],
entropy: 1,
representative_time: "04:53",
credible_range: ["04:48", "05:07"],
};
}
function deliveryResult(delivery: ReturnType<typeof buildRangeDelivery>): RectificationCandidateResult {
return {
resultId: "result-provenance",
candidates: delivery.columns.map((column, index) => ({
candidateId: column.candidate_id,
rank: index + 1,
time: column.time,
relativeSupport: column.probability_percent,
tiedMinuteCount: 1,
})),
overallConfidence: "medium",
selectionAllowed: true,
canAdopt: true,
confirmationAllowed: false,
decisionReceipt: null,
representativeTime: delivery.representative_time,
selectedTime: null,
selectionKind: null,
houseTable: null,
houseTablesByTime: {},
natalRecast: null,
techniqueAudit: [],
windowTransitions: [],
eventDashaLedger: [],
dashaAgreement: null,
lagnaContrast: null,
nakshatraBoundary: null,
precisionStage: null,
oosBlindPrompts: [],
confirmationGate: { confirmation_allowed: false } as RectificationCandidateResult["confirmationGate"],
validated: false,
completionStatus: null,
sessionOutcome: "adopt_representative",
credibleRange: delivery.range,
rangeDelivery: delivery,
verificationReportMarkdown: delivery.verification_markdown,
};
}
test("GET and decideFromDossier expose hospital / approximate / period_only, missing becomes approximate", () => {
assert.equal(normalizeBirthTimeSource("hospital_record"), "hospital_record");
assert.equal(normalizeBirthTimeSource("approximate"), "approximate");
assert.equal(normalizeBirthTimeSource("period_only"), "period_only");
assert.equal(normalizeBirthTimeSource("family_exact"), "approximate");
assert.equal(normalizeBirthTimeSource(null), "approximate");
assert.equal(normalizeBirthTimeSource(undefined), "approximate");
assert.equal(publicDecisionFields(decideFromDossier(dossier("hospital_record"))).birth_time_source, "hospital_record");
assert.equal(publicDecisionFields(decideFromDossier(dossier("approximate"))).birth_time_source, "approximate");
assert.equal(publicDecisionFields(decideFromDossier(dossier("period_only"))).birth_time_source, "period_only");
assert.equal(publicDecisionFields(decideFromDossier(dossier())).birth_time_source, "approximate");
const response = readFileSync(
new URL("../src/lib/rectification-agentic/v9/case-dossier-response.ts", import.meta.url),
"utf8",
);
assert.match(response, /birth_time_source: birthTimeSource/);
});
test("approximate and period_only copy never says 你的出生时间", () => {
const approximate = [
boardDeclaredTimeLine("05:00", "approximate"),
reportedTimeOffsetCopy({
source: "approximate",
reportedTime: "05:00",
range: ["04:48", "05:07"],
representativeTime: "04:53",
}) ?? "",
];
const period = [
boardDeclaredTimeLine("05:00", "period_only"),
reportedTimeOffsetCopy({
source: "period_only",
reportedTime: "05:00",
range: ["04:48", "05:07"],
representativeTime: "04:53",
}) ?? "",
];
for (const line of [...approximate, ...period]) {
assert.doesNotMatch(line, FORBIDDEN_DECLARED_BIRTH_TIME_PHRASE);
}
assert.match(approximate.join("\n"), /你填的大概时间/);
assert.match(period.join("\n"), /你给的时间段/);
});
test("hospital_record copy names the record and states the offset without picking a side", () => {
const inside = reportedTimeOffsetCopy({
source: "hospital_record",
reportedTime: "05:00",
range: ["04:48", "05:07"],
representativeTime: "04:53",
});
assert.match(inside ?? "", /出生记录时间 05:00,落在目前范围内/);
const outside = reportedTimeOffsetCopy({
source: "hospital_record",
reportedTime: "05:12",
range: ["04:48", "05:07"],
representativeTime: "04:53",
});
assert.match(outside ?? "", /出生记录时间 05:12/);
assert.match(outside ?? "", /相差 5 分钟/);
assert.doesNotMatch(outside ?? "", /以记录为准|以证据为准|应该信/);
const html = renderToStaticMarkup(createElement(RectificationRangeDelivery, {
result: deliveryResult(buildRangeDelivery({
inference: inference(),
publicCandidates: [
{ candidateId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa1", time: "04:53" },
{ candidateId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbb2", time: "04:50" },
],
credibleRange: ["04:48", "05:07"],
representativeTime: "04:53",
reportedBirthTime: "05:12",
birthTimeSource: "hospital_record",
})),
acceptingCandidateId: null,
readonly: false,
onAccept: () => undefined,
}));
assert.match(html, /出生记录时间 05:12/);
assert.match(html, /相差 5 分钟/);
assert.ok(html.includes(RECTIFICATION_USER_COPY.rangeDeliveryMoreLikeThis));
});