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
+41
View File
@@ -244,3 +244,44 @@ def calc_bhava_bala(house_signs: List[str], house_degrees: List[float],
'strongest_house': max(houses, key=lambda h: h['total'])['house'],
'weakest_house': min(houses, key=lambda h: h['total'])['house'],
}
def bhava_bala_from_chart(planets: Dict, asc_sign: str, asc_degree: float, shadbala_planets: Dict) -> Dict:
"""Formal three-component Bhava Bala for one D1 chart (BUG-1210, ruling 甲13).
`planets` rows carry `sign` and a degree (`degree_in_sign`, `degree` or a
longitude); `shadbala_planets` is `shadbala.calc_shadbala(...)['planets']`,
whose `total_virupas` is each lord's Shadbala (Bhavadhipati Bala). Houses are
equal houses from the Lagna degree, as `/api/bhava_bala` uses. Totals are in
Virupas; `total_rupas` = total / 60. The `level` bands of `calc_bhava_bala`
are not a classical scale and are not displayed.
"""
if asc_sign not in SIGNS:
return {}
asc_idx = SIGNS.index(asc_sign)
house_signs = [SIGNS[(asc_idx + i) % 12] for i in range(12)]
house_degrees = [float(asc_degree) % 30 for _ in range(12)]
positions = {}
for name, row in (planets or {}).items():
if not isinstance(row, dict) or row.get('sign') not in SIGNS:
continue
degree = row.get('degree_in_sign')
if degree is None:
degree = row.get('degree', 0)
positions[name] = {'sign': row['sign'], 'degree': float(degree or 0) % 30}
virupas = {}
for name, row in (shadbala_planets or {}).items():
value = row.get('total_virupas') if isinstance(row, dict) else None
if isinstance(value, (int, float)):
virupas[name] = float(value)
if len(virupas) < 7:
return {}
result = calc_bhava_bala(house_signs, house_degrees, asc_sign, float(asc_degree) % 30, positions, virupas)
for house in result['houses']:
house['lord'] = SIGN_LORDS.get(house['sign'])
house['total_rupas'] = round(house['total'] / 60.0, 2)
house.pop('level', None)
result['method'] = 'Bhava Bala 三分量:Bhavadhipati(宫主 Shadbala)+ Bhava Dig + Bhava Drishti(jyotishganit MIT 算法,等宫)'
result['unit'] = 'virupa'
result['source'] = 'scripts/bhava_bala.py'
return result
+22 -18
View File
@@ -5250,11 +5250,10 @@ def render_pl9_markdown(packet: dict) -> str:
row = next((item for item in houses if isinstance(item, dict) and item.get('house') == house_no), None)
if not row:
continue
score = row.get('score')
strength = row.get('strength') or '-'
focus.append(f"{house_no}宫={score}({strength})")
# BUG-1210: formal three components, total in Virupa / Rupa.
focus.append(f"{house_no}宫={row.get('total')} Virupa({row.get('total_rupas')} Rupa)")
if focus:
rows.append(('Bhava Bala 主题宫位', ';'.join(focus), 'simplified_bhava_bala_lord_occupant_aspect', '仅为本地三分量相对分;不是外部同口径 Rupas 表。'))
rows.append(('Bhava Bala 主题宫位', ';'.join(focus), 'bhava_bala_three_components', 'Bhavadhipati + Bhava Dig + Bhava Drishti 三分量(等宫);外部同口径数值对照未闭环。'))
ashtakavarga = strength_sheet.get('ashtakavarga') if isinstance(strength_sheet.get('ashtakavarga'), dict) else {}
assessment = (ashtakavarga.get('sav') or {}).get('assessment') if isinstance(ashtakavarga.get('sav'), dict) else []
@@ -6796,7 +6795,7 @@ def render_pl9_markdown(packet: dict) -> str:
if not isinstance(row, dict):
continue
try:
score = float(row.get('score'))
score = float(row.get('total', row.get('score')))
except (TypeError, ValueError):
continue
if top_score is None or score > top_score:
@@ -7439,7 +7438,7 @@ def render_pl9_markdown(packet: dict) -> str:
return [
'### 力量、Ashtakavarga 与功能性吉凶',
'',
*_blocked_planned_section('#### Bhava Bala(本地三分量评分)', 'bhava_bala_unavailable'),
*_blocked_planned_section('#### Bhava Bala(三分量)', 'bhava_bala_unavailable'),
]
out = [
'### 力量、Ashtakavarga 与功能性吉凶',
@@ -7635,28 +7634,27 @@ def render_pl9_markdown(packet: dict) -> str:
rows = bhava_bala.get('houses') if isinstance(bhava_bala.get('houses'), list) else []
if rows:
out.extend([
'#### Bhava Bala(本地三分量评分)',
'#### Bhava Bala(三分量)',
'',
'该表是本地宫主位置、宫内行星与相位影响的三分量评分;不是 PL9 同口径的 Bhava Bala Rupas 表。PL9 第 31–32 页的字段级数值对照尚未闭环,所有行保持 parameter_sensitive。',
'Bhavadhipati Bala(宫主 Shadbala)+ Bhava Dig Bala + Bhava Drishti Bala,单位 Virupa(等宫,jyotishganit MIT 算法移植)。PL9 第 31–32 页的字段级数值对照尚未闭环,所有行保持 parameter_sensitive。',
'',
'| Bhava | 星座 | 宫主 | Lord | Occupant | Aspect | Local Score | 状态 |',
'|-------|------|------|------|----------|--------|-------------|------|',
'| Bhava | 星座 | 宫主 | Bhavadhipati | Dig | Drishti | 合计 Virupa | 合计 Rupa | 状态 |',
'|-------|------|------|--------------|-----|---------|-------------|-----------|------|',
])
for row in rows:
if not isinstance(row, dict):
continue
components = row.get('components') if isinstance(row.get('components'), dict) else {}
out.append(
f"| {_md_cell(row.get('house'))} | {_md_cell(_humanize_reader_token(row.get('sign')))} | "
f"{_md_cell(_humanize_reader_token(row.get('lord')))} | {_md_cell(components.get('lord_position'))} | "
f"{_md_cell(components.get('occupant_influence'))} | {_md_cell(components.get('aspect_influence'))} | "
f"{_md_cell(row.get('score'))} | parameter_sensitive |"
f"{_md_cell(_humanize_reader_token(row.get('lord')))} | {_md_cell(row.get('adhipathi_bala'))} | "
f"{_md_cell(row.get('dig_bala'))} | {_md_cell(row.get('drik_bala'))} | "
f"{_md_cell(row.get('total'))} | {_md_cell(row.get('total_rupas'))} | parameter_sensitive |"
)
out.append('')
else:
out.extend(_blocked_planned_section('#### Bhava Bala(本地三分量评分)', 'bhava_bala_unavailable'))
out.extend(_blocked_planned_section('#### Bhava Bala(三分量)', 'bhava_bala_unavailable'))
else:
out.extend(_blocked_planned_section('#### Bhava Bala(本地三分量评分)', 'bhava_bala_unavailable'))
out.extend(_blocked_planned_section('#### Bhava Bala(三分量)', 'bhava_bala_unavailable'))
out.extend([
'#### Vimsopaka 十六分盘力量摘要',
'',
@@ -16431,8 +16429,14 @@ def cmd_full_reading(args):
planet.update(get_kp_lords(float(planet.get('degree_raw', 0.0))))
planet['r_c'] = ''
report['modules']['shadbala'] = shadbala_result
if isinstance(shadbala_result, dict) and shadbala_result.get('bhava_bala'):
report['modules']['bhava_bala'] = _normalize_bhava_bala_module(shadbala_result.get('bhava_bala'))
# BUG-1210 (甲13): Bhava Bala is the formal three-component module
# (scripts/bhava_bala.py); the local +2 / -1.5 house score stays out of
# every user-visible surface.
if isinstance(shadbala_result, dict):
from bhava_bala import bhava_bala_from_chart
formal = bhava_bala_from_chart(planets, asc_sign, asc_deg % 30, shadbala_result.get('planets') or {})
if formal:
report['modules']['bhava_bala'] = formal
except Exception as e:
report['errors'].append(f"shadbala: {e}")
+7 -5
View File
@@ -1194,19 +1194,21 @@ def render_pl9_parity_markdown(packet: dict) -> str:
if str(key).isdigit() and isinstance(value, dict)
]
if bhava_rows:
# BUG-1210 (甲13): the formal three components (scripts/bhava_bala.py);
# the local +2 / -1.5 house score is never shown.
lines.extend([
'### p32 Bhava Bala 十二宫力量',
'',
'| House | Sign | Lord | Score | 关键因素 |',
'|-------|------|------|-------|----------|',
'| House | Sign | Lord | Bhavadhipati | Dig | Drishti | Total (Virupa) | Total (Rupa) |',
'|-------|------|------|--------------|-----|---------|----------------|--------------|',
])
for row in bhava_rows[:12]:
if not isinstance(row, dict):
if not isinstance(row, dict) or row.get('total') is None:
continue
factors = row.get('factors') if isinstance(row.get('factors'), list) else []
lines.append(
f"| {_cell(row.get('house') or row.get('number'))} | {_cell(_token(row.get('sign')))} | {_cell(_token(row.get('lord')))} | "
f"{_num(row.get('score'))} | {_cell(_brief(factors, 2))} |"
f"{_num(row.get('adhipathi_bala'))} | {_num(row.get('dig_bala'))} | {_num(row.get('drik_bala'))} | "
f"{_num(row.get('total'))} | {_num(row.get('total_rupas'))} |"
)
lines.append('')
friendship = _dict(strengths.get('planetary_friendship'))
+10 -8
View File
@@ -616,8 +616,9 @@ def calc_shadbala(planets: Dict, asc_sign: str, birth_hour: float,
for i, (name, _) in enumerate(ranked):
results[name]['rank'] = i + 1
# Bhava Bala(宫位力量)v6.9.12 新增
bhava_bala = calc_bhava_bala(planets, asc_sign)
# BUG-1210 (甲13): the local house score (calc_local_bhava_score, formerly
# calc_bhava_bala: benefic +2, malefic -1.5 ...) is no longer part of this
# result; Bhava Bala is the formal three components in scripts/bhava_bala.py.
return {
'method': 'Shadbala六重力量(absolute Virupas; precise Sthana/Dig/Kala/Drik; bounded BPHS Chesta)',
@@ -629,7 +630,6 @@ def calc_shadbala(planets: Dict, asc_sign: str, birth_hour: float,
'is_night_birth': is_night,
'sun_uttarayana': sun_northern,
'planets': results,
'bhava_bala': bhava_bala,
'ranking': [name for name, _ in ranked],
'strongest': ranked[0][0] if ranked else None,
'weakest': ranked[-1][0] if ranked else None,
@@ -1138,14 +1138,16 @@ def calc_drik_bala(pname: str, sign: str, house: int,
# ============================================================================
# Bhava Bala(宫位力量)v6.9.12 新增
# 本地宫位评分(不是 Bhava Bala)v6.9.12;BUG-1210 改名
# ============================================================================
def calc_bhava_bala(planets: Dict, asc_sign: str) -> Dict:
def calc_local_bhava_score(planets: Dict, asc_sign: str) -> Dict:
"""
Bhava Bala(宫位力量)—— 评估每个宫位的综合强度。
基于 BPHS 和 Parashara 传统:
本地宫位评分(原名 calc_bhava_bala)—— 网站自定的加减分,不是 BPHS Bhava Bala。
占星师定稿 2026-10-03 甲13:删除无出处的「吉星 +2、凶星 -1.5」口径,用户可见面
一律改用 scripts/bhava_bala.py 的正式三分量(Bhavadhipati / Dig / Drishti)。
本函数只留作审计,不进报告、卡片或任何用户可见文字。原说明:
- 宫主星力量(该宫主宰行星的 Shadbala 投影)
- 宫内行星影响(自然吉凶 + 入庙/落陷)
- 相位影响(吉星/凶星对该宫的相位)