Files
Jyotisha/frontend/tests/consultation-domain-registry.test.ts
T
Jesse_ChenandClaude Opus 5.5 5c65834dda feat(consult): split family into parents and children domains (engine still family)
parents (D12, 4/9 houses, Sun/Moon) and children (D7, 5th house, Jupiter,
PK) are plan/card domains with Chinese labels and the brief's aliases. They
send the family route contract to the engine and are rejected as a stored
session theme, so neither Python nor the chat_sessions.theme CHECK changes.
Methodology reports no strict checklist for them; must-use layers follow the
plan domain. Tests with 原值/新值/原因 where assertions changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-09-27 02:54:11 +08:00

121 lines
7.7 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 { readFileSync } from "node:fs";
import test from "node:test";
import {
consultationDomainIds,
consultationDomainRegistry,
normalizeConsultationDomain,
validateConsultationDomainPlan,
} from "../src/lib/consultation-domain-registry.ts";
import { consultationInputSchema } from "../src/mastra/consultation-workflow.ts";
import { chatSessionWriteSchema } from "../src/lib/chat-session-write-contract.ts";
import {
defaultGuidedJyotishTopics,
generalGuidedJyotishTopics,
} from "../src/lib/guided-jyotish-topics.ts";
// 原值:十个领域,无 parents / children。
// 新值:family 后面加 parents、children(数据卡子领域,引擎侧按 family 计算)。
// 原因:TASK-consult-evidence-card-20260927 D2。会话主题(chat_sessions.theme CHECK)仍是原十个,见 sessionThemes。
const expected = ["career", "marriage", "wealth", "health", "education", "migration", "family", "parents", "children", "annual", "timing", "general"];
const sessionThemes = ["career", "marriage", "wealth", "health", "education", "migration", "family", "annual", "timing", "general"];
test("domain registry is the complete allowlist used by UI and workflow input", () => {
assert.deepEqual([...consultationDomainIds], expected);
assert.deepEqual(consultationDomainRegistry.map((domain) => domain.id), expected);
assert.deepEqual(defaultGuidedJyotishTopics.map((topic) => topic.id), expected);
for (const theme of expected) {
assert.equal(consultationInputSchema.safeParse({ year: 2000, month: 1, day: 1, hour: 0, minute: 0, lat: 0, lon: 0, tz: 0, city: "Test", question: "test", theme }).success, true);
}
});
test("aliases normalize to stable persisted domain ids", () => {
assert.equal(normalizeConsultationDomain("relationship"), "marriage");
assert.equal(normalizeConsultationDomain("finance"), "wealth");
assert.equal(normalizeConsultationDomain("health-pressure"), "health");
assert.equal(normalizeConsultationDomain("home"), "migration");
assert.equal(normalizeConsultationDomain("unknown"), null);
});
test("server domain plans are allowlisted, ordered and deduplicated", () => {
assert.deepEqual(validateConsultationDomainPlan(["career", "finance", "career", "home"]), ["career", "wealth", "migration"]);
assert.throws(() => validateConsultationDomainPlan([]), /invalid_consultation_domain_plan/);
assert.throws(() => validateConsultationDomainPlan(["career", "prashna"]), /unsupported_consultation_domain/);
});
test("default guided topics derive every canonical domain with evidence and claim boundaries", () => {
// Moved from starter-questions.test.ts when homepage theme cards were removed.
const guidedTopicsSource = readFileSync(new URL("../src/lib/guided-jyotish-topics.ts", import.meta.url), "utf8");
// 原值:assert.equal(consultationDomainIds.length, 10);新值:12;原因:D2 新增 parents / children。
assert.equal(consultationDomainIds.length, 12);
assert.deepEqual(consultationDomainRegistry.map((domain) => domain.id), [...consultationDomainIds]);
assert.match(guidedTopicsSource, /consultationDomainRegistry\.map/);
assert.deepEqual(defaultGuidedJyotishTopics.map((topic) => topic.id), [...consultationDomainIds]);
assert.equal(defaultGuidedJyotishTopics.length, consultationDomainRegistry.length);
for (const [index, topic] of defaultGuidedJyotishTopics.entries()) {
const domain = consultationDomainRegistry[index];
assert.equal(topic.id, domain.id);
assert.equal(topic.label, domain.label);
assert.equal(topic.prompt, domain.prompt);
assert.equal(topic.strictWorkflowRoute, domain.strictWorkflowRoute);
assert.deepEqual(topic.evidencePreview, [...domain.evidencePreview]);
assert.equal(topic.confidenceCap, domain.confidenceCap);
assert.equal(topic.claimBoundary, domain.claimBoundary);
}
const domainById = new Map(consultationDomainRegistry.map((domain) => [domain.id, domain]));
assert.match(domainById.get("career")?.requiredLayers.join(" ") ?? "", /D10/);
assert.match(domainById.get("marriage")?.requiredLayers.join(" ") ?? "", /D9/);
assert.match(domainById.get("wealth")?.requiredLayers.join(" ") ?? "", /Ashtakavarga/);
assert.match(domainById.get("timing")?.requiredLayers.join(" ") ?? "", /negative holdout gate/);
});
test("general guided topics keep user-centered prompts and the same claim boundaries", () => {
// Moved from starter-questions.test.ts. Homepage no longer renders these cards;
// the registry remains the contract for personal-report and no-minute copy.
assert.deepEqual(generalGuidedJyotishTopics.map((topic) => topic.id), [...consultationDomainIds]);
assert.deepEqual(generalGuidedJyotishTopics.map((topic) => topic.prompt), [
"请帮我梳理目前的事业方向和下一步重点。",
"请帮我看看我在关系中容易重复什么模式。",
"请帮我分析目前的财富重点、风险和更稳妥的选择。",
"请从非医疗诊断的角度,帮我看看近期的身心压力和调整重点。",
"请帮我看看我更适合怎样学习,以及如何安排下一步。",
"请帮我分析现阶段是否适合迁居、置业或考虑海外发展。",
"请帮我看看家庭关系中最需要处理的问题和责任边界。",
// 新增两行(D2):父母、子女的无出生分钟版问法。
"请帮我看看我和父母之间最需要处理的问题。",
"请帮我看看我和孩子相处时最需要留意的地方。",
"请帮我看看未来一年的主要趋势、机会和需要留意的阶段。",
"请帮我看看目前适合推进什么,哪些事情需要再等等。",
"请结合我的情况,帮我找出现在最值得优先处理的三个问题。",
]);
for (const topic of generalGuidedJyotishTopics) {
const personalTopic = defaultGuidedJyotishTopics.find((candidate) => candidate.id === topic.id);
assert.ok(personalTopic);
assert.equal(topic.label, personalTopic.label);
assert.equal(topic.strictWorkflowRoute, personalTopic.strictWorkflowRoute);
assert.deepEqual(topic.evidencePreview, personalTopic.evidencePreview);
assert.equal(topic.confidenceCap, personalTopic.confidenceCap);
assert.equal(topic.claimBoundary, personalTopic.claimBoundary);
assert.notEqual(topic.prompt, personalTopic.prompt);
}
assert.ok(generalGuidedJyotishTopics.every((topic) => /我/.test(topic.prompt)));
assert.ok(generalGuidedJyotishTopics.every((topic) => !/印度占星|一般如何|通常|哪些因素|证据层/.test(topic.prompt)));
});
test("session persistence accepts every canonical domain and rejects product routes", () => {
// 原值:遍历 expected(全部领域)都能落库。
// 新值:遍历 sessionThemes(chat_sessions.theme CHECK 的十个)能落库;parents / children 不能落库。
// 原因:D2 的两个子领域只作用于数据卡,不改数据库;写入口先挡住,免得撞 CHECK 变成 500。
for (const theme of ["parents", "children"]) {
const parsed = chatSessionWriteSchema.safeParse({ title: "Test", theme, model_id: "test", messages: [], session_type: "consultation", rectification_case_id: null, updated_at: "2026-08-14T00:00:00.000Z" });
assert.equal(parsed.success, false, theme);
}
for (const theme of sessionThemes) {
const parsed = chatSessionWriteSchema.safeParse({ title: "Test", theme, model_id: "test", messages: [], session_type: "consultation", rectification_case_id: null, updated_at: "2026-08-14T00:00:00.000Z" });
assert.equal(parsed.success, true, theme);
}
assert.equal(chatSessionWriteSchema.safeParse({ title: "Test", theme: "muhurta", model_id: "test", messages: [], session_type: "consultation", rectification_case_id: null, updated_at: "2026-08-14T00:00:00.000Z" }).success, false);
});