feat(narayana): Sanjay Rath profile replayed against the book tables; not wired (BUG-1215)
Round-4 ruling Q4. scripts/narayana_rath.py builds the Rath sequence from the rule ledger and book rows ported verbatim from upstream yinduzhanxing c7117f92 (sanjay_rath_narayana_book_ledger JSON + md, chara_narayana_book_golden_cases): Table 10 order (Saturn -> Table 11, Ketu -> reversed), years by jaimini_odd_footed_dignity_v2 with jaimini_dual_lord_source_v1, ADs twelve equal parts from the MD sign (Table 13 / 11 / 14), start sign by the existing select_narayana_seed_sign, ties reported as parameter_sensitive with both sequences. Given the book's start sign, Tables 15 / 17 / 18 match 36/36 MD signs and years. The start sign does not: Table 17 picks Virgo (book Pisces), Table 18 ties (book Cancer). Task red line 2: the profile stays out of every user-visible surface and out of scoring; T5 trial not run. A test pins that no script imports it. 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
0afcc2d9a3
commit
7cb332bce6
@@ -0,0 +1,107 @@
|
||||
"""Sanjay Rath Narayana Dasha profile vs the book tables (BUG-1215, round-4 Q4).
|
||||
|
||||
The rule ledger and the book rows are ported verbatim from upstream
|
||||
yinduzhanxing c7117f92 (`references/oracle/sanjay_rath_narayana_book_ledger_2026_08_28.json`,
|
||||
Tables 15 / 17 / 18). Birth data are the published charts the book tables are
|
||||
computed for (public figures / the book's own example chart):
|
||||
|
||||
- Table 15: Sri Aurobindo, 1872-08-15 05:00 LMT, Calcutta 22N34 88E22;
|
||||
- Table 17: the book's "standard nativity", 1963-08-07 21:15 IST, Sambalpur 21N28 84E01;
|
||||
- Table 18: Indira Gandhi, 1917-11-19 23:11 IST, Allahabad 25N27 81E51.
|
||||
|
||||
Lahiri ayanamsa. With the book's start sign, all 36 MD signs and years match.
|
||||
The start sign itself is not reproduced by the existing strength rule for
|
||||
Tables 17 and 18 — pinned below as the current state (task red line 2: the
|
||||
profile stays out of every user-visible surface and out of scoring).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts.jyotish_engine import compute_chart_data
|
||||
from scripts.narayana_rath import SIGNS, antardasha, calc_narayana_rath, md_order
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
LEDGER = ROOT / "references/oracle/sanjay_rath_narayana_book_ledger_2026_08_28.json"
|
||||
UPSTREAM_BLOBS = { # c7117f92
|
||||
"references/oracle/sanjay_rath_narayana_book_ledger_2026_08_28.json": "a52f181c22ba1b1e6a330b73711eb0e791785e56",
|
||||
"references/oracle/chara_narayana_book_golden_cases_2026_08_28.json": "552c40448502c15a47035da064914dd76a4d919b",
|
||||
"docs/research/sanjay_rath_narayana_book_ledger_2026_08_28.md": "8d18c2320d32dcd364784351ac52dc8bcb4ef053",
|
||||
}
|
||||
CHARTS = {
|
||||
"Table 15": (1872, 8, 15, 5, 0, 22.5667, 88.3667, 88.3667 / 15),
|
||||
"Table 17": (1963, 8, 7, 21, 15, 21.4667, 84.0167, 5.5),
|
||||
"Table 18": (1917, 11, 19, 23, 11, 25.45, 81.85, 5.5),
|
||||
}
|
||||
|
||||
|
||||
def _book(table: str) -> dict:
|
||||
return next(case for case in json.loads(LEDGER.read_text(encoding="utf-8"))["golden_cases"] if case["source_table"] == table)
|
||||
|
||||
|
||||
def _chart(table: str):
|
||||
chart, asc, *_ = compute_chart_data(*CHARTS[table], ayanamsa_name="lahiri")
|
||||
return asc, {name: row["degree"] for name, row in chart["planets"].items()}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path,blob", sorted(UPSTREAM_BLOBS.items()))
|
||||
def test_ported_files_are_the_upstream_blobs(path: str, blob: str) -> None:
|
||||
data = (ROOT / path).read_bytes()
|
||||
assert hashlib.sha1(b"blob %d\0" % len(data) + data).hexdigest() == blob
|
||||
|
||||
|
||||
@pytest.mark.parametrize("table", sorted(CHARTS))
|
||||
def test_book_start_sign_reproduces_order_and_years(table: str) -> None:
|
||||
book = _book(table)
|
||||
asc, lons = _chart(table)
|
||||
result = calc_narayana_rath(asc, lons, seed_sign_idx=SIGNS.index(book["start_sign"]), cycle_count=1)
|
||||
assert [row["sign"] for row in result["mahadasha"]] == [row[0] for row in book["rows"]]
|
||||
assert [row["years"] for row in result["mahadasha"]] == [row[1] for row in book["rows"]]
|
||||
|
||||
|
||||
def test_saturn_in_the_start_sign_gives_the_zodiacal_order() -> None:
|
||||
asc, lons = _chart("Table 18") # Saturn in Cancer
|
||||
assert md_order(SIGNS.index("Cancer"), lons)["direction"] == "table11_saturn_zodiacal"
|
||||
asc, lons = _chart("Table 15")
|
||||
assert md_order(SIGNS.index("Cancer"), lons)["direction"] == "table10_general"
|
||||
|
||||
|
||||
def test_start_sign_selection_current_state() -> None:
|
||||
"""Existing strength rule (occupants, then aspect by Jupiter / Mercury /
|
||||
lord): Table 15 right; Table 17 picks Virgo (book Pisces: Pisces holds its
|
||||
own lord Jupiter, which the aspect-only rule does not count); Table 18 ties
|
||||
(book Cancer). Update this test together with the rule once the astrologer
|
||||
answers; until then the profile is not wired anywhere."""
|
||||
asc, lons = _chart("Table 15")
|
||||
assert (calc_narayana_rath(asc, lons)["status"], calc_narayana_rath(asc, lons)["seed_sign"]) == ("partial_verified", "Cancer")
|
||||
asc, lons = _chart("Table 17")
|
||||
assert calc_narayana_rath(asc, lons)["seed_sign"] == "Virgo"
|
||||
asc, lons = _chart("Table 18")
|
||||
result = calc_narayana_rath(asc, lons)
|
||||
assert result["status"] == "parameter_sensitive" and result["reason"] == "later_narayana_sign_strength_rules_required"
|
||||
assert set(result["alternatives"]) == {"Cancer", "Capricorn"}
|
||||
assert [row["sign"] for row in result["alternatives"]["Cancer"]["mahadasha"][:12]] == [row[0] for row in _book("Table 18")["rows"]]
|
||||
|
||||
|
||||
def test_antardasha_is_twelve_equal_parts_from_the_md_sign() -> None:
|
||||
asc, lons = _chart("Table 15")
|
||||
md = calc_narayana_rath(asc, lons, cycle_count=1)["mahadasha"][0] # Cancer, 7 years; Mars and Jupiter there
|
||||
rows = antardasha(md, lons)
|
||||
assert [row["sign"] for row in rows][:3] == ["Cancer", "Gemini", "Taurus"] # Table 13, even sign reverse
|
||||
assert all(abs(row["years"] - 7 / 12) < 1e-6 for row in rows)
|
||||
asc, lons = _chart("Table 18")
|
||||
md = calc_narayana_rath(asc, lons, seed_sign_idx=SIGNS.index("Cancer"), cycle_count=1)["mahadasha"][0]
|
||||
assert [row["sign"] for row in antardasha(md, lons)][:3] == ["Cancer", "Leo", "Virgo"] # Saturn in Cancer: Table 11
|
||||
|
||||
|
||||
def test_rath_profile_is_not_wired_into_any_product_path() -> None:
|
||||
pattern = re.compile(r"\bnarayana_rath\b")
|
||||
users = sorted(path.name for path in (ROOT / "scripts").rglob("*.py")
|
||||
if path.name != "narayana_rath.py" and pattern.search(path.read_text(encoding="utf-8")))
|
||||
assert users == []
|
||||
Reference in New Issue
Block a user