feat(shadbala): BPHS minimum check first, bands labelled as the site's (T3, BUG-1208)
Astrologer ruling 甲13. New scripts/shadbala_display.py (display only; shadbala.py is frozen and unchanged): chart.shadbala now carries min_required, meets_minimum, minimum_check (达到/未达到 BPHS 最低要求), level_source=site_band and level '<band>(网站分档)'; report Shadbala table gains 'BPHS minimum' and 'Site band' columns (zh/en); evidence snapshot ranking and health narrative say the minimum check first and mark the band as the site's. 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
de79390175
commit
5fdf2f7498
@@ -6554,7 +6554,9 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
planets_data.get('Sun',{}).get('lon',0),
|
||||
moon_lon,
|
||||
)
|
||||
shadbala_summary = {p: {'rupas': round(d['total_rupas'],2), 'level': d['strength_level']}
|
||||
# BUG-1208 (甲13): BPHS minimum first, then the site band, labelled.
|
||||
from shadbala_display import row_fields
|
||||
shadbala_summary = {p: {'rupas': round(d['total_rupas'],2), **row_fields(d)}
|
||||
for p,d in sb.get('planets',{}).items()}
|
||||
except Exception as e:
|
||||
import logging
|
||||
@@ -6833,6 +6835,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
{
|
||||
'planet': planet,
|
||||
'rupas': pdata.get('rupas'),
|
||||
'minimum_check': pdata.get('minimum_check'),
|
||||
'level': pdata.get('level'),
|
||||
}
|
||||
for planet, pdata in shadbala.items()
|
||||
|
||||
@@ -153,6 +153,7 @@ from cmd_solar_return import cmd_solar_return # v6.0.18
|
||||
from cmd_narayana_dasha import cmd_narayana_dasha as _cmd_narayana_dasha_impl # v6.0.20
|
||||
from cmd_muhurta import cmd_muhurta # v6.0.21
|
||||
from yoga_engine import detect_yogas # v6.0.26: data-driven Yoga engine
|
||||
from shadbala_display import check_text as _shadbala_check_text, row_fields as _shadbala_row_fields # BUG-1208
|
||||
from kp_system import calc_kp_analysis, get_kp_lords # v6.9.10: KP完整系统
|
||||
from bhava_chalit import BhavaChalitCalculator, cmd_bhava_chalit # v6.9.13: Bhava Chalit 不等宫边界调整
|
||||
from sudarshana_chakra import calc_sudarshana_chakra, generate_sudarshana_report # v6.9.14: Sudarshana Chakra 三参考点盘
|
||||
@@ -11361,7 +11362,9 @@ def _build_health_narrative_payload(modules, chart=None):
|
||||
total = row.get('total_rupas', row.get('total_rupa'))
|
||||
minimum = row.get('min_required')
|
||||
level = row.get('strength_level') or row.get('level')
|
||||
strengths.append(f'{lord} Shadbala:total={total if total is not None else "-"},minimum={minimum if minimum is not None else "-"},level={level or "-"}。')
|
||||
# BUG-1208 (甲13): the BPHS minimum check first, the band marked as the site's.
|
||||
check = _shadbala_check_text(_shadbala_row_fields(row)['meets_minimum'])
|
||||
strengths.append(f'{lord} Shadbala:total={total if total is not None else "-"},minimum={minimum if minimum is not None else "-"},{check},网站分档={level or "-"}。')
|
||||
if isinstance(total, (int, float)) and isinstance(minimum, (int, float)) and total < minimum:
|
||||
risks.append(f'{lord} 的 Shadbala 低于本地最低参考值,只能作为压力负荷提示。')
|
||||
|
||||
@@ -11857,7 +11860,10 @@ def _build_ai_prompt_pack(report):
|
||||
'rank': pdata.get('rank'),
|
||||
'total_rupas': pdata.get('total_rupas'),
|
||||
'min_required': pdata.get('min_required'),
|
||||
# BUG-1208 (甲13): BPHS minimum first; the band is the site's own.
|
||||
'meets_minimum': _shadbala_row_fields(pdata)['meets_minimum'],
|
||||
'strength_level': pdata.get('strength_level'),
|
||||
'level_source': 'site_band',
|
||||
})
|
||||
|
||||
current_ad = current_dasha.get('antardasha') if isinstance(current_dasha, dict) else {}
|
||||
|
||||
@@ -1124,9 +1124,12 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
f"- Shadbala 方法:{_cell(shadbala.get('method'))}",
|
||||
f"- 外部边界:{_cell(shadbala.get('external_parity_boundary'))}",
|
||||
'',
|
||||
'| Planet | Sthana | Dig | Kala | Chesta | Naisargika | Drik | Total | Required |',
|
||||
'|--------|--------|-----|------|--------|------------|------|-------|----------|',
|
||||
'| Planet | Sthana | Dig | Kala | Chesta | Naisargika | Drik | Total | Required | BPHS minimum | Site band |',
|
||||
'|--------|--------|-----|------|--------|------------|------|-------|----------|--------------|-----------|',
|
||||
])
|
||||
# BUG-1208 (甲13): whether the BPHS minimum is met comes first; the
|
||||
# 150/125/100/75/50 band is the site's own and is labelled so.
|
||||
from shadbala_display import band_text as _band_text, check_text as _check_text, row_fields as _row_fields
|
||||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn'):
|
||||
row = _dict(shadbala_planets.get(planet))
|
||||
if not row:
|
||||
@@ -1137,7 +1140,9 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
f"| {_cell(_token(planet))} | {_num(_dict(row.get('sthana_bala')).get('total') or row.get('sthana_bala'))} | "
|
||||
f"{_num(_dict(row.get('dig_bala')).get('total') or row.get('dig_bala'))} | {_num(_dict(row.get('kala_bala')).get('total') or row.get('kala_bala'))} | "
|
||||
f"{_num(_dict(row.get('chesta_bala')).get('total') or row.get('chesta_bala'))} | {_num(row.get('naisargika_bala'))} | "
|
||||
f"{_num(_dict(row.get('drik_bala')).get('total') or row.get('drik_bala'))} | {_num(total)} | {_num(required)} |"
|
||||
f"{_num(_dict(row.get('drik_bala')).get('total') or row.get('drik_bala'))} | {_num(total)} | {_num(required)} | "
|
||||
f"{_cell(_check_text(_row_fields(row)['meets_minimum'], _pl9_report_language(packet)))} | "
|
||||
f"{_cell(_band_text(row.get('strength_level'), _pl9_report_language(packet)))} |"
|
||||
)
|
||||
lines.append('')
|
||||
lines.extend([
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""How a Shadbala strength is shown (astrologer rulings 2026-10-03 甲13, BUG-1208).
|
||||
|
||||
First whether the planet meets the BPHS minimum (BPHS ch. 27 vv. 32-33, the
|
||||
`shadbala.MIN_REQUIRED` table), then the site's own band (150/125/100/75/50 % of
|
||||
that minimum -> 极强…极弱), labelled as the site's band. `shadbala.py` is in the
|
||||
frozen rectification identity, so the fields are added here, at display time,
|
||||
from values the engine already returns; nothing is recomputed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
LEVEL_SOURCE = 'site_band'
|
||||
SITE_BAND_SUFFIX = '(网站分档)'
|
||||
SITE_BAND_SUFFIX_EN = ' (site band)'
|
||||
MEETS_ZH = '达到 BPHS 最低要求'
|
||||
MISSES_ZH = '未达到 BPHS 最低要求'
|
||||
MEETS_EN = 'meets the BPHS minimum'
|
||||
MISSES_EN = 'below the BPHS minimum'
|
||||
|
||||
|
||||
def _number(value: Any) -> Optional[float]:
|
||||
return float(value) if isinstance(value, (int, float)) and not isinstance(value, bool) else None
|
||||
|
||||
|
||||
def minimum_fields(rupas: Any, min_required: Any, band: Any) -> Dict[str, Any]:
|
||||
"""`min_required`, `meets_minimum`, `minimum_check`, band label and source."""
|
||||
total = _number(rupas)
|
||||
minimum = _number(min_required)
|
||||
meets = None if total is None or minimum is None else total >= minimum
|
||||
fields: Dict[str, Any] = {
|
||||
'min_required': minimum,
|
||||
'meets_minimum': meets,
|
||||
'minimum_check': None if meets is None else (MEETS_ZH if meets else MISSES_ZH),
|
||||
'level_source': LEVEL_SOURCE,
|
||||
}
|
||||
if band not in (None, ''):
|
||||
fields['level'] = f'{band}{SITE_BAND_SUFFIX}'
|
||||
return fields
|
||||
|
||||
|
||||
def row_fields(row: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Display fields for one `shadbala.calc_shadbala` planet row."""
|
||||
return minimum_fields(row.get('total_rupas', row.get('rupas')), row.get('min_required'), row.get('strength_level'))
|
||||
|
||||
|
||||
BAND_EN = {'极强': 'very strong', '强': 'strong', '充足': 'adequate', '略弱': 'slightly weak', '弱': 'weak', '极弱': 'very weak'}
|
||||
|
||||
|
||||
def band_text(band: Any, language: str = 'zh') -> Optional[str]:
|
||||
if band in (None, ''):
|
||||
return None
|
||||
if language == 'en':
|
||||
return f"{BAND_EN.get(str(band), str(band))}{SITE_BAND_SUFFIX_EN}"
|
||||
return f'{band}{SITE_BAND_SUFFIX}'
|
||||
|
||||
|
||||
def check_text(meets: Optional[bool], language: str = 'zh') -> str:
|
||||
if meets is None:
|
||||
return '-'
|
||||
if language == 'en':
|
||||
return MEETS_EN if meets else MISSES_EN
|
||||
return MEETS_ZH if meets else MISSES_ZH
|
||||
Reference in New Issue
Block a user