fix(report,chart): blank columns — drop Kranti, split Ashtakavarga house SAV, ascendant nakshatra, engine varga dignity, no duplicate charts (BUG-1199..1203)
- Report p33 table: no Kranti column (no producer; materials.kranti stays blocked). - Report p53 table: the "SAV" column was the with-Lagna total (386). Split in the reader into SAV / Lagna BAV / SAV + Lagna with a totals row; ashtakavarga.py is untouched (frozen rectification identity, ERR-115). - /api/chart ascendant carries nakshatra / pada / lord (_nakshatra_fields shared with the nine bodies); chart cache schema 4 -> 5. - /api/varga_full bodies carry dignity_level from varga_dignity_level, the function the Vimsopaka path now shares (A/B identical); the chart page shows the D1 status wording and no longer derives a three-level dignity itself. - Report page draws each chart once: the trailing block skips charts the body already fenced (same rule as the export); the birth-time caveat moves under the body's chart heading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0e856f9cb6
commit
ae18952e51
@@ -2575,7 +2575,7 @@ def _vedastro_runtime_fingerprint() -> dict:
|
||||
|
||||
def _build_api_chart_cache_payload(body: dict) -> dict:
|
||||
return {
|
||||
'cache_schema_version': 4,
|
||||
'cache_schema_version': 5,
|
||||
'birth': {
|
||||
'year': body.get('year'),
|
||||
'month': body.get('month'),
|
||||
@@ -7820,7 +7820,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
result = {}
|
||||
for varga in selected:
|
||||
key = f'D{varga.division}_{varga.varga_name}'
|
||||
result[key] = calc._calculate_single_varga(varga, planet_lons, asc_lon)
|
||||
result[key] = _load_local_module('varga_dignity').annotate_varga_dignity(calc._calculate_single_varga(varga, planet_lons, asc_lon))
|
||||
except KeyError as e:
|
||||
raise BadRequest('unsupported standard division; use custom mode for arbitrary D-N') from e
|
||||
except ValueError as e:
|
||||
|
||||
+33
-12
@@ -701,6 +701,20 @@ def _get_dignity_level(planet, sign, deg_in_sign=None, planets_data=None):
|
||||
return 'NEUTRAL'
|
||||
|
||||
|
||||
def varga_dignity_level(planet, varga_planets):
|
||||
"""Dignity of a body inside a divisional chart (Vimsopaka and the chart page alike).
|
||||
|
||||
The temporary half of the compound relationship is read from that varga's
|
||||
own positions, as the Vimsopaka path always has. Returns None when the body
|
||||
has no usable sign.
|
||||
"""
|
||||
pinfo = varga_planets.get(planet) if isinstance(varga_planets, dict) else None
|
||||
if not isinstance(pinfo, dict) or pinfo.get('sign') not in SIGN_LORDS:
|
||||
return None
|
||||
deg_in_sign = pinfo.get('degree_in_sign', pinfo.get('degree', 0) % 30)
|
||||
return _get_dignity_level(planet, pinfo['sign'], deg_in_sign, _build_dignity_context(varga_planets))
|
||||
|
||||
|
||||
def _get_planet_status_label(planet, sign, deg_in_sign=None, planets_data=None):
|
||||
"""Return the user-facing D1 dignity label for chart output."""
|
||||
return DIGNITY_LABELS.get(_get_dignity_level(planet, sign, deg_in_sign, planets_data), '中性')
|
||||
@@ -12228,6 +12242,15 @@ def _load_strict_evidence_collector():
|
||||
# ============================================================================
|
||||
# 公共星盘计算(供 chart/shadbala/ashtakavarga 共用,v3.4提取)
|
||||
# ============================================================================
|
||||
def _nakshatra_fields(sidereal_lon):
|
||||
"""Nakshatra, pada and lord of a sidereal longitude — planets and the ascendant alike."""
|
||||
nak_span = 360.0 / 27
|
||||
lon = sidereal_lon % 360
|
||||
pada = int((lon % nak_span) / (nak_span / 4)) + 1
|
||||
name, lord, _ = NAKSHATRA_LIST[int(lon / nak_span) % 27]
|
||||
return {"nakshatra": name, "nakshatra_pada": pada, "nakshatra_lord": lord}
|
||||
|
||||
|
||||
def compute_chart_data(year, month, day, hour, minute, lat, lon, tz, node_mode='mean', second=0, ayanamsa_name=None):
|
||||
"""计算星盘核心数据,返回 (result_dict, asc_idx, jd, ayanamsa)。node_mode: mean|true。"""
|
||||
if not HAS_SWE:
|
||||
@@ -12264,7 +12287,8 @@ def compute_chart_data(year, month, day, hour, minute, lat, lon, tz, node_mode='
|
||||
"degree_in_sign": round(deg_in_sign, 4),
|
||||
"degree_in_sign_raw": deg_in_sign,
|
||||
"lon": round(asc_deg, 4),
|
||||
"lord": SIGN_LORDS[asc_sign]}
|
||||
"lord": SIGN_LORDS[asc_sign],
|
||||
**_nakshatra_fields(asc_deg)}
|
||||
|
||||
for i in range(12):
|
||||
c = (asc_lon[i] - ayanamsa) % 360
|
||||
@@ -12273,7 +12297,6 @@ def compute_chart_data(year, month, day, hour, minute, lat, lon, tz, node_mode='
|
||||
"cusp_sign_cn": SIGNS_CN[SIGNS[si]], "cusp_degree": round(c, 4),
|
||||
"lord": SIGN_LORDS[SIGNS[si]]}
|
||||
|
||||
nak_span = 360.0 / 27
|
||||
planets_swe = {**BASE_PLANETS_SWE, 'Rahu': node_pid}
|
||||
calc_flags = getattr(swe, 'FLG_SWIEPH', 2)
|
||||
for pname, pid in planets_swe.items():
|
||||
@@ -12283,26 +12306,22 @@ def compute_chart_data(year, month, day, hour, minute, lat, lon, tz, node_mode='
|
||||
si = int(lon_p / 30); d_in_s = lon_p - si * 30; sign = SIGNS[si]
|
||||
retro = spd < 0
|
||||
house = ((si - asc_idx) % 12) + 1
|
||||
ni = int(lon_p / nak_span); pada = int((lon_p % nak_span) / (nak_span / 4)) + 1
|
||||
nak_n, nak_l, _ = NAKSHATRA_LIST[ni % 27]
|
||||
result["planets"][pname] = {
|
||||
"sign": sign, "sign_cn": SIGNS_CN[sign], "degree": round(lon_p, 4),
|
||||
"degree_raw": lon_p,
|
||||
"degree_in_sign": round(d_in_s, 4), "degree_in_sign_raw": d_in_s,
|
||||
"house": house, "status": "",
|
||||
"retrograde": retro, "speed": round(spd, 6),
|
||||
"nakshatra": nak_n, "nakshatra_pada": pada, "nakshatra_lord": nak_l}
|
||||
**_nakshatra_fields(lon_p)}
|
||||
if pname == 'Rahu':
|
||||
klon = (lon_p + 180) % 360; ksi = int(klon / 30); kd = klon - ksi * 30
|
||||
kni = int(klon / nak_span); kp = int((klon % nak_span) / (nak_span / 4)) + 1
|
||||
kn, kl, _ = NAKSHATRA_LIST[kni % 27]
|
||||
result["planets"]["Ketu"] = {
|
||||
"sign": SIGNS[ksi], "sign_cn": SIGNS_CN[SIGNS[ksi]],
|
||||
"degree": round(klon, 4), "degree_raw": klon,
|
||||
"degree_in_sign": round(kd, 4), "degree_in_sign_raw": kd,
|
||||
"house": ((ksi - asc_idx) % 12) + 1, "status": "",
|
||||
"retrograde": True, "speed": round(spd, 6),
|
||||
"nakshatra": kn, "nakshatra_pada": kp, "nakshatra_lord": kl}
|
||||
**_nakshatra_fields(klon)}
|
||||
except Exception as e:
|
||||
result["planets"][pname] = {"error": str(e)}
|
||||
|
||||
@@ -15689,10 +15708,12 @@ def cmd_full_reading(args):
|
||||
# 不使用 _dignity 字典(只有 Exalted/Debilitated/Own Sign)
|
||||
pinfo = vdata.get(pn, {})
|
||||
if isinstance(pinfo, dict) and 'sign' in pinfo:
|
||||
dignity_context = planets if div == 1 else _build_dignity_context(vdata)
|
||||
dl_key = _get_dignity_level(pn, pinfo['sign'],
|
||||
pinfo.get('degree_in_sign', pinfo.get('degree', 0) % 30),
|
||||
dignity_context)
|
||||
if div == 1:
|
||||
dl_key = _get_dignity_level(pn, pinfo['sign'],
|
||||
pinfo.get('degree_in_sign', pinfo.get('degree', 0) % 30),
|
||||
planets)
|
||||
else:
|
||||
dl_key = varga_dignity_level(pn, vdata)
|
||||
planet_vargas_input[pn][vt] = dignity_map.get(dl_key, DignityLevel.NEUTRAL)
|
||||
elif div == 1:
|
||||
# D1 直接用本命盘数据
|
||||
|
||||
@@ -35,6 +35,8 @@ _PHRASES: dict[str, str] = {
|
||||
"特殊上升与派生点补充": "Special Lagnas and Points (supplement)",
|
||||
"敏感点与特殊落点": "Sensitive Points and Special Placements",
|
||||
"本命与分盘北印度图盘": "Natal and Divisional Charts (North Indian)",
|
||||
"分盘对出生时间敏感,原始计算供核对,不单独增加结论的确定性。":
|
||||
"Divisional charts are sensitive to birth time. The raw calculations are here for checking and do not by themselves make a conclusion more certain.",
|
||||
" 数值位置表": " Position Table",
|
||||
"Shadbala / Bhava Bala 摘要": "Shadbala / Bhava Bala Summary",
|
||||
"Avastha 行星状态": "Avastha Planetary States",
|
||||
@@ -84,7 +86,7 @@ _PHRASES: dict[str, str] = {
|
||||
"Sign lord, star lord, sub lord and sub-sub lord: the rulers of the planet's sign, nakshatra and KP subdivisions. Strength ratio: Shadbala against the required strength; 1 or above meets it.",
|
||||
"| 对象 | 落座 | 落座度数 | 黄经 | 整宫宫位 |": "| Body | Sign | Degree in sign | Longitude | Whole-sign house |",
|
||||
"| House | Sign | Lord | Score | 关键因素 |": "| House | Sign | Lord | Score | Key factors |",
|
||||
"| House | Sign | SAV | BAV/承接 | Full with Lagna |": "| House | Sign | SAV | BAV / carry | Full with Lagna |",
|
||||
"| 合计 | - |": "| Total | - |",
|
||||
"| 序号 | 主运 | 起始 | 结束 | 年数 |": "| No. | Mahadasha | Start | End | Years |",
|
||||
"| Vimshottari AD | 主运 | 子运 | 起始 | 结束 |": "| Vimshottari AD | Mahadasha | Antardasha | Start | End |",
|
||||
"| Vimshottari PD | 主运 | 子运 | 次子运 | 起始 | 结束 |": "| Vimshottari PD | Mahadasha | Antardasha | Pratyantardasha | Start | End |",
|
||||
|
||||
@@ -1008,7 +1008,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
]
|
||||
ready_natal_specs = [spec for spec in natal_specs if spec[2]]
|
||||
if ready_natal_specs:
|
||||
lines.extend(['### p6-p17 本命与分盘北印度图盘', ''])
|
||||
lines.extend(['### p6-p17 本命与分盘北印度图盘', '', '分盘对出生时间敏感,原始计算供核对,不单独增加结论的确定性。', ''])
|
||||
for page, label, _ready in ready_natal_specs:
|
||||
lines.extend([f'##### p{page} · {label}', ''])
|
||||
# The original PL9 charts are visual, but their sign placements are
|
||||
@@ -1140,12 +1140,13 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
)
|
||||
lines.append('')
|
||||
lines.extend([
|
||||
'### p33 Declination / Kranti / Speed',
|
||||
'### p33 Declination / Speed',
|
||||
'',
|
||||
'| Planet | Degree | Declination | Kranti | Speed |',
|
||||
'|--------|--------|-------------|--------|-------|',
|
||||
'| Planet | Degree | Declination | Speed |',
|
||||
'|--------|--------|-------------|-------|',
|
||||
])
|
||||
kranti_planets = _dict(_dict(strengths.get('kranti_local')).get('planets'))
|
||||
# Kranti has no local producer (materials.kranti stays blocked), so
|
||||
# the reader shows no Kranti column rather than a column of dashes.
|
||||
speed_planets = _dict(_dict(strengths.get('speed_display_local')).get('planets'))
|
||||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn'):
|
||||
row = _dict(shadbala_planets.get(planet))
|
||||
@@ -1153,11 +1154,10 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
continue
|
||||
kala = _dict(row.get('kala_bala'))
|
||||
planet_row = _dict((packet.get('core_chart') or {}).get('planets', {}).get(planet)) if isinstance((packet.get('core_chart') or {}).get('planets'), dict) else {}
|
||||
kranti_degrees = _dict(kranti_planets.get(planet)).get('kranti_degrees')
|
||||
speed_dms = _dict(speed_planets.get(planet)).get('speed_dms') or _num(planet_row.get('speed'), 6)
|
||||
lines.append(
|
||||
f"| {_cell(_token(planet))} | {_cell(_degree(planet_row.get('degree_in_sign') or planet_row.get('longitude')))} | "
|
||||
f"{_cell(_degree(kala.get('declination')))} | {_cell(_degree(kranti_degrees))} | {_cell(speed_dms)} |"
|
||||
f"{_cell(_degree(kala.get('declination')))} | {_cell(speed_dms)} |"
|
||||
)
|
||||
lines.append('')
|
||||
avasthas = _dict(advanced.get('avasthas'))
|
||||
@@ -1329,15 +1329,38 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
lines.extend([
|
||||
'### p53 Ashtakavarga 完整宫位分数',
|
||||
'',
|
||||
'| House | Sign | SAV | BAV/承接 | Full with Lagna |',
|
||||
'|-------|------|-----|----------|-----------------|',
|
||||
'| House | Sign | SAV | Lagna BAV | SAV + Lagna |',
|
||||
'|-------|------|-----|-----------|-------------|',
|
||||
])
|
||||
# `house_scores_full[*].sav_score` is the with-Lagna total (386
|
||||
# scale), never plain SAV (BUG-1200). Split it from the engine's
|
||||
# per-sign SAV and the Lagna BAV; ashtakavarga.py stays untouched
|
||||
# because it belongs to the frozen rectification identity.
|
||||
sign_list = list(globals().get('SIGNS', []))
|
||||
lagna_bindus = _dict(_dict(ashtakavarga.get('bav')).get('Lagna')).get('bindus')
|
||||
splits = []
|
||||
for row in full_rows:
|
||||
sign = row.get('sign')
|
||||
sav_value = sav_scores.get(sign)
|
||||
lagna_value = None
|
||||
if isinstance(lagna_bindus, list) and len(lagna_bindus) == 12 and sign in sign_list:
|
||||
lagna_value = lagna_bindus[sign_list.index(sign)]
|
||||
consistent = (
|
||||
isinstance(sav_value, int) and isinstance(lagna_value, int)
|
||||
and sav_value + lagna_value == row.get('sav_score')
|
||||
)
|
||||
splits.append((sav_value, lagna_value) if consistent else None)
|
||||
split = all(splits)
|
||||
for row, parts in zip(full_rows, splits):
|
||||
sav_value, lagna_value = parts if split else (None, None)
|
||||
lines.append(
|
||||
f"| {_cell(row.get('house'))} | {_cell(_token(row.get('sign')))} | "
|
||||
f"{_cell(row.get('sav') or row.get('sav_score') or row.get('score'))} | "
|
||||
f"{_cell(row.get('bav') or row.get('bav_score') or row.get('bindus'))} | "
|
||||
f"{_cell(row.get('full_with_lagna') or row.get('full_with_lagna_house_score'))} |"
|
||||
f"{_cell(sav_value)} | {_cell(lagna_value)} | {_cell(row.get('sav_score'))} |"
|
||||
)
|
||||
if split:
|
||||
lines.append(
|
||||
f"| 合计 | - | {sum(parts[0] for parts in splits)} | "
|
||||
f"{sum(parts[1] for parts in splits)} | {sum(row['sav_score'] for row in full_rows)} |"
|
||||
)
|
||||
lines.append('')
|
||||
bav = _dict(ashtakavarga.get('bav'))
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Engine dignity on each body of a `/api/varga_full` divisional chart (BUG-1202).
|
||||
|
||||
The chart page's divisional tables show the same ten-level dignity as the D1
|
||||
"状态" column. The level is `jyotish_engine.varga_dignity_level`, the function
|
||||
the Vimsopaka path uses, so the page never carries a second rule set.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from jyotish_engine import varga_dignity_level
|
||||
|
||||
|
||||
def annotate_varga_dignity(varga: dict) -> dict:
|
||||
"""Add `dignity_level` to every body of one `_calculate_single_varga` result."""
|
||||
planets = varga.get("planets") if isinstance(varga, dict) else None
|
||||
if not isinstance(planets, dict):
|
||||
return varga
|
||||
for name, row in planets.items():
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
level = varga_dignity_level(name, planets)
|
||||
if level:
|
||||
row["dignity_level"] = level
|
||||
return varga
|
||||
Reference in New Issue
Block a user