feat(consult): evidence card v2 per the astrologer's review
TASK-consult-evidence-card-v2-20260927 T3. Card version evidence-card-v2. - Base section (every card): the engine's D9 summary (D9 lagna, each planet's D9 sign and dignity, Vargottama, D1/D9 reversals); D9 houses and aspects stay out. - Career: + AL (engine pada A1), 10H SAV, SAV of the Jupiter / Saturn transit signs. - Marriage: + 5H / 5L, day / night, Punarphoo (observation_only), Double Transit on 7H / 7L (house-7 run) and DK / UL, Vivah Saham, gender unknown. - Wealth: 8H / 12H named. - Annual: the annual Tajika chart verbatim (parameter_sensitive), or 年盘未接入 when the pack is blocked / not attached (never natal data); houses 1 + running / next AD lords' houses + the year's Jupiter / Saturn transit houses, with their basis; ingress / station dates only. - Timing: Rahu / Ketu with ingress dates, Jupiter / Saturn SAV and BAV, Double Transit conclusions (no degrees); vargas follow the turn's other domain, D9 when alone. - Chara Dasha leaves every card; KP is never on a card. The lookup enum adds karakamsha, dispositor_chains, inter_chart_linkage, argala, moon_transit; a KP lookup travels with its blocked note. - System prompt and tool descriptions name the D9 summary, the parameter_sensitive / observation_only tags, 年盘未接入 and the lookups. - Telemetry card version / agentVersion bumped to v2 (three-column notes in the touched tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
33739e3789
commit
db17074430
@@ -13,6 +13,7 @@ import {
|
||||
buildEvidenceCard,
|
||||
citedEvidenceCardFields,
|
||||
EVIDENCE_CARD_BACKSTAGE_LINE,
|
||||
EVIDENCE_CARD_LABELS,
|
||||
EVIDENCE_CARD_SPECS,
|
||||
EVIDENCE_CARD_VERSION,
|
||||
evidenceCardFieldValues,
|
||||
@@ -145,10 +146,16 @@ test("every card value is a value the projection or the engine already holds (no
|
||||
}
|
||||
return into;
|
||||
}
|
||||
// 原值: every leaf must be an engine / projection value.
|
||||
// 新值: the same, except the two labels the v2 card writes itself
|
||||
// (EVIDENCE_CARD_LABELS: 性别未知, 年盘未接入).
|
||||
// 原因: TASK-consult-evidence-card-v2-20260927 decision table mandates both labels.
|
||||
const cardLabels = new Set(Object.values(EVIDENCE_CARD_LABELS).map((label) => JSON.stringify(label)));
|
||||
for (const chart of golden.charts) {
|
||||
for (const domain of consultationDomainIds) {
|
||||
const input = sources(chart.workflow, domain);
|
||||
const known = scalars({ packet: input.packet, context: input.context });
|
||||
for (const label of cardLabels) known.add(label);
|
||||
const card = buildEvidenceCard([input]);
|
||||
for (const [fieldId, values] of evidenceCardFieldValues(card)) {
|
||||
for (const value of values) {
|
||||
|
||||
@@ -44,7 +44,8 @@ test("the record has exactly the six approved fields", async () => {
|
||||
assert.ok(record);
|
||||
assert.deepEqual(Object.keys(record).sort(), [...EVIDENCE_CARD_TELEMETRY_FIELDS].sort());
|
||||
assert.deepEqual(record.domains, ["parents"]);
|
||||
assert.equal(record.cardVersion, "evidence-card-v1");
|
||||
// 原值: "evidence-card-v1" / 新值: "evidence-card-v2" / 原因: TASK-consult-evidence-card-v2-20260927 T3 卡版本升级
|
||||
assert.equal(record.cardVersion, "evidence-card-v2");
|
||||
assert.equal(record.cardChars, state.evidenceCardChars);
|
||||
assert.equal(record.cardTokenEstimate, Math.round(record.cardChars / 3.5));
|
||||
assert.deepEqual(record.citedFieldIds, []);
|
||||
@@ -52,7 +53,7 @@ test("the record has exactly the six approved fields", async () => {
|
||||
});
|
||||
|
||||
test("the schema is strict: no extra field, no free text in field ids", () => {
|
||||
const valid = { domains: ["parents"], cardVersion: "evidence-card-v1", cardChars: 10, cardTokenEstimate: 3, citedFieldIds: ["base.natal.ascendant.sign"], feedback: "up" };
|
||||
const valid = { domains: ["parents"], cardVersion: "evidence-card-v2", cardChars: 10, cardTokenEstimate: 3, citedFieldIds: ["base.natal.ascendant.sign"], feedback: "up" };
|
||||
assert.equal(evidenceCardTelemetrySchema.safeParse(valid).success, true);
|
||||
for (const extra of ["question", "answer", "sessionId", "userId", "birthDate", "matched"]) {
|
||||
assert.equal(evidenceCardTelemetrySchema.safeParse({ ...valid, [extra]: "x" }).success, false, extra);
|
||||
@@ -70,7 +71,7 @@ test("the logged line keeps field ids and drops every word of the user's text",
|
||||
const answer = `${secrets[0]}(${secrets[1]},生日 ${secrets[2]})问:${secrets[3]}。子运从 ${pdStart} 开始。`;
|
||||
const lines: AgentObservabilityEvent[] = [];
|
||||
const log = createAgentObservabilityLogger((event) => lines.push(event));
|
||||
log({ runId: "req-telemetry", agentVersion: "consultation-evidence-card-v1", evidenceCard: consultationEvidenceCardTelemetry(state, answer) });
|
||||
log({ runId: "req-telemetry", agentVersion: "consultation-evidence-card-v2", evidenceCard: consultationEvidenceCardTelemetry(state, answer) });
|
||||
const text = JSON.stringify(lines);
|
||||
for (const secret of secrets) assert.equal(text.includes(secret), false, secret);
|
||||
assert.equal(text.includes(pdStart), false, "the matched date is not kept");
|
||||
@@ -82,7 +83,8 @@ test("the logged line keeps field ids and drops every word of the user's text",
|
||||
test("the consult route logs the record once per natal turn, without session or user id", () => {
|
||||
assert.match(route, /const evidenceCard = consultationEvidenceCardTelemetry\(state, answerForTelemetry\);/);
|
||||
const block = route.slice(route.indexOf("const evidenceCard = consultationEvidenceCardTelemetry"), route.indexOf("const settleRun = async"));
|
||||
assert.match(block, /agentVersion: "consultation-evidence-card-v1"/);
|
||||
// 原值: consultation-evidence-card-v1 / 新值: v2 / 原因: 卡 v2(TASK-consult-evidence-card-v2-20260927 T3)
|
||||
assert.match(block, /agentVersion: "consultation-evidence-card-v2"/);
|
||||
assert.doesNotMatch(block, /sessionId|userId/);
|
||||
const natal = route.slice(route.indexOf("const agent = getJyotishAgent(selectedModel, agentContext);"));
|
||||
assert.match(natal, /answerForTelemetry = output;[\s\S]*return settleRun\(\(\) => completeResponse\(/);
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
// TASK-consult-evidence-card-v2-20260927 T3: evidence card v2, card by card,
|
||||
// item by item, against real engine responses (three public AA charts; family,
|
||||
// annual and timing routes; scripts/research/capture_consult_evidence_card_golden.py,
|
||||
// AGENTS §7.4). Expected values are read from the engine's own layers
|
||||
// (chart.consultation_native_layers, chart.modules), so a card that rewrote,
|
||||
// converted or re-derived a value fails here.
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
buildEvidenceCard,
|
||||
EVIDENCE_CARD_LABELS,
|
||||
EVIDENCE_CARD_VERSION,
|
||||
EVIDENCE_LOOKUP_KP_NOTE,
|
||||
EVIDENCE_LOOKUP_SECTIONS,
|
||||
type EvidenceCard,
|
||||
} from "../src/lib/consultation-evidence-card.ts";
|
||||
import { consultationDomainIds, type ConsultationDomain } from "../src/lib/consultation-domain-registry.ts";
|
||||
import { createConsultationPlan } from "../src/lib/consultation-plan.ts";
|
||||
import { evidenceLookupActivityLabel } from "../src/lib/consultation-activity-labels.ts";
|
||||
import {
|
||||
consultationWorkflowResponseSchema,
|
||||
toAgentConsultationContext,
|
||||
toModelOutput,
|
||||
} from "../src/mastra/consultation-workflow.ts";
|
||||
import { createConsultationRuntimeState, createConsultationTools, readEvidenceLookupSection } from "../src/mastra/consultation-tools.ts";
|
||||
|
||||
type Json = Record<string, unknown>;
|
||||
type GoldenChart = { id: string; workflow: Json; annual_workflow: Json; timing_workflow: Json };
|
||||
const golden = JSON.parse(readFileSync(
|
||||
new URL("./fixtures/consult-evidence-card-golden.json", import.meta.url),
|
||||
"utf8",
|
||||
)) as { charts: GoldenChart[] };
|
||||
|
||||
function dig(value: unknown, ...keys: string[]): unknown {
|
||||
let current = value;
|
||||
for (const key of keys) {
|
||||
if (!current || typeof current !== "object" || Array.isArray(current)) return undefined;
|
||||
current = (current as Json)[key];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function sources(workflow: Json, domain: ConsultationDomain) {
|
||||
const context = toAgentConsultationContext(consultationWorkflowResponseSchema.parse(structuredClone(workflow)));
|
||||
const packet = toModelOutput(context, createConsultationPlan({ userIntent: "q", theme: domain }), domain);
|
||||
return { domain, packet, context };
|
||||
}
|
||||
|
||||
function card(workflow: Json, ...domains: ConsultationDomain[]): EvidenceCard {
|
||||
return buildEvidenceCard(domains.map((domain) => sources(workflow, domain)));
|
||||
}
|
||||
|
||||
function native(workflow: Json): Json {
|
||||
return dig(workflow, "chart", "consultation_native_layers") as Json;
|
||||
}
|
||||
|
||||
function modules(workflow: Json): Json {
|
||||
return dig(workflow, "chart", "modules") as Json;
|
||||
}
|
||||
|
||||
function pick(source: unknown, keys: readonly string[]) {
|
||||
const out: Json = {};
|
||||
for (const key of keys) {
|
||||
const value = (source as Json)[key];
|
||||
if (value !== undefined) out[key] = value;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
test("the card version is v2", () => {
|
||||
assert.equal(EVIDENCE_CARD_VERSION, "evidence-card-v2");
|
||||
assert.equal(card(golden.charts[0]!.workflow, "career").card_version, "evidence-card-v2");
|
||||
});
|
||||
|
||||
test("every base section carries the engine's D9 summary: lagna, D9 sign and dignity, Vargottama, reversals", () => {
|
||||
let reversalsSeen = 0;
|
||||
for (const chart of golden.charts) {
|
||||
const summary = native(chart.workflow).d9_summary as Json;
|
||||
for (const domain of consultationDomainIds) {
|
||||
const d9 = card(chart.workflow, domain).base.natal.d9;
|
||||
const where = `${chart.id}/${domain}`;
|
||||
assert.equal(d9.lagna, summary.lagna, `${where} D9 lagna`);
|
||||
assert.equal(d9.lagna_vargottama, summary.lagna_vargottama, `${where} lagna vargottama`);
|
||||
assert.deepEqual(d9.reversals, summary.reversals, `${where} reversals`);
|
||||
for (const [planet, row] of Object.entries(summary.planets as Json)) {
|
||||
assert.deepEqual((d9.planets as Json)[planet], pick(row, ["d9_sign", "d9_dignity", "vargottama"]), `${where} ${planet}`);
|
||||
}
|
||||
// The D9 houses and aspects stay out of the base section.
|
||||
assert.equal(JSON.stringify(d9).includes("house"), false, `${where} no D9 houses`);
|
||||
}
|
||||
reversalsSeen += (summary.reversals as unknown[]).length;
|
||||
}
|
||||
assert.ok(reversalsSeen > 0, "a golden chart has a D1/D9 dignity reversal on the card");
|
||||
});
|
||||
|
||||
test("no card carries Chara Dasha or KP; both are lookups", () => {
|
||||
for (const chart of golden.charts) {
|
||||
for (const [workflow, domains] of [
|
||||
[chart.workflow, consultationDomainIds],
|
||||
[chart.annual_workflow, ["annual"]],
|
||||
[chart.timing_workflow, ["timing"]],
|
||||
] as const) {
|
||||
for (const domain of domains) {
|
||||
const built = card(workflow, domain);
|
||||
const text = JSON.stringify({ base: built.base, sections: built.domain_sections });
|
||||
for (const absent of ["chara_dasha", "kp_cusps", "sublord", "sub_lord", "cusps"]) {
|
||||
assert.equal(text.includes(`"${absent}"`), false, `${chart.id}/${domain} ${absent}`);
|
||||
}
|
||||
assert.ok(built.meta.supplementable_sections.includes("chara_dasha"), `${chart.id}/${domain} chara lookup`);
|
||||
assert.ok(built.meta.supplementable_sections.includes("kp_cusps"), `${chart.id}/${domain} KP lookup`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("career: AL, 10H SAV and the SAV of the Jupiter / Saturn transit signs join v1's career section", () => {
|
||||
for (const chart of golden.charts) {
|
||||
const section = card(chart.workflow, "career").domain_sections.career!;
|
||||
const padas = dig(modules(chart.workflow), "arudha_padas", "padas") as Json;
|
||||
assert.deepEqual(section.arudha?.AL, pick(padas.A1, ["sign", "lord"]), `${chart.id} AL is pada A1`);
|
||||
assert.deepEqual(section.arudha?.A10, pick(padas.A10, ["sign", "lord"]), `${chart.id} A10`);
|
||||
const house10 = dig(modules(chart.workflow), "ashtakavarga", "house_scores", "house_10");
|
||||
assert.deepEqual(section.sav_by_house?.["10"], pick(house10, ["sign", "sav_score", "level"]), `${chart.id} 10H SAV`);
|
||||
const current = dig(native(chart.workflow), "slow_transits", "current") as Json;
|
||||
for (const body of ["Jupiter", "Saturn"]) {
|
||||
assert.deepEqual((section.slow_transits as Json)[body], pick(current[body], ["sign", "natal_house", "sav"]), `${chart.id} ${body}`);
|
||||
}
|
||||
assert.ok(section.vargas.D10 && section.karakas?.AmK && section.yogas && section.shadbala !== undefined, `${chart.id} v1 items kept`);
|
||||
const supplementable = card(chart.workflow, "career").meta.supplementable_sections;
|
||||
for (const lookup of ["karakamsha", "dispositor_chains", "inter_chart_linkage", "argala", "kp_cusps", "chara_dasha"]) {
|
||||
assert.ok(supplementable.includes(lookup as never), `${chart.id} ${lookup} is a lookup`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("marriage: 5H / 5L, day / night, Punarphoo, Double Transit on 7H / 7L / DK / UL, Vivah Saham, gender unknown", () => {
|
||||
for (const chart of golden.charts) {
|
||||
const built = card(chart.workflow, "marriage");
|
||||
const section = built.domain_sections.marriage!;
|
||||
const layers = native(chart.workflow);
|
||||
const where = chart.id;
|
||||
assert.deepEqual(Object.keys(section.houses).sort(), ["5", "7"], `${where} houses`);
|
||||
const owned = built.base.natal.functional_benefic_malefic.owned_houses as Json;
|
||||
const fifthLords = Object.entries(owned).filter(([, houses]) => (houses as number[]).includes(5)).map(([name]) => name);
|
||||
assert.deepEqual((section.houses["5"] as Json).lords, fifthLords, `${where} 5L`);
|
||||
assert.deepEqual(section.day_night, { is_day: dig(modules(chart.workflow), "gulika", "daynight_evidence", "is_daytime") }, `${where} is_day`);
|
||||
const punarphoo = layers.punarphoo as Json;
|
||||
assert.deepEqual(section.punarphoo, {
|
||||
status: "observation_only",
|
||||
natal_present: punarphoo.natal_present,
|
||||
orb_deg: punarphoo.orb_deg,
|
||||
saturn_return_to_natal_moon: pick(punarphoo.saturn_return_to_natal_moon, ["active", "orb_deg"]),
|
||||
claim_boundary: punarphoo.claim_boundary,
|
||||
}, `${where} Punarphoo`);
|
||||
const double = layers.double_transit as Json;
|
||||
const seventh = (double.conclusions as Json[]).filter((row) => row.house === 7)
|
||||
.map((row) => pick(row, ["layer", "target", "strength", "jupiter_pac", "saturn_pac"]));
|
||||
assert.deepEqual(dig(section.double_transit, "house_7"), seventh, `${where} 7H / 7L double transit`);
|
||||
for (const key of ["DK", "UL"]) {
|
||||
assert.deepEqual(
|
||||
dig(section.double_transit, key),
|
||||
pick((double.karaka_targets as Json)[key], ["target", "jupiter_pac", "saturn_pac", "double_transit"]),
|
||||
`${where} ${key} double transit`,
|
||||
);
|
||||
}
|
||||
const vivah = layers.vivah_saham as Json;
|
||||
assert.deepEqual(section.vivah_saham, pick(vivah, ["longitude", "sign", "degree_in_sign", "house", "natal_conjuncts"]), `${where} Vivah`);
|
||||
assert.equal(typeof (section.vivah_saham as Json).house, "number");
|
||||
assert.equal(section.gender, EVIDENCE_CARD_LABELS.genderUnknown);
|
||||
assert.ok(section.vargas.D9 && section.arudha?.UL && section.karakas?.DK, `${where} v1 items kept`);
|
||||
}
|
||||
});
|
||||
|
||||
test("wealth names 8H and 12H beside 2 / 11 / 9 / 5", () => {
|
||||
for (const chart of golden.charts) {
|
||||
const section = card(chart.workflow, "wealth").domain_sections.wealth!;
|
||||
assert.deepEqual(Object.keys(section.houses).map(Number).sort((a, b) => a - b), [2, 5, 8, 9, 11, 12]);
|
||||
for (const house of ["8", "12"]) {
|
||||
assert.equal((section.houses[house] as Json).sign, card(chart.workflow, "wealth").base.natal.houses[house], `${chart.id} ${house}H sign`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("annual: the annual chart verbatim, AD-lord and transit houses, key dates only", () => {
|
||||
for (const chart of golden.charts) {
|
||||
const built = card(chart.annual_workflow, "annual");
|
||||
const section = built.domain_sections.annual!;
|
||||
const layers = native(chart.annual_workflow);
|
||||
const annual = layers.annual_tajika as Json;
|
||||
const where = chart.id;
|
||||
assert.deepEqual(section.annual_chart, pick(annual, [
|
||||
"status", "varsha_year", "annual_lagna", "varshesha", "muntha", "mudda_dasha", "tajika_aspects", "boundary",
|
||||
]), `${where} annual chart`);
|
||||
assert.equal(dig(section.annual_chart, "status"), "parameter_sensitive");
|
||||
// Houses: 1, the running and next AD lords' houses, the year's Jupiter / Saturn transit houses.
|
||||
const vim = built.base.timing.vimshottari as Json;
|
||||
const owned = built.base.natal.functional_benefic_malefic.owned_houses as Json;
|
||||
const lordHouses = (lord: unknown) => ((owned[String(lord)] as number[] | undefined) ?? []);
|
||||
const inWindow = dig(layers, "slow_transits", "houses_in_window") as Json;
|
||||
const expected = [...new Set([
|
||||
1,
|
||||
...lordHouses(dig(vim, "antardasha", "lord")),
|
||||
...lordHouses(dig(vim, "next_antardasha", "lord")),
|
||||
...(inWindow.Jupiter as number[]),
|
||||
...(inWindow.Saturn as number[]),
|
||||
])];
|
||||
assert.deepEqual(Object.keys(section.houses).map(Number).sort((a, b) => a - b), [...expected].sort((a, b) => a - b), `${where} annual houses`);
|
||||
assert.deepEqual(section.house_basis, {
|
||||
antardasha_lord: dig(vim, "antardasha", "lord"),
|
||||
antardasha_houses: lordHouses(dig(vim, "antardasha", "lord")),
|
||||
next_antardasha_lord: dig(vim, "next_antardasha", "lord"),
|
||||
next_antardasha_houses: lordHouses(dig(vim, "next_antardasha", "lord")),
|
||||
jupiter_transit_houses: inWindow.Jupiter,
|
||||
saturn_transit_houses: inWindow.Saturn,
|
||||
}, `${where} house basis`);
|
||||
// Key dates: Jupiter / Saturn ingress and stations, Rahu / Ketu ingress; no month-by-month rows.
|
||||
assert.deepEqual(dig(section.slow_transits, "key_dates"), dig(layers, "slow_transits", "key_dates"), `${where} key dates`);
|
||||
assert.ok(((dig(section.slow_transits, "key_dates") as Json[]).length) <= 24);
|
||||
for (const row of dig(section.slow_transits, "key_dates") as Json[]) {
|
||||
assert.ok(["ingress", "station"].includes(String(row.kind)));
|
||||
assert.ok(["jupiter", "saturn", "rahu", "ketu"].includes(String(row.body)));
|
||||
}
|
||||
assert.equal(section.transits, undefined, `${where} no generic transit layer on the annual card`);
|
||||
}
|
||||
});
|
||||
|
||||
test("annual: a missing or blocked annual pack says 年盘未接入 and never shows natal data in its place", () => {
|
||||
// Family route: the engine attaches the pack on the annual route only.
|
||||
const notAttached = card(golden.charts[0]!.workflow, "annual").domain_sections.annual!;
|
||||
assert.deepEqual(notAttached.annual_chart, {
|
||||
status: EVIDENCE_CARD_LABELS.annualNotAttached,
|
||||
reason: "annual pack is attached for the annual route only",
|
||||
});
|
||||
const blocked = structuredClone(golden.charts[1]!.annual_workflow);
|
||||
((blocked.chart as Json).consultation_native_layers as Json).annual_tajika = {
|
||||
status: "blocked",
|
||||
reason: "annual_pack_not_reliable:mudda_dasha_periods",
|
||||
};
|
||||
const section = card(blocked, "annual").domain_sections.annual!;
|
||||
assert.deepEqual(section.annual_chart, { status: "年盘未接入", reason: "annual_pack_not_reliable:mudda_dasha_periods" });
|
||||
assert.deepEqual(Object.keys(section.annual_chart as Json), ["status", "reason"], "no annual field is filled from natal data");
|
||||
assert.deepEqual(Object.keys(notAttached.annual_chart as Json), ["status", "reason"]);
|
||||
});
|
||||
|
||||
test("timing: Rahu / Ketu, Jupiter / Saturn SAV and BAV, Double Transit conclusions without degrees", () => {
|
||||
for (const chart of golden.charts) {
|
||||
const section = card(chart.timing_workflow, "timing").domain_sections.timing!;
|
||||
const layers = native(chart.timing_workflow);
|
||||
const current = dig(layers, "slow_transits", "current") as Json;
|
||||
const where = chart.id;
|
||||
for (const body of ["Jupiter", "Saturn"]) {
|
||||
assert.deepEqual(dig(section.slow_transits, "current", body), pick(current[body], ["sign", "degree_in_sign", "retrograde", "natal_house", "sav", "bav"]), `${where} ${body}`);
|
||||
}
|
||||
for (const body of ["Rahu", "Ketu"]) {
|
||||
assert.deepEqual(dig(section.slow_transits, "current", body), pick(current[body], ["sign", "degree_in_sign", "retrograde", "natal_house"]), `${where} ${body}`);
|
||||
}
|
||||
const nodeDates = (dig(layers, "slow_transits", "key_dates") as Json[]).filter((row) => row.body === "rahu" || row.body === "ketu");
|
||||
assert.deepEqual(dig(section.slow_transits, "node_ingresses"), nodeDates, `${where} node ingresses`);
|
||||
const double = layers.double_transit as Json;
|
||||
assert.deepEqual(dig(section.double_transit, "conclusions"), (double.conclusions as Json[]).map((row) => pick(row, ["house", "layer", "target", "strength"])), `${where} conclusions`);
|
||||
assert.deepEqual(dig(section.double_transit, "active_houses"), double.active_houses, `${where} active houses`);
|
||||
assert.equal(/\d+\.\d+°/.test(JSON.stringify(section.double_transit)), false, `${where} no raw degrees`);
|
||||
// Moon transit is a lookup, not on the card.
|
||||
assert.equal(JSON.stringify(section).includes("moon_transit"), false);
|
||||
}
|
||||
});
|
||||
|
||||
test("timing vargas follow the turn's other domain; D9 when timing is alone", () => {
|
||||
const workflow = golden.charts[1]!.timing_workflow;
|
||||
assert.deepEqual(Object.keys(card(workflow, "timing").domain_sections.timing!.vargas), ["D1", "D9"]);
|
||||
for (const [other, code] of [["career", "D10"], ["marriage", "D9"], ["wealth", "D2"], ["health", "D30"]] as const) {
|
||||
for (const order of [["timing", other], [other, "timing"]] as ConsultationDomain[][]) {
|
||||
const built = card(workflow, ...order);
|
||||
const timing = built.domain_sections.timing!;
|
||||
assert.deepEqual(Object.keys(timing.vargas), ["D1"], `${order.join("+")} timing keeps D1 only`);
|
||||
assert.equal(timing.vargas_follow, other, `${order.join("+")} follows ${other}`);
|
||||
assert.ok(built.domain_sections[other]!.vargas[code], `${order.join("+")} ${code} is in the ${other} section`);
|
||||
}
|
||||
}
|
||||
// Annual carries only D1, so timing falls back to D9.
|
||||
const withAnnual = card(workflow, "timing", "annual").domain_sections.timing!;
|
||||
assert.deepEqual(Object.keys(withAnnual.vargas), ["D1", "D9"]);
|
||||
assert.equal(withAnnual.vargas_follow, undefined);
|
||||
});
|
||||
|
||||
test("the lookup enum carries the optional layers and returns the engine's own values", async () => {
|
||||
for (const section of ["karakamsha", "dispositor_chains", "inter_chart_linkage", "argala", "moon_transit", "chara_dasha", "kp_cusps", "varga:D10"] as const) {
|
||||
assert.ok(EVIDENCE_LOOKUP_SECTIONS.includes(section), section);
|
||||
assert.equal(evidenceLookupActivityLabel(section).includes("一段盘面数据"), false, `${section} has a plain label`);
|
||||
}
|
||||
const workflow = golden.charts[0]!.workflow;
|
||||
const career = sources(workflow, "career");
|
||||
const execution = { ...career, modelOutput: career.packet };
|
||||
const cache = { executions: [execution] } as never;
|
||||
const layers = native(workflow);
|
||||
assert.deepEqual(readEvidenceLookupSection(cache, "karakamsha"), layers.karakamsha);
|
||||
assert.deepEqual(readEvidenceLookupSection(cache, "moon_transit"), layers.moon_transit);
|
||||
assert.deepEqual(readEvidenceLookupSection(cache, "dispositor_chains"), pick(layers.dispositor_chains, ["chains", "boundary"]));
|
||||
assert.deepEqual(readEvidenceLookupSection(cache, "inter_chart_linkage"), pick(layers.inter_chart_linkage, ["planets", "boundary"]));
|
||||
assert.deepEqual(readEvidenceLookupSection(cache, "argala"), pick(layers.argala, ["method", "summary", "houses"]));
|
||||
const kp = readEvidenceLookupSection(cache, "kp_cusps") as Json;
|
||||
assert.equal(kp.note, EVIDENCE_LOOKUP_KP_NOTE);
|
||||
assert.ok(kp.kp_cusps, "KP data travels with its blocked note");
|
||||
assert.ok(readEvidenceLookupSection(cache, "varga:D10"), "D10 for income from work");
|
||||
|
||||
// Through the tool: a KP lookup answers with the note.
|
||||
const state = createConsultationRuntimeState();
|
||||
const tools = createConsultationTools({
|
||||
userId: "u", sessionId: "s", requestId: "kp-lookup", consultationMode: "verified_chart",
|
||||
serverChart: { name: "public", toolInput: { year: 2000, month: 1, day: 1, hour: 0, minute: 0, city: "fixture", lat: 0, lon: 0, tz: 0, ayanamsa: "raman" }, truth: { birthTimeSource: "reported" } } as never,
|
||||
state,
|
||||
runWorkflow: async () => structuredClone(workflow) as never,
|
||||
});
|
||||
await tools["run-jyotish-consultation"].execute!({ question: "q", domains: ["wealth"] } as never, { writer: { custom: async () => {} } } as never);
|
||||
const result = await tools["read-consultation-evidence"].execute!({ section: "kp_cusps" } as never, { writer: { custom: async () => {} } } as never) as Json;
|
||||
assert.equal(dig(result, "data", "note"), EVIDENCE_LOOKUP_KP_NOTE);
|
||||
});
|
||||
|
||||
test("size: contract + card stay under 12,000 characters without the checklist, annual and timing included", async () => {
|
||||
const serverChart = {
|
||||
name: "public",
|
||||
toolInput: { year: 2000, month: 1, day: 1, hour: 0, minute: 0, city: "fixture", lat: 0, lon: 0, tz: 0, ayanamsa: "raman" },
|
||||
truth: { birthTimeSource: "reported" },
|
||||
};
|
||||
for (const chart of golden.charts) {
|
||||
for (const [workflow, domain] of [
|
||||
...consultationDomainIds.map((domain) => [chart.workflow, domain] as const),
|
||||
[chart.annual_workflow, "annual"] as const,
|
||||
[chart.timing_workflow, "timing"] as const,
|
||||
]) {
|
||||
const state = createConsultationRuntimeState();
|
||||
const tools = createConsultationTools({
|
||||
userId: "u", sessionId: "s", requestId: `size-${domain}`, consultationMode: "verified_chart",
|
||||
serverChart: serverChart as never, state,
|
||||
runWorkflow: async () => structuredClone(workflow) as never,
|
||||
});
|
||||
const view = await tools["run-jyotish-consultation"].execute!({ question: "q", domains: [domain] } as never, { writer: { custom: async () => {} } } as never) as Json;
|
||||
const { methodology: _methodology, ...rest } = view;
|
||||
void _methodology;
|
||||
assert.ok(JSON.stringify(rest).length <= 12_000, `${chart.id}/${domain} contract + card ${JSON.stringify(rest).length}`);
|
||||
assert.deepEqual(dig(view, "evidence_card", "gaps") ?? [], [], `${chart.id}/${domain} gaps`);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -107,7 +107,8 @@ test("the answer-writing call sees the evidence card, not the full result (red l
|
||||
const writer = firstPromptWithToolResult(result.prompts, "run-jyotish-consultation");
|
||||
assert.equal(writer, 1);
|
||||
const card = toolResultText(result.prompts[writer], "run-jyotish-consultation");
|
||||
assert.ok(card.includes("evidence-card-v1"), "the card reaches the writer");
|
||||
// 原值: "evidence-card-v1" / 新值: "evidence-card-v2" / 原因: 卡 v2(TASK-consult-evidence-card-v2-20260927 T3)
|
||||
assert.ok(card.includes("evidence-card-v2"), "the card reaches the writer");
|
||||
assert.ok(card.includes(PD_START), "with the engine's pratyantar start");
|
||||
assert.ok(card.includes(NARAYANA_SIGN), "and the running Narayana sign");
|
||||
assert.ok(card.includes("can_answer_direction"), "and the answer contract");
|
||||
@@ -135,7 +136,8 @@ test("a length continuation carries the card (red line 4)", async () => {
|
||||
assert.deepEqual(result.terminal.map((event) => event.type), ["run.completed"]);
|
||||
assert.equal(result.continuations.length, 1);
|
||||
const continuation = JSON.stringify(result.prompts[2]);
|
||||
assert.ok(continuation.includes("evidence-card-v1"));
|
||||
// 原值: "evidence-card-v1" / 新值: "evidence-card-v2" / 原因: 同上
|
||||
assert.ok(continuation.includes("evidence-card-v2"));
|
||||
assert.ok(continuation.includes(PD_START));
|
||||
assert.equal(result.completed, ANSWER);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user