完整数据版补上行星和宫主解释、后两年年运细项,以及 72 年逐年年返。

定向测试和 200 张英文扫描通过。一张虚构盘中英合计约 20 秒。土星和 KP 结果段仍按修复单让步。

Co-Authored-By: Grok (agent loop)
This commit is contained in:
jesse-ux
2026-10-23 15:04:25 +08:00
co-authored by Grok (agent loop)
parent 2a4646d8b2
commit cfd0ab68ac
10 changed files with 291 additions and 26 deletions
+11 -2
View File
@@ -25,8 +25,8 @@ def attach_annual_expansion(pack: dict, payload: dict, args: Any, *, include_ove
Later years are separate annual packs. This does not recompute the year
lord, Muntha, Mudda, Sahams or Tajika already stored on ``pack``.
The 72-year lifetime table is not built here: one report would calculate
dozens of extra charts and can miss the 180 second wait.
The 72-year lifetime table is built only for the report year. Later years
do not repeat it.
"""
if not isinstance(pack, dict):
raise TypeError("annual pack must be a dict")
@@ -147,6 +147,15 @@ def attach_annual_expansion(pack: dict, payload: dict, args: Any, *, include_ove
"status": "blocked",
"reason": f"{exc.__class__.__name__}: {exc}",
}
try:
pack["lifetime_annual_chart_pages"] = _lifetime(args, place)
except Exception as exc:
import logging
logging.getLogger(__name__).warning("lifetime annual returns failed: %s", exc)
pack["lifetime_annual_chart_pages"] = {
"status": "blocked",
"reason": f"{exc.__class__.__name__}: {exc}",
}
return pack
+1 -1
View File
@@ -3080,7 +3080,7 @@ def _annual_series_surface(annual_pack: dict) -> dict:
'solar_return', 'annual_chart', 'year_lord', 'external_engine_comparison',
'patyayini_dasha', 'sahams', 'tajika_yogas', 'muntha', 'mudda_dasha',
'monthly_chart_pack', 'tithi_pravesh_years', 'native_patyayini',
'pl9_annual_dasha_results', 'annual_houses', 'tripataki_chakra',
'pl9_annual_dasha_results', 'annual_houses', 'tripataki_chakra', 'tajika_strength',
'annual_year_pages', 'normalized_tables',
)
surface = {
+91
View File
@@ -765,6 +765,80 @@ def _delta_days(left: Any, right: Any) -> int | None:
return None
def _owned_topic_text(owned: list, language: str) -> str:
topics = _HOUSE_TOPIC_ZH if language == "zh" else _HOUSE_TOPIC_EN
parts = []
for item in owned:
try:
number = int(item)
except (TypeError, ValueError):
continue
label = topics.get(number)
if not label:
continue
if language == "zh":
parts.append(f"第{number}宫({label})")
else:
parts.append(f"house {number} ({label})")
if not parts:
return "未列出" if language == "zh" else "not listed"
return "、".join(parts) if language == "zh" else ", ".join(parts)
def _traditional_sections(
planet: str,
language: str,
*,
dignity: str,
role: str,
owned_text: str,
house_topic: str | None = None,
) -> dict[str, str]:
"""Traditional paragraphs already stored on this module. No new judgment."""
lang = "zh" if language == "zh" else "en"
themes = _THEMES.get(planet, {}).get(lang)
phala = _PHALA.get(planet, {}).get(lang)
if not themes or not phala:
return {}
lead, follow = themes
topic, support, strain = phala
name = _planet(planet, language)
if language == "zh":
where = f"放在{house_topic}这一层时," if house_topic else ""
return {
"traditional": (
f"传统段落把{name}读成可能涉及{topic}。{where}"
f"条件顺的时候,可能表现为{support}。"
f"条件紧的时候,可能表现为{strain}。"
f"{lead}{follow}"
),
"condition": (
f"尊贵是{dignity}。入庙或旺时,这些主题可能更容易说清楚;"
f"落陷或落在不喜的星座时,同一主题可能更慢、更杂。"
f"功能吉凶是{role}。它和这颗星平时的主题如果方向不同,两层都保留,不合成一个确定结果。"
f"相关的宫是{owned_text}。这段时间可能把这些宫的主题带出来,不是职位、收入或关系上的确定结果。"
),
}
where = f"Read against {house_topic}. " if house_topic else ""
return {
"traditional": (
f"A traditional reading treats {name} as possibly touching {topic}. {where}"
f"When conditions are supportive this may show as {support}. "
f"When conditions are tight this may show as {strain}. "
f"{lead} {follow}"
),
"condition": (
f"Dignity is {dignity}. In its own sign or exaltation these topics may be easier to express. "
f"In debilitation or an unwelcoming sign the same topics may be slower or more mixed. "
f"Functional nature is {role}. If that layer and the planet's usual topics point different ways, keep both. "
f"Do not fold them into one certain result. "
f"The related houses are {owned_text}. This period may bring those house topics forward. "
f"It is not a certain post, income, or relationship outcome."
),
}
def build_graha_interpretation_pack(
chart_planets: dict[str, Any],
owned_houses: dict[str, Any] | None = None,
@@ -829,6 +903,14 @@ def build_graha_interpretation_pack(
else f"{planet} is not inside the current three Dasha levels, so it stays natal background."
),
}
owned_text = _owned_topic_text(owned, language)
sections.update(_traditional_sections(
planet,
language,
dignity=dignity,
role=role,
owned_text=owned_text,
))
pages.append({"planet": planet if language == "en" else _planet(planet, language), "sections": sections})
return {
"schema": "pl9.graha_interpretation_pack.v1",
@@ -981,6 +1063,15 @@ def build_bhavesh_interpretation_pack(
else f"{lord} is not inside the current three Dasha levels, so house {number} stays natal background."
),
}
house_topic = topics[number]
sections.update(_traditional_sections(
str(lord),
language,
dignity=dignity,
role=role,
owned_text=house_topic,
house_topic=house_topic,
))
pages.append({"house": number, "lord": lord, "sections": sections})
return {"schema": "pl9.bhavesh_interpretation_pack.v1", "status": "partial_verified" if pages else "blocked", "pages": pages}
+2 -2
View File
@@ -1385,7 +1385,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
if not planet:
continue
lines.extend([f'### {planet}', ''])
for key in ('placement', 'sign_and_dispositor', 'nakshatra', 'functional_role', 'strength_and_context', 'activation'):
for key in ('placement', 'sign_and_dispositor', 'nakshatra', 'functional_role', 'strength_and_context', 'traditional', 'condition', '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 {}
@@ -3140,7 +3140,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
else f"第{row.get('house')}宫主:{_token(row.get('lord'))}"
)
lines.extend([f'#### {title}', ''])
for key in ('house_context', 'lord_placement', 'result', 'context', 'activation'):
for key in ('house_context', 'lord_placement', 'result', 'traditional', 'condition', 'context', 'activation'):
value = str(sections.get(key) or '').strip()
if value:
lines.extend([_public_report_text(value), ''])
+86 -13
View File
@@ -879,8 +879,51 @@ def _phase_word(phase: Any, language: str) -> str | None:
return None
def _annual_houses(packet: dict, language: str) -> str:
pack = _first(packet, ("timing_and_predictive_systems", "annual_tajika_pack")) or {}
def _year_packs(packet: dict) -> list[tuple[str, dict]]:
"""Primary year plus the next two, when the series was calculated."""
series = _first(packet, ("timing_and_predictive_systems", "annual_tajika_series")) or {}
years = series.get("years") if isinstance(series, dict) else None
found: list[tuple[str, dict]] = []
if isinstance(years, dict):
for year in sorted(years, key=str):
pack = years.get(year)
if isinstance(pack, dict) and pack:
found.append((str(year), pack))
if found:
return found
primary = _first(packet, ("timing_and_predictive_systems", "annual_tajika_pack")) or {}
if isinstance(primary, dict) and primary:
return [("", primary)]
return []
def _each_year(packet: dict, language: str, render) -> str:
packs = _year_packs(packet)
if not packs:
return render({})
if len(packs) == 1 and not packs[0][0]:
return render(packs[0][1])
parts = []
for year, pack in packs:
body = render(pack)
parts.append(f"#### {year}\n\n{body}" if year else body)
return "\n\n".join(parts)
def _year_lord_basis(pack: dict, language: str) -> str:
field = pack.get("year_lord") if isinstance(pack, dict) and isinstance(pack.get("year_lord"), dict) else {}
data = field.get("data") if isinstance(field.get("data"), dict) else {}
blocked = field.get("blocked_detail") if isinstance(field.get("blocked_detail"), dict) else {}
source = data or blocked
if language == "en":
text = str(source.get("selection_basis_text_en") or "")
if re.search(r"[\u4e00-\u9fff]", text):
return ""
return text.strip()
return str(source.get("selection_basis_text") or "").strip()
def _annual_house_table(pack: dict, language: str) -> str:
data = _pack_data(pack.get("annual_houses") if isinstance(pack, dict) else None)
houses = data.get("houses") if isinstance(data, dict) else None
rows = []
@@ -896,8 +939,11 @@ def _annual_houses(packet: dict, language: str) -> str:
return _rows_from(rows, language, [("house", "text"), ("sign", "sign"), ("lord", "planet"), ("occupant", "text")])
def _tripataki(packet: dict, language: str) -> str:
pack = _first(packet, ("timing_and_predictive_systems", "annual_tajika_pack")) or {}
def _annual_houses(packet: dict, language: str) -> str:
return _each_year(packet, language, lambda pack: _annual_house_table(pack, language))
def _tripataki_table(pack: dict, language: str) -> str:
data = _pack_data(pack.get("tripataki_chakra") if isinstance(pack, dict) else None)
if not isinstance(data, dict):
return _blank(language, ["point", "sign", "degree", "longitude"])
@@ -925,6 +971,10 @@ def _tripataki(packet: dict, language: str) -> str:
return "\n\n".join(parts)
def _tripataki(packet: dict, language: str) -> str:
return _each_year(packet, language, lambda pack: _tripataki_table(pack, language))
def _trace_table(traces: Any, language: str) -> str:
rows = []
if isinstance(traces, list):
@@ -982,21 +1032,23 @@ def _annual_numeric(packet: dict, language: str) -> str:
return _rows_from(rows, language, [("year", "text"), ("planet", "planet"), ("sign", "sign"), ("longitude", "text")])
def _annual_saham_trace(packet: dict, language: str) -> str:
pack = _first(packet, ("timing_and_predictive_systems", "annual_tajika_pack")) or {}
def _annual_saham_one(pack: dict, language: str) -> str:
sahams = _pack_data(pack.get("sahams") if isinstance(pack, dict) else None)
traces = sahams.get("formula_traces") if isinstance(sahams, dict) else None
return _trace_table(traces, language)
def _annual_saham_trace(packet: dict, language: str) -> str:
return _each_year(packet, language, lambda pack: _annual_saham_one(pack, language))
def _natal_saham_trace(packet: dict, language: str) -> str:
module = _first(packet, ("advanced_systems", "sahams")) or {}
traces = module.get("formula_traces") if isinstance(module, dict) else None
return _trace_table(traces, language)
def _annual(packet: dict, language: str) -> str:
pack = _first(packet, ("timing_and_predictive_systems", "annual_tajika_pack")) or {}
def _annual_contract(pack: dict, language: str) -> str:
solar = pack.get("solar_return") if isinstance(pack, dict) else {}
chart = pack.get("annual_chart") if isinstance(pack, dict) else {}
muntha = pack.get("muntha") if isinstance(pack, dict) else {}
@@ -1011,11 +1063,25 @@ def _annual(packet: dict, language: str) -> str:
"value": (muntha_data or {}).get("muntha_sign"),
"lord": (lord_data or {}).get("year_lord"),
}
return _rows_from([row], language, [("date", "text"), ("sign", "sign"), ("value", "sign"), ("lord", "planet")])
table = _rows_from([row], language, [("date", "text"), ("sign", "sign"), ("value", "sign"), ("lord", "planet")])
basis = _year_lord_basis(pack, language)
if not basis:
return table
label = "年主依据" if language == "zh" else "Year-lord basis"
sep = ":" if language == "zh" else ": "
muntha_lord = muntha_data.get("muntha_lord") if isinstance(muntha_data, dict) else None
lines = [table, f"{label}{sep}{basis}"]
if muntha_lord:
muntha_label = "Muntha 主星" if language == "zh" else "Muntha lord"
lines.append(f"{muntha_label}{sep}{planet_name(muntha_lord, language)}")
return "\n\n".join(lines)
def _tajika_candidates(packet: dict, language: str) -> str:
pack = _first(packet, ("timing_and_predictive_systems", "annual_tajika_pack")) or {}
def _annual(packet: dict, language: str) -> str:
return _each_year(packet, language, lambda pack: _annual_contract(pack, language))
def _tajika_candidate_table(pack: dict, language: str) -> str:
field = pack.get("tajika_yogas") if isinstance(pack, dict) else {}
data = field.get("data") if isinstance(field, dict) else {}
rows = []
@@ -1034,8 +1100,11 @@ def _tajika_candidates(packet: dict, language: str) -> str:
return _rows_from(rows, language, [("name", "text"), ("planet", "planets"), ("aspect", "text"), ("residual", "text"), ("status", "text")])
def _tajika_strength(packet: dict, language: str) -> str:
pack = _first(packet, ("timing_and_predictive_systems", "annual_tajika_pack")) or {}
def _tajika_candidates(packet: dict, language: str) -> str:
return _each_year(packet, language, lambda pack: _tajika_candidate_table(pack, language))
def _tajika_strength_table(pack: dict, language: str) -> str:
field = pack.get("tajika_strength") if isinstance(pack, dict) else {}
data = field.get("data") if isinstance(field, dict) else {}
rows = []
@@ -1049,6 +1118,10 @@ def _tajika_strength(packet: dict, language: str) -> str:
return _rows_from(rows, language, [("name", "text"), ("planet", "planet"), ("score", "text")])
def _tajika_strength(packet: dict, language: str) -> str:
return _each_year(packet, language, lambda pack: _tajika_strength_table(pack, language))
def _shadbala_planets(packet: dict) -> dict:
module = _first(packet, ("strengths_and_scores", "shadbala")) or {}
planets = module.get("planets") if isinstance(module, dict) else None