Files
Jyotisha/scripts/kp_monthly_theme_support.py
ClaudeandClaude Opus 5.5 7d1057ccc5
Independent Staging Quality Gate / validate (push) Successful in 21m50s
Independent Staging Quality Gate / publish (push) Successful in 4m21s
feat(report): Vimshottari levels 大运 / 小运 / 细运 across the report pipeline
The personal report now uses the astrologer-approved names, matching the
chart page and the consult prompts since 7a56840f (issue #163 Q5): MD 大运,
AD 小运, PD 细运. Old names replaced in code-rendered text only: 主运 / 主大运
-> 大运, 子运 / 分运 / 副运 (AD) -> 小运, 次子运 / 次小运 / 再小运 and the
fact table's PD 小运 -> 细运. PD strings were converted before AD, and AD
before MD.

Covered: the focus card and its export text, the raw fact tables, theme
claim cards, the timing interpretation pack, the PL9 full-data export
(tables, dasha prose templates, AD/PD density anchors), rashi / Narayana
second level, KP monthly focus, orchestrator texts and technique-audit
rows. English translation keys follow the new Chinese labels
(大运 = Mahadasha, 小运 = Antardasha, 细运 = Pratyantardasha), and a new test
pins that no English mapping reads 小运 as the third level.

The five-level monthly sample table used 细运 for Sookshma; it now shows
Sookshma until the astrologer names the fourth level.

Saved reports are not migrated. Focus fences render from md/ad/pd codes,
and stored fact tables validate by structure, so old and new labels both
open. Goldens were updated in place and two hash pins re-pinned; reverting
only the term swap reproduces the old hashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
2026-11-02 21:32:51 +08:00

129 lines
5.0 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Theme-level KP monthly support aggregation."""
from __future__ import annotations
_DOMAIN_HOUSES = {
"career": (2, 6, 10, 11),
"wealth": (2, 5, 9, 11),
"relationship": (2, 7, 11),
}
_DOMAIN_LABELS = {
"career": "事业",
"wealth": "财务",
"relationship": "关系",
}
_HOUSE_LABELS = {
2: "收入与资源",
5: "机会与投入",
6: "职责与压力",
7: "合作与伴侣",
9: "运气与扩张",
10: "事业与位置",
11: "回报与支持",
}
_DOMAIN_TEMPLATES = {
"career": "这段时间更适合把重心放在职责变化、位置调整和回报兑现的节奏里观察。",
"wealth": "这段时间更适合把注意力放在现金流、机会选择和回报兑现的节奏里观察。",
"relationship": "这段时间更适合把重心放在合作默契、关系回应和现实支持的变化里观察。",
}
_PLANET_CN = {
"Sun": "太阳",
"Moon": "月亮",
"Mars": "火星",
"Mercury": "水星",
"Jupiter": "木星",
"Venus": "金星",
"Saturn": "土星",
"Rahu": "北交点",
"Ketu": "南交点",
}
_SIGN_CN = {
"Aries": "白羊座",
"Taurus": "金牛座",
"Gemini": "双子座",
"Cancer": "巨蟹座",
"Leo": "狮子座",
"Virgo": "处女座",
"Libra": "天秤座",
"Scorpio": "天蝎座",
"Sagittarius": "射手座",
"Capricorn": "摩羯座",
"Aquarius": "水瓶座",
"Pisces": "双鱼座",
}
def _collect_active_houses(houses: dict, house_numbers: tuple[int, ...]) -> list[int]:
active: list[int] = []
for house in house_numbers:
row = houses.get(str(house)) if isinstance(houses.get(str(house)), dict) else {}
significators = row.get("significators") if isinstance(row.get("significators"), dict) else {}
if significators.get("A") not in (None, "", [], (), set()):
active.append(house)
return active
def _house_signal_text(active_houses: list[int], fallback_houses: tuple[int, ...]) -> str:
if not active_houses:
return "相关宫位线索暂时偏弱"
labels = [f"{house}宫{_HOUSE_LABELS.get(house, '')}" for house in active_houses]
return "、".join(labels)
def _summary_text(domain: str, active_houses: list[int], fallback_houses: tuple[int, ...], md_lord: str, saturn_sign: str) -> str:
label = _DOMAIN_LABELS.get(domain, domain)
focus = _house_signal_text(active_houses, fallback_houses)
template = _DOMAIN_TEMPLATES.get(domain, "这段时间更适合先看结构支持,再看外部触发。")
md_label = _PLANET_CN.get(md_lord, md_lord)
saturn_label = _SIGN_CN.get(saturn_sign, saturn_sign)
if active_houses:
return (
f"{label}线本月先看 {focus} 的本命承诺;"
f"当前大运星为{md_label},土星行运落在{saturn_label},{template}"
)
fallback_text = "、".join(
f"{house}宫{_HOUSE_LABELS.get(house, '')}" for house in fallback_houses
)
return (
f"{label}线本月更适合先按 {fallback_text} 这组标准宫位阅读,再等待更强触发出现;"
f"当前大运星为{md_label},土星行运落在{saturn_label},{template}"
)
def _brief_summary_text(domain: str, active_houses: list[int], fallback_houses: tuple[int, ...], md_lord: str) -> str:
label = _DOMAIN_LABELS.get(domain, domain)
md_label = _PLANET_CN.get(md_lord, md_lord)
focus_houses = active_houses or list(fallback_houses[:2])
focus_text = "、".join(f"{house}宫" for house in focus_houses)
return f"本命焦点:{focus_text};月运主轴:{label}线跟随{md_label}大运推进。"
def build_kp_monthly_theme_support(
*, natal_kp: dict, monthly_levels: dict, monthly_transits: dict
) -> dict:
houses = natal_kp.get("houses") if isinstance(natal_kp.get("houses"), dict) else {}
ruling = natal_kp.get("ruling_planets") if isinstance(natal_kp.get("ruling_planets"), dict) else {}
md_lord = ((monthly_levels.get("mahadasha") or {}) if isinstance(monthly_levels, dict) else {}).get("lord") or "-"
saturn_sign = ((((monthly_transits.get("planets") or {}) if isinstance(monthly_transits, dict) else {}).get("Saturn") or {}) if isinstance((monthly_transits.get("planets") or {}).get("Saturn"), dict) else {}).get("sign") or "-"
out = {}
for domain, house_numbers in _DOMAIN_HOUSES.items():
active_houses = _collect_active_houses(houses, house_numbers)
out[domain] = {
"houses": list(house_numbers),
"active_houses": active_houses,
"promise_code": " / ".join(str(h) for h in active_houses) if active_houses else "-",
"summary": _summary_text(domain, active_houses, house_numbers, md_lord, saturn_sign),
"brief_summary": _brief_summary_text(domain, active_houses, house_numbers, md_lord),
"ruling_planet_snapshot": ruling.get("day_lord") or "-",
"status": "parameter_sensitive",
"must_not_claim": ["exact_event_timing", "specific_event_prediction"],
}
return out