fix(report,chart): blank columns — drop Kranti, split Ashtakavarga house SAV, ascendant nakshatra, engine varga dignity, no duplicate charts (BUG-1199..1203)
Independent Staging Quality Gate / validate (push) Successful in 15m27s
Independent Staging Quality Gate / publish (push) Successful in 3m42s

- 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:
Jesse_Chen
2026-10-03 09:11:15 +08:00
co-authored by Claude Opus 5.5
parent 0e856f9cb6
commit ae18952e51
28 changed files with 1011 additions and 346 deletions
+35 -12
View File
@@ -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'))