Files
Jyotisha/frontend/tests/birth-time-guide-agent.test.ts
T
732642856 4ceb3a5157 feat: enforce commercial rectification evidence contracts
* feat: enforce precise timing output contract

* fix: recognize package imports in fragment audit

* test: make workflow stream contract formatting-independent

* fix: preserve VedAstro evidence across async workflows

* feat: enforce commercial technique truth contract

* feat: add rectification technique receipt

* feat: extend rectification event evidence

* feat: score rectification arudha evidence

* feat: gate high rigor rectification confirmation

* feat: add controlled transit to rectification

* feat: include d11 in rectification finance scoring

* feat: add ashtakavarga rectification auxiliary

* feat: show rectification technique receipt

* feat: use verified shadbala components in rectification

* fix: trace transitive script references in fragment audit

* feat: run request-level rectification parity packet
2026-07-19 22:23:29 +08:00

230 lines
8.3 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 {
BirthTimeGuideOutputError,
fallbackQuestionCopy,
guideQuestionVariants,
parseEvidenceDraftOutput,
parseGuideQuestionOutput,
renderQuestionVariant,
} from "../src/lib/birth-time-guide-agent.ts";
import { evidenceDomains, type QuestionSpec } from "../src/lib/birth-time-question-planner.ts";
import { draftEvidenceStructureTool, getBirthTimeGuideAgent } from "../src/mastra/index.ts";
import type { ResolvedLanguageModel } from "../src/mastra/model.ts";
function question(domain: QuestionSpec["domain"]): QuestionSpec {
return {
questionId: `baseline_${domain}_1`,
phase: "baseline",
domain,
requestedPrecision: ["year", "month"],
allowUnknown: true,
purposeCode: `candidate_difference_${domain}`,
plannerVersion: "candidate-difference-v1",
};
}
test("draft parser fails closed when the model changes the server-selected domain", () => {
assert.throws(
() => parseEvidenceDraftOutput(
{ domain: "relationship", precision: "month", date: "2023-04" },
{ requiredDomain: "career", sourceMessage: "2023 年 4 月换了工作" },
),
BirthTimeGuideOutputError,
);
});
test("ambiguous dates remain incomplete instead of being invented", () => {
const draft = parseEvidenceDraftOutput(
{ domain: "career", precision: null, date: null },
{ requiredDomain: "career", sourceMessage: "大概是前几年" },
);
assert.deepEqual(draft, {
domain: "career",
precision: null,
date: null,
needsReview: true,
});
});
test("an ungrounded model date is discarded", () => {
const draft = parseEvidenceDraftOutput(
{ domain: "career", precision: "month", date: "2023-04" },
{ requiredDomain: "career", sourceMessage: "记不清具体时间" },
);
assert.equal(draft.precision, null);
assert.equal(draft.date, null);
assert.equal(draft.needsReview, true);
});
test("precision and date mismatch remains a review-only draft", () => {
const draft = parseEvidenceDraftOutput(
{ domain: "career", precision: "day", date: "2023-04" },
{ requiredDomain: "career", sourceMessage: "2023 年 4 月换了工作" },
);
assert.equal(draft.precision, "day");
assert.equal(draft.date, "2023-04");
assert.equal(draft.needsReview, true);
});
test("grounded dates can produce a complete review-only proposal", () => {
const draft = parseEvidenceDraftOutput(
{ domain: "career", precision: "month", date: "2023-04" },
{ requiredDomain: "career", sourceMessage: "我在 2023 年 4 月换了工作" },
);
assert.deepEqual(draft, {
domain: "career",
precision: "month",
date: "2023-04",
needsReview: false,
});
});
test("date grounding accepts ISO and year-only evidence but never adds an unstated day", () => {
const iso = parseEvidenceDraftOutput(
{ domain: "career", precision: "month", date: "2023-04" },
{ requiredDomain: "career", sourceMessage: "工作变动发生于 2023-04" },
);
const year = parseEvidenceDraftOutput(
{ domain: "career", precision: "year", date: "2023" },
{ requiredDomain: "career", sourceMessage: "只记得是 2023 年" },
);
const inventedDay = parseEvidenceDraftOutput(
{ domain: "career", precision: "day", date: "2023-04-18" },
{ requiredDomain: "career", sourceMessage: "只记得 2023 年 4 月" },
);
assert.equal(iso.needsReview, false);
assert.equal(year.needsReview, false);
assert.equal(inventedDay.date, null);
assert.equal(inventedDay.precision, null);
assert.equal(inventedDay.needsReview, true);
});
test("date grounding accepts fullwidth Chinese month and explicit Chinese or ISO days", () => {
const cases = [
{
output: { domain: "career", precision: "month", date: "2023-04" },
message: "工作变化发生在 2023 年 4 月",
},
{
output: { domain: "career", precision: "day", date: "2023-04-18" },
message: "工作变化发生在 2023 年 4 月 18 号",
},
{
output: { domain: "career", precision: "day", date: "2023-04-18" },
message: "工作变化发生在 2023-04-18",
},
] as const;
for (const value of cases) {
const draft = parseEvidenceDraftOutput(value.output, {
requiredDomain: "career",
sourceMessage: value.message,
});
assert.equal(draft.needsReview, false, value.message);
}
});
test("date grounding rejects substrings, partial ISO tokens, and scattered numbers", () => {
const unsafeMessages = [
"订单号20230",
"编号 2023-04-X",
"2023 年有变化,另外 4 月去了旅行",
"账号 1202304",
] as const;
for (const sourceMessage of unsafeMessages) {
const draft = parseEvidenceDraftOutput(
{ domain: "career", precision: "month", date: "2023-04" },
{ requiredDomain: "career", sourceMessage },
);
assert.equal(draft.precision, null, sourceMessage);
assert.equal(draft.date, null, sourceMessage);
assert.equal(draft.needsReview, true, sourceMessage);
}
});
test("year-only grounding requires a complete year token or explicit year semantics", () => {
for (const sourceMessage of ["2023", " 2023 ", "大约在 2023 年"] as const) {
const draft = parseEvidenceDraftOutput(
{ domain: "career", precision: "year", date: "2023" },
{ requiredDomain: "career", sourceMessage },
);
assert.equal(draft.needsReview, false, sourceMessage);
}
for (const sourceMessage of ["订单20230", "编号A2023B"] as const) {
const draft = parseEvidenceDraftOutput(
{ domain: "career", precision: "year", date: "2023" },
{ requiredDomain: "career", sourceMessage },
);
assert.equal(draft.date, null, sourceMessage);
}
});
test("server renders every approved variant from its QuestionSpec domain and precision", () => {
const semanticMarkers = {
education: /升学|转学|学习方向/,
relocation: /搬家|离乡|居住地/,
relationship: /关系进入|关系结束|关系.*转变/,
career: /工作|职业方向|身份变化/,
finance: /收入|资产|负债|资源渠道/,
health_pressure: /健康压力|生活压力/,
} as const;
for (const domain of evidenceDomains) {
for (const variant of guideQuestionVariants) {
const copy = renderQuestionVariant(question(domain), variant);
assert.equal((copy.match(/[?]/g) ?? []).length, 1);
assert.ok(copy.length <= 120);
assert.match(copy, semanticMarkers[domain]);
assert.doesNotMatch(copy, /哪一天|几号|具体日期|候选|支持|更符合|更接近|出生分钟/);
for (const otherDomain of evidenceDomains.filter((item) => item !== domain)) {
assert.doesNotMatch(copy, semanticMarkers[otherDomain]);
}
}
assert.equal(fallbackQuestionCopy(question(domain)), renderQuestionVariant(question(domain), "direct"));
}
});
test("question parser accepts only a finite variant and binds it to the server QuestionSpec", () => {
const parsed = parseGuideQuestionOutput({ variant: "gentle" }, question("career"));
assert.equal(parsed, renderQuestionVariant(question("career"), "gentle"));
for (const unsafe of [
{ variant: "relationship" },
{ variant: "direct", domain: "relationship" },
{ question: "Which relationship changed?" },
{ question: "具体是哪一天?" },
{ question: "0400 这个候选时间更符合吗?" },
{ variant: "direct", question: "越权覆盖" },
]) {
assert.throws(() => parseGuideQuestionOutput(unsafe, question("career")), BirthTimeGuideOutputError);
}
});
test("model output schemas reject extra fields", () => {
assert.throws(() => parseEvidenceDraftOutput(
{ domain: "career", precision: null, date: null, confidence: "high" },
{ requiredDomain: "career", sourceMessage: "记不清" },
));
assert.throws(() => parseGuideQuestionOutput({ variant: "direct", score: 9 }, question("career")));
});
test("birth-time guide registers only the review-only structure tool", async () => {
const model: ResolvedLanguageModel = {
id: "guide-test-model",
label: "Guide test",
description: "",
creditCost: 1,
isDefault: false,
mode: "openai",
model: "openai/gpt-5-mini",
};
const tools = await getBirthTimeGuideAgent(model).listTools();
assert.deepEqual(Object.keys(tools), ["draftEvidenceStructureTool"]);
assert.equal(draftEvidenceStructureTool.id, "draft-evidence-structure");
});