feat(consult): rule engine always runs and merges with chart yogas (BUG-1174); closed-list traditional_yogas on general/career/wealth cards (BUG-1175)

Goldens regenerated by the capture scripts (PYTHONHASHSEED=0, cache TTL 0; two runs byte-identical).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-10-02 08:32:11 +08:00
co-authored by Claude Opus 5.5
parent 8e978cabc4
commit c74bfcc06b
7 changed files with 236 additions and 23 deletions
+21 -3
View File
@@ -53,6 +53,12 @@ export const EVIDENCE_CARD_LABELS = {
functionalBenefic: "benefic",
functionalMalefic: "malefic",
functionalNeutral: "neutral",
/**
* Closed-list classical yogas the engine found formed (TASK-consult-career-
* yoga-functional-20261002 D3, BUG-1175): strength only, never a stand-alone
* conclusion, never counted as affliction or as support against it.
*/
traditionalYogas: "传统格局,本站未验证,只作参考",
} as const;
/** KP stays off every card: exact cusps are still blocked (decision table). */
@@ -77,6 +83,8 @@ export type EvidenceCardDomainSpec = Readonly<{
* scripts/consultation_native_layers.py YOGA_PACKET_DOMAINS).
*/
yogaTags: readonly string[];
/** `traditional_yogas` (engine layer of the same name), only when one is formed (BUG-1175). */
traditionalYogas: boolean;
transits: boolean;
ashtakavarga: boolean;
shadbala: boolean;
@@ -129,6 +137,7 @@ const spec = (value: Partial<EvidenceCardDomainSpec> & Pick<EvidenceCardDomainSp
planets: [],
yogas: false,
yogaTags: [],
traditionalYogas: false,
transits: false,
ashtakavarga: false,
shadbala: false,
@@ -168,7 +177,7 @@ const spec = (value: Partial<EvidenceCardDomainSpec> & Pick<EvidenceCardDomainSp
*/
export const EVIDENCE_CARD_SPECS: Readonly<Record<ConsultationDomain, EvidenceCardDomainSpec>> = {
career: spec({
vargas: ["D10"], houses: [10], arudha: ["A10", "AL"], karakas: ["AmK"], yogas: true, yogaTags: ["career", "general"],
vargas: ["D10"], houses: [10], arudha: ["A10", "AL"], karakas: ["AmK"], yogas: true, yogaTags: ["career", "general"], traditionalYogas: true,
transits: true, shadbala: true,
savHouses: [10], slowTransits: "sav",
}),
@@ -177,7 +186,7 @@ export const EVIDENCE_CARD_SPECS: Readonly<Record<ConsultationDomain, EvidenceCa
punarphoo: true, dayNight: true, vivahSaham: true, doubleTransit: "seventh_and_karakas", gender: true,
}),
wealth: spec({
vargas: ["D2", "D11"], houses: [2, 11, 9, 5, 8, 12], yogas: true, yogaTags: ["wealth", "general"], ashtakavarga: true, shadbala: true,
vargas: ["D2", "D11"], houses: [2, 11, 9, 5, 8, 12], yogas: true, yogaTags: ["wealth", "general"], traditionalYogas: true, ashtakavarga: true, shadbala: true,
}),
health: spec({ vargas: ["D6", "D8", "D30"], houses: [1, 6, 8, 12], transits: true, shadbala: true }),
education: spec({ vargas: ["D5", "D24"], houses: [4, 5, 9], planets: ["Mercury", "Jupiter"] }),
@@ -197,7 +206,7 @@ export const EVIDENCE_CARD_SPECS: Readonly<Record<ConsultationDomain, EvidenceCa
slowTransits: "nodes_sav_bav", doubleTransit: "all_houses",
}),
general: spec({
vargas: ["D9", "D10", "D2"], houses: [1, 10, 7, 2], arudha: ["A10"], yogas: true, yogaTags: ["general"], transits: true,
vargas: ["D9", "D10", "D2"], houses: [1, 10, 7, 2], arudha: ["A10"], yogas: true, yogaTags: ["general"], traditionalYogas: true, transits: true,
ashtakavarga: true, shadbala: true,
}),
};
@@ -273,6 +282,8 @@ export type EvidenceCardDomainSection = {
arudha?: JsonRecord;
karakas?: JsonRecord;
yogas?: JsonRecord;
/** BUG-1175: `{ label, names }`, absent when no closed-list yoga is formed. */
traditional_yogas?: JsonRecord;
transits?: Json;
ashtakavarga?: JsonRecord;
shadbala?: Json;
@@ -752,6 +763,13 @@ function domainSection(
gaps.push(`${domain}.yogas`);
}
}
if (rule.traditionalYogas) {
const traditional = record(natal.traditional_yogas);
const names = Array.isArray(traditional.names)
? traditional.names.filter((name): name is string => typeof name === "string")
: [];
if (names.length) section.traditional_yogas = { label: EVIDENCE_CARD_LABELS.traditionalYogas, names };
}
if (rule.transits) {
const transits = copy(timing.transits);
if (transits !== undefined) section.transits = transits;
@@ -533,6 +533,8 @@ const nativeGrahaDrishtiKeys = new Set([
]);
// BUG-1157: each yoga's domain tag (the Raman packet that governs it) and the sieve's hit.
const nativeYogaDomainKeys = new Set(["status", "reason", "source", "rows", "name", "hit", "domain"]);
// BUG-1175: closed-list classical yogas found formed; the card writes the label itself.
const nativeTraditionalYogaKeys = new Set(["status", "reason", "source", "names"]);
const nativeSlowTransitKeys = new Set([
"status", "source", "referencedate", "window", "start", "end", "current", "sign", "degreeinsign",
"retrograde", "natalhouse", "sav", "bav", "keydates", "kind", "date", "body", "fromsign", "tosign",
@@ -593,6 +595,7 @@ function projectNatalFoundation(context: ReturnType<typeof toAgentConsultationCo
["day_night", nativeDayNightKeys],
["graha_drishti", nativeGrahaDrishtiKeys],
["yoga_domains", nativeYogaDomainKeys],
["traditional_yogas", nativeTraditionalYogaKeys],
]));
return Object.keys(output).length ? output : undefined;
}