feat(narayana): show the Sanjay Rath version beside the legacy one (T4, BUG-1218)
Display only; scoring and timing conclusions keep the legacy algorithm. narayana_legacy_label attaches a Rath view (seed, deciding level, direction, 24 MDs, running MD/AD) under modules.narayana_dasha.rath for the consultation modules, full reading Step 4.13 and the CLI. Reference report: one summary line under the legacy line plus Rath MD / running-MD AD tables (English terms added). Evidence card: narayana_rath (label, seed sign, level, running MD) on timing / annual cards only — other cards sit at 11,979 of 12,000 characters. Goldens re-captured from the real engine. 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:
co-authored by
Claude Opus 5.5
parent
3264831a23
commit
bf8d704708
@@ -331,6 +331,8 @@ export type EvidenceCard = {
|
||||
timing: {
|
||||
vimshottari: JsonRecord;
|
||||
narayana: JsonRecord;
|
||||
/** BUG-1218: Sanjay Rath Narayana beside the legacy one; display only (scoring and timing stay legacy). */
|
||||
narayana_rath?: JsonRecord;
|
||||
};
|
||||
};
|
||||
domain_sections: Partial<Record<ConsultationDomain, EvidenceCardDomainSection>>;
|
||||
@@ -582,7 +584,16 @@ function baseNatal(natal: Record<string, unknown>, gaps: string[]) {
|
||||
return { ascendant: ascendant ?? null, houses, planets, functional_benefic_malefic: functional, d9 };
|
||||
}
|
||||
|
||||
function baseTiming(timing: Record<string, unknown>, gaps: string[]) {
|
||||
/**
|
||||
* BUG-1218 (T4): domains whose card carries the Sanjay Rath Narayana beside
|
||||
* the legacy one. Other single-domain cards sit at up to 11,979 of the 12,000
|
||||
* characters (Jobs / marriage golden) and the Rath MD adds about 174, so it
|
||||
* rides only on the timing-led cards (own 12,000 / 18,000 budget); the report
|
||||
* shows the full Rath table for every chart.
|
||||
*/
|
||||
const RATH_NARAYANA_CARD_DOMAINS: ReadonlySet<ConsultationDomain> = new Set(["timing", "annual"]);
|
||||
|
||||
function baseTiming(timing: Record<string, unknown>, gaps: string[], withRath = false) {
|
||||
const sub = record(timing.dasha_sub_periods);
|
||||
const current = record(sub.current);
|
||||
const pratyantar = record(sub.pratyantar_dasha_timeline);
|
||||
@@ -623,6 +634,19 @@ function baseTiming(timing: Record<string, unknown>, gaps: string[]) {
|
||||
const algorithmLabel = copy(record(timing.narayana_dasha).algorithm_label);
|
||||
if (narayana.md && typeof algorithmLabel === "string" && algorithmLabel) narayana.algorithm = algorithmLabel;
|
||||
if (!narayana.md) gaps.push("base.narayana.md");
|
||||
// BUG-1218 (T4): the Sanjay Rath version rides beside the legacy one — its
|
||||
// label, start sign, the level that decided it and the running MD only
|
||||
// (sub-periods stay in the report; 12,000-character card budget).
|
||||
const rath = record(record(timing.narayana_dasha).rath);
|
||||
const rathMd = pickKeys(record(rath.current_dasha).md, NARAYANA_PERIOD_KEYS);
|
||||
if (withRath && nonEmpty(rathMd) && typeof rath.algorithm_label === "string") {
|
||||
const narayanaRath: JsonRecord = { algorithm: rath.algorithm_label, md: rathMd };
|
||||
for (const [from, to] of [["seed_sign", "seed_sign"], ["strength_level_decided", "seed_level"]] as const) {
|
||||
const value = copy(rath[from]);
|
||||
if (value !== undefined && value !== null) narayanaRath[to] = value;
|
||||
}
|
||||
return { vimshottari, narayana, narayana_rath: narayanaRath };
|
||||
}
|
||||
return { vimshottari, narayana };
|
||||
}
|
||||
|
||||
@@ -997,9 +1021,10 @@ export function buildEvidenceCard(
|
||||
if (sources.length === 0) throw new Error("evidence_card_needs_a_domain");
|
||||
const gaps: string[] = [];
|
||||
const first = sources[0]!;
|
||||
const withRath = sources.some((item) => RATH_NARAYANA_CARD_DOMAINS.has(item.domain));
|
||||
const base = {
|
||||
natal: baseNatal(claimEvidence(first.packet, "natal_foundation"), gaps),
|
||||
timing: baseTiming(claimEvidence(first.packet, "timing"), gaps),
|
||||
timing: baseTiming(claimEvidence(first.packet, "timing"), gaps, withRath),
|
||||
};
|
||||
const domainSections: EvidenceCard["domain_sections"] = {};
|
||||
const domains: ConsultationDomain[] = [];
|
||||
@@ -1024,7 +1049,7 @@ export function buildEvidenceCard(
|
||||
const ownGaps: string[] = [];
|
||||
const candidate = {
|
||||
natal: baseNatal(claimEvidence(source.packet, "natal_foundation"), ownGaps),
|
||||
timing: baseTiming(claimEvidence(source.packet, "timing"), ownGaps),
|
||||
timing: baseTiming(claimEvidence(source.packet, "timing"), ownGaps, withRath),
|
||||
};
|
||||
if (JSON.stringify(candidate) !== JSON.stringify(base)) {
|
||||
own = candidate;
|
||||
|
||||
@@ -348,6 +348,9 @@ const timingKeys = new Set([
|
||||
"md", "ad", "pd", "years", "startage", "endage", "remainingyears", "pratyantardashatimeline",
|
||||
// BUG-1215: the running Narayana is the legacy algorithm; its label travels with it.
|
||||
"algorithmlabel",
|
||||
// BUG-1218: the Sanjay Rath Narayana beside it (display only): label, start
|
||||
// sign, deciding level and the running MD.
|
||||
"rath", "seedsign", "strengthleveldecided",
|
||||
]);
|
||||
|
||||
const validationKeys = new Set([
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// TASK-astrologer-rulings-batch3-20261003 T4 (BUG-1218): the Sanjay Rath
|
||||
// Narayana rides beside the legacy one on the timing-led cards; the legacy
|
||||
// Narayana keeps its 「旧算法」 label and stays what scoring / timing use.
|
||||
//
|
||||
// Fixture: real engine consultation responses for three public AA charts
|
||||
// (scripts/research/capture_consult_evidence_card_golden.py, AGENTS §7.4);
|
||||
// expected values are read straight from the raw engine payload.
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
import { buildEvidenceCard } from "../src/lib/consultation-evidence-card.ts";
|
||||
import type { ConsultationDomain } from "../src/lib/consultation-domain-registry.ts";
|
||||
import { createConsultationPlan } from "../src/lib/consultation-plan.ts";
|
||||
import {
|
||||
consultationWorkflowResponseSchema,
|
||||
toAgentConsultationContext,
|
||||
toModelOutput,
|
||||
} from "../src/mastra/consultation-workflow.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; annual_workflow: Json; timing_workflow: Json }> };
|
||||
|
||||
function card(workflow: Json, domain: ConsultationDomain) {
|
||||
const context = toAgentConsultationContext(consultationWorkflowResponseSchema.parse(structuredClone(workflow)));
|
||||
const packet = toModelOutput(context, createConsultationPlan({ userIntent: "q", theme: domain }), domain);
|
||||
return buildEvidenceCard([{ domain, packet, context }]);
|
||||
}
|
||||
|
||||
function engineRath(workflow: Json): Json {
|
||||
return ((((workflow.chart as Json).modules as Json).narayana_dasha as Json).rath ?? {}) as Json;
|
||||
}
|
||||
|
||||
test("timing and annual cards carry the Rath running MD beside the legacy Narayana", () => {
|
||||
for (const chart of golden.charts) {
|
||||
for (const [workflow, domain] of [[chart.timing_workflow, "timing"], [chart.annual_workflow, "annual"]] as const) {
|
||||
const rath = engineRath(workflow);
|
||||
const md = (rath.current_dasha as Json).md as Json;
|
||||
const timing = card(workflow, domain).base.timing as Json;
|
||||
const shown = timing.narayana_rath as Json;
|
||||
assert.ok(shown, `${chart.id}/${domain} narayana_rath`);
|
||||
assert.equal(shown.algorithm, rath.algorithm_label);
|
||||
assert.equal(shown.algorithm, "Sanjay Rath 版(并列参考,未进打分与时间轴结论)");
|
||||
assert.equal(shown.seed_sign, rath.seed_sign);
|
||||
assert.equal(shown.seed_level, rath.strength_level_decided);
|
||||
assert.deepEqual(shown.md, { sign: md.sign, lord: md.lord, years: md.years, start_age: md.start_age, end_age: md.end_age });
|
||||
// The legacy Narayana is unchanged and still labelled.
|
||||
assert.equal((timing.narayana as Json).algorithm, "旧算法(从上升起、一律顺行)");
|
||||
assert.equal(rath.used_for_scoring, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("other domains leave the Rath MD to the report (12,000-character card budget)", () => {
|
||||
for (const chart of golden.charts) {
|
||||
for (const domain of ["marriage", "career", "wealth", "health"] as const) {
|
||||
const timing = card(chart.workflow, domain).base.timing as Json;
|
||||
assert.equal(timing.narayana_rath, undefined, `${chart.id}/${domain}`);
|
||||
assert.equal(typeof ((timing.narayana as Json).md as Json).sign, "string");
|
||||
}
|
||||
}
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user