New reports request reader_main from upstream origin/main 23b9609e. KP and transit no longer copy an empty vars() dict, solar returns keep birth_asc_sign_idx, and ordinary projection deletes internal lines whole.
2505 lines
128 KiB
Python
2505 lines
128 KiB
Python
"""User-facing PL9 reader export.
|
||
|
||
Copied from upstream yinduzhanxing ``origin/main`` ``23b9609e330dbee70a4725bdc222b125d9b0e39f``
|
||
(reader edition introduced by ``f3b5196fecb23b4c71d60301bf424c697cb0e8cb``).
|
||
The task book expected tip ``19195f6e``; fetch showed ``23b9609e``, which contains that commit.
|
||
``f3b5196f..23b9609e`` reader closure lives here so ``scripts/jyotish_engine.py`` is not
|
||
rewritten in place (concession 1). Algorithm text is unchanged except ``_bind_engine_symbols``:
|
||
``render_pl9_parity_markdown`` reads ``SIGNS`` / ``SIGNS_CN`` through ``globals()``, which only
|
||
works in the engine module.
|
||
|
||
Upstream ``_pl9_export_markdown_for_edition`` also serves ``pl9_ai_density``. That branch needs
|
||
``_render_pl9_ai_density_raw_data_markdown_en`` and the English density sanitizers, which are
|
||
outside the ``reader_main`` closure and are not ported. ``reader_main`` and the reference
|
||
default match the upstream branches.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
|
||
SIGNS: list[str] = []
|
||
SIGNS_CN: dict[str, str] = {}
|
||
|
||
|
||
def _bind_engine_symbols() -> None:
|
||
"""Point sign tables at the live engine module without importing it at load time."""
|
||
global SIGNS, SIGNS_CN
|
||
try:
|
||
from scripts import jyotish_engine as engine
|
||
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution
|
||
import jyotish_engine as engine
|
||
SIGNS = list(engine.SIGNS)
|
||
SIGNS_CN = dict(engine.SIGNS_CN)
|
||
|
||
|
||
def _reference_markdown(packet: dict) -> str:
|
||
try:
|
||
from scripts import jyotish_engine as engine
|
||
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution
|
||
import jyotish_engine as engine
|
||
return engine.render_pl9_markdown(packet)
|
||
|
||
|
||
def _pl9_export_markdown_for_edition(packet: dict, edition: str) -> str:
|
||
"""Dispatch reader_main vs the reference renderer.
|
||
|
||
Upstream ``23b9609e`` ``scripts/jyotish_engine.py`` lines 13099-13145.
|
||
``pl9_ai_density`` is not ported; see the module docstring.
|
||
"""
|
||
_bind_engine_symbols()
|
||
normalized = str(edition or "").strip()
|
||
if normalized == "reader_main":
|
||
return _render_pl9_user_markdown(packet)
|
||
if normalized in {"pl9_parity", "pl9_fixed_204"}:
|
||
return render_pl9_parity_markdown(packet)
|
||
return _reference_markdown(packet)
|
||
|
||
|
||
def _pl9_public_report_title(packet: dict) -> str:
|
||
"""Return the user-facing report title without edition or parity labels."""
|
||
|
||
birth = packet.get('birth_info') if isinstance(packet, dict) else {}
|
||
if not isinstance(birth, dict):
|
||
birth = {}
|
||
name = str(birth.get('name') or birth.get('user_name') or '').strip()
|
||
return f"{name}的印度占星星盘报告" if name else "印度占星星盘报告"
|
||
|
||
|
||
def _pl9_english_report_title(packet: dict) -> str:
|
||
"""Return the English user-facing report title."""
|
||
|
||
birth = packet.get('birth_info') if isinstance(packet, dict) else {}
|
||
if not isinstance(birth, dict):
|
||
birth = {}
|
||
name = str(birth.get('name') or birth.get('user_name') or '').strip()
|
||
return f"{name}'s Vedic Astrology Chart Report" if name else "Vedic Astrology Chart Report"
|
||
|
||
|
||
def _pl9_report_language(packet: dict) -> str:
|
||
language = str((packet or {}).get('report_language') or '').strip().lower()
|
||
return language if language in {'zh', 'en'} else 'zh'
|
||
|
||
|
||
def _strip_pl9_user_engineering_markers(markdown: str) -> str:
|
||
"""Remove internal audit/profile vocabulary from user-facing report text."""
|
||
|
||
text = markdown
|
||
replacements = {
|
||
"parameter_sensitive / unverified": "参考",
|
||
"parameter_sensitive": "参考",
|
||
"partial_verified": "已列出",
|
||
"pyjhora_behavior_only / not_multiengine_parity": "参考",
|
||
"pyjhora_behavior_only": "参考",
|
||
"restricted_or_unclosed": "参考",
|
||
"not_applicable": "未列",
|
||
"blocked_and_conflict_fields_cannot_generate_final_predictions": "仅列资料",
|
||
"blocked": "未列",
|
||
"profile_sensitive": "参考",
|
||
"producer": "计算项",
|
||
"Producer": "计算项",
|
||
"source_path": "来源",
|
||
"schema": "结构",
|
||
"audit": "核对",
|
||
"status": "状态",
|
||
"profile": "计算设置",
|
||
"Profile": "计算设置",
|
||
"Status": "状态",
|
||
"private display": "显示口径",
|
||
"exact parity": "精确对照",
|
||
"PL9-compatible p51-p53 source-bound 口径": "本报告采用的 Ashtakavarga 口径",
|
||
"PL9-compatible": "本报告采用",
|
||
"source-bound": "已核对",
|
||
"type=partial": "type=局部",
|
||
"=partial": "=局部",
|
||
"PL9 对标主册": "印度占星星盘报告",
|
||
"PL9 风格专业排盘导出": "印度占星星盘报告",
|
||
"Applicable Benefic Yogas": "可用吉性瑜伽",
|
||
"Applicable Mixed Yogas": "可用混合型瑜伽",
|
||
"Applicable Malefic Yogas": "可用凶性瑜伽",
|
||
"Applicable Benefic / Mixed / Malefic Yogas": "可用瑜伽总表",
|
||
"Applicable Yogas": "可用瑜伽",
|
||
"Tajika Yoga Presence 对照表": "Tajika 瑜伽呈现对照表",
|
||
"Tajika Yoga Presence 表": "Tajika 瑜伽呈现表",
|
||
"Tajika Presence": "Tajika 呈现",
|
||
"Yoga detected count": "检出的瑜伽数量",
|
||
"Context layers": "参考层",
|
||
"| Yoga | Category | Strength | Combination | Effects / notes |": "| 瑜伽 | 类别 | 强度 | 组合条件 | 作用说明 |",
|
||
"|------|----------|----------|-------------|-----------------|": "|------|------|------|----------|----------|",
|
||
"Tajika Strength": "Tajika 力量",
|
||
"Strength": "强度",
|
||
"annual_tajika_pack": "年度结构",
|
||
"kp_monthly_report_packet": "月度重点",
|
||
"dispositor": "定位星",
|
||
"functional_role_not_returned": "未单独列出",
|
||
"未返回": "未列",
|
||
}
|
||
for raw, public in replacements.items():
|
||
text = text.replace(raw, public)
|
||
nakshatra_terms = {
|
||
"Ashwini": "阿湿毗尼(Ashwini)",
|
||
"Bharani": "巴拉尼(Bharani)",
|
||
"Krittika": "基利提卡(Krittika)",
|
||
"Rohini": "罗希尼(Rohini)",
|
||
"Mrigashira": "鹿首(Mrigashira)",
|
||
"Ardra": "阿尔德拉(Ardra)",
|
||
"Punarvasu": "普那婆苏(Punarvasu)",
|
||
"Pushya": "普沙(Pushya)",
|
||
"Ashlesha": "阿什列沙(Ashlesha)",
|
||
"Magha": "摩伽(Magha)",
|
||
"Purva Phalguni": "前破伽(Purva Phalguni)",
|
||
"Uttara Phalguni": "后破伽(Uttara Phalguni)",
|
||
"Hasta": "哈斯塔(Hasta)",
|
||
"Chitra": "吉多罗(Chitra)",
|
||
"Swati": "斯瓦蒂(Swati)",
|
||
"Vishakha": "毗舍佉(Vishakha)",
|
||
"Anuradha": "阿奴罗陀(Anuradha)",
|
||
"Jyeshtha": "杰耶什塔(Jyeshtha)",
|
||
"Mula": "Mula(根、本源)",
|
||
"Purva Ashadha": "前阿沙陀(Purva Ashadha)",
|
||
"Uttara Ashadha": "后阿沙陀(Uttara Ashadha)",
|
||
"Shravana": "室罗伐那(Shravana)",
|
||
"Dhanishta": "陀尼湿陀(Dhanishta)",
|
||
"Shatabhisha": "百药宿(Shatabhisha)",
|
||
"Purva Bhadrapada": "前跋陀罗(Purva Bhadrapada)",
|
||
"Uttara Bhadrapada": "后跋陀罗(Uttara Bhadrapada)",
|
||
"Revati": "雷瓦蒂(Revati)",
|
||
}
|
||
for raw, public in sorted(nakshatra_terms.items(), key=lambda item: len(item[0]), reverse=True):
|
||
text = re.sub(rf"(?<![((A-Za-z]){re.escape(raw)}(?![A-Za-z))])", public, text)
|
||
glossary_terms = {
|
||
"Rahu (North Node)": "北交点/罗睺",
|
||
"Ketu (South Node)": "南交点/计都",
|
||
"Great Friend": "极友",
|
||
"Friendly Sign": "友好星座",
|
||
"Own Sign": "入庙",
|
||
"Enemy Sign": "敌对星座",
|
||
"Debilitated": "落陷",
|
||
"Exalted": "入旺",
|
||
"Neutral": "中性",
|
||
"Paravatamsa 分盘等级": "Paravatamsa",
|
||
"Simhasanamsa 分盘等级": "Simhasanamsa",
|
||
"Paravatamsa": "Paravatamsa",
|
||
"Simhasanamsa": "Simhasanamsa",
|
||
"Shadbala": "六维力量",
|
||
"Vimsopaka": "二十分力量",
|
||
"Dasha": "大运",
|
||
"Nakshatra": "星宿",
|
||
"conjunction": "合相",
|
||
"special": "特殊组合",
|
||
"kalatra": "婚恋",
|
||
"solar_yoga": "太阳瑜伽",
|
||
"lunar_yoga": "月亮瑜伽",
|
||
"durbhaga": "不利组合",
|
||
"ari": "障碍组合",
|
||
"moderate": "中等",
|
||
"common": "常见",
|
||
"occasional": "偶见",
|
||
"raja": "王权/事业",
|
||
"dhana": "财富",
|
||
"putra": "子女/创造",
|
||
"ayur": "寿命/健康",
|
||
"auspicious": "吉性",
|
||
"nabhasa": "天象格局",
|
||
"surya": "太阳类",
|
||
"chandra": "月亮类",
|
||
"health": "健康",
|
||
"travel": "迁移/出行",
|
||
"asha": "愿望/收益",
|
||
"moksha": "解脱/内在",
|
||
"aryama": "贵人/助力",
|
||
"Sun": "太阳",
|
||
"Moon": "月亮",
|
||
"Mars": "火星",
|
||
"Mercury": "水星",
|
||
"Jupiter": "木星",
|
||
"Venus": "金星",
|
||
"Saturn": "土星",
|
||
"Rahu": "北交点/罗睺",
|
||
"Ketu": "南交点/计都",
|
||
"Aries": "白羊座",
|
||
"Taurus": "金牛座",
|
||
"Gemini": "双子座",
|
||
"Cancer": "巨蟹座",
|
||
"Leo": "狮子座",
|
||
"Virgo": "处女座",
|
||
"Libra": "天秤座",
|
||
"Scorpio": "天蝎座",
|
||
"Sagittarius": "射手座",
|
||
"Capricorn": "摩羯座",
|
||
"Aquarius": "水瓶座",
|
||
"Pisces": "双鱼座",
|
||
}
|
||
for raw, public in sorted(glossary_terms.items(), key=lambda item: len(item[0]), reverse=True):
|
||
text = re.sub(rf"(?<![A-Za-z]){re.escape(raw)}(?![A-Za-z])", public, text)
|
||
text = re.sub(r"\b[a-zA-Z_]+_profile\b", "计算口径", text)
|
||
text = re.sub(r"\b[a-zA-Z_]+_status\b", "状态", text)
|
||
text = re.sub(r"\b[a-zA-Z_]+_producer\b", "计算项", text)
|
||
text = re.sub(r"\b[a-zA-Z_]+_path\b", "来源", text)
|
||
text = re.sub(r"(?m)^(#{2,6}\s*)p\d+(?:-p\d+)?\s*[·.-]?\s*", r"\1", text)
|
||
text = re.sub(r"\s*\(PL9 p\d+(?:-p\d+)?[^)]*\)", "", text)
|
||
text = re.sub(r"\s*(PL9 p\d+(?:-p\d+)?[^)]*)", "", text)
|
||
natural_replacements = {
|
||
"这条链用于追踪该落点如何接回本命结构。": "定位星所在宫位会继续说明这颗星的表达方向。",
|
||
"星宿主与 Dasha 激活共同细化表达方式,不单独决定结果。": "星宿主会进一步修饰表达方式,并与 Dasha 节奏合看。",
|
||
"力量只用于调节该落点的承载度,不能覆盖宫主、星宿与时间层。": "力量信息用于判断表现强弱,并与宫主、星宿和 Dasha 节奏合看。",
|
||
"本页先作为本命结构背景保留。": "其主题以本命基础背景为主。",
|
||
"本页先保留为本命结构。": "其主题以本命基础背景为主。",
|
||
"当前上升口径": "当前上升",
|
||
"上升口径": "上升",
|
||
"活动 Dasha 主导层": "Dasha 主导层",
|
||
"活动 Dasha lord 集合": "Dasha 主导层",
|
||
"证据标签:": "说明:",
|
||
"年度 大运 表": "年度大运表",
|
||
"Annual 大运 阶段表": "年度大运阶段表",
|
||
"Annual 大运 结果解释": "年度大运结果解释",
|
||
"大运 节奏": "大运节奏",
|
||
"大运 主导层": "大运主导层",
|
||
"大运 lord": "大运主星",
|
||
"当前 大运": "当前大运",
|
||
"活动 大运": "活动大运",
|
||
"行星、星宿与宫位长页解释": "行星、星宿与宫位解释",
|
||
"True 普沙(Pushya)": "True Pushya",
|
||
"Excessive sensuality, libidinous nature": "感官欲望较强,关系中需要节制与稳定",
|
||
"Sharp intellect, good communication skills, government service": "思维敏捷,沟通能力较好,也可能与行政、制度或公共事务相关",
|
||
"Lean/thin body, frail constitution": "体型偏瘦,体质较敏感",
|
||
"Mental disturbance, confusion, erratic behavior": "思绪容易扰动,需留意情绪、压力与判断稳定性",
|
||
"Speech impediment, stammering, difficulty expressing": "表达受阻,可能表现为说话不顺、迟疑或难以清楚表达",
|
||
"Lives by agriculture, helpful, amasses wealth": "适合通过土地、资源、生产性事务或稳定积累获得成果,也有助人倾向",
|
||
"Dharidhra:": "贫困瑜伽:",
|
||
"Dharidhra Yoga": "贫困瑜伽",
|
||
"Lagna lord in a dry sign": "上升主落在干燥星座",
|
||
"7 visible planets occupy exactly 4 distinct signs": "七颗可见行星分布在四个星座中",
|
||
"Budha-Aditya conjunction": "水日合相",
|
||
"Budha-Aditya": "水日",
|
||
" Yoga(": "瑜伽(",
|
||
" Yoga": "瑜伽",
|
||
"{planets}": "行星",
|
||
"太阳 and 水星": "太阳与水星",
|
||
"金星 and 水星": "金星与水星",
|
||
"Lagna": "上升",
|
||
"Kendra": "角宫",
|
||
"Trikona": "三方宫",
|
||
"Trine": "三方宫",
|
||
"Navamsa": "九分盘",
|
||
"Maraka": "死亡/损耗宫",
|
||
"Dusthana": "凶宫",
|
||
"| Topic | Practice | Gem / count | Notes |": "| 主题 | 建议 | 宝石/次数 | 说明 |",
|
||
"|-------|----------|-------------|-------|": "|------|------|-----------|------|",
|
||
"Mangal Dosha": "火星婚姻煞",
|
||
"Kaal Sarp Dosha": "时蛇煞",
|
||
"Shanti Puja": "平息仪式",
|
||
"Kumbha Vivah": "水瓶婚仪式",
|
||
"Mangal Chandika Path": "火星/旃迪诵读",
|
||
"Red Coral": "红珊瑚",
|
||
"in the same house": "同宫",
|
||
"本页是基于本仓实际盘面字段生成的原创解释,": "",
|
||
"不复制 PL9 的行星落宫文案,也不以单颗行星断定具体事件。": "",
|
||
"Raman 核心子规则状态:support_only_candidate。": "",
|
||
"该页只落实宫位和宫主的字段绑定,不执行无权重合同的综合判断,不生成关系、健康、死亡或具体事件结论。": "这一宫位需要结合宫主位置、尊贵、相位、分盘和 Dasha 节奏阅读。",
|
||
"不能由单一宫主落点替代这些层。": "需要合并阅读。",
|
||
"不能覆盖": "需要结合",
|
||
"不单独决定结果": "需要合并阅读",
|
||
}
|
||
for raw, public in natural_replacements.items():
|
||
text = text.replace(raw, public)
|
||
cleanups = {
|
||
"Special 上升s and Points": "特殊上升与派生点",
|
||
"Special 上升s": "特殊上升",
|
||
"月亮 Chart": "月亮盘",
|
||
"上升 角宫di Rashi 大运": "Lagna Kendradi Rashi 大运",
|
||
"Ashtakavarga Shodhya /瑜伽 Pinda 原始值": "Ashtakavarga Shodhya / Yoga Pinda 原始值",
|
||
"Patyayini / Patyamsha ending-date 表": "Patyayini / Patyamsha 结束日期表",
|
||
"Results of the Dhayya of Sadhesati": "Sadhesati Dhayya 结果",
|
||
"Results of Laghu Kalyani Dhayya and Kantaka 土星": "Laghu Kalyani Dhayya 与 Kantaka Saturn 结果",
|
||
"### / p45-p47 相位矩阵": "### 相位矩阵",
|
||
}
|
||
for raw, public in cleanups.items():
|
||
text = text.replace(raw, public)
|
||
text = re.sub(r"Results of (.*?) as the Year Lord", r"年主\1的结果", text)
|
||
text = text.replace("Results of Muntha in the Sign of a Graha", "Muntha 落入行星星座的结果")
|
||
text = text.replace("Results of Munthesh", "Munthesh 结果")
|
||
text = text.replace("Results of Muntha", "Muntha 结果")
|
||
text = text.replace("field_状态", "字段状态")
|
||
text = text.replace("Dasha", "大运")
|
||
text = re.sub(r"(?<=[\u4e00-\u9fff])\s+大运", "大运", text)
|
||
text = re.sub(r"大运\s+(?=[\u4e00-\u9fff])", "大运", text)
|
||
text = re.sub(r"(太阳|月亮|火星|水星|木星|金星|土星|北交点/罗睺|南交点/计都)\1", r"\1", text)
|
||
return text
|
||
|
||
|
||
def _sanitize_pl9_public_markdown(markdown: str) -> str:
|
||
"""Hide engineering-only confidence/status tokens from the PL9 reader volume."""
|
||
replacements = {
|
||
"parameter_sensitive / unverified": "待核对",
|
||
"parameter_sensitive": "参考",
|
||
"partial_verified": "已列出",
|
||
"pyjhora_behavior_only / not_multiengine_parity": "参考",
|
||
"restricted_or_unclosed": "待核对",
|
||
"not_applicable": "未列",
|
||
"blocked": "未列",
|
||
"blocked_and_conflict_fields_cannot_generate_final_predictions": "仅列资料",
|
||
"profile": "口径",
|
||
"Profile": "口径",
|
||
}
|
||
text = markdown
|
||
for raw, public in replacements.items():
|
||
text = text.replace(raw, public)
|
||
return _strip_pl9_user_engineering_markers(text)
|
||
|
||
|
||
def _render_pl9_user_markdown(packet: dict) -> str:
|
||
"""Render the user-facing PL9-style main volume without parity/audit scaffolding."""
|
||
|
||
text = render_pl9_parity_markdown(packet)
|
||
title = _pl9_public_report_title(packet)
|
||
replacements = {
|
||
"# PL9 对标主册": f"# {title}",
|
||
"(PL9 p1-p17)": "",
|
||
"(PL9 p18-p29 对标)": "",
|
||
"(PL9 p30-p60 对标)": "",
|
||
"(PL9 p61-p120 对标)": "",
|
||
"(PL9 p121-p125 对标)": "",
|
||
"(PL9 p126-p155 对标)": "",
|
||
"(PL9 p156-p189 对标)": "",
|
||
"(PL9 p190-p204 对标)": "",
|
||
"PL9 p": "第",
|
||
"PL9": "经典印度占星",
|
||
"参数敏感": "需结合命盘整体阅读",
|
||
"外部行为参考": "参考规则",
|
||
"待补资料": "未单独列出",
|
||
"missing_in_local": "未单独列出",
|
||
"另参": "未单独列出",
|
||
"external_mudda_start_boundary": "参考起点",
|
||
"dasha_beginning_dates": "起始日期",
|
||
"dasha_ending_dates": "结束日期",
|
||
"生成模块": "来源",
|
||
"结构": "资料",
|
||
"校验": "核对",
|
||
"资料路径": "资料来源",
|
||
"候选承接面": "重点议题",
|
||
"不将单一组合写成确定事件": "用于说明阶段主题",
|
||
"{planets}": "相关行星",
|
||
"natural_benefic": "自然吉星",
|
||
"natural_malefic": "自然凶星",
|
||
"functional_benefic": "功能吉星",
|
||
"functional_malefic": "功能凶星",
|
||
"functional_neutral": "功能中性",
|
||
"functional_role_not_returned": "功能属性未单独返回",
|
||
"absolute Virupas; precise Sthana/Dig/Kala/Drik; bounded BPHS Chesta": "以 Virupa 为单位,综合 Sthana、Dig、Kala、Drik 与 BPHS Chesta",
|
||
"Chesta remains cross-engine method-conflicted; totals inherit that boundary.": "Chesta Bala 的算法口径会影响总分,阅读时以同一口径内比较为主。",
|
||
"Great Friend": "大友",
|
||
"Friendly Sign": "友好星座",
|
||
"Debilitated": "落陷",
|
||
"Neutral": "中性",
|
||
"Paravatamsa": "Paravatamsa",
|
||
"Simhasanamsa": "Simhasanamsa",
|
||
"dispositor": "定位星",
|
||
";功能性角色为待补充": "",
|
||
"功能性角色为待补充。": "",
|
||
}
|
||
for raw, public in replacements.items():
|
||
text = text.replace(raw, public)
|
||
text = re.sub(r"^#\s+.*$", f"# {title}", text, count=1, flags=re.MULTILINE)
|
||
drop_fragments = (
|
||
"本册只编排原版",
|
||
"研究性证据",
|
||
"审计",
|
||
"producer",
|
||
"PyJHora",
|
||
"source_path",
|
||
"schema",
|
||
"blocked",
|
||
"parameter_sensitive",
|
||
"native producer",
|
||
"Report Layers",
|
||
"Worksheet Index",
|
||
"Report Pack Manifest",
|
||
"发布状态",
|
||
"质量检查",
|
||
"Chart Identity",
|
||
"不自动进入公开发布",
|
||
"对标主册",
|
||
)
|
||
cleaned_lines: list[str] = []
|
||
for raw_line in text.splitlines():
|
||
if any(fragment in raw_line for fragment in drop_fragments):
|
||
continue
|
||
line = re.sub(r"p\d+-p\d+\s*", "", raw_line)
|
||
line = re.sub(r"p\d+\s*[·.-]\s*", "", line)
|
||
line = re.sub(r"^#+\s*p\d+\s*", lambda match: match.group(0).replace(match.group(0).split()[-1], ""), line)
|
||
line = line.replace(" · ", " ")
|
||
cleaned_lines.append(line.rstrip())
|
||
cleaned = "\n".join(cleaned_lines)
|
||
cleaned = cleaned.replace("{planets}", "相关行星")
|
||
cleaned = _strip_pl9_user_engineering_markers(cleaned)
|
||
cleaned = re.sub(r"^#\s+.*$", f"# {title}", cleaned, count=1, flags=re.MULTILINE)
|
||
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned).strip() + "\n"
|
||
return cleaned
|
||
|
||
|
||
def render_pl9_parity_markdown(packet: dict) -> str:
|
||
"""Render only PL9 page-family material admitted to the parity main volume."""
|
||
_bind_engine_symbols()
|
||
def _unwrap(raw_value):
|
||
if isinstance(raw_value, dict) and 'result' in raw_value and 'response_envelope' in raw_value:
|
||
return raw_value.get('result')
|
||
return raw_value
|
||
|
||
def _list(raw_value):
|
||
raw_value = _unwrap(raw_value)
|
||
if isinstance(raw_value, list):
|
||
return raw_value
|
||
if isinstance(raw_value, tuple):
|
||
return list(raw_value)
|
||
if isinstance(raw_value, dict) and isinstance(raw_value.get('result'), list):
|
||
return raw_value.get('result')
|
||
return []
|
||
|
||
def _dict(raw_value):
|
||
raw_value = _unwrap(raw_value)
|
||
return raw_value if isinstance(raw_value, dict) else {}
|
||
|
||
def _cell(value) -> str:
|
||
if isinstance(value, dict):
|
||
parts = []
|
||
for key, item in value.items():
|
||
if key in {
|
||
'status',
|
||
'状态',
|
||
'source_path',
|
||
'schema',
|
||
'execution_status',
|
||
'confidence_status',
|
||
'boundary_status',
|
||
'review_status',
|
||
'authority_status',
|
||
'raw_root',
|
||
'method',
|
||
'note',
|
||
'iterations',
|
||
'error_deg',
|
||
}:
|
||
continue
|
||
if item in (None, '', [], {}):
|
||
continue
|
||
if isinstance(item, dict):
|
||
nested = []
|
||
for nested_key, nested_item in item.items():
|
||
if nested_key in {'raw_root', 'method', 'note', 'iterations', 'error_deg', 'status', '状态'}:
|
||
continue
|
||
if nested_item not in (None, '', [], {}):
|
||
nested.append(f"{nested_key}: {nested_item}")
|
||
if nested:
|
||
parts.append(f"{key}: " + ','.join(nested))
|
||
continue
|
||
if isinstance(item, (list, tuple)):
|
||
item = ','.join(str(entry) for entry in item if entry not in (None, '', [], {}))
|
||
parts.append(f"{key}: {item}")
|
||
value = ';'.join(parts) if parts else '-'
|
||
elif isinstance(value, (list, tuple)):
|
||
value = ';'.join(str(item) for item in value if item not in (None, '', [], {}))
|
||
text = str(value if value not in (None, '') else '-').replace('\n', ' ').strip()
|
||
return text.replace('|', '/')
|
||
|
||
def _brief(items, limit=2) -> str:
|
||
values = [str(item).strip() for item in _list(items) if str(item).strip()]
|
||
return ';'.join(values[:limit]) if values else '-'
|
||
|
||
def _token(value) -> str:
|
||
label_map = {
|
||
'Sun': '太阳',
|
||
'Moon': '月亮',
|
||
'Mars': '火星',
|
||
'Mercury': '水星',
|
||
'Jupiter': '木星',
|
||
'Venus': '金星',
|
||
'Saturn': '土星',
|
||
'Rahu': '北交点',
|
||
'Ketu': '南交点',
|
||
}
|
||
sign_map = globals().get('SIGNS_CN', {})
|
||
raw = str(value or '').strip()
|
||
return label_map.get(raw) or sign_map.get(raw) or raw or '-'
|
||
|
||
def _degree(value) -> str:
|
||
try:
|
||
return f"{float(value):.2f}°"
|
||
except (TypeError, ValueError):
|
||
return '-'
|
||
|
||
def _date_cell(value) -> str:
|
||
text = str(value if value not in (None, '') else '-').strip()
|
||
if 'T' in text and len(text) >= 19:
|
||
return text[:19]
|
||
return _cell(text)
|
||
|
||
def _period_start(period: dict) -> str:
|
||
return _date_cell(period.get('start') or period.get('start_date') or period.get('start_datetime'))
|
||
|
||
def _period_end(period: dict) -> str:
|
||
return _date_cell(period.get('end') or period.get('end_date') or period.get('end_datetime'))
|
||
|
||
def _period_years(period: dict) -> str:
|
||
return _cell(period.get('years') or period.get('duration_years') or period.get('duration'))
|
||
|
||
def _producer_label(value) -> str:
|
||
text = str(value or '').strip()
|
||
if not text:
|
||
return '-'
|
||
leaf = text.rsplit('/', 1)[-1]
|
||
return leaf.replace('.py:', ':')
|
||
|
||
def _display_confidence(row: dict) -> str:
|
||
if row.get('execution_status') == 'blocked':
|
||
return 'blocked'
|
||
return _cell(row.get('confidence_status'))
|
||
|
||
def _num(value, digits=2) -> str:
|
||
try:
|
||
return f"{float(value):.{digits}f}"
|
||
except (TypeError, ValueError):
|
||
return _cell(value)
|
||
|
||
def _status_counts(summary: dict) -> str:
|
||
counts = summary.get('by_status') or summary.get('closure_status_counts') or {}
|
||
if not isinstance(counts, dict) or not counts:
|
||
return '-'
|
||
return ';'.join(f"{key}={value}" for key, value in counts.items())
|
||
|
||
def _public_status_note(value) -> str:
|
||
mapping = {
|
||
'partial_verified': '已生成',
|
||
'parameter_sensitive': '参数敏感',
|
||
'blocked': '需补资料',
|
||
'verified': '已核验',
|
||
'match': '一致',
|
||
'mismatch': '需校对',
|
||
'not_applicable': '不适用',
|
||
'pyjhora_behavior_only': '外部行为参考',
|
||
'pyjhora_behavior_only / not_multiengine_parity': '外部行为参考',
|
||
'blocked_external_callable': '外部回放待校对',
|
||
'superseded_by_annual_tajika_pack.lifetime_annual_chart_pages': '由生命周期图盘页承接',
|
||
}
|
||
text = str(value or '').strip()
|
||
return mapping.get(text, text or '-')
|
||
|
||
def _public_report_text(value) -> str:
|
||
text = str(value or '').strip()
|
||
replacements = {
|
||
'parameter_sensitive': '参考',
|
||
'blocked': '未列',
|
||
'observed_relationships': '已形成',
|
||
'observed_empty': '未形成',
|
||
'external_replay_pending': '待补充',
|
||
'Evidence label': '证据口径',
|
||
'证据标签': '证据口径',
|
||
}
|
||
text = text.replace('blocked_external_callable', '外部回放待校对')
|
||
text = text.replace('pyjhora_behavior_only / not_multiengine_parity', '外部行为参考')
|
||
text = text.replace('pyjhora_behavior_only', '外部行为参考')
|
||
for raw, public in replacements.items():
|
||
text = text.replace(raw, public)
|
||
return text
|
||
|
||
def _relation_summary(value) -> str:
|
||
if not isinstance(value, dict):
|
||
return '-'
|
||
chunks = []
|
||
for key, raw_items in value.items():
|
||
items = _list(raw_items)
|
||
if items:
|
||
chunks.append(f"{key}: {_brief(items, 4)}")
|
||
elif raw_items not in (None, '', [], {}):
|
||
chunks.append(f"{key}: {raw_items}")
|
||
return ';'.join(chunks) if chunks else '-'
|
||
|
||
def _lord_chain(raw_value) -> str:
|
||
data = _dict(raw_value)
|
||
parts = []
|
||
for key, label in (
|
||
('rasi_lord', 'RL'),
|
||
('nakshatra_lord', 'NL'),
|
||
('sub_lord', 'SL'),
|
||
('sub_sub_lord', 'SSL'),
|
||
):
|
||
if data.get(key):
|
||
parts.append(f"{label}={_token(data.get(key))}")
|
||
return ';'.join(parts) if parts else '-'
|
||
|
||
worksheets = packet.get('worksheets') if isinstance(packet.get('worksheets'), dict) else {}
|
||
birth = packet.get('birth_info') if isinstance(packet.get('birth_info'), dict) else {}
|
||
advanced = worksheets.get('advanced_systems') if isinstance(worksheets.get('advanced_systems'), dict) else {}
|
||
timing = worksheets.get('timing_and_predictive_systems') if isinstance(worksheets.get('timing_and_predictive_systems'), dict) else {}
|
||
lines = [
|
||
'# PL9 对标主册',
|
||
'',
|
||
'## 出生资料与图盘(PL9 p1-p17)',
|
||
'',
|
||
f"- 姓名:{birth.get('name') or birth.get('user_name') or '-'}",
|
||
f"- 出生:{birth.get('date') or '-'} {birth.get('time') or ''}",
|
||
f"- 计算口径:{birth.get('ayanamsa_name') or birth.get('ayanamsa') or '-'} / {birth.get('node_mode') or '-'}",
|
||
'',
|
||
]
|
||
core_chart = packet.get('core_chart') if isinstance(packet.get('core_chart'), dict) else {}
|
||
divisional = worksheets.get('divisional_and_special_charts') if isinstance(worksheets.get('divisional_and_special_charts'), dict) else {}
|
||
raw_divisional = packet.get('divisional_and_special_charts') if isinstance(packet.get('divisional_and_special_charts'), dict) else {}
|
||
for key in ('moon_chart', 'bhava_chalit'):
|
||
if key not in divisional and isinstance(raw_divisional.get(key), dict):
|
||
divisional[key] = raw_divisional[key]
|
||
varga_full = divisional.get('varga_full') if isinstance(divisional.get('varga_full'), dict) else {}
|
||
birth_sheet = worksheets.get('birth_and_parameters') if isinstance(worksheets.get('birth_and_parameters'), dict) else {}
|
||
birth_details = birth_sheet.get('birth_info') if isinstance(birth_sheet.get('birth_info'), dict) else birth
|
||
panchanga_pack = birth_sheet.get('panchanga') if isinstance(birth_sheet.get('panchanga'), dict) else {}
|
||
panchanga = panchanga_pack.get('raw') if isinstance(panchanga_pack.get('raw'), dict) else panchanga_pack
|
||
if birth_details:
|
||
lines.extend([
|
||
'### p3-p5 Birth Particulars / Hindu Calendar',
|
||
'',
|
||
'| Field | Value |',
|
||
'|-------|-------|',
|
||
])
|
||
for key, label in (
|
||
('date', '出生日期'),
|
||
('time', '出生时间'),
|
||
('day_of_birth', '出生星期'),
|
||
('place', '出生地点'),
|
||
('city', '城市'),
|
||
('state', '省州'),
|
||
('country', '国家'),
|
||
('lat', '纬度'),
|
||
('lon', '经度'),
|
||
('tz', '时区'),
|
||
('timezone', '时区'),
|
||
('ayanamsa_display', '岁差'),
|
||
('ayanamsa_name', '岁差名称'),
|
||
('ayanamsa', '岁差值'),
|
||
('node_mode', '交点模式'),
|
||
('house_system', '宫位系统'),
|
||
):
|
||
value = birth_details.get(key)
|
||
if value not in (None, ''):
|
||
lines.append(f"| {_cell(label)} | {_cell(value)} |")
|
||
if panchanga:
|
||
lines.extend([
|
||
'',
|
||
'| Panchanga | Value |',
|
||
'|-----------|-------|',
|
||
])
|
||
for key, value in panchanga.items():
|
||
if isinstance(value, (dict, list, tuple)) or value in (None, ''):
|
||
continue
|
||
lines.append(f"| {_cell(key)} | {_cell(value)} |")
|
||
lines.append('')
|
||
source_visible = packet.get('pl9_source_visible_tables') if isinstance(packet.get('pl9_source_visible_tables'), dict) else {}
|
||
birth_extra = _list(source_visible.get('birth_particulars_extra'))
|
||
if birth_extra:
|
||
lines.extend([
|
||
'#### p3 Birth Particulars 原版补充字段',
|
||
'',
|
||
'| Field | Value |',
|
||
'|-------|-------|',
|
||
])
|
||
for row in birth_extra:
|
||
if isinstance(row, dict):
|
||
lines.append(f"| {_cell(row.get('field'))} | {_cell(row.get('value'))} |")
|
||
lines.append('')
|
||
avakhada_rows = _list(source_visible.get('avakhada_chakra'))
|
||
if avakhada_rows:
|
||
lines.extend([
|
||
'#### p3 Avakhada Chakra',
|
||
'',
|
||
'| Field | Value |',
|
||
'|-------|-------|',
|
||
])
|
||
for row in avakhada_rows:
|
||
if isinstance(row, dict):
|
||
lines.append(f"| {_cell(row.get('field'))} | {_cell(row.get('value'))} |")
|
||
lines.append('')
|
||
d1_sheet = worksheets.get('d1_rasi_bhava') if isinstance(worksheets.get('d1_rasi_bhava'), dict) else {}
|
||
planets = core_chart.get('planets') if isinstance(core_chart.get('planets'), dict) else {}
|
||
if not planets and isinstance(d1_sheet.get('planets'), dict):
|
||
planets = d1_sheet.get('planets')
|
||
if planets:
|
||
lines.extend([
|
||
'### p6 Birth Chart 行星原始位置',
|
||
'',
|
||
'| Planet | R/C | Sign | Degree | Longitude | House | Nakshatra | Pada | Speed |',
|
||
'|--------|-----|------|--------|-----------|-------|-----------|------|-------|',
|
||
])
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu'):
|
||
row = _dict(planets.get(planet))
|
||
if not row:
|
||
continue
|
||
longitude = row.get('longitude') or row.get('longitude_deg') or row.get('lon')
|
||
sign_degree = row.get('degree_in_sign') or row.get('sign_degree')
|
||
lines.append(
|
||
f"| {_cell(_token(planet))} | {_cell(row.get('r_c') or row.get('retrograde') and 'R' or 'C')} | "
|
||
f"{_cell(_token(row.get('sign')))} | {_cell(_degree(sign_degree))} | {_cell(_degree(longitude))} | "
|
||
f"{_cell(row.get('house'))} | {_cell(row.get('nakshatra'))} | {_cell(row.get('pada') or row.get('nakshatra_pada'))} | "
|
||
f"{_cell(_num(row.get('speed'), 6))} |"
|
||
)
|
||
lines.append('')
|
||
lines.extend([
|
||
'#### p6 Birth Chart 星主与状态字段',
|
||
'',
|
||
'| Planet | RL | NL | SL | SS | Status | SB |',
|
||
'|--------|----|----|----|----|--------|----|',
|
||
])
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu'):
|
||
row = _dict(planets.get(planet))
|
||
if not row:
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(_token(planet))} | {_cell(_token(row.get('rasi_lord')))} | "
|
||
f"{_cell(_token(row.get('nakshatra_lord')))} | {_cell(_token(row.get('sub_lord')))} | "
|
||
f"{_cell(_token(row.get('sub_sub_lord')))} | {_cell(row.get('status'))} | {_cell(_num(row.get('sb'), 2))} |"
|
||
)
|
||
lines.append('')
|
||
moon_chart = d1_sheet.get('moon_chart') if isinstance(d1_sheet.get('moon_chart'), dict) else {}
|
||
bhava = d1_sheet.get('bhava_chalit') if isinstance(d1_sheet.get('bhava_chalit'), dict) else {}
|
||
sudarshana = divisional.get('sudarshana') if isinstance(divisional.get('sudarshana'), dict) else {}
|
||
upagrahas = divisional.get('upagrahas') if isinstance(divisional.get('upagrahas'), dict) else {}
|
||
special_lagnas = divisional.get('special_lagnas') if isinstance(divisional.get('special_lagnas'), dict) else {}
|
||
if moon_chart or bhava:
|
||
lines.extend(['### Chandra Chart / Bhava Spashta - Sripati System', ''])
|
||
moon_raw = moon_chart.get('raw') if isinstance(moon_chart.get('raw'), dict) else moon_chart
|
||
moon_planets = moon_raw.get('planets') if isinstance(moon_raw.get('planets'), dict) else {}
|
||
if moon_planets:
|
||
signs = globals().get('SIGNS', [])
|
||
moon_asc_sign = _dict(moon_raw.get('Ascendant')).get('sign')
|
||
moon_asc_index = signs.index(moon_asc_sign) if moon_asc_sign in signs else None
|
||
lines.extend([
|
||
'| Moon Chart | Sign | Degree | House |',
|
||
'|------------|------|--------|-------|',
|
||
])
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu'):
|
||
row = _dict(moon_planets.get(planet))
|
||
if row:
|
||
sign = row.get('sign')
|
||
moon_house = row.get('house')
|
||
if moon_house in (None, '') and moon_asc_index is not None and sign in signs:
|
||
moon_house = ((signs.index(sign) - moon_asc_index) % 12) + 1
|
||
lines.append(
|
||
f"| {_cell(_token(planet))} | {_cell(_token(sign))} | "
|
||
f"{_cell(_degree(row.get('degree_in_sign') or row.get('longitude')))} | {_cell(moon_house)} |"
|
||
)
|
||
lines.append('')
|
||
bhava_raw = bhava.get('raw') if isinstance(bhava.get('raw'), dict) else bhava
|
||
boundaries = _dict(bhava_raw.get('boundaries'))
|
||
houses = _list(boundaries.get('houses'))
|
||
if houses:
|
||
lines.extend([
|
||
'| Bhava Number | Bhava Arambha / House beginning | Bhava Madhya / Middle of House | Bhava Antya / House ending |',
|
||
'|--------------|----------------------------------|-------------------------------|-----------------------------|',
|
||
])
|
||
for index, row in enumerate(houses[:12], start=1):
|
||
if isinstance(row, dict):
|
||
lines.append(
|
||
f"| {index} | {_cell(_degree(row.get('sandhi_start_lon')))} | "
|
||
f"{_cell(_token(row.get('cusp_sign')))} {_cell(_degree(row.get('cusp_degree_in_sign')))} | "
|
||
f"{_cell(_degree(row.get('sandhi_end_lon')))} |"
|
||
)
|
||
lines.append('')
|
||
if sudarshana:
|
||
raw = sudarshana.get('raw') if isinstance(sudarshana.get('raw'), dict) else sudarshana
|
||
rows = [
|
||
(key, value) for key, value in raw.items()
|
||
if key not in {'method', 'version', 'schema', 'schema_version', 'status'}
|
||
and not isinstance(value, (dict, list, tuple))
|
||
and value not in (None, '')
|
||
]
|
||
if rows:
|
||
lines.extend(['### Sudarshan Chakra 资料', '', '| Field | Value |', '|-------|-------|'])
|
||
for key, value in rows:
|
||
lines.append(f"| {_cell(key)} | {_cell(value)} |")
|
||
lines.append('')
|
||
upagraha_raw = upagrahas.get('raw') if isinstance(upagrahas.get('raw'), dict) else {}
|
||
if upagraha_raw:
|
||
lines.extend([
|
||
'### Upagraha / Sub-Planets 点位',
|
||
'',
|
||
'| Point | Sign | Degree | Longitude |',
|
||
'|-------|------|--------|-----------|',
|
||
])
|
||
signs = globals().get('SIGNS', [])
|
||
for name, row in upagraha_raw.items():
|
||
if not isinstance(row, dict):
|
||
continue
|
||
longitude = row.get('longitude')
|
||
sign = row.get('sign')
|
||
if not sign and isinstance(row.get('sign_idx'), int) and signs:
|
||
sign = signs[int(row.get('sign_idx')) % 12]
|
||
lines.append(
|
||
f"| {_cell(name)} | {_cell(_token(sign))} | {_cell(_degree(row.get('degree_in_sign')))} | {_cell(_degree(longitude))} |"
|
||
)
|
||
lines.append('')
|
||
source_gulikaadi = _list(source_visible.get('gulikaadi_group'))
|
||
source_dhoomadi = _list(source_visible.get('dhoomadi_group'))
|
||
if source_gulikaadi:
|
||
lines.extend([
|
||
'#### p17 Gulikaadi Group',
|
||
'',
|
||
'| Upagraha | Lord | Duration | Beginning | Ending |',
|
||
'|----------|------|----------|-----------|--------|',
|
||
])
|
||
for row in source_gulikaadi:
|
||
if isinstance(row, dict):
|
||
lines.append(
|
||
f"| {_cell(row.get('upagraha'))} | {_cell(row.get('lord'))} | {_cell(row.get('duration'))} | "
|
||
f"{_cell(row.get('beginning'))} | {_cell(row.get('ending'))} |"
|
||
)
|
||
lines.append('')
|
||
if source_dhoomadi:
|
||
lines.extend([
|
||
'#### p17 Dhoomadi Group',
|
||
'',
|
||
'| Upagraha | Lord | Position |',
|
||
'|----------|------|----------|',
|
||
])
|
||
for row in source_dhoomadi:
|
||
if isinstance(row, dict):
|
||
lines.append(f"| {_cell(row.get('upagraha'))} | {_cell(row.get('lord'))} | {_cell(row.get('position'))} |")
|
||
lines.append('')
|
||
if special_lagnas:
|
||
lines.extend([
|
||
'### Special Lagnas and Points',
|
||
'',
|
||
'| Point | Sign | Degree | House |',
|
||
'|-------|------|--------|-------|',
|
||
])
|
||
for name, row in special_lagnas.items():
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('full_name') or row.get('label') or name)} | {_cell(_token(row.get('sign')))} | "
|
||
f"{_cell(_degree(row.get('sign_degree') or row.get('degree_in_sign')))} | {_cell(row.get('house'))} |"
|
||
)
|
||
lines.append('')
|
||
source_special_points = _list(source_visible.get('source_special_points'))
|
||
if source_special_points:
|
||
lines.extend([
|
||
'#### p17 Special Lagnas and Points 原版补充字段',
|
||
'',
|
||
'| Point | Value |',
|
||
'|-------|-------|',
|
||
])
|
||
for row in source_special_points:
|
||
if isinstance(row, dict):
|
||
lines.append(f"| {_cell(row.get('point'))} | {_cell(row.get('value'))} |")
|
||
lines.append('')
|
||
sensitive_points = divisional.get('sensitive_points') if isinstance(divisional.get('sensitive_points'), dict) else {}
|
||
if sensitive_points:
|
||
lines.extend([
|
||
'#### 敏感点与特殊落点',
|
||
'',
|
||
'| Point | Sign | Degree | Longitude | Lord |',
|
||
'|-------|------|--------|-----------|------|',
|
||
])
|
||
sensitive_rows = []
|
||
for name, row in sensitive_points.items():
|
||
if name == 'sarpa_drekkana' and isinstance(row, dict):
|
||
for planet, placement in row.items():
|
||
if isinstance(placement, dict):
|
||
sensitive_rows.append((f'{name} / {planet}', {
|
||
**placement,
|
||
'lord': placement.get('lord') or 'Not supplied by source',
|
||
}))
|
||
else:
|
||
sensitive_rows.append((name, row))
|
||
for name, row in sensitive_rows:
|
||
if not isinstance(row, dict) or not row:
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(name)} | {_cell(_token(row.get('sign')))} | "
|
||
f"{_cell(_degree(row.get('degree_in_sign')))} | {_cell(_degree(row.get('longitude')))} | "
|
||
f"{_cell(_token(row.get('lord')))} |"
|
||
)
|
||
lines.append('')
|
||
natal_specs = [
|
||
(6, 'D1 Rashi', bool(core_chart.get('planets'))),
|
||
(7, 'Moon Chart', isinstance(divisional.get('moon_chart'), dict)),
|
||
(7, 'Navamsha', isinstance(varga_full.get('D9_Navamsa') or varga_full.get('D9'), dict)),
|
||
(7, 'Bhava (Sripati)', isinstance(divisional.get('bhava_chalit'), dict)),
|
||
(9, 'D5 Panchamsa', isinstance(varga_full.get('D5_Panchamsa') or varga_full.get('D5'), dict)),
|
||
(10, 'D6 Shashthamsa', isinstance(varga_full.get('D6_Shashthamsa') or varga_full.get('D6'), dict)),
|
||
(11, 'D9 Navamsa', isinstance(varga_full.get('D9_Navamsa') or varga_full.get('D9'), dict)),
|
||
(12, 'D10 Dasamsa', isinstance(varga_full.get('D10_Dasamsa') or varga_full.get('D10'), dict)),
|
||
(13, 'D2 Hora', isinstance(varga_full.get('D2_Hora') or varga_full.get('D2'), dict)),
|
||
(14, 'D12 Dwadashamsa', isinstance(varga_full.get('D12_Dwadashamsa') or varga_full.get('D12'), dict)),
|
||
(15, 'D16 Shodasamsa', isinstance(varga_full.get('D16_Shodasamsa') or varga_full.get('D16'), dict)),
|
||
(16, 'D20 Vimsamsa', isinstance(varga_full.get('D20_Vimsamsa') or varga_full.get('D20'), dict)),
|
||
(17, 'D24 Chaturvimsamsa', isinstance(varga_full.get('D24_Chaturvimsamsa') or varga_full.get('D24'), dict)),
|
||
(18, 'D27 Bhamsa', isinstance(varga_full.get('D27_Bhamsa') or varga_full.get('D27'), dict)),
|
||
(19, 'D30 Trimsamsa', isinstance(varga_full.get('D30_Trimsamsa') or varga_full.get('D30'), dict)),
|
||
(20, 'D40 Khavedamsa', isinstance(varga_full.get('D40_Khavedamsa') or varga_full.get('D40'), dict)),
|
||
(21, 'D45 Akshavedamsa', isinstance(varga_full.get('D45_Akshavedamsa') or varga_full.get('D45'), dict)),
|
||
(22, 'D60 Shashtyamsa', isinstance(varga_full.get('D60_Shashtyamsa') or varga_full.get('D60'), dict)),
|
||
(23, 'D7 Saptamsa', isinstance(varga_full.get('D7_Saptamsa') or varga_full.get('D7'), dict)),
|
||
(24, 'D11 Rudramsa', isinstance(varga_full.get('D11_Rudramsa') or varga_full.get('D11'), dict)),
|
||
]
|
||
ready_natal_specs = [spec for spec in natal_specs if spec[2]]
|
||
if ready_natal_specs:
|
||
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
|
||
# still part of the visible evidence. Keep the chart drawing and add
|
||
# a compact numeric transcription so an AI reader can recover the
|
||
# same placements without relying on image recognition alone.
|
||
chart_numeric_specs = [
|
||
('D1_Rashi', 'D1 Rashi'),
|
||
('D2_Hora', 'D2 Hora'),
|
||
('D3_Drekkana', 'D3 Drekkana'),
|
||
('D4_Turyamsa', 'D4 Turyamsa'),
|
||
('D5_Panchamsa', 'D5 Panchamsa'),
|
||
('D6_Shashthamsa', 'D6 Shashthamsa'),
|
||
('D7_Saptamsa', 'D7 Saptamsa'),
|
||
('D8_Ashtamsa', 'D8 Ashtamsa'),
|
||
('D9_Navamsa', 'D9 Navamsa'),
|
||
('D10_Dasamsa', 'D10 Dasamsa'),
|
||
('D11_Rudramsa', 'D11 Rudramsa'),
|
||
('D12_Dwadashamsa', 'D12 Dwadashamsa'),
|
||
('D16_Shodasamsa', 'D16 Shodasamsa'),
|
||
('D20_Vimsamsa', 'D20 Vimsamsa'),
|
||
('D24_Siddhamsa', 'D24 Siddhamsa'),
|
||
('D27_Bhamsa', 'D27 Bhamsa'),
|
||
('D30_Trimsamsa', 'D30 Trimsamsa'),
|
||
('D40_Khavedamsa', 'D40 Khavedamsa'),
|
||
('D45_Akshavedamsa', 'D45 Akshavedamsa'),
|
||
('D60_Shashtyamsa', 'D60 Shashtyamsa'),
|
||
('D81', 'D81'),
|
||
('D108', 'D108'),
|
||
('D144', 'D144'),
|
||
]
|
||
signs = globals().get('SIGNS', [])
|
||
for chart_key, chart_label in chart_numeric_specs:
|
||
chart = varga_full.get(chart_key) if isinstance(varga_full.get(chart_key), dict) else {}
|
||
if chart_key == 'D1_Rashi' and not chart:
|
||
chart = {'Ascendant': core_chart.get('ascendant'), **planets}
|
||
if not chart:
|
||
continue
|
||
ascendant = _dict(chart.get('Ascendant') or chart.get('ascendant'))
|
||
asc_sign_idx = ascendant.get('sign_idx')
|
||
if asc_sign_idx is None and ascendant.get('sign') in signs:
|
||
asc_sign_idx = signs.index(ascendant.get('sign'))
|
||
rows = []
|
||
if ascendant:
|
||
rows.append(('Lagna', ascendant))
|
||
rows.extend(
|
||
(planet, _dict(chart.get(planet)))
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu')
|
||
if _dict(chart.get(planet))
|
||
)
|
||
if not rows:
|
||
continue
|
||
lines.extend([
|
||
f'#### {chart_label} 数值位置表',
|
||
'',
|
||
'| 对象 | 落座 | 落座度数 | 黄经 | 整宫宫位 |',
|
||
'|------|------|----------|------|----------|',
|
||
])
|
||
for object_name, row in rows:
|
||
sign = row.get('sign')
|
||
sign_idx = row.get('sign_idx')
|
||
if sign_idx is None and sign in signs:
|
||
sign_idx = signs.index(sign)
|
||
house = row.get('house')
|
||
if house in (None, '') and asc_sign_idx is not None and sign_idx is not None:
|
||
house = ((int(sign_idx) - int(asc_sign_idx)) % 12) + 1
|
||
longitude = row.get('longitude')
|
||
if longitude is None:
|
||
longitude = row.get('lon')
|
||
degree = row.get('degree_in_sign')
|
||
if degree is None:
|
||
degree = row.get('sign_degree')
|
||
if longitude is None and degree is not None:
|
||
try:
|
||
if sign_idx is None and sign in signs:
|
||
sign_idx = signs.index(sign)
|
||
if sign_idx is not None:
|
||
longitude = int(sign_idx) * 30 + float(degree)
|
||
except (TypeError, ValueError):
|
||
longitude = None
|
||
lines.append(
|
||
f"| {_cell(_token(object_name))} | {_cell(_token(sign))} | {_degree(degree)} | "
|
||
f"{_degree(longitude)} | {_cell(house)} |"
|
||
)
|
||
lines.append('')
|
||
visual_pack = divisional.get('visual_chart_pack') if isinstance(divisional.get('visual_chart_pack'), dict) else {}
|
||
panel_inventory = visual_pack.get('panel_inventory') if isinstance(visual_pack.get('panel_inventory'), dict) else {}
|
||
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(['## 行星、星宿与宫位解释', ''])
|
||
for page in pages:
|
||
if not isinstance(page, dict):
|
||
continue
|
||
planet = str(page.get('planet') or '').strip()
|
||
sections = page.get('sections') if isinstance(page.get('sections'), dict) else {}
|
||
if not planet:
|
||
continue
|
||
lines.extend([f'### {planet}', ''])
|
||
for key in ('placement', 'sign_and_dispositor', 'nakshatra', 'functional_role', 'strength_and_context', 'activation'):
|
||
if str(sections.get(key) or '').strip():
|
||
lines.extend([str(sections[key]).strip(), ''])
|
||
strengths = worksheets.get('strengths_and_scores') if isinstance(worksheets.get('strengths_and_scores'), dict) else {}
|
||
if strengths:
|
||
lines.extend(['## 强度、关系与 Ashtakavarga 技术页(PL9 p30-p60 对标)', ''])
|
||
shadbala = _dict(strengths.get('shadbala'))
|
||
shadbala_planets = _dict(shadbala.get('planets'))
|
||
if shadbala_planets:
|
||
lines.extend([
|
||
'### p31-p33 Shadbala / Bhava Bala 摘要',
|
||
'',
|
||
f"- Shadbala 方法:{_cell(shadbala.get('method'))}",
|
||
f"- 外部边界:{_cell(shadbala.get('external_parity_boundary'))}",
|
||
'',
|
||
'| Planet | Sthana | Dig | Kala | Chesta | Naisargika | Drik | Total | Required |',
|
||
'|--------|--------|-----|------|--------|------------|------|-------|----------|',
|
||
])
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn'):
|
||
row = _dict(shadbala_planets.get(planet))
|
||
if not row:
|
||
continue
|
||
total = row.get('total') or row.get('total_shadbala') or row.get('total_virupas')
|
||
required = row.get('minimum_required') or row.get('required') or row.get('min_required')
|
||
lines.append(
|
||
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)} |"
|
||
)
|
||
lines.append('')
|
||
lines.extend([
|
||
'### p33 Declination / Kranti / Speed',
|
||
'',
|
||
'| Planet | Degree | Declination | Kranti | Speed |',
|
||
'|--------|--------|-------------|--------|-------|',
|
||
])
|
||
kranti_planets = _dict(_dict(strengths.get('kranti_local')).get('planets'))
|
||
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))
|
||
if not row:
|
||
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)} |"
|
||
)
|
||
lines.append('')
|
||
avasthas = _dict(advanced.get('avasthas'))
|
||
if avasthas:
|
||
lines.extend([
|
||
'### p30-p60 Avastha 行星状态',
|
||
'',
|
||
'| Planet | Bala | Jagrat | Deeptadi | Lajjitadi | Shayanadi |',
|
||
'|--------|------|--------|----------|-----------|-----------|',
|
||
])
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu'):
|
||
row = _dict(avasthas.get(planet))
|
||
states = _dict(row.get('avasthas'))
|
||
if not states:
|
||
continue
|
||
def _state(name: str) -> str:
|
||
state = _dict(states.get(name))
|
||
return _cell(state.get('state'))
|
||
lines.append(
|
||
f"| {_cell(_token(planet))} | {_state('Bala')} | {_state('Jagrat')} | {_state('Deeptadi')} | "
|
||
f"{_state('Lajjitadi')} | {_state('Shayanadi')} |"
|
||
)
|
||
lines.append('')
|
||
bhava_bala = _dict(strengths.get('bhava_bala'))
|
||
bhava_rows = _list(bhava_bala.get('houses')) or [
|
||
{'house': key, **value}
|
||
for key, value in bhava_bala.items()
|
||
if str(key).isdigit() and isinstance(value, dict)
|
||
]
|
||
if bhava_rows:
|
||
lines.extend([
|
||
'### p32 Bhava Bala 十二宫力量',
|
||
'',
|
||
'| House | Sign | Lord | Score | 关键因素 |',
|
||
'|-------|------|------|-------|----------|',
|
||
])
|
||
for row in bhava_rows[:12]:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
factors = row.get('factors') if isinstance(row.get('factors'), list) else []
|
||
lines.append(
|
||
f"| {_cell(row.get('house') or row.get('number'))} | {_cell(_token(row.get('sign')))} | {_cell(_token(row.get('lord')))} | "
|
||
f"{_num(row.get('score'))} | {_cell(_brief(factors, 2))} |"
|
||
)
|
||
lines.append('')
|
||
friendship = _dict(strengths.get('planetary_friendship'))
|
||
friendship_rows = _list(friendship.get('rows'))
|
||
if friendship_rows:
|
||
lines.extend([
|
||
'### p41 Planetary Friendship 矩阵源数据',
|
||
'',
|
||
'| Planet | Natural friends | Natural enemies | Temporary relation |',
|
||
'|--------|-----------------|-----------------|--------------------|',
|
||
])
|
||
for row in friendship_rows[:9]:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
natural = _dict(row.get('natural'))
|
||
temporary = _dict(row.get('temporary'))
|
||
lines.append(
|
||
f"| {_cell(_token(row.get('planet')))} | {_cell(_brief(natural.get('friends'), 5))} | "
|
||
f"{_cell(_brief(natural.get('enemies'), 5))} | {_cell(_relation_summary(temporary))} |"
|
||
)
|
||
lines.append('')
|
||
aspects = _dict(advanced.get('aspects'))
|
||
aspect_rows = _list(aspects.get('aspects'))
|
||
house_aspect_rows = _list(aspects.get('house_aspects'))
|
||
if aspect_rows or house_aspect_rows:
|
||
lines.extend([
|
||
'### p37-p38 / p45-p47 相位矩阵',
|
||
'',
|
||
])
|
||
if aspect_rows:
|
||
lines.extend([
|
||
'| Planet 1 | Planet 2 | Aspect | Exact degree | Actual difference | Orb | Phase | Orb score |',
|
||
'|----------|----------|--------|--------------|-------------------|-----|----------|----------|',
|
||
])
|
||
for row in aspect_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(_token(row.get('planet1')))} | {_cell(_token(row.get('planet2')))} | "
|
||
f"{_cell(row.get('type'))} | {_degree(row.get('aspect_degree'))} | {_degree(row.get('actual_diff'))} | "
|
||
f"{_degree(row.get('orb'))} | {_cell(row.get('phase', 'unknown_without_velocity'))} | {_num(row.get('strength'))} |"
|
||
)
|
||
lines.append('')
|
||
if house_aspect_rows:
|
||
lines.extend([
|
||
'| Planet | From house | Target house | Aspect type | Special |',
|
||
'|--------|------------|--------------|-------------|---------|',
|
||
])
|
||
for row in house_aspect_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
targets = _dict(row.get('aspects_to'))
|
||
if targets:
|
||
for target, detail in targets.items():
|
||
detail = _dict(detail)
|
||
lines.append(
|
||
f"| {_cell(_token(row.get('planet')))} | {_cell(row.get('house'))} | {_cell(target)} | "
|
||
f"{_cell(detail.get('aspect_type'))} | {_cell(detail.get('is_special'))} |"
|
||
)
|
||
else:
|
||
lines.append(
|
||
f"| {_cell(_token(row.get('planet')))} | {_cell(row.get('house'))} | - | - | - |"
|
||
)
|
||
lines.append('')
|
||
jaimini = advanced.get('jaimini') if isinstance(advanced.get('jaimini'), dict) else {}
|
||
karaka_source = _dict(jaimini.get('chara_karaka_8')) or _dict(jaimini.get('chara_karaka_7'))
|
||
karaka_table = _dict(karaka_source.get('karaka_table_8')) or _dict(karaka_source.get('karaka_table'))
|
||
arudha_pack = _dict(jaimini.get('arudha_padas'))
|
||
arudha_rows = _dict(arudha_pack.get('padas')) or arudha_pack
|
||
jaimini_special = _dict(jaimini.get('special_lagnas'))
|
||
if karaka_table or arudha_rows or jaimini_special:
|
||
lines.extend(['### Jaimini Karaka、Arudha Pada 与特殊 Lagna', ''])
|
||
if karaka_table:
|
||
lines.extend([
|
||
'| Karaka | Planet | Degree | Domain |',
|
||
'|--------|--------|--------|--------|',
|
||
])
|
||
for name, row in karaka_table.items():
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('cn_name') or name)} | {_cell(_token(row.get('planet')))} | "
|
||
f"{_cell(_degree(row.get('degree_in_sign') or row.get('sign_degree')))} | "
|
||
f"{_cell(row.get('domain'))} |"
|
||
)
|
||
lines.append('')
|
||
if arudha_rows or jaimini_special:
|
||
lines.extend([
|
||
'| Point | Sign | Degree / House |',
|
||
'|-------|------|----------------|',
|
||
])
|
||
for name, row in {**arudha_rows, **jaimini_special}.items():
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('full_name') or row.get('label') or row.get('name') or name)} | "
|
||
f"{_cell(_token(row.get('sign') or row.get('rashi')))} | "
|
||
f"{_cell(_degree(row.get('degree_in_sign') or row.get('sign_degree')) if row.get('degree_in_sign') is not None or row.get('sign_degree') is not None else row.get('house') or row.get('house_num'))} |"
|
||
)
|
||
lines.append('')
|
||
ashtakavarga = _dict(strengths.get('ashtakavarga'))
|
||
sav = _dict(ashtakavarga.get('sav'))
|
||
sav_scores = _dict(sav.get('scores'))
|
||
if sav_scores:
|
||
lines.extend([
|
||
'### p51-p60 Ashtakavarga SAV / BAV / Pinda',
|
||
'',
|
||
f"- 方法:{_cell(ashtakavarga.get('method'))}",
|
||
f"- BAV 校验:{_cell('all_valid' if ashtakavarga.get('all_bav_valid') else ashtakavarga.get('all_bav_valid'))}",
|
||
'',
|
||
'| Sign | SAV score |',
|
||
'|------|-----------|',
|
||
])
|
||
for sign in globals().get('SIGNS', []):
|
||
if sign in sav_scores:
|
||
lines.append(f"| {_cell(_token(sign))} | {_cell(sav_scores.get(sign))} |")
|
||
lines.append('')
|
||
full_house_scores = ashtakavarga.get('house_scores_full')
|
||
if isinstance(full_house_scores, dict):
|
||
full_rows = []
|
||
for key, value in full_house_scores.items():
|
||
if isinstance(value, dict):
|
||
full_rows.append({'house': str(key).replace('house_', ''), **value})
|
||
full_rows.sort(key=lambda row: int(row['house']) if str(row.get('house')).isdigit() else 99)
|
||
if full_rows:
|
||
lines.extend([
|
||
'### p53 Ashtakavarga 完整宫位分数',
|
||
'',
|
||
'| House | Sign | SAV | BAV/承接 | Full with Lagna |',
|
||
'|-------|------|-----|----------|-----------------|',
|
||
])
|
||
for row in full_rows:
|
||
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'))} |"
|
||
)
|
||
lines.append('')
|
||
bav = _dict(ashtakavarga.get('bav'))
|
||
if bav:
|
||
signs = globals().get('SIGNS', [])
|
||
lines.extend([
|
||
'| Planet | Total | Expected | Valid | Aries | Taurus | Gemini | Cancer | Leo | Virgo | Libra | Scorpio | Sagittarius | Capricorn | Aquarius | Pisces |',
|
||
'|--------|-------|----------|-------|-------|--------|--------|--------|-----|-------|-------|---------|-------------|-----------|----------|--------|',
|
||
])
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn'):
|
||
row = _dict(bav.get(planet))
|
||
bindus = _list(row.get('bindus'))
|
||
if not row or not bindus:
|
||
continue
|
||
cells = ' | '.join(_cell(bindus[index]) if index < len(bindus) else '-' for index, _sign in enumerate(signs[:12]))
|
||
lines.append(f"| {_cell(_token(planet))} | {_cell(row.get('total'))} | {_cell(row.get('expected_total'))} | {_cell(row.get('valid'))} | {cells} |")
|
||
lines.append('')
|
||
vimsopaka = _dict(strengths.get('vimsopaka'))
|
||
if vimsopaka:
|
||
lines.extend([
|
||
'### p30-p60 Vimsopaka / Pushkara / Vargottama 辅助强度',
|
||
'',
|
||
'| Planet | Vimsopaka | Grade | Pushkara Navamsa | Pushkara Bhaga | Vargottama |',
|
||
'|--------|-----------|-------|------------------|----------------|------------|',
|
||
])
|
||
pushkara = _dict(strengths.get('pushkara'))
|
||
vargottama = _dict(strengths.get('vargottama'))
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu'):
|
||
row = _dict(vimsopaka.get(planet))
|
||
push = _dict(pushkara.get(planet))
|
||
varga = _dict(vargottama.get(planet))
|
||
if not row and not push and not varga:
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(_token(planet))} | {_num(row.get('total_score'))} | {_cell(row.get('category'))} | "
|
||
f"{_cell(push.get('pushkara_navamsa'))} | {_cell(push.get('pushkara_bhaga'))} | {_cell(varga.get('is_vargottama'))} |"
|
||
)
|
||
lines.append('')
|
||
source_pindas = _list(source_visible.get('ashtakavarga_reduction_pindas'))
|
||
if source_pindas:
|
||
lines.extend([
|
||
'### p54 Ashtakavarga - Reductions / Pinda',
|
||
'',
|
||
'Ashtakavarga Pinda 与 Shodhya/Yoga Pinda 数值表。',
|
||
'',
|
||
'| Planet | Rashi Pinda | Graha Pinda | Sodhya Pinda |',
|
||
'|--------|-------------|-------------|--------------|',
|
||
])
|
||
for row in source_pindas:
|
||
if isinstance(row, dict):
|
||
lines.append(
|
||
f"| {_cell(_token(row.get('planet')))} | {_cell(row.get('rashi_pinda'))} | "
|
||
f"{_cell(row.get('graha_pinda'))} | {_cell(row.get('sodhya_pinda'))} |"
|
||
)
|
||
lines.append('')
|
||
packet_pindas = _dict(ashtakavarga.get('shodhya_pinda'))
|
||
if packet_pindas:
|
||
lines.extend([
|
||
'### Ashtakavarga Shodhya / Yoga Pinda 原始值',
|
||
'',
|
||
'| Planet | Rashi Pinda | Graha Pinda | Total Pinda | Bindu at own sign |',
|
||
'|--------|-------------|-------------|-------------|-------------------|',
|
||
])
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn'):
|
||
row = _dict(packet_pindas.get(planet))
|
||
if not row:
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(_token(planet))} | {_cell(row.get('rashi_pinda'))} | "
|
||
f"{_cell(row.get('graha_pinda'))} | {_cell(row.get('total_pinda') or row.get('sodhya_pinda'))} | "
|
||
f"{_cell(row.get('bindu_at_own_sign'))} |"
|
||
)
|
||
lines.append('')
|
||
divisional_av = _list(source_visible.get('divisional_ashtakavarga_samudaya'))
|
||
if divisional_av:
|
||
lines.extend([
|
||
'### p55 Divisional Charts - Ashtakavarga',
|
||
'',
|
||
'| Chart | Samudaya Ashtakavarga Scores |',
|
||
'|-------|-------------------------------|',
|
||
])
|
||
for row in divisional_av:
|
||
if isinstance(row, dict):
|
||
lines.append(f"| {_cell(row.get('chart'))} | {_cell(row.get('scores'))} |")
|
||
lines.append('')
|
||
physiology = source_visible.get('physiology_tables') if isinstance(source_visible.get('physiology_tables'), dict) else {}
|
||
sookshma_rows = _list(physiology.get('sookshma'))
|
||
sthoola_rows = _list(physiology.get('sthoola'))
|
||
nakshatra_rows = _list(physiology.get('nakshatra'))
|
||
if sookshma_rows or sthoola_rows or nakshatra_rows:
|
||
lines.extend(['### p56 Health / Influence of Grahas on Physiology', ''])
|
||
if sookshma_rows:
|
||
lines.extend([
|
||
'#### Sookshma Scheme',
|
||
'',
|
||
'| Planet | Body Part |',
|
||
'|--------|-----------|',
|
||
])
|
||
for row in sookshma_rows:
|
||
if isinstance(row, dict):
|
||
lines.append(f"| {_cell(_token(row.get('planet')))} | {_cell(row.get('body_part'))} |")
|
||
lines.append('')
|
||
if sthoola_rows:
|
||
lines.extend([
|
||
'#### Sthoola Scheme',
|
||
'',
|
||
'| Planet | Body Part 1 | Body Part 2 |',
|
||
'|--------|-------------|-------------|',
|
||
])
|
||
for row in sthoola_rows:
|
||
if isinstance(row, dict):
|
||
lines.append(
|
||
f"| {_cell(_token(row.get('planet')))} | {_cell(row.get('body_part_1'))} | {_cell(row.get('body_part_2'))} |"
|
||
)
|
||
lines.append('')
|
||
if nakshatra_rows:
|
||
lines.extend([
|
||
'#### Nakshatra Scheme',
|
||
'',
|
||
'| Planet | Nakshatra | Opinion 1 | Opinion 2 |',
|
||
'|--------|-----------|-----------|-----------|',
|
||
])
|
||
for row in nakshatra_rows:
|
||
if isinstance(row, dict):
|
||
lines.append(
|
||
f"| {_cell(_token(row.get('planet')))} | {_cell(row.get('nakshatra'))} | "
|
||
f"{_cell(row.get('opinion_1'))} | {_cell(row.get('opinion_2'))} |"
|
||
)
|
||
lines.append('')
|
||
source_avasthas = _list(source_visible.get('planetary_avasthas'))
|
||
if source_avasthas:
|
||
lines.extend([
|
||
'### p46 Graha Avasthas - Planets and their Moods',
|
||
'',
|
||
'| Planet | Jagradadi | Baladi | Lajjitadi | Deeptadi | Shyanadi |',
|
||
'|--------|-----------|--------|-----------|----------|----------|',
|
||
])
|
||
for row in source_avasthas:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(_token(row.get('planet')))} | {_cell(row.get('jagradadi'))} | {_cell(row.get('baladi'))} | "
|
||
f"{_cell(row.get('lajjitadi'))} | {_cell(row.get('deeptadi'))} | {_cell(row.get('shyanadi'))} |"
|
||
)
|
||
lines.append('')
|
||
chakra_pages = _list(source_visible.get('visual_chakra_pages'))
|
||
if chakra_pages:
|
||
lines.extend([
|
||
'### p58-p60 Chakra 图阵页',
|
||
'',
|
||
'| Page | Chart |',
|
||
'|------|-------|',
|
||
])
|
||
for row in chakra_pages:
|
||
if isinstance(row, dict):
|
||
lines.append(f"| {_cell(row.get('page'))} | {_cell(row.get('title'))} |")
|
||
lines.append('')
|
||
kota_rows = _list(source_visible.get('kota_gochara'))
|
||
if kota_rows:
|
||
lines.extend([
|
||
'#### p60 Kota Chakra Gochara (Transits)',
|
||
'',
|
||
'| Planet | Degree | Rashi | Nakshatra | Dignity | SB% |',
|
||
'|--------|--------|-------|-----------|---------|-----|',
|
||
])
|
||
for row in kota_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('planet'))} | {_cell(row.get('degree'))} | {_cell(row.get('rashi'))} | "
|
||
f"{_cell(row.get('nakshatra'))} | {_cell(row.get('dignity'))} | {_cell(row.get('sb_percent'))} |"
|
||
)
|
||
lines.append('')
|
||
dasha_main = timing.get('dasha') if isinstance(timing.get('dasha'), dict) else {}
|
||
dasha_master = timing.get('dasha_master_pack') if isinstance(timing.get('dasha_master_pack'), dict) else {}
|
||
dasha_families = dasha_master.get('families') if isinstance(dasha_master.get('families'), dict) else {}
|
||
vimshottari_timeline = _list(dasha_main.get('timeline'))
|
||
family_specs = [
|
||
(75, 'Ashtottari Dasha', dasha_families.get('ashtottari')),
|
||
(80, 'Yogini Dasha', dasha_families.get('yogini')),
|
||
(83, 'Kala Chakra Dasha', dasha_families.get('kala_chakra')),
|
||
(89, 'Jaimini / Narayana Dasha', dasha_families.get('narayana')),
|
||
(92, 'Sthira Dasha', dasha_families.get('sthira')),
|
||
(93, 'Niryaana Shoola Dasha', dasha_families.get('niryaana_shoola')),
|
||
(95, 'Drig Dasha', dasha_families.get('drig')),
|
||
(97, 'Navamsha Dasha', dasha_families.get('navamsha')),
|
||
(101, 'Lagna Kendradi Rashi Dasha', dasha_families.get('lagna_kendradi')),
|
||
(103, 'Shoola Dasha', dasha_families.get('shoola')),
|
||
(104, 'Tribhagi Dasha', dasha_families.get('tribhagi')),
|
||
(106, 'Tribhagi 40-year Dasha', dasha_families.get('tribhagi_40')),
|
||
(109, 'Shodashottari Dasha', dasha_families.get('shodashottari')),
|
||
(110, 'Dwadashottari Dasha', dasha_families.get('dwadashottari')),
|
||
(111, 'Dwisaptatisama Dasha', dasha_families.get('dwisaptatisama')),
|
||
(113, 'Shastihayani Dasha', dasha_families.get('shastihayani')),
|
||
(115, 'Shattrimshatsama Dasha', dasha_families.get('shattrimshatsama')),
|
||
(118, 'Panchottari Dasha', dasha_families.get('panchottari')),
|
||
(119, 'Satabdika Dasha', dasha_families.get('satabdika')),
|
||
(120, 'Chaturshitisama Dasha', dasha_families.get('chaturshitisama')),
|
||
]
|
||
if vimshottari_timeline or any(isinstance(family, dict) and _list(family.get('periods')) for _page, _title, family in family_specs):
|
||
lines.extend(['## 标准 Dasha 表格页(PL9 p61-p120 对标)', ''])
|
||
if vimshottari_timeline:
|
||
current = _dict(dasha_main.get('current_dasha'))
|
||
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',
|
||
'',
|
||
'| 序号 | 主运 | 起始 | 结束 | 年数 |',
|
||
'|------|------|------|------|------|',
|
||
])
|
||
for index, period in enumerate(vimshottari_timeline, start=1):
|
||
if not isinstance(period, dict):
|
||
continue
|
||
status = 'birth_balance' if period.get('is_balance') else 'native'
|
||
lines.append(
|
||
f"| {index} | {_cell(_token(period.get('lord')))} | {_period_start(period)} | {_period_end(period)} | {_period_years(period)} | {status} |"
|
||
)
|
||
ad_rows = []
|
||
pd_rows = []
|
||
for md_period in vimshottari_timeline:
|
||
if not isinstance(md_period, dict):
|
||
continue
|
||
md_lord = md_period.get('lord')
|
||
for ad_period in _list(md_period.get('antardasha_timeline')):
|
||
if not isinstance(ad_period, dict):
|
||
continue
|
||
ad_rows.append((md_lord, ad_period))
|
||
ad_lord = ad_period.get('lord')
|
||
for pd_period in _list(ad_period.get('pratyantar_dasha_timeline')):
|
||
if isinstance(pd_period, dict):
|
||
pd_rows.append((md_lord, ad_lord, pd_period))
|
||
if ad_rows:
|
||
lines.extend(['', '| 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 | 主运 | 子运 | 次子运 | 起始 | 结束 |', '|--------------|------|------|--------|------|------|'])
|
||
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')))} | "
|
||
f"{_period_start(period)} | {_period_end(period)} |"
|
||
)
|
||
lines.append('')
|
||
for page, title_text, family in family_specs:
|
||
if not isinstance(family, dict):
|
||
continue
|
||
periods = _list(family.get('periods'))
|
||
if not periods:
|
||
continue
|
||
lines.extend([
|
||
f'### p{page} {title_text}',
|
||
'',
|
||
'| 序号 | 主星/星座 | 起始 | 结束 | 年数 |',
|
||
'|------|-----------|------|------|------|',
|
||
])
|
||
for index, period in enumerate(periods, start=1):
|
||
if not isinstance(period, dict):
|
||
continue
|
||
raw = _dict(period.get('raw_period'))
|
||
lord = period.get('lord') or period.get('planet') or period.get('sign') or period.get('rashi') or raw.get('lord') or raw.get('planet') or raw.get('sign') or raw.get('rashi')
|
||
lines.append(
|
||
f"| {index} | {_cell(_token(lord))} | {_period_start(period) or _cell(raw.get('start_date') or raw.get('start'))} | "
|
||
f"{_period_end(period) or _cell(raw.get('end_date') or raw.get('end'))} | {_period_years(period)} |"
|
||
)
|
||
child_rows = []
|
||
for period in periods:
|
||
if not isinstance(period, dict):
|
||
continue
|
||
raw = _dict(period.get('raw_period'))
|
||
for key in ('antardasha_periods', 'antardasha', 'antardashas', 'children'):
|
||
rows = _list(period.get(key))
|
||
if not rows:
|
||
rows = _list(raw.get(key))
|
||
for row in rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
identity = (
|
||
str(row.get('main') or row.get('mahadasha_lord') or row.get('parent_lord') or ''),
|
||
str(row.get('lord') or row.get('sub') or row.get('antardasha_lord') or row.get('planet') or ''),
|
||
str(_period_start(row)),
|
||
str(_period_end(row)),
|
||
)
|
||
if any(
|
||
identity == (
|
||
str(existing.get('main') or existing.get('mahadasha_lord') or existing.get('parent_lord') or ''),
|
||
str(existing.get('lord') or existing.get('sub') or existing.get('antardasha_lord') or existing.get('planet') or ''),
|
||
str(_period_start(existing)),
|
||
str(_period_end(existing)),
|
||
)
|
||
for existing in child_rows
|
||
):
|
||
continue
|
||
child_rows.append(row)
|
||
if child_rows:
|
||
lines.extend(['', '| AD/child | 主层 | 子层 | 起始 | 结束 |', '|----------|------|------|------|------|'])
|
||
for child_index, child in enumerate(child_rows, start=1):
|
||
child_raw = _dict(child.get('raw_period'))
|
||
main_lord = (
|
||
child.get('main')
|
||
or child.get('mahadasha_lord')
|
||
or child.get('parent_lord')
|
||
or child.get('parent_sign')
|
||
or child_raw.get('main')
|
||
or child_raw.get('parent_sign')
|
||
or '-'
|
||
)
|
||
sub_lord = (
|
||
child.get('lord')
|
||
or child.get('sub')
|
||
or child.get('antardasha_lord')
|
||
or child.get('planet')
|
||
or child.get('sign')
|
||
or child_raw.get('lord')
|
||
or child_raw.get('planet')
|
||
or child_raw.get('sign')
|
||
)
|
||
lines.append(
|
||
f"| {child_index} | {_cell(_token(main_lord))} | {_cell(_token(sub_lord))} | "
|
||
f"{_period_start(child) or _cell(child_raw.get('start_date') or child_raw.get('start'))} | "
|
||
f"{_period_end(child) or _cell(child_raw.get('end_date') or child_raw.get('end'))} |"
|
||
)
|
||
lines.append('')
|
||
source_dasha_pages = _list(source_visible.get('dasha_pages'))
|
||
if source_dasha_pages:
|
||
source_titles = {
|
||
92: 'Sthira Dasha',
|
||
93: 'Niryaana Shoola Dasha',
|
||
94: 'Niryaana Shoola Dasha',
|
||
95: 'Drig Dasha',
|
||
96: 'Drig Dasha',
|
||
97: 'Navamsha Dasha',
|
||
98: 'Navamsha Dasha',
|
||
99: 'Narayana Dasha',
|
||
100: 'Narayana Dasha',
|
||
101: 'Lagna Kendradi Rashi Dasha',
|
||
102: 'Lagna Kendradi Rashi Dasha',
|
||
103: 'Shoola Dasha',
|
||
}
|
||
lines.extend([
|
||
'## 原版 Dasha 日期表(同案 PL9 数据)',
|
||
'',
|
||
'以下表格保留同一命盘原版报告中的完整可见日期和星座边界,便于逐项读取与校对。',
|
||
'',
|
||
])
|
||
for source_page in source_dasha_pages:
|
||
if not isinstance(source_page, dict):
|
||
continue
|
||
page_number = int(source_page.get('page_number') or 0)
|
||
groups = _list(source_page.get('groups'))
|
||
if not groups:
|
||
continue
|
||
lines.extend([
|
||
f"### p{page_number} {source_titles.get(page_number, 'Dasha')}",
|
||
'',
|
||
'| 主运 | 起始年龄 | 子段 | 起始 | 结束 |',
|
||
'|------|----------|------|------|------|',
|
||
])
|
||
for group in groups:
|
||
if not isinstance(group, dict):
|
||
continue
|
||
group_title = group.get('title') or ''
|
||
annotations = _list(group.get('title_annotations'))
|
||
age = annotations[0] if annotations else ''
|
||
group_rows = _list(group.get('rows'))
|
||
boundaries = [
|
||
str((_dict(row.get('cells')).get('Boundary')) or '')
|
||
for row in group_rows
|
||
if isinstance(row, dict)
|
||
]
|
||
for index, row in enumerate(group_rows):
|
||
if not isinstance(row, dict):
|
||
continue
|
||
cells = _dict(row.get('cells'))
|
||
beginning = cells.get('Boundary') or ''
|
||
ending = boundaries[index + 1] if index + 1 < len(boundaries) else group.get('period_end') or ''
|
||
lines.append(
|
||
f"| {_cell(group_title)} | {_cell(age)} | {_cell(cells.get('Period'))} | "
|
||
f"{_cell(beginning)} | {_cell(ending)} |"
|
||
)
|
||
lines.append('')
|
||
kp = advanced.get('kp') if isinstance(advanced.get('kp'), dict) else {}
|
||
yogas_doshas = advanced.get('yogas_doshas') if isinstance(advanced.get('yogas_doshas'), dict) else {}
|
||
if kp or yogas_doshas.get('sade_sati'):
|
||
lines.extend(['## Saturn 与 KP 支持页(PL9 p121-p125 对标)', ''])
|
||
sade_sati = _dict(yogas_doshas.get('sade_sati'))
|
||
if sade_sati:
|
||
phases = _dict(sade_sati.get('phases'))
|
||
dosha_pack = _dict(advanced.get('dosha_remedy_pack'))
|
||
saturn_status = _dict(dosha_pack.get('saturn_status'))
|
||
timeline = _dict(saturn_status.get('cycle_timeline'))
|
||
display_rows = _dict(timeline.get('pl9_display_rows'))
|
||
sadhesati_rows = _list(timeline.get('sadhesati_rows')) or _list(display_rows.get('sadhesati'))
|
||
# Customer tables retain computed boundaries and reference points;
|
||
# print-layout rows deliberately blank repeated values.
|
||
kantaka_rows = _list(timeline.get('kantaka_rows')) or _list(display_rows.get('kantaka'))
|
||
lines.extend([
|
||
'### p121-p122 Sade Sati / Dhayya / Kantaka Saturn',
|
||
'',
|
||
f"- Moon sign:{_cell(_token(sade_sati.get('moon_sign')))}",
|
||
f"- 当前阶段:{_cell(sade_sati.get('phase') or sade_sati.get('status'))}",
|
||
f"- 是否 active:{_cell(sade_sati.get('is_active'))}",
|
||
f"- 摘要:{_cell(sade_sati.get('summary'))}",
|
||
'',
|
||
'| Phase | Sign |',
|
||
'|-------|------|',
|
||
f"| rising | {_cell(_token(phases.get('rising')))} |",
|
||
f"| peak | {_cell(_token(phases.get('peak')))} |",
|
||
f"| setting | {_cell(_token(phases.get('setting')))} |",
|
||
'',
|
||
])
|
||
if sadhesati_rows:
|
||
lines.extend([
|
||
'#### p121 Results of the Dhayya of Sadhesati',
|
||
'',
|
||
'| Cycle / phase | Transit of Saturn | Beginning date | Ending date | Duration Yr-Mn-Dy | Saturn AV | Sarva AV |',
|
||
'|---------------|-------------------|----------------|-------------|-------------------|-----------|----------|',
|
||
])
|
||
for item in sadhesati_rows:
|
||
if not isinstance(item, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(item.get('cycle_phase'))} | {_cell(item.get('transit_sign'))} | {_cell(item.get('beginning_date'))} | "
|
||
f"{_cell(item.get('ending_date'))} | {_cell(item.get('duration'))} | {_cell(item.get('saturn_av'))} | {_cell(item.get('sarva_av'))} |"
|
||
)
|
||
lines.append('')
|
||
if kantaka_rows:
|
||
lines.extend([
|
||
'#### p122 Results of Laghu Kalyani Dhayya and Kantaka Saturn',
|
||
'',
|
||
'| Cycle / phase | Transit of Saturn | Beginning date | Ending date | Duration Yr-Mn-Dy | Ashtakavarga |',
|
||
'|---------------|-------------------|----------------|-------------|-------------------|--------------|',
|
||
])
|
||
for item in kantaka_rows:
|
||
if not isinstance(item, dict):
|
||
continue
|
||
av_value = ' '.join(str(value) for value in (item.get('saturn_av'), item.get('sarva_av')) if value not in (None, ''))
|
||
lines.append(
|
||
f"| {_cell(item.get('cycle_phase'))} | {_cell(item.get('transit_sign'))} | {_cell(item.get('beginning_date'))} | "
|
||
f"{_cell(item.get('ending_date'))} | {_cell(item.get('duration'))} | {_cell(av_value)} |"
|
||
)
|
||
lines.append('')
|
||
if kp:
|
||
lines.extend([
|
||
'### p123 KP Chart / Cusps',
|
||
'',
|
||
f"- House basis:{_cell(kp.get('house_basis'))}",
|
||
'',
|
||
'| House | Sign | Cusp longitude | KP lord chain |',
|
||
'|-------|------|----------------|---------------|',
|
||
])
|
||
houses = _dict(kp.get('houses'))
|
||
for house in [str(i) for i in range(1, 13)]:
|
||
row = _dict(houses.get(house))
|
||
if not row:
|
||
continue
|
||
lines.append(
|
||
f"| {house} | {_cell(_token(row.get('sign')))} | {_num(row.get('cusp_longitude'), 4)} | {_cell(_lord_chain(row.get('kp_lords')))} |"
|
||
)
|
||
lines.extend(['', '### p124 KP Significators / Planet Lords', '', '| Planet | KP lord chain | A | B | C | D |', '|--------|---------------|---|---|---|---|'])
|
||
planets = _dict(kp.get('planets'))
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu'):
|
||
row = _dict(planets.get(planet))
|
||
if not row:
|
||
continue
|
||
sig = _dict(row.get('significators'))
|
||
lines.append(
|
||
f"| {_cell(_token(planet))} | {_cell(_lord_chain(row.get('kp_lords')))} | {_cell(sig.get('A'))} | {_cell(sig.get('B'))} | {_cell(sig.get('C'))} | {_cell(sig.get('D'))} |"
|
||
)
|
||
ruling = _dict(kp.get('ruling_planets'))
|
||
if ruling:
|
||
lines.extend(['', '### p125 Natal / KP Ruling Planets', '', '| Field | Planet |', '|-------|--------|'])
|
||
for key in ('day_lord', 'moon_sign_lord', 'moon_star_lord', 'moon_sub_lord', 'ascendant_sign_lord', 'ascendant_star_lord', 'ascendant_sub_lord'):
|
||
lines.append(f"| {_cell(key)} | {_cell(_token(ruling.get(key)))} |")
|
||
lines.append('')
|
||
kp_source_cells = _dict(source_visible.get('kp_p124_visual_cells'))
|
||
if kp_source_cells:
|
||
lines.extend(['', '### p124 KP 原版细格', ''])
|
||
for table_key, title, columns in (
|
||
('house_significations', 'House significators', ('house', 'planets_in_nak_of_occupants', 'occupants', 'planets_in_nak_of_cusp_sign_lord', 'cusp_sign_lord')),
|
||
('planet_significations', 'Planet significators', ('planet', 'very_strong', 'strong', 'normal', 'weak')),
|
||
('ruling_planets', 'Ruling planets', ()),
|
||
):
|
||
table = _dict(kp_source_cells.get(table_key))
|
||
rows = _list(table.get('rows'))
|
||
if not rows:
|
||
fields = _dict(table.get('fields'))
|
||
rows = [{'field': key, 'value': value} for key, value in fields.items()]
|
||
if not rows:
|
||
continue
|
||
lines.extend([f'#### {title}', ''])
|
||
if table_key == 'ruling_planets':
|
||
lines.extend(['| Field | Value |', '|-------|-------|'])
|
||
for row in rows:
|
||
lines.append(f"| {_cell(row.get('field'))} | {_cell(row.get('value'))} |")
|
||
else:
|
||
available = [key for key in columns if any(row.get(key) not in (None, '') for row in rows)]
|
||
if not available:
|
||
continue
|
||
lines.extend([
|
||
'| ' + ' | '.join(available) + ' |',
|
||
'|' + '|'.join('---' for _ in available) + '|',
|
||
])
|
||
for row in rows:
|
||
lines.append('| ' + ' | '.join(_cell(row.get(key)) for key in available) + ' |')
|
||
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 对标)', ''])
|
||
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 基础资料与年度行星表',
|
||
'',
|
||
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'))}",
|
||
'',
|
||
])
|
||
year_lord = _dict(annual.get('year_lord'))
|
||
year_lord_data = _dict(year_lord.get('data') or year_lord)
|
||
if year_lord_data:
|
||
candidates = _list(year_lord_data.get('candidates'))
|
||
lines.extend([
|
||
'#### Varshesha / Year Lord',
|
||
'',
|
||
'| Field | Value |',
|
||
'|-------|-------|',
|
||
f"| Varshesha | {_cell(_token(year_lord_data.get('year_lord') or year_lord_data.get('lord')))} |",
|
||
f"| Selection basis | {_cell(year_lord_data.get('selection_basis'))} |",
|
||
f"| Annual ascendant sign | {_cell(_token(year_lord_data.get('annual_asc_sign')))} |",
|
||
f"| Muntha sign | {_cell(_token(year_lord_data.get('muntha_sign')))} |",
|
||
])
|
||
if candidates:
|
||
lines.append(f"| Candidate count | {_cell(len(candidates))} |")
|
||
lines.append('')
|
||
if candidates:
|
||
lines.extend([
|
||
'| Candidate source | Planet | House | Sign | Panchavargiya score |',
|
||
'|------------------|--------|-------|------|---------------------|',
|
||
])
|
||
for row in candidates:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('source'))} | {_cell(_token(row.get('planet')))} | {_cell(row.get('planet_house'))} | "
|
||
f"{_cell(_token(row.get('planet_sign')))} | {_cell(row.get('panchavargiya_score'))} |"
|
||
)
|
||
lines.append('')
|
||
planet_longitudes = _dict(annual_chart_data.get('planet_longitudes'))
|
||
if planet_longitudes:
|
||
signs = globals().get('SIGNS', [])
|
||
lines.extend([
|
||
'| Planet | Sign | Degree | Longitude |',
|
||
'|--------|------|--------|-----------|',
|
||
])
|
||
if annual_chart_data.get('asc_sign') is not None:
|
||
lines.append(f"| Lagna | {_cell(_token(annual_chart_data.get('asc_sign')))} | - | - |")
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu'):
|
||
longitude = planet_longitudes.get(planet)
|
||
if not isinstance(longitude, (int, float)):
|
||
continue
|
||
sign = signs[int(float(longitude) // 30) % 12] if signs else '-'
|
||
lines.append(
|
||
f"| {_cell(_token(planet))} | {_cell(_token(sign))} | {_cell(_degree(float(longitude) % 30))} | "
|
||
f"{_cell(_degree(longitude))} |"
|
||
)
|
||
lines.append('')
|
||
current_year = _dict(annual.get('pl9_current_year_results'))
|
||
current_sections = _list(current_year.get('sections'))
|
||
if current_sections:
|
||
lines.extend([
|
||
'### p139-p141 Varshaphala 年度结果解释',
|
||
'',
|
||
'| 项目 | 锚点 | 年度主题 |',
|
||
'|------|------|----------|',
|
||
])
|
||
for section in current_sections:
|
||
if not isinstance(section, dict):
|
||
continue
|
||
anchor = _dict(section.get('anchor'))
|
||
anchor_text = ', '.join(
|
||
f"{_token(key)}={_token(value)}"
|
||
for key, value in anchor.items()
|
||
if key != 'field_status' and value not in (None, '')
|
||
)
|
||
lines.append(
|
||
f"| {_cell(section.get('title'))} | {_cell(anchor_text)} | "
|
||
f"{_cell(_brief(section.get('themes'), 3))} |"
|
||
)
|
||
lines.append('')
|
||
annual_dasha = _dict(annual.get('pl9_annual_dasha_results'))
|
||
dasha_rows = _list(annual_dasha.get('rows'))
|
||
if dasha_rows:
|
||
lines.extend([
|
||
'### p144-p145 Annual Dasha 结果解释',
|
||
'',
|
||
'| 阶段 | 主星 | 年盘宫位 | 日期边界 | 主题 |',
|
||
'|------|------|----------|----------|------|',
|
||
])
|
||
for row in dasha_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
boundary = f"{row.get('start')} - {row.get('end')}" if row.get('start') and row.get('end') else row.get('start') or '-'
|
||
lines.append(
|
||
f"| {_cell(row.get('order'))} | {_cell(_token(row.get('lord')))} | {_cell(row.get('annual_whole_sign_house'))} | "
|
||
f"{_cell(boundary)} | {_cell(_brief(row.get('themes'), 2))} |"
|
||
)
|
||
lines.append('')
|
||
normalized = _dict(annual.get('normalized_tables'))
|
||
mudda_rows = _list(normalized.get('mudda_rows'))
|
||
if mudda_rows:
|
||
lines.extend([
|
||
'### p132-p135 Mudda / Patyayini 年度 Dasha 表',
|
||
'',
|
||
'| balance profile | 主星 | 开始 | 结束 | 边界口径 |',
|
||
'|-----------------|------|------|------|----------|',
|
||
])
|
||
for row in mudda_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('balance_profile'))} | {_cell(_token(row.get('lord')))} | "
|
||
f"{_cell(row.get('start'))} | {_cell(row.get('end'))} | {_public_status_note(row.get('boundary_status'))} |"
|
||
)
|
||
lines.append('')
|
||
patyayini_rows = _list(normalized.get('patyayini_rows'))
|
||
if patyayini_rows:
|
||
lines.extend([
|
||
'### p134 Patyayini / Patyamsha ending-date 表',
|
||
'',
|
||
'| 主层 | 子层 | 边界 | 边界语义 |',
|
||
'|------|------|------|----------|',
|
||
])
|
||
for row in patyayini_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(_token(row.get('main')))} | {_cell(_token(row.get('sub')))} | "
|
||
f"{_cell(row.get('boundary'))} | {_public_status_note(row.get('boundary_status'))} |"
|
||
)
|
||
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 | 结果 | 形成行星 | 相位运动(入相/分离) |',
|
||
'|------|------|----------|----------------------|',
|
||
])
|
||
for row in tajika_rows[:32]:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
forming = row.get('forming_planets') or row.get('planets') or row.get('raw_relationships')
|
||
lines.append(
|
||
f"| {_cell(row.get('yoga') or row.get('name'))} | {_cell(_public_report_text(row.get('observation_status') or row.get('condition_status') or row.get('present')))} | "
|
||
f"{_cell(forming)} | {_cell({'applying': '入相', 'separating': '分离'}.get(str(row.get('motion') or row.get('aspect_motion') or '-'), row.get('motion') or row.get('aspect_motion') or '-'))} |"
|
||
)
|
||
lines.append('')
|
||
saham_rows = _list(normalized.get('saham_rows'))
|
||
if saham_rows:
|
||
lines.extend([
|
||
'### p130 Sahams 点位表',
|
||
'',
|
||
'| Saham | 星座 | 星座内度数 |',
|
||
'|-------|------|------------|',
|
||
])
|
||
for row in saham_rows[:48]:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('name') or row.get('saham'))} | {_cell(_token(row.get('sign')))} | "
|
||
f"{_cell(_degree(row.get('degree_in_sign')))} |"
|
||
)
|
||
lines.append('')
|
||
public_replay = _dict(annual.get('public_panchavargiya_replay'))
|
||
replay_coverage = _dict(public_replay.get('coverage'))
|
||
if public_replay:
|
||
lines.extend([
|
||
'### 公开 Panchavargiya 数值复核边界',
|
||
'',
|
||
'| 项目 | 结果 |',
|
||
'|------|------|',
|
||
f"| 公开复核状态 | {_cell(public_replay.get('status'))} |",
|
||
f"| 覆盖行星 | {_cell(replay_coverage.get('planet_count'))} |",
|
||
f"| 已匹配分量 | {_cell(replay_coverage.get('exact_component_match_count'))} / {_cell(replay_coverage.get('component_count'))} |",
|
||
f"| 已匹配总分 | {_cell(replay_coverage.get('exact_total_match_count'))} / {_cell(replay_coverage.get('planet_count'))} |",
|
||
f"| 可直接用于公式升级 | {_cell(public_replay.get('promotion_allowed'))} |",
|
||
'',
|
||
])
|
||
annual_extra_specs = (
|
||
('annual_anchor_rows', 'p126 年度锚点', ('name', 'label', 'value', 'sign', 'degree', 'house')),
|
||
('annual_planet_rows', 'p127 年度行星资料', ('planet', 'sign', 'degree', 'longitude', 'nakshatra', 'pada')),
|
||
('true_solar_return_rows', 'True Solar Return', ('field', 'value', 'date', 'time', 'dt_local')),
|
||
('panchadhikari_rows', 'Panchadhikari', ('planet', 'role', 'value', 'sign', 'house')),
|
||
('tripataki_placement_rows', 'Tripataki Placement', ('planet', 'sign', 'house', 'degree', 'placement')),
|
||
('tripataki_vedha_rows', 'Tripataki Vedha', ('planet', 'target', 'sign', 'house', 'result')),
|
||
('current_year_result_rows', 'p139-p141 年度结果', ('title', 'value', 'theme', 'summary', 'anchor')),
|
||
('tajika_presence_rows', 'Tajika Presence', ('yoga', 'name', 'present', 'forming_planets')),
|
||
('tajika_condition_status_rows', 'Tajika Condition', ('yoga', 'name', 'condition', 'present')),
|
||
('tajika_forming_pair_rows', 'Tajika Forming Pair', ('yoga', 'name', 'planet_a', 'planet_b', 'forming_planets')),
|
||
('tajika_strength_rows', 'Tajika Strength', ('planet', 'yoga', 'strength', 'value')),
|
||
('raman_saham_rows', 'Raman Sahams', ('name', 'saham', 'sign', 'degree_in_sign', 'degree')),
|
||
)
|
||
for table_key, title, preferred_keys in annual_extra_specs:
|
||
rows = _list(normalized.get(table_key))
|
||
if not rows:
|
||
continue
|
||
scalar_rows = [row for row in rows if isinstance(row, dict)]
|
||
if table_key == 'true_solar_return_rows':
|
||
hidden_fields = {'error_deg', 'iterations', 'method', 'raw_root', 'status'}
|
||
scalar_rows = [
|
||
row for row in scalar_rows
|
||
if str(row.get('field') or '').strip() not in hidden_fields
|
||
]
|
||
if not scalar_rows:
|
||
continue
|
||
keys = [key for key in preferred_keys if any(row.get(key) not in (None, '') for row in scalar_rows)]
|
||
if not keys:
|
||
keys = [
|
||
key for key in scalar_rows[0]
|
||
if key not in {'source_path', 'execution_status', 'confidence_status', 'boundary_status', 'status', 'selection_status'}
|
||
and not isinstance(scalar_rows[0].get(key), (dict, list, tuple))
|
||
][:8]
|
||
if not keys:
|
||
continue
|
||
lines.extend([
|
||
f'### {title}',
|
||
'',
|
||
'| ' + ' | '.join(_cell(key) for key in keys) + ' |',
|
||
'|' + '|'.join('---' for _key in keys) + '|',
|
||
])
|
||
for row in scalar_rows:
|
||
lines.append('| ' + ' | '.join(_cell(row.get(key)) for key in keys) + ' |')
|
||
lines.append('')
|
||
annual_special = _dict(annual.get('annual_special_points'))
|
||
annual_special_data = _dict(annual_special.get('data'))
|
||
if annual_special_data:
|
||
lines.extend([
|
||
'### 年度特殊点',
|
||
'',
|
||
'| Point | Sign | Degree | Longitude | Lord |',
|
||
'|-------|------|--------|-----------|------|',
|
||
])
|
||
for name, row in annual_special_data.items():
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('name') or name)} | {_cell(_token(row.get('sign')))} | "
|
||
f"{_cell(_degree(row.get('degree_in_sign')))} | {_cell(_degree(row.get('longitude')))} | "
|
||
f"{_cell(_token(row.get('lord')))} |"
|
||
)
|
||
lines.append('')
|
||
tripataki = _dict(annual.get('tripataki_chakra'))
|
||
tripataki_placements = _list(tripataki.get('placements'))
|
||
tripataki_vedha = _list(tripataki.get('vedha_rows'))
|
||
if tripataki_placements:
|
||
lines.extend([
|
||
'### Tripataki Chakra 年盘落点',
|
||
'',
|
||
'| Body | Sign | Degree | Longitude |',
|
||
'|------|------|--------|-----------|',
|
||
])
|
||
for row in tripataki_placements:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('body'))} | {_cell(_token(row.get('sign')))} | "
|
||
f"{_cell(_degree(row.get('degree_in_sign')))} | {_cell(_degree(row.get('longitude')))} |"
|
||
)
|
||
lines.append('')
|
||
if tripataki_vedha:
|
||
lines.extend([
|
||
'| Target | Vedha sources |',
|
||
'|--------|---------------|',
|
||
])
|
||
for row in tripataki_vedha:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
sources = row.get('sources') if isinstance(row.get('sources'), list) else []
|
||
lines.append(f"| {_cell(row.get('target'))} | {_cell('、'.join(map(str, sources)))} |")
|
||
lines.append('')
|
||
monthly_pack = _dict(annual.get('monthly_chart_pack'))
|
||
monthly_rows = _list(monthly_pack.get('rows'))
|
||
if monthly_rows:
|
||
lines.extend([
|
||
'### 年度月返照图盘数据',
|
||
'',
|
||
'| 月份 | 本地时刻 | 年盘上升 | 上升度数 | 行星黄经(度) |',
|
||
'|------|----------|----------|----------|-----------------|',
|
||
])
|
||
for row in monthly_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
asc = _dict(row.get('ascendant'))
|
||
longitudes = _dict(row.get('planet_longitudes'))
|
||
longitude_text = ';'.join(
|
||
f"{_token(planet)} {_degree(value)}"
|
||
for planet, value in longitudes.items()
|
||
if isinstance(value, (int, float))
|
||
)
|
||
lines.append(
|
||
f"| {_cell(row.get('month_index'))} | {_cell(row.get('local_datetime'))} | "
|
||
f"{_cell(_token(asc.get('sign')))} | {_cell(_degree(asc.get('degree_in_sign') or asc.get('degree')))} | "
|
||
f"{_cell(longitude_text)} |"
|
||
)
|
||
lines.append('')
|
||
lifetime_pack = _dict(annual.get('lifetime_annual_chart_pages'))
|
||
lifetime_pages = _list(lifetime_pack.get('pages'))
|
||
lifetime_rows = [
|
||
row for page in lifetime_pages if isinstance(page, dict)
|
||
for row in _list(page.get('rows')) if isinstance(row, dict)
|
||
]
|
||
if lifetime_rows:
|
||
lines.extend([
|
||
'### 生命周期年度返照图盘数据',
|
||
'',
|
||
'| 年龄 | 年份 | 年返时刻 | 上升 | 上升度数 | 行星黄经(度) |',
|
||
'|------|------|----------|------|----------|-----------------|',
|
||
])
|
||
for row in lifetime_rows:
|
||
asc = _dict(row.get('ascendant'))
|
||
longitudes = _dict(row.get('planet_longitudes'))
|
||
longitude_text = ';'.join(
|
||
f"{_token(planet)} {_degree(value)}"
|
||
for planet, value in longitudes.items()
|
||
if isinstance(value, (int, float))
|
||
)
|
||
lines.append(
|
||
f"| {_cell(row.get('completed_age'))} | {_cell(row.get('target_year'))} | "
|
||
f"{_cell(row.get('local_datetime'))} | {_cell(_token(asc.get('sign')))} | "
|
||
f"{_cell(_degree(asc.get('degree_in_sign') or asc.get('degree')))} | {_cell(longitude_text)} |"
|
||
)
|
||
lines.append('')
|
||
annual_series = _dict(annual.get('annual_chart_series_pack'))
|
||
for series_key, title in (
|
||
('birth_place_eight_year_overview', '出生地八年年度总览'),
|
||
('local_eight_year_overview', '现居地八年年度总览'),
|
||
):
|
||
series = _dict(annual_series.get(series_key))
|
||
series_rows = _list(series.get('rows'))
|
||
if not series_rows:
|
||
continue
|
||
lines.extend([
|
||
f'### {title}',
|
||
'',
|
||
'| 年份 | 年龄 | 年返时刻 | 年盘上升 |',
|
||
'|------|------|----------|----------|',
|
||
])
|
||
for row in series_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('target_year'))} | {_cell(row.get('age'))} | "
|
||
f"{_cell(row.get('solar_return_local') or row.get('local_datetime'))} | "
|
||
f"{_cell(_token(row.get('annual_ascendant')))} |"
|
||
)
|
||
lines.append('')
|
||
annual_visual = annual.get('pl9_annual_visual_panels') if isinstance(annual.get('pl9_annual_visual_panels'), dict) else {}
|
||
panels = annual_visual.get('panels') if isinstance(annual_visual.get('panels'), list) else []
|
||
if panels:
|
||
lines.extend(['### p139-p155 年度北印度图盘', ''])
|
||
for panel in panels:
|
||
if not isinstance(panel, dict):
|
||
continue
|
||
lines.append(f"##### p{panel.get('pl9_page')} · {panel.get('chart_label') or panel.get('panel_id')}")
|
||
lines.append('')
|
||
annual_profile = _dict(annual.get('profile'))
|
||
primary_target_year = str(annual_profile.get('target_year') or '') if annual_profile else ''
|
||
annual_series = _dict(timing.get('annual_tajika_series'))
|
||
annual_series_years = _dict(annual_series.get('years'))
|
||
future_year_items = [
|
||
(year, _dict(pack))
|
||
for year, pack in sorted(annual_series_years.items(), key=lambda item: str(item[0]))
|
||
if str(year) and str(year) != primary_target_year and isinstance(pack, dict)
|
||
]
|
||
for year, annual_pack in future_year_items:
|
||
annual_chart = _dict(annual_pack.get('annual_chart'))
|
||
annual_chart_data = _dict(annual_chart.get('data'))
|
||
solar_return = _dict(annual_pack.get('solar_return'))
|
||
solar_return_data = _dict(solar_return.get('data') or solar_return)
|
||
lines.extend([f'### {year} 年度 Varshaphala / Tajika 细项', ''])
|
||
if solar_return_data or annual_chart_data:
|
||
lines.extend([
|
||
'| 项目 | 数值 |',
|
||
'|------|------|',
|
||
f"| Varshapravesha | {_cell(solar_return_data.get('dt_local') or solar_return_data.get('local_time') or solar_return_data.get('date') or '-')} |",
|
||
f"| 年返上升 | {_cell(_token(annual_chart_data.get('asc_sign')))} |",
|
||
])
|
||
year_lord = _dict(annual_pack.get('year_lord'))
|
||
year_lord_data = _dict(year_lord.get('data') or year_lord)
|
||
if year_lord_data:
|
||
lines.extend([
|
||
f"| Varshesha | {_cell(_token(year_lord_data.get('year_lord') or year_lord_data.get('lord')))} |",
|
||
f"| Muntha sign | {_cell(_token(year_lord_data.get('muntha_sign')))} |",
|
||
])
|
||
lines.append('')
|
||
planet_longitudes = _dict(annual_chart_data.get('planet_longitudes'))
|
||
if planet_longitudes:
|
||
signs = globals().get('SIGNS', [])
|
||
lines.extend([
|
||
'#### 年度行星资料',
|
||
'',
|
||
'| 行星 | 星座 | 落座度数 | 黄经 |',
|
||
'|------|------|----------|------|',
|
||
])
|
||
if annual_chart_data.get('asc_sign') is not None:
|
||
lines.append(f"| Lagna | {_cell(_token(annual_chart_data.get('asc_sign')))} | - | - |")
|
||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu'):
|
||
longitude = planet_longitudes.get(planet)
|
||
if not isinstance(longitude, (int, float)):
|
||
continue
|
||
sign = signs[int(float(longitude) // 30) % 12] if signs else '-'
|
||
lines.append(
|
||
f"| {_cell(_token(planet))} | {_cell(_token(sign))} | {_cell(_degree(float(longitude) % 30))} | "
|
||
f"{_cell(_degree(longitude))} |"
|
||
)
|
||
lines.append('')
|
||
annual_dasha = _dict(annual_pack.get('pl9_annual_dasha_results'))
|
||
dasha_rows = _list(annual_dasha.get('rows'))
|
||
if dasha_rows:
|
||
lines.extend([
|
||
'#### Annual Dasha 阶段表',
|
||
'',
|
||
'| 阶段 | 主星 | 年盘宫位 | 日期边界 | 主题 |',
|
||
'|------|------|----------|----------|------|',
|
||
])
|
||
for row in dasha_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
boundary = f"{row.get('start')} - {row.get('end')}" if row.get('start') and row.get('end') else row.get('start') or '-'
|
||
lines.append(
|
||
f"| {_cell(row.get('order'))} | {_cell(_token(row.get('lord')))} | {_cell(row.get('annual_whole_sign_house'))} | "
|
||
f"{_cell(boundary)} | {_cell(_brief(row.get('themes'), 2))} |"
|
||
)
|
||
lines.append('')
|
||
normalized = _dict(annual_pack.get('normalized_tables'))
|
||
future_specs = (
|
||
('mudda_rows', 'Mudda / Patyayini 年度 Dasha 表', ('balance_profile', 'lord', 'start', 'end', 'boundary_status')),
|
||
('patyayini_rows', 'Patyayini / Patyamsha ending-date 表', ('main', 'sub', 'boundary', 'boundary_status')),
|
||
('saham_rows', 'Sahams 点位表', ('name', 'saham', 'sign', 'degree_in_sign')),
|
||
('tajika_yoga_rows', 'Tajika Yoga Presence 表', ('yoga', 'name', 'present', 'forming_planets')),
|
||
('pyjhora_tajika_yoga_rows', 'Tajika Yoga Presence 表', ('yoga', 'name', 'present', 'forming_planets')),
|
||
('annual_planet_rows', '年度行星扩展字段', ('planet', 'sign', 'degree', 'longitude', 'nakshatra', 'pada')),
|
||
('annual_anchor_rows', '年度锚点', ('name', 'label', 'value', 'sign', 'degree', 'house')),
|
||
)
|
||
rendered_titles = set()
|
||
for table_key, title, preferred_keys in future_specs:
|
||
rows = [row for row in _list(normalized.get(table_key)) if isinstance(row, dict)]
|
||
if not rows:
|
||
continue
|
||
if title in rendered_titles and table_key == 'pyjhora_tajika_yoga_rows':
|
||
continue
|
||
rendered_titles.add(title)
|
||
keys = [key for key in preferred_keys if any(row.get(key) not in (None, '') for row in rows)]
|
||
if not keys:
|
||
keys = [
|
||
key for key in rows[0]
|
||
if key not in {'source_path', 'execution_status', 'confidence_status', 'boundary_status', 'status', 'selection_status'}
|
||
and not isinstance(rows[0].get(key), (dict, list, tuple))
|
||
][:6]
|
||
if not keys:
|
||
continue
|
||
lines.extend([
|
||
f'#### {title}',
|
||
'',
|
||
'| ' + ' | '.join(_cell(key) for key in keys) + ' |',
|
||
'|' + '|'.join('---' for _key in keys) + '|',
|
||
])
|
||
for row in rows:
|
||
lines.append('| ' + ' | '.join(_cell(row.get(key)) for key in keys) + ' |')
|
||
lines.append('')
|
||
special = _dict(annual_pack.get('annual_special_points'))
|
||
special_data = _dict(special.get('data'))
|
||
if special_data:
|
||
lines.extend([
|
||
'#### 年度特殊点',
|
||
'',
|
||
'| 点位 | 星座 | 落座度数 | 黄经 | 守护星 |',
|
||
'|------|------|----------|------|--------|',
|
||
])
|
||
for name, row in special_data.items():
|
||
if not isinstance(row, dict):
|
||
continue
|
||
lines.append(
|
||
f"| {_cell(row.get('name') or name)} | {_cell(_token(row.get('sign')))} | "
|
||
f"{_cell(_degree(row.get('degree_in_sign')))} | {_cell(_degree(row.get('longitude')))} | "
|
||
f"{_cell(_token(row.get('lord')))} |"
|
||
)
|
||
lines.append('')
|
||
monthly_pack = _dict(annual_pack.get('monthly_chart_pack'))
|
||
monthly_rows = _list(monthly_pack.get('rows'))
|
||
if monthly_rows:
|
||
lines.extend([
|
||
'#### 年度月返照图盘数据',
|
||
'',
|
||
'| 月份 | 本地时刻 | 年盘上升 | 上升度数 |',
|
||
'|------|----------|----------|----------|',
|
||
])
|
||
for row in monthly_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
asc = _dict(row.get('ascendant'))
|
||
lines.append(
|
||
f"| {_cell(row.get('month_index'))} | {_cell(row.get('local_datetime'))} | "
|
||
f"{_cell(_token(asc.get('sign')))} | {_cell(_degree(asc.get('degree_in_sign') or asc.get('degree')))} |"
|
||
)
|
||
lines.append('')
|
||
d1_sheet = worksheets.get('d1_rasi_bhava') if isinstance(worksheets.get('d1_rasi_bhava'), dict) else {}
|
||
d1_houses = _dict(d1_sheet.get('houses')) or _dict(core_chart.get('houses'))
|
||
d1_planets = _dict(d1_sheet.get('planets')) or _dict(core_chart.get('planets'))
|
||
advanced_yoga = advanced.get('yoga') if isinstance(advanced.get('yoga'), dict) else {}
|
||
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 对标)', ''])
|
||
if d1_houses and d1_planets:
|
||
lines.extend([
|
||
'### p156-p162 宫主(Bhavesh)落宫解释',
|
||
'',
|
||
'| House | Sign | Lord | Lord placement | Lord dignity | Dasha activation |',
|
||
'|-------|------|------|----------------|--------------|------------------|',
|
||
])
|
||
active_lords = set()
|
||
current_dasha = _dict(timing.get('dasha')).get('current_dasha') if isinstance(timing.get('dasha'), dict) else {}
|
||
if isinstance(current_dasha, dict) and current_dasha.get('lord'):
|
||
active_lords.add(str(current_dasha.get('lord')))
|
||
for ad in _list(current_dasha.get('antardasha_timeline')):
|
||
if isinstance(ad, dict) and ad.get('is_current') and ad.get('lord'):
|
||
active_lords.add(str(ad.get('lord')))
|
||
for index in range(1, 13):
|
||
house = _dict(d1_houses.get(f'house_{index}') or d1_houses.get(str(index)))
|
||
lord = house.get('lord')
|
||
lord_row = _dict(d1_planets.get(str(lord)))
|
||
placement = '-'
|
||
if lord_row:
|
||
placement = f"{_token(lord_row.get('sign'))} H{lord_row.get('house') or '-'} {_degree(lord_row.get('degree_in_sign'))}"
|
||
lines.append(
|
||
f"| {index} | {_cell(_token(house.get('cusp_sign') or house.get('sign')))} | {_cell(_token(lord))} | "
|
||
f"{_cell(placement)} | {_cell(lord_row.get('status') or lord_row.get('dignity'))} | {_cell('active' if str(lord) in active_lords else 'background')} |"
|
||
)
|
||
lines.append('')
|
||
bhavesh_pages = _list(bhavesh_pack.get('pages'))
|
||
if bhavesh_pages:
|
||
lines.extend([
|
||
'### 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'))}"
|
||
lines.extend([f'#### {title}', ''])
|
||
for key in ('house_context', 'lord_placement', 'result', 'context', 'activation'):
|
||
value = str(sections.get(key) or '').strip()
|
||
if value:
|
||
lines.extend([_public_report_text(value), ''])
|
||
if yogas_doshas:
|
||
lines.extend([
|
||
'### p163-p171 Mangala / Sade Sati / Dosha Results',
|
||
'',
|
||
'| Topic | Detected / phase | Key facts | Summary |',
|
||
'|-------|------------------|-----------|---------|',
|
||
])
|
||
for key, label in (
|
||
('mangal_dosha', 'Mangal / Kuja Dosha'),
|
||
('kaal_sarp_dosha', 'Kaal Sarp Dosha'),
|
||
('pitra_dosha', 'Pitra Dosha'),
|
||
('sade_sati', 'Sade Sati'),
|
||
):
|
||
row = _dict(yogas_doshas.get(key))
|
||
if not row:
|
||
continue
|
||
detected = row.get('has_dosha')
|
||
if detected is None:
|
||
detected = row.get('is_active') if row.get('is_active') is not None else row.get('phase')
|
||
fact_bits = []
|
||
for fact_key in ('mars_house', 'mars_sign', 'sun_house', 'sun_sign', 'rahu_house', 'ketu_house', 'moon_sign', 'severity', 'type'):
|
||
if row.get(fact_key) not in (None, ''):
|
||
fact_bits.append(f"{fact_key}={_token(row.get(fact_key))}")
|
||
lines.append(
|
||
f"| {_cell(label)} | {_cell(detected)} | {_cell(';'.join(map(str, fact_bits)) or '-')} | {_cell(row.get('summary'))} |"
|
||
)
|
||
lines.append('')
|
||
dosha_rows = _list(dosha_pack.get('dosha_rows'))
|
||
if dosha_rows:
|
||
lines.extend([
|
||
'### Dosha 逐项事实与解释',
|
||
'',
|
||
])
|
||
for row in dosha_rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
title = str(row.get('title') or row.get('id') or '').strip()
|
||
sections = _dict(row.get('sections'))
|
||
if not title:
|
||
continue
|
||
lines.extend([f'#### {title}', ''])
|
||
for key in ('detected', 'consideration', 'facts', 'result', 'cancellation'):
|
||
value = str(sections.get(key) or '').strip()
|
||
if value:
|
||
lines.extend([_public_report_text(value), ''])
|
||
practices = _dict(dosha_pack.get('supportive_practices'))
|
||
recommendations = _list(practices.get('recommendations'))
|
||
if recommendations:
|
||
lines.extend([
|
||
'### 传统辅助建议',
|
||
'',
|
||
'| 对象 | 建议 | 宝石/次数 |',
|
||
'|------|------|-----------|',
|
||
])
|
||
for row in recommendations:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
actions = row.get('actions') if isinstance(row.get('actions'), list) else []
|
||
advice = row.get('note') or ';'.join(str(item) for item in actions if item)
|
||
lines.append(
|
||
f"| {_cell(_token(row.get('planet') or row.get('dosha')))} | "
|
||
f"{_cell(_public_report_text(advice))} | "
|
||
f"{_cell(row.get('gem') or row.get('repetitions') or ('No gem or repetition count supplied' if _pl9_report_language(packet) == 'en' else '源数据未提供宝石或次数'))} |"
|
||
)
|
||
lines.append('')
|
||
if detected_yogas:
|
||
lines.extend([
|
||
'### p172-p189 Applicable Benefic / Mixed / Malefic Yogas',
|
||
'',
|
||
f"- Yoga detected count:{len(detected_yogas)}",
|
||
f"- Context layers:{_cell(_brief(advanced_yoga.get('context_layers'), 6))}",
|
||
'',
|
||
])
|
||
def _yoga_group_label(row: dict) -> str:
|
||
category = str(row.get('category') or row.get('subtype') or '').lower()
|
||
name = str(row.get('name') or row.get('name_cn') or row.get('type') or '').lower()
|
||
if category in {'durbhaga', 'ari'} or 'daridra' in name or 'arishta' in name:
|
||
return 'Applicable Malefic Yogas'
|
||
if category in {'conjunction', 'special', 'kalatra', 'solar_yoga', 'lunar_yoga'} or 'dosha' in name:
|
||
return 'Applicable Mixed Yogas'
|
||
return 'Applicable Benefic Yogas'
|
||
|
||
grouped_yogas: dict[str, list[dict]] = {
|
||
'Applicable Benefic Yogas': [],
|
||
'Applicable Mixed Yogas': [],
|
||
'Applicable Malefic Yogas': [],
|
||
}
|
||
for row in detected_yogas:
|
||
if isinstance(row, dict):
|
||
grouped_yogas[_yoga_group_label(row)].append(row)
|
||
for group_title, group_rows in grouped_yogas.items():
|
||
if not group_rows:
|
||
continue
|
||
lines.extend([
|
||
f'#### {group_title}',
|
||
'',
|
||
'| Yoga | Category | Strength | Combination | Effects / notes |',
|
||
'|------|----------|----------|-------------|-----------------|',
|
||
])
|
||
for row in group_rows:
|
||
effects = row.get('effects') if isinstance(row.get('effects'), list) else []
|
||
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(_public_report_text(_brief(effects, 3) or row.get('interpretation') or row.get('summary')))} |"
|
||
)
|
||
lines.append('')
|
||
supportive_raw = dosha_pack.get('supportive_practices')
|
||
supportive_practices = _list(supportive_raw)
|
||
if isinstance(supportive_raw, dict):
|
||
supportive_practices = _list(supportive_raw.get('recommendations'))
|
||
if supportive_practices:
|
||
lines.extend([
|
||
'### Remedies / Supportive Practices',
|
||
'',
|
||
'| Topic | Practice | Gem / count | Notes |',
|
||
'|-------|----------|-------------|-------|',
|
||
])
|
||
for row in supportive_practices:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
actions = row.get('actions') if isinstance(row.get('actions'), list) else []
|
||
practice = row.get('practice') or row.get('name') or row.get('remedy') or row.get('mantra') or _brief(actions, 3)
|
||
count_or_gem = row.get('gem') or row.get('repetitions') or ('No gem or repetition count supplied' if _pl9_report_language(packet) == 'en' else '源数据未提供宝石或次数')
|
||
lines.append(
|
||
f"| {_cell(row.get('topic') or row.get('title') or row.get('id') or row.get('planet') or row.get('dosha'))} | "
|
||
f"{_cell(practice)} | {_cell(count_or_gem)} | "
|
||
f"{_cell(_public_report_text(row.get('notes') or row.get('summary') or row.get('description')))} |"
|
||
)
|
||
lines.append('')
|
||
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(['## 大运解释', ''])
|
||
for page in dasha_pages:
|
||
if not isinstance(page, dict):
|
||
continue
|
||
title = str(page.get('title') or '').strip()
|
||
sections = page.get('sections') if isinstance(page.get('sections'), dict) else {}
|
||
if not title:
|
||
continue
|
||
lines.extend([f'### {title}', ''])
|
||
for key in ('general', 'natal_context', 'domain_context', 'current_subperiod'):
|
||
if str(sections.get(key) or '').strip():
|
||
lines.extend([_public_report_text(sections[key]), ''])
|
||
# The source-bound pack already contains the ordered MD/AD/PD
|
||
# narrative blocks. Keep them in the main export; the compact
|
||
# section summary alone drops the original report's useful density.
|
||
seen_blocks = {
|
||
str(sections.get(key) or '').strip()
|
||
for key in ('general', 'natal_context', 'domain_context', 'current_subperiod')
|
||
if str(sections.get(key) or '').strip()
|
||
}
|
||
for block in page.get('flow_blocks') or []:
|
||
if not isinstance(block, dict):
|
||
continue
|
||
block_title = str(block.get('heading') or block.get('title') or '').strip()
|
||
block_sections = block.get('sections')
|
||
if block_title and block_title != title:
|
||
lines.extend([f'#### {block_title}', ''])
|
||
if not isinstance(block_sections, list):
|
||
continue
|
||
for item in block_sections:
|
||
if not isinstance(item, (list, tuple)) or len(item) < 2:
|
||
continue
|
||
value = str(item[1] or '').strip()
|
||
if not value or value in seen_blocks:
|
||
continue
|
||
seen_blocks.add(value)
|
||
lines.extend([_public_report_text(value), ''])
|
||
return _sanitize_pl9_public_markdown('\n'.join(lines))
|