feat(bhava-bala): reports use the formal three components; local +2/-1.5 score hidden (T4, BUG-1210)

Astrologer ruling 甲13. shadbala.calc_bhava_bala renamed calc_local_bhava_score and
removed from calc_shadbala's result (it fed the report's Bhava Bala table). New
bhava_bala.bhava_bala_from_chart (Bhavadhipati = lord's Shadbala virupas, Bhava
Dig, Bhava Drishti; equal houses as /api/bhava_bala; total and total_rupas; the
module's invented level bands not shown) drives modules.bhava_bala in the full
reading. Reader table: Bhavadhipati | Dig | Drishti | Total (Virupa) | Total
(Rupa); reference edition table and wealth/summary lines read the formal totals.
Other users of the local score: none outside shadbala.py (remedies and health
narrative read Shadbala rupas only).

Changed assertions (three columns in PROGRESS): test_shadbala_complete local-score
tests renamed; test_report_longform_gaps2 fixture rows and heading.

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:
Jesse_Chen
2026-10-03 12:45:58 +08:00
co-authored by Claude Opus 5.5
parent ea78804b78
commit 6feb1a4aaa
7 changed files with 160 additions and 41 deletions
+63
View File
@@ -0,0 +1,63 @@
"""Bhava Bala is the formal three-component module on every surface (BUG-1210).
Astrologer rulings 2026-10-03 甲13: the local house score (benefic +2, malefic
-1.5 ...; `shadbala.calc_local_bhava_score`, formerly `calc_bhava_bala`) has no
source and is not shown; reports use `bhava_bala.bhava_bala_from_chart`
(Bhavadhipati + Bhava Dig + Bhava Drishti). Fictional chart.
"""
from __future__ import annotations
import copy
import re
from pathlib import Path
from scripts.bhava_bala import bhava_bala_from_chart, calc_bhava_adhipathi_bala
from scripts.jyotish_engine import cmd_full_reading
from scripts.pl9_reader_export import _pl9_export_markdown_for_edition
from scripts.shadbala import calc_local_bhava_score, calc_shadbala
from tests.test_report_english_edition import CASES, _packet
ROOT = Path(__file__).resolve().parents[1]
def test_local_score_is_renamed_and_not_in_shadbala_result() -> None:
import scripts.shadbala as shadbala
assert not hasattr(shadbala, "calc_bhava_bala")
planets = {"Sun": {"sign": "Leo", "house": 1, "degree": 10.0, "lon": 130.0},
"Moon": {"sign": "Cancer", "house": 12, "degree": 5.0, "lon": 95.0}}
assert calc_local_bhava_score(planets, "Leo")[1]["lord"] == "Sun"
result = calc_shadbala(planets, "Leo", 12.0, 130.0, 95.0)
assert "bhava_bala" not in result and "local_bhava_score" not in result
def test_full_reading_bhava_bala_is_the_formal_module() -> None:
case = CASES["day"]
reading = cmd_full_reading(type("Args", (), dict(case))())
module = reading["modules"]["bhava_bala"]
assert module["source"] == "scripts/bhava_bala.py" and module["unit"] == "virupa"
shadbala = reading["modules"]["shadbala"]["planets"]
lords = [row["lord"] for row in module["houses"]]
assert [row["adhipathi_bala"] for row in module["houses"]] == [round(shadbala[lord]["total_virupas"], 2) for lord in lords]
for row in module["houses"]:
assert row["total"] == round(row["adhipathi_bala"] + row["dig_bala"] + row["drik_bala"], 2) or abs(
row["total"] - (row["adhipathi_bala"] + row["dig_bala"] + row["drik_bala"])) < 0.02
assert row["total_rupas"] == round(row["total"] / 60.0, 2)
assert "factors" not in row and "score" not in row and "level" not in row
def test_reader_report_shows_three_components_and_no_plus_minus_scoring() -> None:
packet = _packet(CASES["day"])
for language in ("zh", "en"):
edition = copy.deepcopy(packet)
edition["report_language"] = language
markdown = _pl9_export_markdown_for_edition(edition, "reader_main")
header = next(line for line in markdown.splitlines() if line.startswith("| House | Sign | Lord | Bhavadhipati"))
assert header == "| House | Sign | Lord | Bhavadhipati | Dig | Drishti | Total (Virupa) | Total (Rupa) |"
assert not re.search(r"\(Benefic\): \+2|\(Malefic\): -1\.5|吉星 \+2|凶星 -1\.5", markdown)
def test_adhipathi_is_the_lords_shadbala() -> None:
assert calc_bhava_adhipathi_bala(["Aries", "Taurus"], {"Mars": 400.0, "Venus": 350.0}) == [400.0, 350.0]
assert bhava_bala_from_chart({}, "Aries", 10.0, {}) == {}
+10 -3
View File
@@ -168,13 +168,18 @@ def test_auxiliary_dasha_headings_render_from_native_modules() -> None:
},
},
"strengths_and_scores": {
# BUG-1210: formal three-component rows (was the local score row
# with lord_position / occupant / aspect components).
"bhava_bala": {
"houses": [{
"house": 1,
"sign": "Aries",
"lord": "Mars",
"score": 1,
"components": {"lord_position": 1, "occupant_influence": 0, "aspect_influence": 0},
"adhipathi_bala": 420.0,
"dig_bala": 30.0,
"drik_bala": 1.5,
"total": 451.5,
"total_rupas": 7.53,
}],
},
},
@@ -182,7 +187,9 @@ def test_auxiliary_dasha_headings_render_from_native_modules() -> None:
assert "#### Yogini Dasha 周期(本地计算)" in markdown
assert "#### Narayana Rashi Dasha" in markdown
assert "#### Kala Chakra Dasha(本地原始周期,冲突保留)" in markdown
assert "#### Bhava Bala(本地三分量评分)" in markdown
# BUG-1210: was "#### Bhava Bala(本地三分量评分)".
assert "#### Bhava Bala(三分量)" in markdown
assert "| 1 | 白羊座 | 火星 | 420.0 | 30.0 | 1.5 | 451.5 | 7.53 | parameter_sensitive |" in markdown
def test_quality_gate_runs_this_file() -> None:
+7 -7
View File
@@ -12,7 +12,7 @@ if SCRIPTS not in sys.path:
from shadbala import (
calc_shadbala, calc_sthana_bala, calc_dig_bala, calc_kala_bala,
calc_chesta_bala, calc_drik_bala, calc_bhava_bala,
calc_chesta_bala, calc_drik_bala, calc_local_bhava_score,
NAISARGIKA_BALA, MIN_REQUIRED, DIG_BALA_HOUSE,
EXALTATION_DEG, DEBILITATION_DEG, FRIENDSHIP, SHADBALA_CONSTANTS_PATH,
)
@@ -286,31 +286,31 @@ class TestShadbalaFull:
assert NAISARGIKA_BALA['Saturn'] == 8.57
# ── Bhava Bala Tests ────────────────────────────────────────────────
# ── Local house score (was calc_bhava_bala; renamed BUG-1210, not user-visible) ──
class TestBhavaBala:
def test_12_houses_calculated(self):
result = calc_bhava_bala(_sample_planets(), 'Leo')
result = calc_local_bhava_score(_sample_planets(), "Leo")
for house in range(1, 13):
assert house in result
def test_benefic_in_house_positive(self):
# Jupiter in house 5 = benefic
result = calc_bhava_bala(_sample_planets(), 'Leo')
result = calc_local_bhava_score(_sample_planets(), "Leo")
assert result[5]['score'] > 0
def test_strength_classification(self):
result = calc_bhava_bala(_sample_planets(), 'Leo')
result = calc_local_bhava_score(_sample_planets(), "Leo")
for house, data in result.items():
assert data['strength'] in ('Strong', 'Moderate', 'Weak')
def test_lord_info_present(self):
result = calc_bhava_bala(_sample_planets(), 'Leo')
result = calc_local_bhava_score(_sample_planets(), "Leo")
for house, data in result.items():
assert 'lord' in data
assert 'sign' in data
def test_factors_list(self):
result = calc_bhava_bala(_sample_planets(), 'Leo')
result = calc_local_bhava_score(_sample_planets(), "Leo")
for house, data in result.items():
assert isinstance(data['factors'], list)