refactor: make birth time rectification conversational
This commit is contained in:
@@ -3,7 +3,6 @@ import test from "node:test";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { createRectificationV4CaseService } from "../src/lib/rectification-v4/case-service.ts";
|
||||
import type { CalculationSpec } from "../src/lib/rectification-v4/contracts.ts";
|
||||
import { calculationSpecHash } from "../src/lib/rectification-v4/fingerprints.ts";
|
||||
import { createRectificationV4MemoryStore } from "../src/lib/rectification-v4/memory-store.ts";
|
||||
import { createRectificationV4Worker } from "../src/lib/rectification-v4/worker.ts";
|
||||
|
||||
@@ -21,7 +20,6 @@ const spec: CalculationSpec = {
|
||||
};
|
||||
|
||||
|
||||
|
||||
test("same calculation spec resumes the unfinished case", async () => {
|
||||
const store = createRectificationV4MemoryStore();
|
||||
const service = createRectificationV4CaseService(store, { now: fixedNow });
|
||||
@@ -84,13 +82,16 @@ test("answer is durably queued and poll remains read only", async () => {
|
||||
const userId = randomUUID();
|
||||
const created = await service.createCase({ userId, actionId: randomUUID(), calculationSpec: spec });
|
||||
assert.equal(created.case.status, "awaiting_answer");
|
||||
assert.equal(created.case.currentQuestion?.domain, "education");
|
||||
assert.equal(created.case.currentQuestion?.domain, "other");
|
||||
assert.match(created.case.currentQuestion?.prompt ?? "", /不需要按固定领域回答/);
|
||||
const queued = await service.answer({
|
||||
userId, caseId: created.case.id, actionId: randomUUID(), expectedCaseVersion: 0,
|
||||
answer: "2015年7月高中毕业后复读一年,2016年6月再次毕业",
|
||||
modelId: "gpt-5.5",
|
||||
});
|
||||
assert.equal(queued?.case.status, "processing");
|
||||
assert.equal(queued?.job?.status, "pending");
|
||||
assert.equal(queued?.turns.at(-1)?.modelId, "gpt-5.5");
|
||||
const before = JSON.stringify([...store.jobs.values()]);
|
||||
const polled = await service.loadCase(userId, created.case.id);
|
||||
assert.equal(polled?.job, null);
|
||||
@@ -115,7 +116,9 @@ test("worker extracts dated events, keeps one question and never confirms an exa
|
||||
const done = await service.loadCase(userId, created.case.id);
|
||||
assert.equal(done?.case.status, "awaiting_answer");
|
||||
assert.equal(done?.events.length, 2);
|
||||
assert.equal(done?.case.currentQuestion?.domain, "relocation");
|
||||
assert.equal(done?.case.currentQuestion?.domain, "education");
|
||||
assert.equal(done?.events.some((event) => event.eventId === done.case.currentQuestion?.targetEventId), true);
|
||||
assert.match(done?.case.currentQuestion?.prompt ?? "", /月份或日期/);
|
||||
assert.equal(done?.case.latestSnapshot, null);
|
||||
assert.equal(queued?.job?.status, "pending");
|
||||
});
|
||||
@@ -136,81 +139,65 @@ test("completed job rejects stale case or calculation hashes", async () => {
|
||||
}, fixedNow().toISOString()), /stale_job/);
|
||||
});
|
||||
|
||||
test("worker moves from domain coverage to targeted date refinement without a null-question dead state", async () => {
|
||||
test("worker gives the selected model full conversation context for the next question", async () => {
|
||||
const store = createRectificationV4MemoryStore();
|
||||
const service = createRectificationV4CaseService(store, { now: fixedNow });
|
||||
const userId = randomUUID();
|
||||
const created = await service.createCase({ userId, actionId: randomUUID(), calculationSpec: spec });
|
||||
const scoreableCounts: number[] = [];
|
||||
const contexts: Array<{
|
||||
modelId: string | null;
|
||||
turnAnswers: string[];
|
||||
eventSummaries: string[];
|
||||
}> = [];
|
||||
const worker = createRectificationV4Worker({
|
||||
store,
|
||||
now: fixedNow,
|
||||
engine: {
|
||||
async score({ calculationSpec, events }) {
|
||||
scoreableCounts.push(events.length);
|
||||
const ids = events.map((event) => event.eventId);
|
||||
return {
|
||||
resultId: randomUUID(),
|
||||
calculationSpecHash: calculationSpecHash(calculationSpec),
|
||||
candidates: [
|
||||
{ time: "05:13", score: 100, supportingEventIds: ids, conflictingEventIds: [] },
|
||||
{ time: "05:14", score: 99, supportingEventIds: ids, conflictingEventIds: [] },
|
||||
{ time: "05:15", score: 98, supportingEventIds: ids, conflictingEventIds: [] },
|
||||
],
|
||||
robustness: {
|
||||
neighborSupportMinutes: 3,
|
||||
leaveOneOutRetentionRate: 1,
|
||||
dateSensitivityRetentionRate: 0.5,
|
||||
},
|
||||
missingLayers: [],
|
||||
};
|
||||
},
|
||||
engine: { async score() { throw new Error("engine must not run before enough events"); } },
|
||||
questionAuthor: async (context) => {
|
||||
contexts.push({
|
||||
modelId: context.modelId,
|
||||
turnAnswers: context.turns.map((turn) => turn.answer),
|
||||
eventSummaries: context.events.map((event) => event.summary),
|
||||
});
|
||||
return {
|
||||
id: randomUUID(),
|
||||
domain: "other",
|
||||
targetEventId: null,
|
||||
prompt: context.turns.length === 1
|
||||
? "你提到复读后再次毕业,这段连续变化很清楚。后来还有哪一次环境变化让你印象很深?"
|
||||
: "你提到毕业和搬家是连续发生的。那次搬家前后,生活节奏还有什么明显变化?",
|
||||
recallCost: "low",
|
||||
reason: "根据完整对话选择下一条高信息量追问。",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
let current = created;
|
||||
for (const answer of [
|
||||
"2016年高中毕业",
|
||||
"2018年8月搬家到北京",
|
||||
"2020年5月开始恋爱",
|
||||
"2021年3月入职公司",
|
||||
"2022年4月收入明显变化",
|
||||
"2023年5月住院",
|
||||
"2024年6月家庭发生变化",
|
||||
]) {
|
||||
for (const [answer, modelId] of [
|
||||
["2015年7月高中毕业后复读一年,2016年6月再次毕业", "gpt-5.5"],
|
||||
["2018年8月搬到北京,之后开始独立生活", "deepseek-chat"],
|
||||
] as const) {
|
||||
const queued = await service.answer({
|
||||
userId,
|
||||
caseId: created.case.id,
|
||||
actionId: randomUUID(),
|
||||
expectedCaseVersion: current.case.version,
|
||||
answer,
|
||||
modelId,
|
||||
});
|
||||
assert.ok(queued?.job);
|
||||
assert.equal(await worker.runOnce(), true);
|
||||
current = (await service.loadCase(userId, created.case.id))!;
|
||||
assert.equal(current.case.status === "awaiting_answer" && current.case.currentQuestion === null, false);
|
||||
}
|
||||
|
||||
const targetEventId = current.case.currentQuestion?.targetEventId;
|
||||
assert.ok(targetEventId);
|
||||
assert.equal(current.case.status, "awaiting_answer");
|
||||
assert.equal(current.case.currentQuestion?.prompt.includes("更具体的日期"), true);
|
||||
|
||||
const queued = await service.answer({
|
||||
userId,
|
||||
caseId: created.case.id,
|
||||
actionId: randomUUID(),
|
||||
expectedCaseVersion: current.case.version,
|
||||
answer: "2016年6月8日",
|
||||
});
|
||||
assert.ok(queued?.job);
|
||||
assert.equal(await worker.runOnce(), true);
|
||||
current = (await service.loadCase(userId, created.case.id))!;
|
||||
|
||||
const targetedRevisions = current.events.filter((event) => event.eventId === targetEventId);
|
||||
assert.deepEqual(targetedRevisions.map((event) => event.revision), [1, 2]);
|
||||
assert.equal(targetedRevisions[1]?.dateRange.precision, "day");
|
||||
assert.equal(scoreableCounts.at(-1), 6);
|
||||
assert.equal(contexts.length, 2);
|
||||
assert.equal(contexts[0]?.modelId, "gpt-5.5");
|
||||
assert.deepEqual(contexts[1]?.turnAnswers, [
|
||||
"2015年7月高中毕业后复读一年,2016年6月再次毕业",
|
||||
"2018年8月搬到北京,之后开始独立生活",
|
||||
]);
|
||||
assert.equal(contexts[1]?.modelId, "deepseek-chat");
|
||||
assert.equal(contexts[1]?.eventSummaries.length, 3);
|
||||
assert.match(current.case.currentQuestion?.prompt ?? "", /毕业和搬家/);
|
||||
assert.equal(current.case.status === "awaiting_answer" && current.case.currentQuestion === null, false);
|
||||
assert.notEqual(current.case.currentQuestion?.targetEventId, targetEventId);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user