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
+72 -7
View File
@@ -212,6 +212,11 @@ def _sanitize_pl9_ai_density_markdown(markdown: str, title: str = "印度占星
text = text.replace("Natural friends", "天然友星")
text = text.replace("Natural enemies", "天然敌星")
text = text.replace("Temporary relation", "临时关系")
text = text.replace("Natal / KP Ruling Planets", "本命 / KP 征象星")
text = text.replace("Ruling Planets", "征象星")
text = text.replace("Sub-Planets", "副行星")
text = text.replace("Planetary", "行星")
text = text.replace("Planets", "行星")
text = text.replace("Planet", "行星")
text = text.replace("Sign", "星座")
text = text.replace("Degree in sign", "落座度数")
@@ -409,6 +414,9 @@ def _sanitize_pl9_ai_density_markdown(markdown: str, title: str = "印度占星
"Divisional Charts Numeric Tables": "分盘数值位置表",
"Sensitive Points": "敏感点",
"Upagraha and Sub-行星 Points": "Upagraha 与副行星点",
"Upagraha and 副行星 Points": "Upagraha 与副行星点",
"行星 Friendship Matrix": "行星友敌关系矩阵",
"行星 Friendship 矩阵源数据": "行星友敌关系矩阵源数据",
"六维力量 Components": "六维力量细项",
"Functional 自然吉星 / 自然凶星 Layer": "功能性吉凶星层",
"Bhava 年龄状态 宫位 强度": "十二宫力量",
@@ -1123,6 +1131,18 @@ def _sanitize_pl9_ai_density_markdown_en(markdown: str, title: str = "Vedic Astr
"表达受阻,可能表现为说话不顺、迟疑或难以清楚表达": "speech may be obstructed, hesitant or difficult to express clearly",
"思绪容易扰动,需留意情绪、压力与判断稳定性": "the mind may be disturbed; emotional pressure and judgment stability need care",
"体型偏瘦,体质较敏感": "leaner body type and sensitive constitution",
"水瓶": "Aquarius",
"天蝎": "Scorpio",
"白羊": "Aries",
"摩羯": "Capricorn",
"金牛": "Taurus",
"双子": "Gemini",
"巨蟹": "Cancer",
"狮子": "Leo",
"处女": "Virgo",
"天秤": "Libra",
"射手": "Sagittarius",
"双鱼": "Pisces",
"火": "Mars",
"水": "Mercury",
"木": "Jupiter",
@@ -2110,16 +2130,61 @@ _SITE_BHAVA_SCORE = re.compile(r"\(Benefic\):\s*\+2|\(Malefic\):\s*-1\.5|吉星\
_NEECHA_RAJA = re.compile(r"Neecha Bhanga Raja Yoga|落陷取消王瑜伽|坏座位消王")
def _apply_local_report_conventions(markdown: str) -> str:
def _apply_local_report_conventions(markdown: str, *, language: str = "zh") -> str:
"""Keep this site's report wording inside the upstream appendix."""
text = _NEECHA_RAJA.sub("落陷取消(Neecha Bhanga)", markdown)
if language == "en":
text = _NEECHA_RAJA.sub(
"Neecha Bhanga (debilitation cancellation, not a Raja Yoga)",
markdown,
)
text = re.sub(r"\bguaranteed\b", "certain", text, flags=re.IGNORECASE)
text = re.sub(r"\bguarantee\b", "certain outcome", text, flags=re.IGNORECASE)
text = re.sub(r"\bdivorce\b", "relationship strain", text, flags=re.IGNORECASE)
text = re.sub(r"\bmiscarriage\b", "a sensitive health topic", text, flags=re.IGNORECASE)
text = re.sub(r"\binfertility\b", "a sensitive fertility topic", text, flags=re.IGNORECASE)
else:
text = _NEECHA_RAJA.sub("落陷取消(Neecha Bhanga)", markdown)
text = text.replace("疾病诊断", "身体负荷话题")
text = text.replace("一定会", "有可能")
text = re.sub(r"(?<!不是)必然", "可能", text)
text = text.replace("再婚", "关系变化")
text = text.replace("几段关系", "关系主题")
text = text.replace("早夭", "早年体质")
text = text.replace("疾病", "身体负荷")
text = text.replace("落陷取消王", "落陷取消")
text = _SITE_BHAVA_SCORE.sub("", text)
return text
def _insert_opening_focus(markdown: str, focus: str) -> str:
"""Put the current-period focus directly under the title."""
first = next((line.strip() for line in focus.splitlines() if line.strip()), "")
if not first or first in markdown:
return markdown
match = re.search(r"^#\s+.+$", markdown, flags=re.MULTILINE)
if not match:
return focus.rstrip() + "\n\n" + markdown
return markdown[: match.end()] + "\n\n" + focus.rstrip() + "\n" + markdown[match.end() :]
def _interpretation_tools():
try:
from pl9_interpretation_packs import attach_full_data_interpretation, opening_focus_block
except ModuleNotFoundError: # pragma: no cover - package import compatibility
from scripts.pl9_interpretation_packs import attach_full_data_interpretation, opening_focus_block
return attach_full_data_interpretation, opening_focus_block
def render_full_data_markdown(packet: dict) -> str:
"""Upstream `pl9_ai_density` branch at 23be1807, on this site's parity body."""
if _pl9_report_language(packet) == "en":
attach_full_data_interpretation, opening_focus_block = _interpretation_tools()
if isinstance(packet, dict):
attach_full_data_interpretation(packet)
focus = opening_focus_block(packet)
else:
focus = ""
language = _pl9_report_language(packet if isinstance(packet, dict) else {})
if language == "en":
structured_appendix = re.sub(
r"^#\s+.*?(?:\n{2,}|\n)",
"",
@@ -2127,7 +2192,7 @@ def render_full_data_markdown(packet: dict) -> str:
count=1,
flags=re.DOTALL,
).strip()
english_markdown = render_pl9_parity_markdown(packet)
english_markdown = _insert_opening_focus(render_pl9_parity_markdown(packet), focus)
if structured_appendix:
english_markdown = (
english_markdown.rstrip()
@@ -2140,7 +2205,7 @@ def render_full_data_markdown(packet: dict) -> str:
english_markdown,
_pl9_english_report_title(packet),
)
return _apply_local_report_conventions(rendered)
return _apply_local_report_conventions(rendered, language="en")
zh_structured_markdown = _sanitize_pl9_ai_density_markdown(
_render_pl9_ai_density_raw_data_markdown_en(packet),
_pl9_public_report_title(packet),
@@ -2152,7 +2217,7 @@ def render_full_data_markdown(packet: dict) -> str:
count=1,
flags=re.DOTALL,
).strip()
zh_markdown = render_pl9_parity_markdown(packet)
zh_markdown = _insert_opening_focus(render_pl9_parity_markdown(packet), focus)
if zh_structured_appendix and "## 结构化资料附录" not in zh_markdown:
zh_markdown = (
zh_markdown.rstrip()
@@ -2162,7 +2227,7 @@ def render_full_data_markdown(packet: dict) -> str:
+ "\n\n</details>\n"
)
rendered = _sanitize_pl9_ai_density_markdown(zh_markdown, _pl9_public_report_title(packet))
return _apply_local_report_conventions(rendered)
return _apply_local_report_conventions(rendered, language="zh")
def read_full_data_domain(packet: Mapping, domain: str) -> dict:
File diff suppressed because it is too large Load Diff
+177 -1
View File
@@ -297,6 +297,8 @@ def normalize_zh_markdown_terms(markdown: str) -> str:
text = _normalize_zh_bhava_factor_text(text)
text = text.replace("Bhava 年龄状态 十二宫力量", "Bhava Bala 十二宫力量")
text = text.replace("Shadbala / Bhava 年龄状态", "Shadbala / Bhava Bala")
text = text.replace("六维力量 / Bhava 年龄状态", "六维力量 / Bhava Bala")
text = text.replace("行星s", "行星")
text = text.replace("星宿 Scheme", "星宿身体对应表")
text = _normalize_mula_gloss(_restore_zh_nakshatra_glosses(_collapse_duplicate_zh_parentheses(text)))
text = text.replace("### 是 Solar Return", "### 真实太阳返照")
@@ -559,6 +561,180 @@ def _normalize_zh_dasha_prose(markdown: str) -> str:
return text
# Fragments left after the longer English glossary. Longer keys are applied first.
_EN_RESIDUAL_HAN: tuple[tuple[str, str], ...] = (
("没有候选以吉相位照年盘", "no candidate aspects the annual Lagna by a benefic aspect"),
("按子运起点星座奇偶", "by the odd or even sign where the sub-period starts"),
("按七级比较所落星座", "compare the occupied signs across seven levels"),
("两主分落别的星座", "the two lords fall in other signs"),
("星座或其主所落星座", "the sign, or the sign its lord occupies"),
("实际位置判断是否", "judge from the actual position whether"),
("以吉相位照年盘", "aspect the annual Lagna by a benefic aspect"),
("不单独增加结论的确定性", "does not by itself make a conclusion more certain"),
("年数数到该", "the year count reaches that sign"),
("一律顺行", "always direct"),
("只倒算计都", "only Ketu is counted backward"),
("未进打分", "not used in the score"),
("时间轴结论", "timeline conclusion"),
("主三角色", "primary triangle role"),
("七轮裁定", "seven-round decision"),
("书内分歧", "the book itself disagrees"),
("待替换为", "still to be replaced by"),
("取为年主", "is taken as the year lord"),
("五分力量", "five-fold strength"),
("五类候选", "five candidate groups"),
("以凶相位照", "aspect by a malefic aspect"),
("阶段框架", "phase frame"),
("起始年龄", "start age"),
("结束年龄", "end age"),
("六重力量", "six-fold strength"),
("矩阵源数据", "matrix source"),
("相位矩阵", "aspect matrix"),
("灵魂使命", "soul aim"),
("核心自我", "core self"),
("人生最高目标", "highest aim"),
("主要谋士", "main advisor"),
("权力代理", "delegated authority"),
("兄弟姐妹", "siblings"),
("冒险精神", "appetite for risk"),
("家庭根基", "home base"),
("情感安全感", "emotional security"),
("祖先业力", "ancestral pattern"),
("传统传承", "inherited tradition"),
("智能成果", "crafted results"),
("竞争对手", "rivals"),
("转化力量", "capacity for change"),
("配偶特质", "spouse traits"),
("伴侣关系", "partnership"),
("书例校准", "book-example calibration"),
("八分法", "eightfold division"),
("要素数量", "factor count"),
("要素总数", "factor total"),
("综合性质", "overall nature"),
("基本年数", "base years"),
("并列参考", "side reference"),
("双主星", "dual lords"),
("子运方向", "sub-period direction"),
("身份按同", "identity follows the same"),
("明显迹象", "a clear indication"),
("严重程度", "severity"),
("轴线之间", "between the axis"),
("颗行星", "planets"),
("高峰期", "peak phase"),
("下降期", "setting phase"),
("最困难", "most difficult"),
("需结合", "read together with"),
("灵魂星", "soul planet"),
("兄弟星", "sibling planet"),
("母亲星", "mother planet"),
("父亲星", "father planet"),
("子女星", "child planet"),
("障碍星", "obstacle planet"),
("配偶星", "spouse planet"),
("旧算法", "previous algorithm"),
("按原书", "per the book"),
("起运", "period start"),
("从主", "from the lord"),
("级取", "the level selects"),
("两主", "two lords"),
("等分", "equal parts"),
("未采用", "not used"),
("星座奇偶", "odd or even sign"),
("的子运", "sub-period"),
("待评估", "not yet assessed"),
("进行medium", "underway"),
("当before", "current "),
("原始值", "raw value"),
("主层", "main level"),
("子层", "sub level"),
("年数", "years"),
("主星", "lord"),
("序号", "index"),
("起始", "start"),
("结束", "end"),
("星座", "sign"),
("相位", "aspect"),
("年度", "annual"),
("细项", "detail"),
("年返", "solar return"),
("摘要", "summary"),
("项目", "item"),
("数值", "value"),
("方法", "method"),
("校验", "check"),
("辅助", "supporting"),
("算法", "algorithm"),
("并列", "listed alongside"),
("顺序", "order"),
("修正", "adjustment"),
("最终", "final"),
("正文", "body text"),
("阶段", "phase"),
("是否", "whether"),
("只有", "only"),
("特殊", "special"),
("方向", "direction"),
("勇气", "courage"),
("母亲", "mother"),
("父亲", "father"),
("子女", "children"),
("创造力", "creativity"),
("学生", "students"),
("障碍", "obstacles"),
("质量", "quality"),
("顺行", "direct"),
("逆行", "retrograde"),
("比较", "compare"),
("另一", "another"),
("部分", "partial"),
("进行", "in progress"),
("占据planet尊贵", "occupied planet dignity"),
("占据planet数", "occupied planet count"),
("partial减weak", "partially reduced"),
("成立条件", "conditions"),
("不in煞", "not in a malefic house"),
("迹象", "indication"),
("相邻", "adjacent"),
("占据", "occupies"),
("尊贵", "dignity"),
("克", "restrains"),
("其", " its "),
("减", "reduced"),
("最", "most "),
("条", "items"),
("的", " of "),
("不", "not "),
("吉", "benefic"),
("数", "count"),
("煞", "malefic"),
("看", "see"),
("起", "from"),
("版", "edition"),
("级", "level"),
("主", "lord"),
("期", "phase"),
("星", "planet"),
("度", "degree"),
("从", "from"),
("取", "take"),
("所", "at"),
("较", "comparatively"),
("当", "when"),
("按", "by"),
("有", "present"),
("无", "absent"),
("表", "table"),
)
def apply_en_residual_han(markdown: str) -> str:
"""Translate Han fragments that survive the longer English glossary."""
text = markdown
for raw, public in sorted(_EN_RESIDUAL_HAN, key=lambda item: len(item[0]), reverse=True):
text = text.replace(raw, public)
return text
def normalize_en_markdown_terms(markdown: str) -> str:
text = markdown.replace("(", "(").replace(")", ")").replace("、", ", ")
text = (
@@ -576,7 +752,7 @@ def normalize_en_markdown_terms(markdown: str) -> str:
r"\1 \2",
text,
)
return _thicken_en_dasha_density(text)
return apply_en_residual_han(_thicken_en_dasha_density(text))
def _thicken_en_dasha_density(markdown: str) -> str:
+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