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
@@ -56,8 +56,9 @@ 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)
|
||||
from narayana_legacy_label import label_legacy_narayana # BUG-1215; Rath view BUG-1218
|
||||
label_legacy_narayana(result, lagna_sign_idx=asc_idx, planet_lons=planet_lons,
|
||||
current_age=current_age, birth_year=birth_year)
|
||||
|
||||
# 格式化输出
|
||||
lines = [
|
||||
@@ -98,4 +99,20 @@ def cmd_narayana_dasha(args, chart_data):
|
||||
for line in result.get('interpretation', []):
|
||||
lines.append(f" {line}")
|
||||
|
||||
rath = result.get('rath') or {}
|
||||
if rath:
|
||||
lines.append("")
|
||||
lines.append(f"── {rath.get('algorithm_label')} ──")
|
||||
if rath.get('seed_sign'):
|
||||
lines.append(f" 起运宫: {rath['seed_sign']}(第 {rath.get('strength_level_decided')} 级:{rath.get('strength_level_text')});{rath.get('direction_text')}")
|
||||
if rath.get('method_note'):
|
||||
lines.append(f" {rath['method_note']}")
|
||||
for p in rath.get('mahadasha', [])[:12]:
|
||||
lines.append(f" {p['sign']:>12s} ({p['lord']:>7s}): {p['years']:2d}年 [{p['start_age']:5.1f}→{p['end_age']:5.1f}]")
|
||||
rmd = (rath.get('current_dasha') or {}).get('md')
|
||||
if rmd:
|
||||
lines.append(f" 当前大运: {rmd['sign']}({rmd['lord']}){rmd['years']}年")
|
||||
else:
|
||||
lines.append(f" 起运宫未定({rath.get('reason') or rath.get('status')})")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -1623,7 +1623,7 @@ def _attach_local_consultation_layers(handler, chart: dict, birth_payload: dict,
|
||||
)
|
||||
if isinstance(narayana_result, dict):
|
||||
# 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)
|
||||
modules['narayana_dasha'] = _load_local_module('narayana_legacy_label').label_legacy_narayana(narayana_result, lagna_sign_idx=asc_sign_idx, planet_lons=planet_lons, current_age=_consultation_current_age(birth_payload, body), birth_year=int(birth_payload.get('year', 0) or 0))
|
||||
except Exception as exc:
|
||||
diagnostics.append({'layer': 'narayana_dasha', 'status': 'unavailable', 'reason': exc.__class__.__name__})
|
||||
|
||||
|
||||
@@ -65,7 +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 narayana_legacy_label import LEGACY_NOTE_ZH as LEGACY_NARAYANA_NOTE_ZH, rath_report_lines_zh # BUG-1215 / BUG-1218
|
||||
from capability_evidence_pool import build_capability_evidence_pool_summary
|
||||
from guided_topic_discovery import build_guided_topics
|
||||
|
||||
@@ -9449,6 +9449,7 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
'本地年龄轴与 PL9 日期边界/起运序列的字段级 parity 尚未闭环,因此不替代主时间判断。',
|
||||
'',
|
||||
f'- 算法:{LEGACY_NARAYANA_NOTE_ZH}', # BUG-1215
|
||||
*rath_report_lines_zh(((worksheets.get('timing_and_predictive_systems') or {}).get('narayana_dasha') or {}).get('rath')), # BUG-1218
|
||||
'',
|
||||
'#### Patyayini 年内节奏支持',
|
||||
'',
|
||||
@@ -16143,7 +16144,8 @@ def cmd_full_reading(args):
|
||||
birth_year=args.year,
|
||||
)
|
||||
from narayana_legacy_label import label_legacy_narayana # BUG-1215
|
||||
report['modules']['narayana_dasha'] = label_legacy_narayana(narayana_result)
|
||||
report['modules']['narayana_dasha'] = label_legacy_narayana( # + Rath view, display only (BUG-1218)
|
||||
narayana_result, lagna_sign_idx=asc_idx, planet_lons=planet_lons, current_age=current_age, birth_year=args.year)
|
||||
except Exception as e:
|
||||
report['errors'].append(f"narayana-dasha: {e}")
|
||||
|
||||
|
||||
@@ -8,11 +8,16 @@ 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).
|
||||
|
||||
BUG-1218 (T4, product owner 2026-10-03): when the caller hands the chart, the
|
||||
Sanjay Rath profile (`narayana_rath`, round-5 rules) rides along under `rath`
|
||||
for display only. Scoring and timeline conclusions keep reading the legacy
|
||||
fields; nothing here changes them.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
LEGACY_PROFILE = "legacy_reference_only"
|
||||
LEGACY_NOTE_ZH = "旧算法(从上升起、一律顺行),待替换为 Sanjay Rath 版"
|
||||
@@ -21,11 +26,159 @@ LEGACY_NOTE_EN = "Legacy algorithm (starts from the Lagna, always zodiacal); to
|
||||
LEGACY_LABEL_ZH = "旧算法(从上升起、一律顺行)"
|
||||
|
||||
|
||||
def label_legacy_narayana(result: Any) -> Any:
|
||||
"""Add the legacy profile and note to a `narayana_dasha_full_report` result."""
|
||||
RATH_LABEL_ZH = "Sanjay Rath 版(并列参考,未进打分与时间轴结论)"
|
||||
RATH_LABEL_EN = "Sanjay Rath version (shown alongside for reference; not used in scoring or timing conclusions)"
|
||||
STRENGTH_LEVEL_ZH = {
|
||||
1: "占据星数", 2: "木星 / 水星 / 宫主三角色", 3: "占据星尊贵", 4: "星座性质",
|
||||
5: "宫主星座内度数", 6: "宫主落奇偶相反星座", 7: "大运年数",
|
||||
}
|
||||
STRENGTH_LEVEL_EN = {
|
||||
1: "occupants", 2: "Jupiter / Mercury / lord roles", 3: "occupant dignity", 4: "modality",
|
||||
5: "lord's degree in sign", 6: "lord in a sign of opposite parity", 7: "dasha years",
|
||||
}
|
||||
DIRECTION_ZH = {
|
||||
"table10_general": "按 Table 10", "table11_saturn_zodiacal": "土星在起运宫:顺行(Table 11)",
|
||||
"table12_ketu_reversed": "计都在起运宫:反向(Table 12)",
|
||||
}
|
||||
DIRECTION_EN = {
|
||||
"table10_general": "Table 10 order", "table11_saturn_zodiacal": "Saturn in the start sign: zodiacal (Table 11)",
|
||||
"table12_ketu_reversed": "Ketu in the start sign: reversed (Table 12)",
|
||||
}
|
||||
|
||||
|
||||
def _period(row: Dict[str, Any], birth_year: Optional[int]) -> Dict[str, Any]:
|
||||
out = {key: row.get(key) for key in ("sign", "lord", "years")}
|
||||
out["start_age"] = round(float(row["start_age"]), 2)
|
||||
out["end_age"] = round(float(row["end_age"]), 2)
|
||||
if isinstance(out["years"], float):
|
||||
out["years"] = round(out["years"], 2)
|
||||
if birth_year:
|
||||
out["start_year"] = round(birth_year + float(row["start_age"]), 2)
|
||||
out["end_year"] = round(birth_year + float(row["end_age"]), 2)
|
||||
return out
|
||||
|
||||
|
||||
def _running(rows: List[Dict[str, Any]], age: Optional[float]) -> Optional[Dict[str, Any]]:
|
||||
if age is None:
|
||||
return None
|
||||
return next((row for row in rows if float(row["start_age"]) <= age < float(row["end_age"])), None)
|
||||
|
||||
|
||||
def rath_narayana_view(lagna_sign_idx: int, planet_lons: Dict[str, float], *,
|
||||
current_age: Optional[float] = None, birth_year: Optional[int] = None) -> Dict[str, Any]:
|
||||
"""Display-only Sanjay Rath Narayana: seed, basis, MDs, running MD / AD."""
|
||||
try:
|
||||
from narayana_rath import antardasha, calc_narayana_rath
|
||||
except ImportError: # pragma: no cover - package import
|
||||
from scripts.narayana_rath import antardasha, calc_narayana_rath
|
||||
result = calc_narayana_rath(int(lagna_sign_idx) % 12, planet_lons, cycle_count=2)
|
||||
view: Dict[str, Any] = {
|
||||
"profile": result["profile"],
|
||||
"algorithm_label": RATH_LABEL_ZH,
|
||||
"algorithm_label_en": RATH_LABEL_EN,
|
||||
"status": result["status"],
|
||||
"used_for_scoring": False,
|
||||
"source": result["source"],
|
||||
"dual_lord_rule": result["dual_lord_rule"],
|
||||
"antardasha_start_rule": result["antardasha_start_rule"],
|
||||
}
|
||||
if not result.get("mahadasha"):
|
||||
view.update({"reason": result.get("reason"),
|
||||
"alternative_seed_signs": sorted((result.get("alternatives") or {}).keys())})
|
||||
return view
|
||||
level = result.get("strength_level_decided")
|
||||
view.update({
|
||||
"seed_sign": result["seed_sign"],
|
||||
"seed_basis": result["seed_basis"],
|
||||
"strength_level_decided": level,
|
||||
"strength_level_text": STRENGTH_LEVEL_ZH.get(level),
|
||||
"strength_level_text_en": STRENGTH_LEVEL_EN.get(level),
|
||||
"direction": result["direction"],
|
||||
"direction_text": DIRECTION_ZH.get(result["direction"]),
|
||||
"direction_text_en": DIRECTION_EN.get(result["direction"]),
|
||||
"exception_applied": result.get("exception_applied"),
|
||||
"method_note": result.get("method_note"),
|
||||
"method_note_en": result.get("method_note_en"),
|
||||
"mahadasha": [_period(row, birth_year) for row in result["mahadasha"]],
|
||||
})
|
||||
md = _running(result["mahadasha"], current_age)
|
||||
if md is not None:
|
||||
ad = antardasha(md, planet_lons)
|
||||
current: Dict[str, Any] = {"md": _period(md, birth_year)}
|
||||
rows = ad.get("rows") or []
|
||||
running_ad = _running(rows, current_age)
|
||||
if running_ad is not None:
|
||||
current["ad"] = _period(running_ad, birth_year)
|
||||
view["current_dasha"] = current
|
||||
view["current_md_antardasha"] = [_period(row, birth_year) for row in rows]
|
||||
view["current_md_antardasha_rule"] = ad.get("antardasha_start_rule")
|
||||
return view
|
||||
|
||||
|
||||
def label_legacy_narayana(result: Any, *, lagna_sign_idx: Optional[int] = None,
|
||||
planet_lons: Optional[Dict[str, float]] = None,
|
||||
current_age: Optional[float] = None,
|
||||
birth_year: Optional[int] = None) -> Any:
|
||||
"""Add the legacy profile and note to a `narayana_dasha_full_report` result.
|
||||
|
||||
With the chart (`lagna_sign_idx`, `planet_lons`) the Rath view is attached
|
||||
under `rath` (display only, BUG-1218); a failure there never touches the
|
||||
legacy result.
|
||||
"""
|
||||
if isinstance(result, dict):
|
||||
result["profile"] = LEGACY_PROFILE
|
||||
result["algorithm_note"] = LEGACY_NOTE_ZH
|
||||
result["algorithm_note_en"] = LEGACY_NOTE_EN
|
||||
result["algorithm_label"] = LEGACY_LABEL_ZH
|
||||
if lagna_sign_idx is not None and planet_lons:
|
||||
try:
|
||||
result["rath"] = rath_narayana_view(lagna_sign_idx, planet_lons,
|
||||
current_age=current_age, birth_year=birth_year)
|
||||
except Exception as exc: # display layer only
|
||||
result["rath"] = {"status": "blocked", "reason": exc.__class__.__name__,
|
||||
"algorithm_label": RATH_LABEL_ZH, "algorithm_label_en": RATH_LABEL_EN}
|
||||
return result
|
||||
|
||||
|
||||
def rath_report_lines_zh(rath: Any) -> List[str]:
|
||||
"""Reference-report block for the Sanjay Rath Narayana beside the legacy
|
||||
line (BUG-1218, display only): summary, 24 MD rows, running MD's ADs."""
|
||||
summary = rath_summary_line_zh(rath)
|
||||
if summary is None:
|
||||
return []
|
||||
lines = [summary]
|
||||
if not rath.get("mahadasha"):
|
||||
return lines
|
||||
if rath.get("direction_text"):
|
||||
lines.append(f"- 顺序:{rath['direction_text']}" + (f";{rath['method_note']}" if rath.get("method_note") else ""))
|
||||
lines.extend(["", "| Rath MD | 星座 | 宫主 | 起始年龄 | 结束年龄 | 年数 |", "|---------|------|------|----------|----------|------|"])
|
||||
for index, row in enumerate(rath["mahadasha"], start=1):
|
||||
lines.append(f"| {index} | {row.get('sign')} | {row.get('lord')} | {row.get('start_age')} | {row.get('end_age')} | {row.get('years')} |")
|
||||
md = (rath.get("current_dasha") or {}).get("md")
|
||||
rule = rath.get("current_md_antardasha_rule") or {}
|
||||
if md and rath.get("current_md_antardasha"):
|
||||
compared = rule.get("compared") or ["-", "-"]
|
||||
lines.extend([
|
||||
"",
|
||||
f"- 当前大运 {md.get('sign')} 的子运:比较 {compared[0]} 与 {compared[-1]},较强 {rule.get('stronger_sign')}"
|
||||
f"(第 {rule.get('strength_level_decided')} 级),从宫主 {rule.get('lord')} 所在 {rule.get('start_sign')} 起,12 等分",
|
||||
"",
|
||||
"| Rath AD | 星座 | 起始年龄 | 结束年龄 |",
|
||||
"|---------|------|----------|----------|",
|
||||
])
|
||||
for index, row in enumerate(rath["current_md_antardasha"], start=1):
|
||||
lines.append(f"| {index} | {row.get('sign')} | {row.get('start_age')} | {row.get('end_age')} |")
|
||||
return lines
|
||||
|
||||
|
||||
def rath_summary_line_zh(rath: Any) -> Optional[str]:
|
||||
"""One report line naming the Rath start sign and running MD (BUG-1218)."""
|
||||
if not isinstance(rath, dict) or not rath:
|
||||
return None
|
||||
if not rath.get("seed_sign"):
|
||||
return f"- 并列:{RATH_LABEL_ZH}:起运宫未定,{rath.get('reason') or rath.get('status')}"
|
||||
md = (rath.get("current_dasha") or {}).get("md") or {}
|
||||
running = f";当前大运 {md.get('sign')},宫主 {md.get('lord')}" if md else ""
|
||||
return (f"- 并列:{RATH_LABEL_ZH}:起运宫 {rath.get('seed_sign')}"
|
||||
f"(第 {rath.get('strength_level_decided')} 级:{rath.get('strength_level_text')}){running}")
|
||||
|
||||
|
||||
@@ -150,6 +150,31 @@ _PHRASES: dict[str, str] = {
|
||||
# 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-1218: narayana_legacy_label rath_report_lines_zh
|
||||
"Sanjay Rath 版(并列参考,未进打分与时间轴结论):": "Sanjay Rath version (shown alongside for reference; not used in scoring or timing conclusions): ",
|
||||
"Sanjay Rath 版(并列参考,未进打分与时间轴结论)": "Sanjay Rath version (shown alongside for reference; not used in scoring or timing conclusions)",
|
||||
",宫主 ": ", lord ",
|
||||
"起运宫未定,": "start sign undecided, ",
|
||||
"- 并列:": "- Alongside: ",
|
||||
"- 顺序:": "- Order: ",
|
||||
"起运宫 ": "start sign ",
|
||||
";当前大运 ": "; running MD ",
|
||||
"- 当前大运 ": "- Running MD ",
|
||||
"按 Table 10": "Table 10 order",
|
||||
"土星在起运宫:顺行(Table 11)": "Saturn in the start sign: zodiacal (Table 11)",
|
||||
"计都在起运宫:反向(Table 12)": "Ketu in the start sign: reversed (Table 12)",
|
||||
"PyJHora 兼容:计都优先": "PyJHora-compatible: Ketu takes precedence",
|
||||
"占据星数": "occupants",
|
||||
"木星 / 水星 / 宫主三角色": "Jupiter / Mercury / lord roles",
|
||||
"占据星尊贵": "occupant dignity",
|
||||
"星座性质": "modality",
|
||||
"宫主星座内度数": "lord's degree in sign",
|
||||
"宫主落奇偶相反星座": "lord in a sign of opposite parity",
|
||||
"大运年数": "dasha years",
|
||||
"| Rath MD | 星座 | 宫主 | 起始年龄 | 结束年龄 | 年数 |": "| Rath MD | Sign | Lord | Start age | End age | Years |",
|
||||
"| Rath AD | 星座 | 起始年龄 | 结束年龄 |": "| Rath AD | Sign | Start age | End age |",
|
||||
" 的子运:比较 ": " sub-periods: compare ",
|
||||
",12 等分": ", twelve equal parts",
|
||||
# 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)",
|
||||
@@ -240,6 +265,11 @@ REGEX_EN: list[tuple[re.Pattern[str], str]] = [
|
||||
f"the strongest by Panchavargiya, {m.group(2)}, is the year lord")),
|
||||
(re.compile(r"五类候选都不照年盘上升,取全体 Panchavargiya 最强的([A-Za-z]+)"),
|
||||
r"None of the five candidates aspects the annual Lagna; the strongest of all by Panchavargiya, \1, is the year lord"),
|
||||
# BUG-1218 Rath Narayana report lines
|
||||
(re.compile(r"(第 (\d) 级:([^)]+))"), r" (level \1: \2)"),
|
||||
(re.compile(r"(第 (\d) 级)"), r" (level \1)"),
|
||||
(re.compile(r" 与 ([^,]+),较强 ([^(]+)"), r" and \1; stronger \2"),
|
||||
(re.compile(r",从宫主 (\S+) 所在 (\S+) 起"), r", from the sign of its lord \1, \2"),
|
||||
# scripts/pl9_reader_export.py: f"第{house}宫主:{lord}"
|
||||
(re.compile(r"第(\d+)宫主:"), r"Lord of house \1: "),
|
||||
# scripts/aspects.py / jyotish_engine.py: f"{n}宫相位"
|
||||
|
||||
@@ -60,8 +60,9 @@ MODULE_KEEP = (
|
||||
"functional_benefic_malefic", "kakshya", "yogas", "chara_dasha", "transits",
|
||||
)
|
||||
# BUG-1215: + profile / algorithm_note / algorithm_label (the legacy-algorithm label the card carries).
|
||||
# BUG-1218: + rath (the Sanjay Rath view the card shows beside the legacy one).
|
||||
NARAYANA_KEEP = ("lagna_sign", "current_dasha", "current_year", "current_age", "mahadasha_sequence",
|
||||
"profile", "algorithm_note", "algorithm_label")
|
||||
"profile", "algorithm_note", "algorithm_label", "rath")
|
||||
|
||||
|
||||
def _pick(value: dict[str, Any], keys: tuple[str, ...]) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user