From 0cf2249e6b6abaa48679544e0f4ced74c04dc710 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Sat, 3 Oct 2026 11:33:16 +0800 Subject: [PATCH] fix(jaimini): 8-karaka order follows BPHS ch. 32 (PiK 5th, DK 8th) (T6, BUG-1204) KARAKA_8 put Putrakaraka 5th, Darakaraka 7th and Pitrukaraka 8th. BPHS ch. 32 order with Rahu counted is AK, AmK, BK, MK, PiK, PK, GK, DK. Test pins the table and a public AA chart (Steve Jobs, Raman, mean nodes) plus the reader report's live 8-karaka rows. jaimini.py is in the frozen rectification identity; re-freeze follows in a separate commit (rectification does not read karakas). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE --- scripts/jaimini.py | 7 ++- tests/test_chara_karaka_8_bphs_order.py | 73 +++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 tests/test_chara_karaka_8_bphs_order.py diff --git a/scripts/jaimini.py b/scripts/jaimini.py index 879b24ef..e6fb909a 100644 --- a/scripts/jaimini.py +++ b/scripts/jaimini.py @@ -49,10 +49,13 @@ KARAKA_7 = { 7: 'Darakaraka', # DK - 配偶(度数最低) } -# 8星制(含Rahu,用绝对度数) +# 8星制(含Rahu;Rahu 按 30° 减星座内度数排位)。BPHS 第 32 章次序: +# AK、AmK、BK、MK、PiK、PK、GK、DK,即 PiK 第 5、PK 第 6、GK 第 7、DK 第 8 +# (度数最低者为 DK)。旧表把第 5/7/8 位写成 PK/DK/PiK(BUG-1204,上游 +# yinduzhanxing 同样写错)。 KARAKA_8 = { 1: 'Atmakaraka', 2: 'Amatyakaraka', 3: 'Bhratrikaraka', 4: 'Matrikaraka', - 5: 'Putrakaraka', 6: 'Gnatikaraka', 7: 'Darakaraka', 8: 'Pitrukaraka', + 5: 'Pitrukaraka', 6: 'Putrakaraka', 7: 'Gnatikaraka', 8: 'Darakaraka', } KARAKA_CN = { diff --git a/tests/test_chara_karaka_8_bphs_order.py b/tests/test_chara_karaka_8_bphs_order.py new file mode 100644 index 00000000..1d6360dd --- /dev/null +++ b/tests/test_chara_karaka_8_bphs_order.py @@ -0,0 +1,73 @@ +"""8-karaka Chara Karaka order follows BPHS ch. 32 (BUG-1204). + +BPHS ch. 32 (Karakas): with Rahu counted, the eight roles in descending +order of degrees are Atma, Amatya, Bhratru, Matru, Pitru, Putra, Gnati, Dara; +Rahu ranks by 30 minus its degree in sign. The old table put Putra 5th, Dara +7th and Pitru 8th (TASK-astrologer-rulings-batch1-20261003 T6). + +Public chart: Steve Jobs, Astro-Databank AA (repository development case +library), Raman ayanamsa, mean nodes. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +from scripts.jaimini import KARAKA_7, KARAKA_8, calc_chara_karaka_8 +from scripts.jyotish_engine import compute_chart_data + +ROOT = Path(__file__).resolve().parents[1] +BPHS_8 = ["Atmakaraka", "Amatyakaraka", "Bhratrikaraka", "Matrikaraka", + "Pitrukaraka", "Putrakaraka", "Gnatikaraka", "Darakaraka"] + + +def _jobs_degrees() -> dict[str, float]: + cases = json.loads((ROOT / "references/real_case_calibration/minute_rectification_development_v1.json").read_text(encoding="utf-8"))["cases"] + birth = next(case for case in cases if case["case_id"] == "steve_jobs_1955_development")["birth"] + assert birth["source"]["rodden_rating"] == "AA" + year, month, day = (int(part) for part in birth["date"].split("-")) + hour, minute = (int(part) for part in birth["time"].split(":")) + chart, *_ = compute_chart_data(year, month, day, hour, minute, birth["latitude"], birth["longitude"], + birth["timezone_offset"], ayanamsa_name="raman") + return {name: row["degree_raw"] % 30 for name, row in chart["planets"].items()} + + +def test_table_is_the_bphs_order() -> None: + assert [KARAKA_8[rank] for rank in range(1, 9)] == BPHS_8 + # The 7-karaka scheme (no Pitrukaraka) is unchanged. + assert [KARAKA_7[rank] for rank in range(1, 8)] == [ + "Atmakaraka", "Amatyakaraka", "Bhratrikaraka", "Matrikaraka", "Putrakaraka", "Gnatikaraka", "Darakaraka"] + + +def test_public_chart_roles_follow_bphs_ranking() -> None: + degrees = _jobs_degrees() + effective = {name: (30.0 - value) % 30.0 if name == "Rahu" else value + for name, value in degrees.items() if name != "Ketu"} + expected_order = sorted(effective, key=effective.get, reverse=True) + table = calc_chara_karaka_8(degrees)["karaka_table_8"] + assert {role: row["planet"] for role, row in table.items()} == dict(zip(BPHS_8, expected_order)) + assert {role: row["rank"] for role, row in table.items()} == {role: rank for rank, role in enumerate(BPHS_8, 1)} + # Lowest effective degree is Dara; Pitru sits between Matru and Putra. + assert table["Darakaraka"]["planet"] == min(effective, key=effective.get) + assert table["Matrikaraka"]["effective_degree_in_sign"] >= table["Pitrukaraka"]["effective_degree_in_sign"] >= table["Putrakaraka"]["effective_degree_in_sign"] + # This chart's own values (Raman, mean nodes): Rahu at 10.72° ranks by 19.28°. + assert table["Pitrukaraka"]["planet"] == "Rahu" + assert table["Darakaraka"]["planet"] == "Mars" + assert table["Putrakaraka"]["planet"] == "Moon" + + +def test_reader_report_karaka_table_is_in_bphs_order() -> None: + """Live render of a fictional chart: the reader's 8-karaka rows run AK..DK.""" + import copy + + from scripts.jaimini import KARAKA_CN + from scripts.pl9_reader_export import _pl9_export_markdown_for_edition + from tests.test_report_english_edition import CASES, _packet + + packet = _packet(CASES["day"]) + markdown = _pl9_export_markdown_for_edition(copy.deepcopy(packet), "reader_main") + lines = markdown.splitlines() + start = next(i for i, line in enumerate(lines) if line.startswith("### Jaimini Karaka")) + rows = [line for line in lines[start + 1:start + 14] if line.startswith("| ") and "星" in line.split("|")[1]] + assert [row.split("|")[1].strip() for row in rows] == [KARAKA_CN[role] for role in BPHS_8]