feat(narayana): label the product Narayana as the legacy algorithm (T4, BUG-1215)
Round-4 ruling Q4 / product decision A: scoring and the consultation and report time axes keep the old Narayana (from the Lagna, always zodiacal) until the Rath trial is decided; every user-visible Narayana now says so. narayana_legacy_label adds profile=legacy_reference_only and the note (zh/en) at the consumers (consultation modules, full reading Step 4.13, CLI), not in the frozen narayana_dasha.py (ERR-110/115). The card's timing.narayana carries 'algorithm'; the projection allowlist passes algorithm_note; the reference report's Narayana section and the dasha-family table show '算法:…' (English twin in the phrase table). The unused narayana-dasha --variant-profile flag, the one place offering a Chara Dasha author's profile for Narayana, is removed. 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
7cb332bce6
commit
9eb22f6363
@@ -615,6 +615,9 @@ function baseTiming(timing: Record<string, unknown>, gaps: string[]) {
|
||||
}
|
||||
const remaining = copy(currentNarayana.remaining_years);
|
||||
if (remaining !== undefined) narayana.remaining_years = remaining;
|
||||
// BUG-1215: say which Narayana this is (legacy: from the Lagna, always zodiacal).
|
||||
const algorithmNote = record(timing.narayana_dasha).algorithm_note;
|
||||
if (narayana.md && typeof algorithmNote === "string" && algorithmNote) narayana.algorithm = algorithmNote;
|
||||
if (!narayana.md) gaps.push("base.narayana.md");
|
||||
return { vimshottari, narayana };
|
||||
}
|
||||
|
||||
@@ -346,6 +346,8 @@ const timingKeys = new Set([
|
||||
// Without these keys the projection kept `current_dasha` as an empty object
|
||||
// and dropped the pratyantar dates, although the engine had both.
|
||||
"md", "ad", "pd", "years", "startage", "endage", "remainingyears", "pratyantardashatimeline",
|
||||
// BUG-1215: the running Narayana is the legacy algorithm; its label travels with it.
|
||||
"algorithmnote",
|
||||
]);
|
||||
|
||||
const validationKeys = new Set([
|
||||
|
||||
@@ -56,6 +56,8 @@ def cmd_narayana_dasha(args, chart_data):
|
||||
current_age=current_age,
|
||||
birth_year=birth_year,
|
||||
)
|
||||
from narayana_legacy_label import label_legacy_narayana # BUG-1215
|
||||
label_legacy_narayana(result)
|
||||
|
||||
# 格式化输出
|
||||
lines = [
|
||||
@@ -65,6 +67,7 @@ def cmd_narayana_dasha(args, chart_data):
|
||||
"",
|
||||
f"Lagna: {result['lagna_sign']}({result['lagna_sign_idx']})",
|
||||
f"总周期: {result['total_cycle_years']} 年",
|
||||
f"算法: {result['algorithm_note']}",
|
||||
"",
|
||||
"── 大运序列 ──",
|
||||
]
|
||||
|
||||
@@ -1622,7 +1622,8 @@ def _attach_local_consultation_layers(handler, chart: dict, birth_payload: dict,
|
||||
birth_year=int(birth_payload.get('year', 0) or 0),
|
||||
)
|
||||
if isinstance(narayana_result, dict):
|
||||
modules['narayana_dasha'] = narayana_result
|
||||
# BUG-1215: legacy algorithm, labelled until the Rath profile replaces it.
|
||||
modules['narayana_dasha'] = _load_local_module('narayana_legacy_label').label_legacy_narayana(narayana_result)
|
||||
except Exception as exc:
|
||||
diagnostics.append({'layer': 'narayana_dasha', 'status': 'unavailable', 'reason': exc.__class__.__name__})
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ except ModuleNotFoundError: # pragma: no cover - minimal environments
|
||||
lines.extend(" | ".join(str(item) for item in row) for row in rows)
|
||||
return "\n".join(lines)
|
||||
from life_stage_hook import generate_life_stage_hooks
|
||||
from narayana_legacy_label import LEGACY_NOTE_ZH as LEGACY_NARAYANA_NOTE_ZH # BUG-1215
|
||||
from capability_evidence_pool import build_capability_evidence_pool_summary
|
||||
from guided_topic_discovery import build_guided_topics
|
||||
|
||||
@@ -9447,6 +9448,8 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
f"参考对齐状态:`{_md_cell(narayana_topic.get('reference_alignment_status') or 'not_closed')}`。"
|
||||
'本地年龄轴与 PL9 日期边界/起运序列的字段级 parity 尚未闭环,因此不替代主时间判断。',
|
||||
'',
|
||||
f'- 算法:{LEGACY_NARAYANA_NOTE_ZH}', # BUG-1215
|
||||
'',
|
||||
'#### Patyayini 年内节奏支持',
|
||||
'',
|
||||
f"Patyayini 当前状态为 `{_md_cell(patyayini_topic.get('status') or 'blocked')}`;"
|
||||
@@ -16111,7 +16114,8 @@ def cmd_full_reading(args):
|
||||
current_age=current_age,
|
||||
birth_year=args.year,
|
||||
)
|
||||
report['modules']['narayana_dasha'] = narayana_result
|
||||
from narayana_legacy_label import label_legacy_narayana # BUG-1215
|
||||
report['modules']['narayana_dasha'] = label_legacy_narayana(narayana_result)
|
||||
except Exception as e:
|
||||
report['errors'].append(f"narayana-dasha: {e}")
|
||||
|
||||
@@ -18353,9 +18357,8 @@ def main():
|
||||
p = sub.add_parser('narayana-dasha', help='Narayana Dasha(Rishi Dasha)星座大运分析')
|
||||
_add_chart_args(p)
|
||||
p.add_argument('--age', type=float, default=None, help='当前年龄(用于定位大运位置)')
|
||||
p.add_argument('--variant-profile', default='legacy_local',
|
||||
choices=['legacy_local', 'kn_rao', 'goel_lagna_start', 'goel_gendered', 'goel_shastri_method2', 'pyjhora_blackbox'],
|
||||
help='Narayana/Chara式星座大运profile:legacy保持旧输出;kn_rao/goel映射到显式本地规则;pyjhora_blackbox只输出PyJHora黑盒当前路径')
|
||||
# BUG-1215: the unused --variant-profile flag is removed (it offered a
|
||||
# Chara Dasha author's name as a profile of this command).
|
||||
|
||||
# 21.7 muhurta (v6.0.21新增)
|
||||
p = sub.add_parser('muhurta', help='Muhurta 择时分析(Panchanga 五要素)')
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Honest label for the product Narayana Dasha (BUG-1215, round-4 ruling Q4).
|
||||
|
||||
`narayana_dasha.narayana_dasha_full_report` (consultation, report, CLI) runs
|
||||
`calc_narayana_mahadasha`: always from the Lagna, always zodiacal. The
|
||||
astrologer's ruling is Sanjay Rath's Narayana Dasha (stronger of Lagna / 7th,
|
||||
order by the start sign); until the product switches (product decision after
|
||||
the trial), every user-visible Narayana carries this label and the data carry
|
||||
`profile: legacy_reference_only`. Kept outside `narayana_dasha.py`, which is in
|
||||
the frozen rectification scoring identity (ERR-110 / ERR-115).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
LEGACY_PROFILE = "legacy_reference_only"
|
||||
LEGACY_NOTE_ZH = "旧算法(从上升起、一律顺行),待替换为 Sanjay Rath 版"
|
||||
LEGACY_NOTE_EN = "Legacy algorithm (starts from the Lagna, always zodiacal); to be replaced by Sanjay Rath's version"
|
||||
|
||||
|
||||
def label_legacy_narayana(result: Any) -> Any:
|
||||
"""Add the legacy profile and note to a `narayana_dasha_full_report` result."""
|
||||
if isinstance(result, dict):
|
||||
result["profile"] = LEGACY_PROFILE
|
||||
result["algorithm_note"] = LEGACY_NOTE_ZH
|
||||
result["algorithm_note_en"] = LEGACY_NOTE_EN
|
||||
return result
|
||||
@@ -147,6 +147,9 @@ _PHRASES: dict[str, str] = {
|
||||
"| 要素总数 |": "| Total elements |",
|
||||
# ── annual labels and values ──
|
||||
"Muntha 所在星座的守护星(当前本地口径)": "Ruler of the Muntha sign (current local method)",
|
||||
# BUG-1215: narayana_legacy_label.LEGACY_NOTE_ZH
|
||||
"旧算法(从上升起、一律顺行),待替换为 Sanjay Rath 版": "Legacy algorithm (starts from the Lagna, always zodiacal); to be replaced by Sanjay Rath's version",
|
||||
"- 算法:": "- Algorithm: ",
|
||||
# BUG-1214: tajika_year_lord.selection_basis_text (zh) → its en twin.
|
||||
"年盘不全,暂用 Muntha 主星": "Annual chart incomplete; Muntha sign lord used for now",
|
||||
"(Panchavargiya 为本地近似计算)": " (Panchavargiya is a local approximation)",
|
||||
|
||||
@@ -23,6 +23,10 @@ for the public reader rather than exposing empty or machine-key rows.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
try: # scripts/ on sys.path
|
||||
from narayana_legacy_label import LEGACY_NOTE_ZH
|
||||
except ImportError: # pragma: no cover - package import
|
||||
from scripts.narayana_legacy_label import LEGACY_NOTE_ZH
|
||||
import re
|
||||
|
||||
SIGNS: list[str] = []
|
||||
@@ -1652,9 +1656,11 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
periods = _list(family.get('periods'))
|
||||
if not periods:
|
||||
continue
|
||||
lines.extend([f'### p{page} {title_text}', ''])
|
||||
if 'Narayana' in title_text:
|
||||
# BUG-1215: the product Narayana is the legacy algorithm.
|
||||
lines.extend([f'- 算法:{LEGACY_NOTE_ZH}', ''])
|
||||
lines.extend([
|
||||
f'### p{page} {title_text}',
|
||||
'',
|
||||
'| 序号 | 主星/星座 | 起始 | 结束 | 年数 |',
|
||||
'|------|-----------|------|------|------|',
|
||||
])
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""The product Narayana Dasha is labelled as the legacy algorithm (BUG-1215, T4).
|
||||
|
||||
Round-4 ruling Q4: until the Sanjay Rath profile replaces it (product decision
|
||||
after the trial), every user-visible Narayana says it is the old algorithm
|
||||
(from the Lagna, always zodiacal) and the data carry
|
||||
`profile: legacy_reference_only`. K.N. Rao is a Chara Dasha name only.
|
||||
Fictional chart.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.narayana_legacy_label import LEGACY_NOTE_EN, LEGACY_NOTE_ZH, LEGACY_PROFILE
|
||||
from scripts.pl9_reader_export import _pl9_export_markdown_for_edition
|
||||
from tests.test_report_english_edition import CASES, _packet
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_report_module_and_reference_edition_carry_the_label() -> None:
|
||||
packet = _packet(CASES["day"])
|
||||
zh = _pl9_export_markdown_for_edition(copy.deepcopy(packet), "reference")
|
||||
assert f"- 算法:{LEGACY_NOTE_ZH}" in zh
|
||||
from scripts.jyotish_engine import cmd_full_reading
|
||||
|
||||
reading = cmd_full_reading(type("Args", (), dict(CASES["day"]))())
|
||||
module = reading["modules"]["narayana_dasha"]
|
||||
assert (module["profile"], module["algorithm_note"], module["algorithm_note_en"]) == (LEGACY_PROFILE, LEGACY_NOTE_ZH, LEGACY_NOTE_EN)
|
||||
|
||||
|
||||
def test_english_reader_translates_the_label() -> None:
|
||||
from scripts.pl9_reader_english_terms import PHRASES_EN
|
||||
|
||||
text = f"- 算法:{LEGACY_NOTE_ZH}"
|
||||
for source, target in PHRASES_EN:
|
||||
text = text.replace(source, target)
|
||||
assert text == f"- Algorithm: {LEGACY_NOTE_EN}"
|
||||
|
||||
|
||||
def test_consultation_modules_carry_the_label() -> None:
|
||||
golden = json.loads((ROOT / "frontend/tests/fixtures/consult-evidence-card-golden.json").read_text(encoding="utf-8"))
|
||||
for chart in golden["charts"]:
|
||||
for route in ("workflow", "annual_workflow", "timing_workflow"):
|
||||
module = chart[route]["chart"]["modules"]["narayana_dasha"]
|
||||
assert (module["profile"], module["algorithm_note"]) == (LEGACY_PROFILE, LEGACY_NOTE_ZH)
|
||||
card = (ROOT / "frontend/src/lib/consultation-evidence-card.ts").read_text(encoding="utf-8")
|
||||
assert "narayana.algorithm = algorithmNote" in card
|
||||
workflow = (ROOT / "frontend/src/mastra/consultation-workflow.ts").read_text(encoding="utf-8")
|
||||
assert '"algorithmnote"' in workflow
|
||||
|
||||
|
||||
def test_kn_rao_never_names_narayana_on_user_visible_surfaces() -> None:
|
||||
pattern = re.compile(r"(K\.?\s?N\.?\s?Rao|kn_rao)[^\n]{0,40}Narayana|Narayana[^\n]{0,40}(K\.?\s?N\.?\s?Rao|kn_rao)", re.IGNORECASE)
|
||||
surfaces = [*(ROOT / "frontend/src").rglob("*.ts"), *(ROOT / "frontend/src").rglob("*.tsx"),
|
||||
ROOT / "scripts/pl9_reader_export.py", ROOT / "scripts/pl9_reader_english_terms.py",
|
||||
ROOT / "scripts/consultation_native_layers.py", ROOT / "scripts/jyotish_engine.py",
|
||||
ROOT / "scripts/narayana_dasha.py", ROOT / "scripts/cmd_narayana_dasha.py"]
|
||||
hits = [path.name for path in surfaces if pattern.search(path.read_text(encoding="utf-8"))]
|
||||
assert hits == []
|
||||
Reference in New Issue
Block a user