Files
Jyotisha/frontend/tests/rectification-v4-domain.test.ts
T

137 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import test from "node:test";
import { randomUUID } from "node:crypto";
import { buildCandidateClusters } from "../src/lib/rectification-v4/candidate-clusters.ts";
import { dateRangeFromDeclared, sampledDates } from "../src/lib/rectification-v4/date-range.ts";
import { evaluateDecisionGate } from "../src/lib/rectification-v4/decision-gate.ts";
import { appendEventRevision, latestEventRevisions } from "../src/lib/rectification-v4/evidence-ledger.ts";
import { extractV4EventRevisions } from "../src/lib/rectification-v4/extraction.ts";
import { openingQuestion, planNextQuestion } from "../src/lib/rectification-v4/question-planner.ts";
const now = new Date("2026-07-26T00:00:00.000Z");
test("declared month, quarter and year retain real boundaries instead of invented midpoints", () => {
assert.deepEqual(dateRangeFromDeclared("2024-02", "month"), {
start: "2024-02-01", end: "2024-02-29", precision: "month", label: "2024-02",
});
assert.deepEqual(dateRangeFromDeclared("2024-Q2", "quarter"), {
start: "2024-04-01", end: "2024-06-30", precision: "quarter", label: "2024-Q2",
});
assert.deepEqual(dateRangeFromDeclared("2024", "year"), {
start: "2024-01-01", end: "2024-12-31", precision: "year", label: "2024",
});
assert.equal(sampledDates(dateRangeFromDeclared("2024-02", "month")).includes("2024-02-15"), false);
});
test("relationship start and end remain separate immutable events", () => {
const startId = randomUUID();
const endId = randomUUID();
const start = appendEventRevision([], {
eventId: startId, domain: "relationship", eventKind: "relationship_start", summary: "关系开始",
rawText: "2024年5月开始", dateRange: dateRangeFromDeclared("2024-05", "month"),
}, { id: randomUUID(), now });
const end = appendEventRevision([start], {
eventId: endId, domain: "relationship", eventKind: "relationship_end", summary: "关系结束",
rawText: "2024年8月结束", dateRange: dateRangeFromDeclared("2024-08", "month"),
}, { id: randomUUID(), now });
assert.notEqual(start.eventId, end.eventId);
assert.equal(start.eventKind, "relationship_start");
assert.equal(end.eventKind, "relationship_end");
});
test("family evidence is retained explicitly as context only", () => {
const revision = appendEventRevision([], {
eventId: randomUUID(), domain: "family", eventKind: "family_event", summary: "家庭变化",
rawText: "家庭发生变化", dateRange: dateRangeFromDeclared("2020", "year"),
}, { id: randomUUID(), now });
assert.equal(revision.scoreability, "context_only");
});
test("candidate minutes merge into ranked contiguous clusters", () => {
const id = randomUUID();
const clusters = buildCandidateClusters([
{ time: "05:13", score: 100, supportingEventIds: [id], conflictingEventIds: [] },
{ time: "05:14", score: 99, supportingEventIds: [id], conflictingEventIds: [] },
{ time: "05:15", score: 98, supportingEventIds: [id], conflictingEventIds: [] },
{ time: "05:16", score: 70, supportingEventIds: [], conflictingEventIds: [id] },
{ time: "05:17", score: 97, supportingEventIds: [id], conflictingEventIds: [] },
{ time: "05:18", score: 97, supportingEventIds: [id], conflictingEventIds: [] },
]);
assert.deepEqual(clusters.map((cluster) => [cluster.rank, cluster.startTime, cluster.endTime]), [
[1, "05:13", "05:15"], [2, "05:17", "05:18"],
]);
});
test("decision gate can accept a stable range but never an exact minute", () => {
const result = evaluateDecisionGate({
clusters: [{ rank: 1, startTime: "05:13", endTime: "05:15", representativeTime: "05:13", widthMinutes: 3, peakScore: 10, scoreMass: 29 }],
robustness: { neighborSupportMinutes: 3, leaveOneOutRetentionRate: 1, dateSensitivityRetentionRate: 0.9, calculationSpecHashMatched: true },
scoreableEventCount: 10,
scoreableDomainCount: 5,
});
assert.equal(result.canAcceptRange, true);
assert.equal(result.canConfirmExactMinute, false);
});
test("opening question invites free narration without a fixed domain", () => {
const question = openingQuestion({ start: "04:50", end: "05:10" }, randomUUID());
assert.equal(question.domain, "other");
assert.equal(question.targetEventId, null);
assert.match(question.prompt, /04:5005:10/);
assert.match(question.prompt, /不是已确认的出生分钟/);
assert.match(question.prompt, /不需要按固定领域回答/);
assert.doesNotMatch(question.prompt, /毕业|搬家|恋爱|工作|财务|健康/);
});
test("fallback planner refines an imprecise event, then returns to open narration", () => {
const eventId = randomUUID();
const event = appendEventRevision([], {
eventId, domain: "education", eventKind: "education_milestone", summary: "高中毕业",
rawText: "2016年高中毕业", dateRange: dateRangeFromDeclared("2016", "year"),
}, { id: randomUUID(), now });
const question = planNextQuestion({
events: [event],
attemptedRefinementEventIds: [],
latestAnswer: "2016年高中毕业",
id: randomUUID(),
});
assert.equal(question.targetEventId, eventId);
assert.equal(question.domain, "education");
assert.match(question.prompt, /高中毕业/);
assert.match(question.prompt, /月份或日期/);
const fallback = planNextQuestion({
events: [event],
attemptedRefinementEventIds: [eventId],
latestAnswer: "2016年高中毕业",
id: randomUUID(),
});
assert.equal(fallback.targetEventId, null);
assert.equal(fallback.domain, "other");
assert.match(fallback.prompt, /继续讲另一件/);
assert.doesNotMatch(fallback.prompt, /搬家|恋爱|事业|财务|健康/);
});
test("targeted date answer appends a revision without duplicating the scoreable event", () => {
const eventId = randomUUID();
const original = appendEventRevision([], {
eventId, domain: "education", eventKind: "education_milestone", summary: "高中毕业",
rawText: "2016年高中毕业", dateRange: dateRangeFromDeclared("2016", "year"),
}, { id: randomUUID(), now });
const revisions = extractV4EventRevisions({
answer: "2016年6月8日",
sourceTurnId: randomUUID(),
asOfDate: "2026-07-26",
existing: [original],
targetEventId: eventId,
now,
});
assert.equal(revisions.length, 1);
assert.equal(revisions[0]?.eventId, eventId);
assert.equal(revisions[0]?.revision, 2);
assert.equal(revisions[0]?.dateRange.precision, "day");
assert.equal(revisions[0]?.dateRange.start, "2016-06-08");
assert.equal(latestEventRevisions([original, ...revisions]).length, 1);
});