fix(report): fill the full-data edition and clear English Han

Add the interpretation pages from fields the engine already computed, project the annual tables the reader prints, and clear English Han on the four measured charts. Patyayini is not implemented. The generate button copy is unchanged.

This commit stays on the feature branch and is not pushed to staging.
This commit is contained in:
jesse-ux
2026-10-08 22:29:41 +08:00
committed by Jesse_Chen
parent 98c98aa535
commit 25d067bf12
11 changed files with 1958 additions and 71 deletions
+140 -34
View File
@@ -634,7 +634,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
return ';'.join(f"{key}={value}" for key, value in counts.items())
def _public_status_note(value) -> str:
mapping = {
zh_mapping = {
'partial_verified': '已生成',
'parameter_sensitive': '参数敏感',
'blocked': '需补资料',
@@ -647,6 +647,20 @@ def render_pl9_parity_markdown(packet: dict) -> str:
'blocked_external_callable': '外部回放待校对',
'superseded_by_annual_tajika_pack.lifetime_annual_chart_pages': '由生命周期图盘页承接',
}
en_mapping = {
'partial_verified': 'listed',
'parameter_sensitive': 'see the listed parameters',
'blocked': 'not supplied',
'verified': 'checked',
'match': 'matches',
'mismatch': 'needs review',
'not_applicable': 'not applicable',
'pyjhora_behavior_only': 'external behavior note',
'pyjhora_behavior_only / not_multiengine_parity': 'external behavior note',
'blocked_external_callable': 'external replay still to review',
'superseded_by_annual_tajika_pack.lifetime_annual_chart_pages': 'carried by the lifetime annual pages',
}
mapping = en_mapping if _lang == 'en' else zh_mapping
text = str(value or '').strip()
return mapping.get(text, text or '-')
@@ -827,7 +841,11 @@ def render_pl9_parity_markdown(packet: dict) -> str:
)
lines.extend([
'',
'星座主、星宿主、分主、分分主:这颗行星所在星座、星宿与 KP 细分各自的主星。力量比:六维力量与所需力量之比,1 以上为达标。',
(
'Sign lord, star lord, sub lord and sub-sub lord name the ruler of the planet sign, nakshatra and KP subdivision. Strength ratio compares Shadbala with the required strength. A ratio of 1 or more meets it.'
if _lang == 'en'
else '星座主、星宿主、分主、分分主:这颗行星所在星座、星宿与 KP 细分各自的主星。力量比:六维力量与所需力量之比,1 以上为达标。'
),
'',
])
moon_chart = d1_sheet.get('moon_chart') if isinstance(d1_sheet.get('moon_chart'), dict) else {}
@@ -1105,7 +1123,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
graha = advanced.get('graha_interpretation_pack') if isinstance(advanced.get('graha_interpretation_pack'), dict) else {}
pages = graha.get('planet_pages') if isinstance(graha.get('planet_pages'), list) else []
if pages:
lines.extend(['## 行星、星宿与宫位解释', ''])
lines.extend(['## Planets, Nakshatras and Houses' if _lang == 'en' else '## 行星、星宿与宫位解释', ''])
for page in pages:
if not isinstance(page, dict):
continue
@@ -1124,10 +1142,18 @@ def render_pl9_parity_markdown(packet: dict) -> str:
shadbala_planets = _dict(shadbala.get('planets'))
if shadbala_planets:
lines.extend([
'### p31-p33 Shadbala / Bhava Bala 摘要',
'### Shadbala / Bhava Bala Summary' if _lang == 'en' else '### p31-p33 Shadbala / Bhava Bala 摘要',
'',
f"- Shadbala 方法:{_cell(shadbala.get('method'))}",
f"- 外部边界:{_cell(shadbala.get('external_parity_boundary'))}",
(
f"- Shadbala method: {_cell(shadbala.get('method'))}"
if _lang == 'en'
else f"- Shadbala 方法:{_cell(shadbala.get('method'))}"
),
(
f"- External boundary: {_cell(shadbala.get('external_parity_boundary'))}"
if _lang == 'en'
else f"- 外部边界:{_cell(shadbala.get('external_parity_boundary'))}"
),
'',
'| Planet | Sthana | Dig | Kala | Chesta | Naisargika | Drik | Total | Required | BPHS minimum | Site band |',
'|--------|--------|-----|------|--------|------------|------|-------|----------|--------------|-----------|',
@@ -1202,7 +1228,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
# 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 十二宫力量',
'### Bhava Bala of the twelve houses' if _lang == 'en' else '### p32 Bhava Bala 十二宫力量',
'',
'| House | Sign | Lord | Bhavadhipati | Dig | Drishti | Total (Virupa) | Total (Rupa) |',
'|-------|------|------|--------------|-----|---------|----------------|--------------|',
@@ -1613,9 +1639,13 @@ def render_pl9_parity_markdown(packet: dict) -> str:
lines.extend([
'### p62-p74 Vimshottari Dasha',
'',
f"- Current Dasha:{_token(current.get('lord'))}({_cell(current.get('start'))} - {_cell(current.get('end'))})" if current else '- Current Dasha:blocked',
(
f"- Current Dasha: {_token(current.get('lord'))} ({_cell(current.get('start'))} - {_cell(current.get('end'))})"
if _lang == 'en'
else f"- Current Dasha:{_token(current.get('lord'))}({_cell(current.get('start'))} - {_cell(current.get('end'))})"
) if current else ('- Current Dasha: not supplied' if _lang == 'en' else '- Current Dasha:未列出'),
'',
'| 序号 | 主运 | 起始 | 结束 | 年数 |',
'| Index | Maha | Start | End | Years |' if _lang == 'en' else '| 序号 | 主运 | 起始 | 结束 | 年数 |',
'|------|------|------|------|------|',
])
for index, period in enumerate(vimshottari_timeline, start=1):
@@ -1640,11 +1670,11 @@ def render_pl9_parity_markdown(packet: dict) -> str:
if isinstance(pd_period, dict):
pd_rows.append((md_lord, ad_lord, pd_period))
if ad_rows:
lines.extend(['', '| Vimshottari AD | 主运 | 子运 | 起始 | 结束 |', '|--------------|------|------|------|------|'])
lines.extend(['', '| Vimshottari AD | Maha | Antar | Start | End |' if _lang == 'en' else '| Vimshottari AD | 主运 | 子运 | 起始 | 结束 |', '|--------------|------|------|------|------|'])
for index, (md_lord, period) in enumerate(ad_rows, start=1):
lines.append(f"| {index} | {_cell(_token(md_lord))} | {_cell(_token(period.get('lord')))} | {_period_start(period)} | {_period_end(period)} |")
if pd_rows:
lines.extend(['', '| Vimshottari PD | 主运 | 子运 | 次子运 | 起始 | 结束 |', '|--------------|------|------|--------|------|------|'])
lines.extend(['', '| Vimshottari PD | Maha | Antar | Pratyantar | Start | End |' if _lang == 'en' else '| Vimshottari PD | 主运 | 子运 | 次子运 | 起始 | 结束 |', '|--------------|------|------|--------|------|------|'])
for index, (md_lord, ad_lord, period) in enumerate(pd_rows, start=1):
lines.append(
f"| {index} | {_cell(_token(md_lord))} | {_cell(_token(ad_lord))} | {_cell(_token(period.get('lord')))} | "
@@ -1743,7 +1773,16 @@ def render_pl9_parity_markdown(packet: dict) -> str:
legacy_line = f'- 算法:{LEGACY_NOTE_ZH}'
if legacy_line not in lines:
lines.extend(['### Narayana Dasha', '', legacy_line, ''])
rath_lines = [line for line in _rath_lines(narayana_packet.get('rath')) if line]
rath_view = narayana_packet.get('rath')
rath_lines = [line for line in _rath_lines(rath_view) if line]
if _lang == 'en' and isinstance(rath_view, dict):
for row in _list(rath_view.get('mahadasha')):
if not isinstance(row, dict):
continue
zh_basis = row.get('dual_lord_basis')
en_basis = row.get('dual_lord_basis_en')
if zh_basis and en_basis:
rath_lines = [line.replace(str(zh_basis), str(en_basis)) for line in rath_lines]
if rath_lines and not any(str(line).startswith('- 并列:') for line in lines):
lines.extend(rath_lines)
lines.append('')
@@ -1932,17 +1971,28 @@ def render_pl9_parity_markdown(packet: dict) -> str:
lines.append('')
annual = timing.get('annual_tajika_pack') if isinstance(timing.get('annual_tajika_pack'), dict) else {}
if annual:
lines.extend(['## 年度图盘与 Tajika(PL9 p126-p155 对标)', ''])
lines.extend([
'## Annual Varshaphala and Tajika' if _lang == 'en' else '## 年度图盘与 Tajika(PL9 p126-p155 对标)',
'',
])
annual_chart = _dict(annual.get('annual_chart'))
annual_chart_data = _dict(annual_chart.get('data'))
solar_return = _dict(annual.get('solar_return'))
solar_return_data = _dict(solar_return.get('data') or solar_return)
if solar_return_data or annual_chart_data:
lines.extend([
'### p126-p127 Varshaphala 基础资料与年度行星表',
'### Varshaphala base data and annual planets' if _lang == 'en' else '### p126-p127 Varshaphala 基础资料与年度行星表',
'',
f"- Varshapravesha:{solar_return_data.get('dt_local') or solar_return_data.get('local_time') or solar_return_data.get('date') or '-'}",
f"- 年返上升:{_token(annual_chart_data.get('asc_sign'))}",
(
f"- Varshapravesha: {solar_return_data.get('dt_local') or solar_return_data.get('local_time') or solar_return_data.get('date') or '-'}"
if _lang == 'en'
else f"- Varshapravesha:{solar_return_data.get('dt_local') or solar_return_data.get('local_time') or solar_return_data.get('date') or '-'}"
),
(
f"- Solar-return Lagna: {_token(annual_chart_data.get('asc_sign'))}"
if _lang == 'en'
else f"- 年返上升:{_token(annual_chart_data.get('asc_sign'))}"
),
'',
])
year_lord = _dict(annual.get('year_lord'))
@@ -2041,8 +2091,8 @@ def render_pl9_parity_markdown(packet: dict) -> str:
lines.extend([
'### p132-p135 Mudda / Patyayini 年度 Dasha 表',
'',
'| balance profile | 主星 | 开始 | 结束 | 边界口径 |',
'|-----------------|------|------|------|----------|',
'| Balance profile | Lord | Start | End | Boundary |' if _lang == 'en' else '| balance profile | 主星 | 开始 | 结束 | 边界口径 |',
'|-----------------|------|-------|-----|----------|' if _lang == 'en' else '|-----------------|------|------|------|----------|',
])
for row in mudda_rows:
if not isinstance(row, dict):
@@ -2068,13 +2118,34 @@ def render_pl9_parity_markdown(packet: dict) -> str:
f"{_cell(row.get('boundary'))} | {_public_status_note(row.get('boundary_status'))} |"
)
lines.append('')
if not patyayini_rows:
lines.extend([
'Patyayini is not implemented in this report.' if _lang == 'en' else 'Patyayini 没有实现。',
'',
])
monthly_windows = _list(annual.get('monthly_windows'))
if monthly_windows:
lines.extend([
'### Monthly mudda windows' if _lang == 'en' else '### Mudda 月段窗口',
'',
'| Index | Lord | Months | Start | End |' if _lang == 'en' else '| 序号 | 主星 | 月数 | 开始 | 结束 |',
'|-------|------|--------|-------|-----|' if _lang == 'en' else '|------|------|------|------|------|',
])
for row in monthly_windows:
if not isinstance(row, dict):
continue
lines.append(
f"| {_cell(row.get('index'))} | {_cell(_token(row.get('lord')))} | "
f"{_cell(row.get('duration_months') or row.get('months'))} | {_cell(row.get('start'))} | {_cell(row.get('end'))} |"
)
lines.append('')
tajika_rows = _list(normalized.get('tajika_yoga_rows')) or _list(normalized.get('pyjhora_tajika_yoga_rows'))
if tajika_rows:
lines.extend([
'### p136-p138 Tajika Yoga Presence 对照表',
'',
'| Yoga | 结果 | 形成行星 | 相位运动(入相/分离) |',
'|------|------|----------|----------------------|',
'| Yoga | Result | Forming planets | Motion (applying/separating) |' if _lang == 'en' else '| Yoga | 结果 | 形成行星 | 相位运动(入相/分离) |',
'|------|--------|-----------------|--------------------------------|' if _lang == 'en' else '|------|------|----------|----------------------|',
])
for row in tajika_rows[:32]:
if not isinstance(row, dict):
@@ -2088,10 +2159,10 @@ def render_pl9_parity_markdown(packet: dict) -> str:
saham_rows = _list(normalized.get('saham_rows'))
if saham_rows:
lines.extend([
'### p130 Sahams 点位表',
'### Sahams' if _lang == 'en' else '### p130 Sahams 点位表',
'',
'| Saham | 星座 | 星座内度数 |',
'|-------|------|------------|',
'| Saham | Sign | Degree in sign |' if _lang == 'en' else '| Saham | 星座 | 星座内度数 |',
'|-------|------|----------------|' if _lang == 'en' else '|-------|------|------------|',
])
for row in saham_rows[:48]:
if not isinstance(row, dict):
@@ -2442,11 +2513,16 @@ def render_pl9_parity_markdown(packet: dict) -> str:
detected_yogas = _list(advanced_yoga.get('detected_yogas')) or _list(advanced_yoga.get('yogas'))
dosha_pack = _dict(advanced.get('dosha_remedy_pack'))
bhavesh_pack = _dict(advanced.get('bhavesh_interpretation_pack'))
if d1_houses or yogas_doshas or detected_yogas or bhavesh_pack or dosha_pack:
lines.extend(['## Bhavesh、Dosha 与 Yoga 解释页(PL9 p156-p189 对标)', ''])
yoga_pack = _dict(advanced.get('yoga_interpretation_pack'))
yoga_pages = _list(yoga_pack.get('pages'))
if d1_houses or yogas_doshas or detected_yogas or bhavesh_pack or dosha_pack or yoga_pages:
lines.extend([
'## Bhavesh, Dosha and Yoga' if _lang == 'en' else '## Bhavesh、Dosha 与 Yoga 解释页(PL9 p156-p189 对标)',
'',
])
if d1_houses and d1_planets:
lines.extend([
'### p156-p162 宫主(Bhavesh)落宫解释',
'### House lords (Bhavesh)' if _lang == 'en' else '### p156-p162 宫主(Bhavesh)落宫解释',
'',
'| House | Sign | Lord | Lord placement | Lord dignity | Dasha activation |',
'|-------|------|------|----------------|--------------|------------------|',
@@ -2473,14 +2549,18 @@ def render_pl9_parity_markdown(packet: dict) -> str:
bhavesh_pages = _list(bhavesh_pack.get('pages'))
if bhavesh_pages:
lines.extend([
'### Bhavesh 宫主逐宫解释',
'### House-lord interpretation' if _lang == 'en' else '### Bhavesh 宫主逐宫解释',
'',
])
for row in bhavesh_pages:
if not isinstance(row, dict):
continue
sections = _dict(row.get('sections'))
title = f"第{row.get('house')}宫主:{_token(row.get('lord'))}"
title = (
f"House {row.get('house')} lord: {_token(row.get('lord'))}"
if _lang == 'en'
else f"第{row.get('house')}宫主:{_token(row.get('lord'))}"
)
lines.extend([f'#### {title}', ''])
for key in ('house_context', 'lord_placement', 'result', 'context', 'activation'):
value = str(sections.get(key) or '').strip()
@@ -2516,7 +2596,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
dosha_rows = _list(dosha_pack.get('dosha_rows'))
if dosha_rows:
lines.extend([
'### Dosha 逐项事实与解释',
'### Dosha facts and interpretation' if _lang == 'en' else '### Dosha 逐项事实与解释',
'',
])
for row in dosha_rows:
@@ -2535,7 +2615,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
recommendations = _list(practices.get('recommendations'))
if recommendations:
lines.extend([
'### 传统辅助建议',
'### Traditional supportive notes' if _lang == 'en' else '### 传统辅助建议',
'',
'| 对象 | 建议 | 宝石/次数 |',
'|------|------|-----------|',
@@ -2587,13 +2667,39 @@ def render_pl9_parity_markdown(packet: dict) -> str:
'|------|----------|----------|-------------|-----------------|',
])
for row in group_rows:
effects = row.get('effects') if isinstance(row.get('effects'), list) else []
if _lang == 'en':
effects = row.get('effects_en') if isinstance(row.get('effects_en'), list) else []
if not effects and isinstance(row.get('effects'), list):
effects = row.get('effects')
yoga_name = row.get('name') or row.get('type') or row.get('name_cn')
combination = row.get('combination_en') or row.get('combination') or row.get('source_reference') or row.get('reason')
else:
effects = row.get('effects') if isinstance(row.get('effects'), list) else []
yoga_name = row.get('name_cn') or row.get('name') or row.get('type')
combination = row.get('combination') or row.get('source_reference') or row.get('reason')
lines.append(
f"| {_cell(row.get('name_cn') or row.get('name') or row.get('type'))} | {_cell(row.get('category') or row.get('subtype'))} | "
f"{_cell(row.get('strength'))} | {_cell(row.get('combination') or row.get('source_reference') or row.get('reason'))} | "
f"| {_cell(yoga_name)} | {_cell(row.get('category') or row.get('subtype'))} | "
f"{_cell(row.get('strength'))} | {_cell(combination)} | "
f"{_cell(_public_report_text(_brief(effects, 3) or row.get('interpretation') or row.get('summary')))} |"
)
lines.append('')
if yoga_pages:
lines.extend([
'### Yoga interpretation' if _lang == 'en' else '### 瑜伽解释',
'',
])
for row in yoga_pages:
if not isinstance(row, dict):
continue
name = str(row.get('name') or '').strip()
sections = _dict(row.get('sections'))
if not name:
continue
lines.extend([f'#### {name}', ''])
for key in ('detected', 'theme', 'result', 'activation'):
value = str(sections.get(key) or '').strip()
if value:
lines.extend([_public_report_text(value), ''])
supportive_raw = dosha_pack.get('supportive_practices')
supportive_practices = _list(supportive_raw)
if isinstance(supportive_raw, dict):
@@ -2620,7 +2726,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
dasha = timing.get('dasha_interpretation_pack') if isinstance(timing.get('dasha_interpretation_pack'), dict) else {}
dasha_pages = dasha.get('page_series') if isinstance(dasha.get('page_series'), list) else []
if dasha_pages:
lines.extend(['## 大运解释', ''])
lines.extend(['## Dasha Interpretation' if _lang == 'en' else '## 大运解释', ''])
for page in dasha_pages:
if not isinstance(page, dict):
continue