Annual / timing single-domain turns take the career / marriage / wealth budget: contract + card <= 12,000, total with the checklist <= 18,000. CONSULT_READING_BUDGET_BLOCKED removed; size assertions and the pinned blocker test changed (原值/新值/原因 in the tests and brief 2's progress record); BUG-1160/1161 resolved, BLOCKED.md entry struck through. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
372 lines
21 KiB
TypeScript
372 lines
21 KiB
TypeScript
// TASK-consult-evidence-card-20260927 T3: the answer model reads the answer
|
||
// contract plus the evidence card instead of the whole projected result.
|
||
//
|
||
// Fixture: real engine consultation responses for three public AA charts
|
||
// (scripts/research/capture_consult_evidence_card_golden.py, AGENTS §7.4).
|
||
// Expected facts are read straight from the raw engine payload, so a card that
|
||
// rewrote, rounded 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,
|
||
citedEvidenceCardFields,
|
||
EVIDENCE_CARD_BACKSTAGE_LINE,
|
||
EVIDENCE_CARD_LABELS,
|
||
EVIDENCE_CARD_SPECS,
|
||
EVIDENCE_CARD_VERSION,
|
||
evidenceCardFieldValues,
|
||
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 { normalizeTechniqueAuditRows } from "../src/lib/consultation-technique-audit.ts";
|
||
import {
|
||
consultationWorkflowResponseSchema,
|
||
toAgentConsultationContext,
|
||
toModelOutput,
|
||
} from "../src/mastra/consultation-workflow.ts";
|
||
import {
|
||
CONSULTATION_MODEL_PACKET_VERSION,
|
||
createConsultationRuntimeState,
|
||
createConsultationTools,
|
||
} from "../src/mastra/consultation-tools.ts";
|
||
|
||
type Json = Record<string, unknown>;
|
||
const golden = JSON.parse(readFileSync(
|
||
new URL("./fixtures/consult-evidence-card-golden.json", import.meta.url),
|
||
"utf8",
|
||
)) as { charts: Array<{ id: string; workflow: Json }> };
|
||
|
||
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 cardFor(workflow: Json, domain: ConsultationDomain): EvidenceCard {
|
||
return buildEvidenceCard([sources(workflow, domain)]);
|
||
}
|
||
|
||
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" },
|
||
};
|
||
|
||
async function runTool(workflow: Json, domains: ConsultationDomain[]) {
|
||
const state = createConsultationRuntimeState();
|
||
const tools = createConsultationTools({
|
||
userId: "u", sessionId: "s", requestId: `card-${domains.join("-")}`, consultationMode: "verified_chart",
|
||
serverChart: serverChart as never, state,
|
||
runWorkflow: async () => structuredClone(workflow) as never,
|
||
});
|
||
const view = await tools["run-jyotish-consultation"].execute!(
|
||
{ question: "测试问题", domains } as never,
|
||
{ writer: { custom: async () => {} } } as never,
|
||
) as Json;
|
||
return { view, state };
|
||
}
|
||
|
||
function engineFacts(workflow: Json) {
|
||
const chart = dig(workflow, "chart") as Json;
|
||
const modules = dig(chart, "modules") as Json;
|
||
const formal = dig(modules, "varga_spectrum", "formal") as Json;
|
||
return {
|
||
ascendantSign: dig(chart, "ascendant", "sign"),
|
||
ascendantDegree: dig(chart, "ascendant", "degree_in_sign"),
|
||
moonSign: dig(chart, "planets", "Moon", "sign"),
|
||
moonHouse: dig(chart, "planets", "Moon", "house"),
|
||
moonDegree: dig(chart, "planets", "Moon", "degree_in_sign"),
|
||
mahadasha: dig(modules, "dasha_sub_periods", "current", "mahadasha"),
|
||
antardasha: dig(modules, "dasha_sub_periods", "current", "antardasha"),
|
||
pratyantar: dig(modules, "dasha_sub_periods", "pratyantar_dasha_timeline", "current"),
|
||
narayanaMd: dig(modules, "narayana_dasha", "current_dasha", "md"),
|
||
functional: dig(modules, "functional_benefic_malefic") as Json,
|
||
varga: (code: string) => formal[code] as Json,
|
||
};
|
||
}
|
||
|
||
test("the card copies the engine's own facts verbatim (red line 2)", () => {
|
||
for (const chart of golden.charts) {
|
||
const facts = engineFacts(chart.workflow);
|
||
for (const domain of ["parents", "children", "marriage", "career"] as const) {
|
||
const card = cardFor(chart.workflow, domain);
|
||
const natal = card.base.natal as Json;
|
||
const timing = card.base.timing as Json;
|
||
const where = `${chart.id}/${domain}`;
|
||
assert.equal(dig(natal, "ascendant", "sign"), facts.ascendantSign, `${where} ascendant sign`);
|
||
assert.equal(dig(natal, "ascendant", "degree_in_sign"), facts.ascendantDegree, `${where} ascendant degree`);
|
||
assert.equal(dig(natal, "planets", "Moon", "sign"), facts.moonSign, `${where} Moon sign`);
|
||
assert.equal(dig(natal, "planets", "Moon", "house"), facts.moonHouse, `${where} Moon house`);
|
||
assert.equal(dig(natal, "planets", "Moon", "degree_in_sign"), facts.moonDegree, `${where} Moon degree`);
|
||
for (const [cardKey, engine] of [
|
||
["mahadasha", facts.mahadasha],
|
||
["antardasha", facts.antardasha],
|
||
["pratyantardasha", facts.pratyantar],
|
||
] as const) {
|
||
for (const key of ["lord", "start", "end"] as const) {
|
||
assert.equal(dig(timing, "vimshottari", cardKey, key), dig(engine, key), `${where} ${cardKey}.${key}`);
|
||
}
|
||
}
|
||
assert.equal(dig(timing, "narayana", "md", "sign"), dig(facts.narayanaMd, "sign"), `${where} Narayana sign`);
|
||
assert.equal(dig(timing, "narayana", "md", "start_age"), dig(facts.narayanaMd, "start_age"), `${where} Narayana start age`);
|
||
assert.deepEqual(dig(natal, "functional_benefic_malefic", "functional_benefics"), facts.functional.functional_benefics, `${where} functional benefics`);
|
||
assert.deepEqual(dig(natal, "functional_benefic_malefic", "functional_malefics"), facts.functional.functional_malefics, `${where} functional malefics`);
|
||
}
|
||
const varga = (domain: ConsultationDomain, code: string) => dig(cardFor(chart.workflow, domain).domain_sections, domain, "vargas", code) as Json;
|
||
for (const [domain, code] of [["parents", "D12"], ["family", "D12"], ["migration", "D12"], ["children", "D7"], ["family", "D7"], ["marriage", "D9"], ["general", "D9"], ["career", "D10"], ["general", "D10"]] as const) {
|
||
const engine = facts.varga(code);
|
||
assert.equal(varga(domain, code)?.lagna, engine.lagna, `${chart.id}/${domain} ${code} lagna`);
|
||
assert.equal(dig(varga(domain, code), "planets", "Moon"), dig(engine, "planets", "Moon"), `${chart.id}/${domain} ${code} Moon`);
|
||
assert.deepEqual(varga(domain, code)?.planets, engine.planets, `${chart.id}/${domain} ${code} planets`);
|
||
}
|
||
}
|
||
});
|
||
|
||
test("every card value is a value the projection or the engine already holds (nothing rewritten)", () => {
|
||
function scalars(value: unknown, into = new Set<string>()) {
|
||
if (value === null || typeof value !== "object") {
|
||
into.add(JSON.stringify(value));
|
||
return into;
|
||
}
|
||
for (const [key, item] of Object.entries(value as object)) {
|
||
if (!Array.isArray(value)) into.add(JSON.stringify(key));
|
||
scalars(item, into);
|
||
}
|
||
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.
|
||
// 原值: 白名单是 EVIDENCE_CARD_LABELS 的两条(性别未知、年盘未接入)
|
||
// 新值: 同一来源多了 roleMother / roleFather(母亲、父亲),断言本身不变
|
||
// 原因: TASK-consult-plain-answer-20261001 D6(BUG-1133):父母卡标出每条依据属于谁,标签由卡自己写
|
||
// 原值: 白名单是 EVIDENCE_CARD_LABELS 的四条(性别未知、年盘未接入、母亲、父亲)
|
||
// 新值: 同一来源多了 functionalBenefic / functionalMalefic / functionalNeutral(benefic、malefic、neutral),断言本身不变
|
||
// 原因: TASK-consult-card-affliction-data-20261001 T3(BUG-1156):父母卡宫主与代表星行标出功能吉凶,
|
||
// 取值是「该星在引擎哪张功能列表里」的选择结果,标签由卡自己写
|
||
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) {
|
||
assert.ok(known.has(JSON.stringify(value)), `${chart.id}/${domain} ${fieldId}=${JSON.stringify(value)} is not an engine value`);
|
||
}
|
||
}
|
||
assert.deepEqual(card.meta.gaps, [], `${chart.id}/${domain} gaps`);
|
||
}
|
||
}
|
||
});
|
||
|
||
test("every domain's card covers Part B: dual dasha, domain vargas, functional benefic/malefic, raw degrees and dates (red line 3)", () => {
|
||
const isoDate = /^\d{4}-\d{2}-\d{2}$/;
|
||
for (const chart of golden.charts) {
|
||
for (const domain of consultationDomainIds) {
|
||
const card = cardFor(chart.workflow, domain);
|
||
const where = `${chart.id}/${domain}`;
|
||
const timing = card.base.timing as Json;
|
||
// Dual dasha: Vimshottari MD/AD/PD with dates and the running Narayana period.
|
||
for (const level of ["mahadasha", "antardasha", "pratyantardasha"]) {
|
||
assert.match(String(dig(timing, "vimshottari", level, "start")), isoDate, `${where} ${level} start`);
|
||
assert.match(String(dig(timing, "vimshottari", level, "end")), isoDate, `${where} ${level} end`);
|
||
}
|
||
assert.equal(typeof dig(timing, "narayana", "md", "sign"), "string", `${where} Narayana md`);
|
||
// Domain vargas: every varga the domain's spec names.
|
||
const section = card.domain_sections[domain];
|
||
assert.ok(section, `${where} domain section`);
|
||
assert.ok(EVIDENCE_CARD_SPECS[domain].vargas.length > 0, `${where} spec names a varga`);
|
||
for (const code of EVIDENCE_CARD_SPECS[domain].vargas) {
|
||
assert.equal(typeof dig(section.vargas, code, "lagna"), "string", `${where} ${code}`);
|
||
}
|
||
// Functional benefics / malefics with house lordship.
|
||
const functional = card.base.natal.functional_benefic_malefic;
|
||
assert.ok(Array.isArray(functional.functional_benefics) && functional.functional_benefics.length > 0, `${where} benefics`);
|
||
assert.ok(Array.isArray(functional.functional_malefics) && functional.functional_malefics.length > 0, `${where} malefics`);
|
||
assert.ok(Object.keys(functional.owned_houses as object).length > 0, `${where} lordship`);
|
||
// Raw degrees.
|
||
assert.equal(typeof dig(card.base.natal, "ascendant", "degree_in_sign"), "number", `${where} ascendant degree`);
|
||
assert.equal(typeof dig(card.base.natal, "planets", "Sun", "degree_in_sign"), "number", `${where} Sun degree`);
|
||
}
|
||
}
|
||
});
|
||
|
||
test("the model sees the answer contract and the card, not the full result (red line 1, D1, D3)", async () => {
|
||
const workflow = golden.charts[0]!.workflow;
|
||
const { view } = await runTool(workflow, ["parents"]);
|
||
const context = toAgentConsultationContext(consultationWorkflowResponseSchema.parse(structuredClone(workflow)));
|
||
const full = toModelOutput(context, createConsultationPlan({ userIntent: "q", theme: "parents" }), "parents");
|
||
|
||
// The keys the system prompt states its rules against, with the same values
|
||
// the full result carried.
|
||
assert.equal(view.packet_version, CONSULTATION_MODEL_PACKET_VERSION);
|
||
assert.equal(view.status, full.status);
|
||
assert.equal(view.route, full.route);
|
||
assert.deepEqual(dig(view, "evidence_contract", "answer_policy"), full.evidence_contract.answer_policy);
|
||
assert.equal(dig(view, "evidence_contract", "answer_policy", "can_answer_direction"), true);
|
||
assert.equal(typeof dig(view, "evidence_contract", "answer_policy", "can_answer_precise_timing"), "boolean");
|
||
assert.deepEqual(dig(view, "evidence_contract", "hard_blockers"), full.evidence_contract.hard_blockers);
|
||
assert.deepEqual(dig(view, "evidence_contract", "missing_route_layers"), full.evidence_contract.missing_route_layers);
|
||
assert.deepEqual(dig(view, "evidence_contract", "available_layers"), full.evidence_contract.available_layers);
|
||
assert.equal(dig(view, "evidence_contract", "user_facing_limitation"), full.evidence_contract.user_facing_limitation);
|
||
assert.deepEqual(view.rectification, full.rectification);
|
||
assert.equal("presentation" in view, false, "required_blocks names the audit table the body must not carry");
|
||
assert.deepEqual((view.claim_cards as Array<{ category: string }>).map((card) => card.category), ["natal_foundation", "timing", "domain"]);
|
||
assert.deepEqual(view.domains, ["parents"]);
|
||
assert.deepEqual(view.omitted_domains, []);
|
||
assert.equal(view.success, true);
|
||
assert.ok(dig(view, "methodology", "sections"), "the domain checklist still travels with the result");
|
||
assert.equal(dig(view, "evidence_card", "card_version"), EVIDENCE_CARD_VERSION);
|
||
assert.equal(dig(view, "evidence_card", "backstage"), EVIDENCE_CARD_BACKSTAGE_LINE);
|
||
// No duplicate package, no audit table, no spectra, no status layers.
|
||
assert.equal("consultations" in view, false);
|
||
const { methodology: _methodology, ...withoutMethod } = view;
|
||
void _methodology;
|
||
const text = JSON.stringify(withoutMethod);
|
||
// As keys: available_layers may still name a layer ("varga_spectrum") as available.
|
||
for (const absent of ["technique_audit_table", "varga_spectrum", "western_spectrum", "must_use_layers", "research_dn", "extended", "technique_truth", "reference_transparency", "vedastro_cross_check", "presentation"]) {
|
||
assert.equal(text.includes(`"${absent}":`), false, absent);
|
||
}
|
||
});
|
||
|
||
test("a two-domain turn carries one base, both domain sections and each domain's own contract", async () => {
|
||
const workflow = golden.charts[1]!.workflow;
|
||
const { view } = await runTool(workflow, ["children", "education"]);
|
||
const claimCards = view.claim_cards as Array<{ category: string; evidence: Json }>;
|
||
assert.deepEqual(claimCards.map((card) => card.category), ["natal_foundation", "timing", "domain"]);
|
||
assert.deepEqual(Object.keys(claimCards[2]!.evidence), ["children", "education"]);
|
||
const consultations = view.consultations as Array<Json>;
|
||
assert.deepEqual(consultations.map((item) => item.domain), ["children", "education"]);
|
||
for (const item of consultations) {
|
||
assert.deepEqual(Object.keys(item).sort(), ["domain", "evidence_contract", "rectification", "route", "status"]);
|
||
assert.equal(typeof dig(item, "evidence_contract", "answer_policy", "can_answer_direction"), "boolean");
|
||
}
|
||
assert.equal(typeof dig(view, "evidence_contract", "answer_policy", "can_answer_direction"), "boolean");
|
||
});
|
||
|
||
test("the 「本轮技法」 rows are exactly the rows the full result produced before the card", async () => {
|
||
for (const chart of golden.charts) {
|
||
for (const domains of [["parents"], ["career"], ["children", "education"]] as ConsultationDomain[][]) {
|
||
const { state } = await runTool(chart.workflow, domains);
|
||
// Before the card: single domain read the domain packet's audit table;
|
||
// several domains read the first packet's (mergeConsultationEvidencePackets).
|
||
const first = sources(chart.workflow, domains[0]!).packet;
|
||
const expected = normalizeTechniqueAuditRows(first.evidence_contract.technique_audit_table);
|
||
assert.ok((expected?.length ?? 0) > 30, "the audit table is the full 40-odd rows");
|
||
assert.deepEqual(state.techniqueAuditTable, expected, `${chart.id}/${domains.join("+")}`);
|
||
}
|
||
}
|
||
});
|
||
|
||
test("model-visible size falls to card scale; only the method checklist can push it past 12,000", async () => {
|
||
// 原值: new Set(["career", "marriage", "wealth"])
|
||
// 新值: 加 annual、timing(契约 + 卡 ≤ 12,000,含清单 ≤ 18,000)
|
||
// 原因: 产品 2026-10-02 决定年运 / 时运与事业 / 婚姻 / 财富同口径,通用读法与年运清单下发(BUG-1160/1161)
|
||
const methodHeavy = new Set(["career", "marriage", "wealth", "annual", "timing"]);
|
||
for (const chart of golden.charts) {
|
||
for (const domain of consultationDomainIds) {
|
||
const { view, state } = await runTool(chart.workflow, [domain]);
|
||
const total = JSON.stringify(view).length;
|
||
const { methodology, ...rest } = view;
|
||
const withoutMethod = JSON.stringify(rest).length;
|
||
assert.equal(state.modelVisibleChars, total);
|
||
assert.ok(withoutMethod <= 12_000, `${chart.id}/${domain}: contract + card ${withoutMethod}`);
|
||
if (!methodHeavy.has(domain)) assert.ok(total <= 12_000, `${chart.id}/${domain}: ${total}`);
|
||
assert.ok(total <= 18_000, `${chart.id}/${domain}: ${total} (methodology ${JSON.stringify(methodology ?? null).length})`);
|
||
}
|
||
}
|
||
});
|
||
|
||
test("the card names what else this calculation holds, without the sections it already carries", () => {
|
||
const card = cardFor(golden.charts[0]!.workflow, "parents");
|
||
assert.equal(card.meta.backstage, EVIDENCE_CARD_BACKSTAGE_LINE);
|
||
assert.ok(card.meta.supplementable_sections.includes("varga:D60"));
|
||
assert.ok(card.meta.supplementable_sections.includes("western:solar_return"));
|
||
assert.ok(card.meta.supplementable_sections.includes("yogas"));
|
||
assert.equal(card.meta.supplementable_sections.includes("varga:D12"), false);
|
||
const career = cardFor(golden.charts[0]!.workflow, "career");
|
||
// 原值: career 卡带 yogas,所以 supplementable_sections 不含 "yogas"(false)
|
||
// 新值: true
|
||
// 原因: TASK-consult-card-affliction-data-20261001 T4(BUG-1157):卡上只放本领域标签的瑜伽,
|
||
// 完整清单仍在服务端,只能靠一次查阅拿到,所以 "yogas" 仍是可补查的部分
|
||
assert.equal(career.meta.supplementable_sections.includes("yogas"), true);
|
||
assert.equal(career.meta.supplementable_sections.includes("varga:D10"), false);
|
||
});
|
||
|
||
test("citation counting keeps field ids only and needs a date, degree or planet-in-sign phrase", () => {
|
||
const card = cardFor(golden.charts[0]!.workflow, "parents");
|
||
const start = String(dig(card.base.timing, "vimshottari", "pratyantardasha", "start"));
|
||
const moonSign = String(dig(card.base.natal, "planets", "Moon", "sign"));
|
||
const answer = `子运从 ${start} 开始;Moon in ${moonSign}。月亮是 ${moonSign}。`;
|
||
const cited = citedEvidenceCardFields(card, answer);
|
||
assert.ok(cited.includes("base.timing.vimshottari.pratyantardasha.start"));
|
||
assert.ok(cited.includes("base.natal.planets.Moon.sign"));
|
||
for (const id of cited) assert.match(id, /^[A-Za-z][A-Za-z0-9._-]*$/);
|
||
// A bare sign name is not a citation.
|
||
assert.deepEqual(citedEvidenceCardFields(card, `月亮 ${moonSign}`), []);
|
||
assert.deepEqual(citedEvidenceCardFields(card, ""), []);
|
||
});
|
||
|
||
test("the parents section says which evidence is the mother's and which the father's (BUG-1133)", () => {
|
||
for (const chart of golden.charts) {
|
||
const card = cardFor(chart.workflow, "parents");
|
||
const section = card.domain_sections.parents!;
|
||
const roles = section.roles as Record<string, Json>;
|
||
const where = chart.id;
|
||
assert.deepEqual(Object.keys(roles), ["mother", "father"], where);
|
||
const mother = roles.mother!;
|
||
const father = roles.father!;
|
||
assert.equal(mother.label, EVIDENCE_CARD_LABELS.roleMother);
|
||
assert.equal(father.label, EVIDENCE_CARD_LABELS.roleFather);
|
||
assert.equal(mother.house, 4);
|
||
assert.equal(father.house, 9);
|
||
// House lords are the section's own 4H / 9H lords, copied, not re-derived.
|
||
assert.deepEqual(mother.house_lords, dig(section.houses, "4", "lords"), where);
|
||
assert.deepEqual(father.house_lords, dig(section.houses, "9", "lords"), where);
|
||
assert.deepEqual(mother.significators, ["Moon"]);
|
||
assert.deepEqual(father.significators, ["Sun"]);
|
||
// MK is on the card (7-karaka scheme); its value sits once in section.karakas.
|
||
assert.deepEqual(mother.karakas, ["MK"], where);
|
||
assert.equal(typeof dig(section.karakas, "MK", "planet"), "string", where);
|
||
// The engine's scheme has no PiK, so the father carries none and no gap is recorded.
|
||
assert.equal(father.karakas, undefined, where);
|
||
assert.equal(dig(section.karakas, "PiK"), undefined, where);
|
||
// D12 4th / 9th counted from the D12 lagna; occupants are D12 planets in that sign.
|
||
const d12 = section.vargas.D12 as { lagna: string; planets: Record<string, string> };
|
||
for (const [role, number] of [[mother, 4], [father, 9]] as const) {
|
||
const house = role.d12_house as { house: number; sign: string; occupants: string[] };
|
||
assert.equal(house.house, number);
|
||
const signs = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"];
|
||
assert.equal(house.sign, signs[(signs.indexOf(d12.lagna) + number - 1) % 12], where);
|
||
assert.deepEqual(house.occupants, Object.entries(d12.planets).filter(([, sign]) => sign === house.sign).map(([name]) => name), where);
|
||
}
|
||
assert.deepEqual(card.meta.gaps, [], where);
|
||
}
|
||
});
|
||
|
||
test("a parents card without MK records the gap and the mother names no karaka (BUG-1133)", () => {
|
||
const workflow = structuredClone(golden.charts[0]!.workflow) as Json;
|
||
const input = sources(workflow, "parents");
|
||
const table = dig(input.context, "local_layers", "jaimini", "chara_karakas") as Json;
|
||
delete table.MK;
|
||
delete table.Matrikaraka;
|
||
const card = buildEvidenceCard([input]);
|
||
assert.ok(card.meta.gaps.includes("parents.karaka.MK"));
|
||
assert.equal((card.domain_sections.parents!.roles as Record<string, Json>).mother!.karakas, undefined);
|
||
});
|