The cache key now includes the request date, and files older than two days are removed. The model card keeps values only. Parashari aspects are copied from the engine. Saving a chart warms the packet without blocking the response. Biography severe conflicts rose from 1 to 3, so this is not accepted for merge. Not pushed.
879 lines
42 KiB
Python
879 lines
42 KiB
Python
"""Copy consult-card facts from the full-data packet.
|
||
|
||
Values are packet leaves. A missing leaf is a gap. A technique with no
|
||
``references/`` heading is blocked. D16 and finer, plus Ghati, stay off the
|
||
card unless the birth minute is confirmed. This module does not assemble the
|
||
judgment table; the card builder does that from these leaves.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from typing import Any
|
||
|
||
from pl9_full_data_export import read_full_data_domain
|
||
|
||
PRECISION_SENTENCE = "出生时间精度不够,不参与"
|
||
RAHU_NOTE = "罗计不单独定功能吉凶"
|
||
CONSULTATION_DOMAINS = (
|
||
"career", "marriage", "wealth", "health", "education", "migration",
|
||
"family", "parents", "children", "annual", "timing", "general",
|
||
)
|
||
PACKET_DOMAIN = {
|
||
"career": "career",
|
||
"marriage": "relationship",
|
||
"wealth": "wealth",
|
||
"health": "health",
|
||
"education": "education",
|
||
"migration": "relocation",
|
||
"family": "family",
|
||
"parents": "parents",
|
||
"children": "children",
|
||
"annual": "annual",
|
||
"timing": "timing",
|
||
"general": "general",
|
||
}
|
||
PLANETS = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu")
|
||
VARGA_KEYS = {
|
||
"D2": ("D2_Hora",),
|
||
"D3": ("D3_Drekkana",),
|
||
"D4": ("D4_Turyamsa", "D4_Chaturthamsa"),
|
||
"D5": ("D5_Panchamsa",),
|
||
"D6": ("D6_Shashthamsa",),
|
||
"D7": ("D7_Saptamsa",),
|
||
"D8": ("D8_Ashtamsa",),
|
||
"D9": ("D9_Navamsa",),
|
||
"D10": ("D10_Dasamsa",),
|
||
"D11": ("D11_Rudramsa",),
|
||
"D12": ("D12_Dwadashamsa",),
|
||
"D16": ("D16_Shodasamsa",),
|
||
"D20": ("D20_Vimsamsa",),
|
||
"D24": ("D24_Siddhamsa", "D24_Chaturvimsamsa"),
|
||
"D27": ("D27_Bhamsa",),
|
||
"D30": ("D30_Trimsamsa",),
|
||
"D40": ("D40_Khavedamsa",),
|
||
"D45": ("D45_Akshavedamsa",),
|
||
"D60": ("D60_Shashtyamsa",),
|
||
}
|
||
EXTENDED_VARGA = {"D5": "Panchamsa", "D6": "Shashthamsa", "D8": "Ashtamsa", "D11": "Rudramsa"}
|
||
PRECISION_VARGA = {"D16", "D20", "D24", "D27", "D30", "D40", "D45", "D60"}
|
||
ROUTER = "references/strict-workflow-router.md"
|
||
MAPPING = "references/house-domain-planet-mapping.md"
|
||
CAREER = "references/event_judgment_career.md"
|
||
MARRIAGE = "references/event_judgment_marriage.md"
|
||
WEALTH = "references/event_judgment_wealth.md"
|
||
JAIMINI = "references/jaimini-complete-system.md"
|
||
DEEP = "references/deep-analysis-complete-workflow.md"
|
||
VARSHAPHALA = "references/varshaphala-annual-chart-guide.md"
|
||
PDF = "references/pdf-chart-reading-guide.md"
|
||
RECTIFY = "references/birth-time-rectification-decision-tree.md"
|
||
SHODASA = "references/shodasavarga-complete-guide.md"
|
||
BADHAKA = "references/badhaka-obstacle-planet-guide.md"
|
||
NARAYANA = "references/bphs-ch48-narayana-dasha.md"
|
||
VIM = "references/vimshottari_dasha_guide.md"
|
||
|
||
|
||
def dig(node: Any, path: str) -> Any:
|
||
current = node
|
||
for part in path.split("."):
|
||
if isinstance(current, dict) and part in current:
|
||
current = current[part]
|
||
elif isinstance(current, list) and part.isdigit():
|
||
index = int(part)
|
||
if index < 0 or index >= len(current):
|
||
return None
|
||
current = current[index]
|
||
else:
|
||
return None
|
||
return current
|
||
|
||
|
||
def _gap(path: str | None = None) -> dict[str, Any]:
|
||
item: dict[str, Any] = {"status": "gap", "check": "path"}
|
||
if path:
|
||
item["path"] = path
|
||
return item
|
||
|
||
|
||
def _leaf(view: dict, path: str) -> dict[str, Any]:
|
||
value = dig(view, path)
|
||
if value is None or value == "" or value == {} or value == []:
|
||
return _gap(path)
|
||
if isinstance(value, (str, int, float, bool)):
|
||
return {"status": "value", "path": path, "value": value, "check": "path"}
|
||
encoded = json.dumps(value, ensure_ascii=False, default=str)
|
||
if "tajika_named_yogas" in encoded or len(encoded) > 800:
|
||
return {"status": "value", "path": path, "check": "nonempty"}
|
||
return {"status": "value", "path": path, "value": value, "check": "path"}
|
||
|
||
|
||
def _first(view: dict, paths: list[str]) -> dict[str, Any]:
|
||
seen = paths[0] if paths else None
|
||
for path in paths:
|
||
item = _leaf(view, path)
|
||
if item["status"] == "value":
|
||
return item
|
||
seen = path
|
||
return _gap(seen)
|
||
|
||
|
||
def minute_confirmed_from_body(body: dict | None) -> bool:
|
||
"""Same gate as persistedConsultationMode's verified_chart, on the raw body.
|
||
|
||
``birth_time_accuracy`` counts only when the request set it. The API's
|
||
internal default of confirmed is not a confirmation.
|
||
"""
|
||
payload = body or {}
|
||
if payload.get("hour") is None or payload.get("minute") is None:
|
||
return False
|
||
status = payload.get("birth_time_status")
|
||
if status in ("accepted", "confirmed"):
|
||
return True
|
||
if "birth_time_accuracy" in payload and payload.get("birth_time_accuracy") == "confirmed":
|
||
return True
|
||
# `rectified` is the profile map for an accepted or confirmed minute.
|
||
# `minute` also covers a reported clock that is not accepted, and that
|
||
# clock still withholds D16 and Ghati.
|
||
if payload.get("declared_accuracy") == "rectified":
|
||
return True
|
||
return False
|
||
|
||
|
||
def _planet_base(view: dict, name: str) -> str | None:
|
||
for path in (
|
||
f"core_chart.planets.{name}",
|
||
f"worksheets.d1_rasi_bhava.planets.{name}",
|
||
):
|
||
node = dig(view, path)
|
||
if isinstance(node, dict) and node.get("sign"):
|
||
return path
|
||
return None
|
||
|
||
|
||
def _functional_role(view: dict, name: str) -> dict[str, Any]:
|
||
base = "worksheets.strengths_and_scores.functional_benefic_malefic"
|
||
if name in ("Rahu", "Ketu"):
|
||
return {"status": "value", "path": base, "value": None, "check": "node_role", "note": RAHU_NOTE}
|
||
table = dig(view, base)
|
||
if not isinstance(table, dict):
|
||
return _gap(base)
|
||
for key in ("functional_benefics", "functional_malefics", "functional_neutrals", "yogakarakas"):
|
||
names = table.get(key)
|
||
if isinstance(names, list) and name in names:
|
||
return {"status": "value", "path": f"{base}.{key}", "value": key, "check": "membership", "member": name}
|
||
return {"status": "value", "path": base, "value": None, "check": "node_role", "note": RAHU_NOTE}
|
||
|
||
|
||
def _bav(view: dict, name: str, sign_idx: Any) -> dict[str, Any]:
|
||
path = f"worksheets.strengths_and_scores.ashtakavarga.bav.{name}.bindus"
|
||
bindus = dig(view, path)
|
||
if not isinstance(bindus, list) or not isinstance(sign_idx, int):
|
||
return _gap(path)
|
||
if sign_idx < 0 or sign_idx >= len(bindus):
|
||
return _gap(path)
|
||
return {"status": "value", "path": path, "index": sign_idx, "value": bindus[sign_idx], "check": "index"}
|
||
|
||
|
||
def _varga_chart(view: dict, code: str) -> tuple[Any, str | None]:
|
||
for key in VARGA_KEYS.get(code, ()):
|
||
path = f"worksheets.divisional_and_special_charts.varga_full.{key}"
|
||
node = dig(view, path)
|
||
if isinstance(node, dict) and node:
|
||
return node, path
|
||
extended = EXTENDED_VARGA.get(code)
|
||
if extended:
|
||
path = f"worksheets.divisional_and_special_charts.varga_extended.{extended}"
|
||
node = dig(view, path)
|
||
if isinstance(node, dict) and node:
|
||
return node, path
|
||
return None, None
|
||
|
||
|
||
def _varga_planet_path(chart_path: str, chart: dict, name: str) -> str | None:
|
||
direct = chart.get(name)
|
||
if isinstance(direct, dict):
|
||
return f"{chart_path}.{name}"
|
||
planets = chart.get("planets")
|
||
if isinstance(planets, dict) and isinstance(planets.get(name), dict):
|
||
return f"{chart_path}.planets.{name}"
|
||
return None
|
||
|
||
|
||
def _domain_varga_house(view: dict, name: str, code: str, minute_ok: bool) -> dict[str, Any]:
|
||
if code in PRECISION_VARGA and not minute_ok:
|
||
return {"status": "withheld", "value": PRECISION_SENTENCE, "check": "withheld"}
|
||
chart, chart_path = _varga_chart(view, code)
|
||
if chart_path:
|
||
planet_path = _varga_planet_path(chart_path, chart, name)
|
||
if planet_path:
|
||
house = _leaf(view, f"{planet_path}.house")
|
||
if house["status"] == "value":
|
||
return house
|
||
linked = _leaf(view, f"worksheets.advanced_systems.inter_chart_linkage.{name}.{code}.house")
|
||
if linked["status"] == "value":
|
||
return linked
|
||
return _gap(f"varga.{code}.{name}.house")
|
||
|
||
|
||
def _d9(view: dict, name: str) -> tuple[dict[str, Any], dict[str, Any]]:
|
||
chart, chart_path = _varga_chart(view, "D9")
|
||
if not chart_path:
|
||
return _gap("varga.D9"), _gap("varga.D9.dignity")
|
||
planet_path = _varga_planet_path(chart_path, chart, name)
|
||
sign = _leaf(view, f"{planet_path}.sign") if planet_path else _gap(f"{chart_path}.{name}.sign")
|
||
dignity = _leaf(view, f"{chart_path}._dignity.{name}")
|
||
if dignity["status"] != "value" and planet_path:
|
||
dignity = _leaf(view, f"{planet_path}.dignity")
|
||
return sign, dignity
|
||
|
||
|
||
def _house_aspect_rows(view: dict) -> list[dict] | None:
|
||
rows = dig(view, "worksheets.graha_drishti.house_aspects")
|
||
if not isinstance(rows, list):
|
||
return None
|
||
return [row for row in rows if isinstance(row, dict)]
|
||
|
||
|
||
def _aspect_targets(row: dict) -> list[int]:
|
||
targets = row.get("aspects_to")
|
||
if not isinstance(targets, dict):
|
||
return []
|
||
numbers = []
|
||
for key in targets:
|
||
try:
|
||
numbers.append(int(key))
|
||
except (TypeError, ValueError):
|
||
continue
|
||
return numbers
|
||
|
||
|
||
def _aspected_by(view: dict, house: Any) -> dict[str, Any]:
|
||
path = "worksheets.graha_drishti.house_aspects"
|
||
rows = _house_aspect_rows(view)
|
||
if rows is None or not isinstance(house, int):
|
||
return _gap(path)
|
||
names = []
|
||
for row in rows:
|
||
planet = row.get("planet")
|
||
if isinstance(planet, str) and house in _aspect_targets(row) and planet not in names:
|
||
names.append(planet)
|
||
return {"status": "value", "path": path, "value": names, "check": "selection"}
|
||
|
||
|
||
def _aspects_houses(view: dict, name: str) -> dict[str, Any]:
|
||
path = "worksheets.graha_drishti.house_aspects"
|
||
rows = _house_aspect_rows(view)
|
||
if rows is None:
|
||
return _gap(path)
|
||
for row in rows:
|
||
if row.get("planet") == name:
|
||
return {"status": "value", "path": path, "value": _aspect_targets(row), "check": "selection"}
|
||
return {"status": "value", "path": path, "value": [], "check": "selection"}
|
||
|
||
|
||
def _parashari_aspects(view: dict) -> dict[str, Any]:
|
||
citation = "references/signs-and-houses.md §六 行星相位"
|
||
leaf = _leaf(view, "worksheets.graha_drishti.house_aspects")
|
||
if leaf["status"] != "value":
|
||
return {"id": "parashari_aspects", "status": "gap", "citation": citation, "check": "path", "path": leaf.get("path")}
|
||
return _sourced("parashari_aspects", citation, leaf)
|
||
|
||
|
||
def _co_tenants(view: dict, name: str, house: Any) -> dict[str, Any]:
|
||
if not isinstance(house, int):
|
||
return {"status": "gap", "check": "selection"}
|
||
names = []
|
||
for other in PLANETS:
|
||
if other == name:
|
||
continue
|
||
base = _planet_base(view, other)
|
||
if base and dig(view, f"{base}.house") == house:
|
||
names.append(other)
|
||
return {"status": "value", "check": "selection", "value": names}
|
||
|
||
|
||
def _planet_record(view: dict, name: str, varga: str | None, minute_ok: bool) -> dict[str, Any]:
|
||
base = _planet_base(view, name)
|
||
if not base:
|
||
return {"present": False}
|
||
sign_idx = dig(view, f"{base}.sign_idx")
|
||
house = dig(view, f"{base}.house")
|
||
d9_sign, d9_dignity = _d9(view, name)
|
||
record = {
|
||
"present": True,
|
||
"d1_sign": _leaf(view, f"{base}.sign"),
|
||
"d1_house": _leaf(view, f"{base}.house"),
|
||
"d1_dignity": _leaf(view, f"{base}.status"),
|
||
"rasi_lord": _leaf(view, f"{base}.rasi_lord"),
|
||
"combust": _leaf(view, f"{base}.combust"),
|
||
"aspected_by": _aspected_by(view, house),
|
||
"aspects_houses": _aspects_houses(view, name),
|
||
"functional_role": _functional_role(view, name),
|
||
"shadbala_rupas": _leaf(view, f"worksheets.strengths_and_scores.shadbala.planets.{name}.total_rupas"),
|
||
"shadbala_min": _leaf(view, f"worksheets.strengths_and_scores.shadbala.planets.{name}.min_required"),
|
||
"bav": _bav(view, name, sign_idx),
|
||
"d9_sign": d9_sign,
|
||
"d9_dignity": d9_dignity,
|
||
"co_tenants": _co_tenants(view, name, house),
|
||
}
|
||
if varga:
|
||
record["domain_varga_house"] = _domain_varga_house(view, name, varga, minute_ok)
|
||
return record
|
||
|
||
|
||
def _house_record(view: dict, number: int) -> dict[str, Any]:
|
||
base = f"core_chart.houses.house_{number}"
|
||
if not isinstance(dig(view, base), dict):
|
||
base = f"worksheets.d1_rasi_bhava.houses.house_{number}"
|
||
occupants = []
|
||
for name in PLANETS:
|
||
planet_base = _planet_base(view, name)
|
||
if planet_base and dig(view, f"{planet_base}.house") == number:
|
||
occupants.append(name)
|
||
return {
|
||
"sign": _leaf(view, f"{base}.cusp_sign"),
|
||
"lord": _leaf(view, f"{base}.lord"),
|
||
"sav_score": _leaf(view, f"worksheets.strengths_and_scores.ashtakavarga.house_scores.house_{number}.sav_score"),
|
||
"sav_sodhita": _leaf(view, f"worksheets.strengths_and_scores.ashtakavarga.house_scores.house_{number}.sav_sodhita"),
|
||
"bhava_bala": _first(view, [
|
||
f"worksheets.strengths_and_scores.bhava_bala.{number}",
|
||
f"worksheets.strengths_and_scores.shadbala.bhava_bala.{number}",
|
||
]),
|
||
"occupants": {"status": "value", "check": "selection", "value": occupants},
|
||
"aspected_by": _aspected_by(view, number),
|
||
}
|
||
|
||
|
||
def _current_period_lord(view: dict, direct: str, timeline: str) -> dict[str, Any]:
|
||
found = _leaf(view, direct)
|
||
if found["status"] == "value":
|
||
return found
|
||
rows = dig(view, timeline)
|
||
if isinstance(rows, list):
|
||
for index, row in enumerate(rows):
|
||
if isinstance(row, dict) and row.get("is_current") is True and isinstance(row.get("lord"), str):
|
||
return {"status": "value", "path": f"{timeline}.{index}.lord", "value": row["lord"], "check": "path"}
|
||
return _gap(direct)
|
||
|
||
|
||
def _timing(view: dict) -> dict[str, Any]:
|
||
dasha = "worksheets.timing_and_predictive_systems.dasha.current_dasha"
|
||
ad = _current_period_lord(view, f"{dasha}.antardasha.lord", f"{dasha}.antardasha_timeline")
|
||
pd = _gap(f"{dasha}.pratyantar.lord")
|
||
rows = dig(view, f"{dasha}.antardasha_timeline")
|
||
if isinstance(rows, list):
|
||
for index, row in enumerate(rows):
|
||
if not (isinstance(row, dict) and row.get("is_current") is True):
|
||
continue
|
||
nested = row.get("pratyantar_dasha_timeline")
|
||
if isinstance(nested, list):
|
||
for sub, item in enumerate(nested):
|
||
if isinstance(item, dict) and item.get("is_current") is True and isinstance(item.get("lord"), str):
|
||
pd = {
|
||
"status": "value",
|
||
"path": f"{dasha}.antardasha_timeline.{index}.pratyantar_dasha_timeline.{sub}.lord",
|
||
"value": item["lord"],
|
||
"check": "path",
|
||
}
|
||
break
|
||
narayana = "worksheets.timing_and_predictive_systems.narayana_dasha.current_dasha"
|
||
return {
|
||
"md": _leaf(view, f"{dasha}.lord"),
|
||
"ad": ad,
|
||
"pd": pd,
|
||
"narayana_sign": _leaf(view, f"{narayana}.md.sign"),
|
||
"narayana_lord": _leaf(view, f"{narayana}.md.lord"),
|
||
"narayana_ad_sign": _leaf(view, f"{narayana}.ad.sign"),
|
||
"narayana_ad_lord": _leaf(view, f"{narayana}.ad.lord"),
|
||
}
|
||
|
||
|
||
def _karaka(view: dict, table: str, role: str) -> dict[str, Any]:
|
||
key = "karaka_table" if table == "7" else "karaka_table_8"
|
||
folder = "chara_karaka_7" if table == "7" else "chara_karaka_8"
|
||
return _leaf(view, f"worksheets.advanced_systems.jaimini.{folder}.{key}.{role}.planet")
|
||
|
||
|
||
def _point(view: dict, paths: list[str], *, precision: bool, minute_ok: bool) -> dict[str, Any]:
|
||
if precision and not minute_ok:
|
||
return {"status": "withheld", "value": PRECISION_SENTENCE, "check": "withheld", "path": paths[0] if paths else None}
|
||
return _first(view, paths)
|
||
|
||
|
||
def _blocked(item_id: str, citation: str) -> dict[str, Any]:
|
||
return {"id": item_id, "status": "blocked", "citation": citation, "check": "blocked"}
|
||
|
||
|
||
def _sourced(item_id: str, citation: str, leaf: dict[str, Any]) -> dict[str, Any]:
|
||
return {"id": item_id, "citation": citation, **leaf}
|
||
|
||
|
||
# Static §3A catalog. blocked items have no packet path. sourced items name a file.
|
||
def domain_source_catalog() -> dict[str, list[dict[str, str]]]:
|
||
shared = [
|
||
("sudarshana", "sourced", f"{ROUTER} Sudarshana Chakra"),
|
||
("d9", "sourced", f"{ROUTER} §2 D9 Navamsa"),
|
||
("bhava_chalit", "sourced", f"{ROUTER} Full Bhava Chalit"),
|
||
("shadbala", "sourced", f"{ROUTER} §2 Shadbala"),
|
||
("bhava_bala", "sourced", f"{ROUTER} §2 Shadbala"),
|
||
("avastha", "sourced", "references/deep-varga-avastha-execution-guide.md"),
|
||
("vimsopaka", "sourced", f"{DEEP} Vimsopaka"),
|
||
("ashtakavarga", "sourced", f"{ROUTER} §2 Ashtakavarga"),
|
||
("functional", "sourced", f"{ROUTER} §2 Functional benefic/malefic"),
|
||
("parashari_aspects", "sourced", "references/signs-and-houses.md §六 行星相位"),
|
||
("jaimini_aspects", "sourced", f"{JAIMINI} Jaimini Drishti"),
|
||
("argala", "sourced", "references/argala-complete-guide.md"),
|
||
("dispositor_chain", "sourced", f"{ROUTER} §2 dispositor chain"),
|
||
("vimshottari", "sourced", VIM),
|
||
("narayana", "sourced", NARAYANA),
|
||
("yogini", "sourced", "references/alternative-dasha-systems.md"),
|
||
("transits", "sourced", f"{ROUTER} §2 Transit"),
|
||
("double_transit", "sourced", f"{CAREER} §2 Transit / Double Transit"),
|
||
("yogas_hits", "sourced", "references/event_judgment_skeleton.md"),
|
||
("kp", "sourced", "决策 8:KP 不参与"),
|
||
]
|
||
extra = {
|
||
"career": [
|
||
("D10", "sourced", f"{ROUTER} §2 D10 for career"),
|
||
("A10", "sourced", f"{CAREER} §2 A10 / Karma Pada"),
|
||
("AL", "sourced", f"{PDF} Arudha Lagna"),
|
||
("AmK", "sourced", f"{JAIMINI} Amatyakaraka"),
|
||
("Karakamsha", "sourced", f"{CAREER} §2 Karakamsha"),
|
||
("Ghati", "sourced", f"{DEEP} Ghati Lagna 权力指标"),
|
||
("Vighati", "blocked", "references/ 未找到 Vighati 条目"),
|
||
("natural:Sun", "sourced", f"{CAREER} §2 Sun"),
|
||
("natural:Saturn", "sourced", f"{CAREER} §2 Saturn"),
|
||
("natural:Mercury", "sourced", f"{CAREER} §2 Mercury"),
|
||
("natural:Jupiter", "sourced", f"{CAREER} §2 Jupiter"),
|
||
("house:10", "sourced", f"{CAREER} §2 10H"),
|
||
("D1_D9_D10", "sourced", f"{ROUTER} §2 D10 for career"),
|
||
],
|
||
"wealth": [
|
||
("D2", "sourced", f"{WEALTH} §3 D2"),
|
||
("D11", "sourced", f"{WEALTH} §3 D11"),
|
||
("Hora", "sourced", f"{DEEP} Hora Lagna 财富指标"),
|
||
("Indu", "blocked", "references/ 未找到 Indu Lagna 财富条目"),
|
||
("Sree", "sourced", "references/promise-assessment-templates.md Hora Lagna + Sree Lagna"),
|
||
("AL2", "blocked", f"{WEALTH} §3 未列出从 AL 起第 2 宫"),
|
||
("AL11", "blocked", f"{WEALTH} §3 未列出从 AL 起第 11 宫"),
|
||
("natural:Jupiter", "sourced", f"{WEALTH} §3 Jupiter"),
|
||
("natural:Venus", "sourced", f"{WEALTH} §3 Venus"),
|
||
("natural:Mercury", "sourced", f"{WEALTH} §3 Mercury"),
|
||
("house:2", "sourced", f"{WEALTH} §3 2H"),
|
||
("house:11", "sourced", f"{WEALTH} §3 11H"),
|
||
("house:5", "sourced", f"{WEALTH} §3 5H"),
|
||
("house:9", "sourced", f"{WEALTH} §3 9H"),
|
||
("house:10", "sourced", f"{WEALTH} §3 10H"),
|
||
],
|
||
"marriage": [
|
||
("D9_7", "sourced", f"{MARRIAGE} §2 D9 7H / 7L"),
|
||
("UL", "sourced", f"{MARRIAGE} §2 UL"),
|
||
("UL2", "blocked", f"{MARRIAGE} §2 未列出 UL 起第 2 宫"),
|
||
("A7", "blocked", f"{MARRIAGE} §2 未列出 A7"),
|
||
("DK", "sourced", f"{JAIMINI} Darakaraka"),
|
||
("natural:Venus", "sourced", f"{MARRIAGE} §2 Venus"),
|
||
("natural:Jupiter", "sourced", f"{MARRIAGE} §2 Jupiter"),
|
||
("Vivah", "sourced", f"{MARRIAGE} §2 Vivah Saham"),
|
||
("Mars", "sourced", f"{MARRIAGE} §2 Mars"),
|
||
("house:7", "sourced", f"{MARRIAGE} §2 7H"),
|
||
],
|
||
"health": [
|
||
("D6", "sourced", f"{ROUTER} §7 D6"),
|
||
("D8", "sourced", f"{ROUTER} §7 D8"),
|
||
("D30", "sourced", f"{ROUTER} §7 D30"),
|
||
("drekkana22", "sourced", f"{SHODASA} 第22Drekkana与寿限"),
|
||
("navamsa64", "blocked", "references/ 未找到第 64 个 Navamsa 条目"),
|
||
("Badhaka", "sourced", BADHAKA),
|
||
("Gulika", "blocked", "references/prashna-complete-guide.md 只在问事章写 Gulika,未列入本命健康参与层"),
|
||
("Mandi", "blocked", "references/prashna-complete-guide.md 只在问事章写 Mandi,未列入本命健康参与层"),
|
||
("Pranapada", "blocked", "references/prashna-complete-guide.md 的 Pranapada 是问事替代计算,未列入本命健康参与层"),
|
||
("natural:Sun", "sourced", f"{ROUTER} §7 Sun"),
|
||
("natural:Moon", "sourced", f"{ROUTER} §7 Moon"),
|
||
("natural:Saturn", "sourced", f"{ROUTER} §7 Saturn"),
|
||
("natural:Mars", "sourced", f"{ROUTER} §7 Mars"),
|
||
("house:1", "sourced", f"{ROUTER} §7 Lagna"),
|
||
("house:6", "sourced", f"{ROUTER} §7 6H"),
|
||
("house:8", "sourced", f"{ROUTER} §7 8H"),
|
||
("house:12", "sourced", f"{ROUTER} §7 12H"),
|
||
],
|
||
"children": [
|
||
("D7", "sourced", f"{ROUTER} §2 D7 for children"),
|
||
("PK", "sourced", f"{JAIMINI} Putrakaraka"),
|
||
("natural:Jupiter", "sourced", f"{MAPPING} 子女 木星"),
|
||
("house:5", "sourced", f"{MAPPING} 子女 5宫"),
|
||
],
|
||
"parents": [
|
||
("D12", "sourced", f"{ROUTER} §2 D12 for family/ancestral themes"),
|
||
("MK", "sourced", f"{JAIMINI} Matrikaraka"),
|
||
("PiK", "sourced", f"{JAIMINI} 可选PiK"),
|
||
("natural:Sun", "sourced", f"{MAPPING} 父亲 太阳"),
|
||
("natural:Moon", "sourced", f"{MAPPING} 母亲 月亮"),
|
||
("house:4", "sourced", f"{MAPPING} 母亲 4宫"),
|
||
("house:9", "sourced", f"{MAPPING} 父亲 9宫"),
|
||
],
|
||
"education": [
|
||
("D24", "sourced", f"{ROUTER} §2 D24 for education"),
|
||
("D5", "blocked", "references/ 未找到把 D5 标成学业分盘的段落"),
|
||
("natural:Mercury", "sourced", f"{MAPPING} 教育 水星"),
|
||
("natural:Jupiter", "sourced", f"{MAPPING} 教育 木星"),
|
||
("house:4", "sourced", f"{MAPPING} 教育(基础教育)4宫"),
|
||
("house:5", "sourced", f"{MAPPING} 智力 5宫"),
|
||
("house:9", "sourced", f"{MAPPING} 高等教育 9宫"),
|
||
],
|
||
"migration": [
|
||
("D4", "sourced", f"{RECTIFY} 房产/搬迁 D4"),
|
||
("D12", "blocked", f"{ROUTER} §2 把 D12 标给家族,未标给迁居"),
|
||
("natural:Rahu", "sourced", f"{MAPPING} 罗睺 外国 / 海外发展"),
|
||
("house:4", "sourced", f"{RECTIFY} 房产/搬迁"),
|
||
("house:9", "sourced", f"{MAPPING} 长途旅行 9宫"),
|
||
("house:12", "sourced", f"{MAPPING} 外国事务 12宫"),
|
||
],
|
||
"family": [
|
||
("D4", "blocked", f"{RECTIFY} 把 D4 标给房产/搬迁,未标给家庭领域"),
|
||
("D7", "blocked", f"{ROUTER} §2 把 D7 标给子女,未标给家庭领域"),
|
||
("D12", "sourced", f"{ROUTER} §2 D12 for family/ancestral themes"),
|
||
("natural", "blocked", f"{MAPPING} 未给出家庭领域的一组自然代表星"),
|
||
],
|
||
"annual": [
|
||
("tajika", "sourced", VARSHAPHALA),
|
||
("Muntha", "sourced", f"{VARSHAPHALA} Muntha"),
|
||
("year_lord", "sourced", f"{VARSHAPHALA} 年主星"),
|
||
("Mudda", "sourced", f"{VARSHAPHALA} Mudda Dasha"),
|
||
("Sahams", "sourced", VARSHAPHALA),
|
||
("natural", "blocked", "references/ 未给出流年单独的一组自然代表星"),
|
||
("house", "blocked", "references/ 未把流年钉在单一本命宫"),
|
||
],
|
||
"timing": [
|
||
("narayana_sub", "sourced", NARAYANA),
|
||
("year_chart", "sourced", VARSHAPHALA),
|
||
("natural", "blocked", "references/ 未给出时间领域单独的一组自然代表星"),
|
||
("house", "blocked", "references/ 未把时间领域钉在单一本命宫"),
|
||
],
|
||
"general": [
|
||
("D1", "sourced", f"{ROUTER} §2 D1 Rashi"),
|
||
("D9", "sourced", f"{ROUTER} §2 D9 Navamsa"),
|
||
("D10", "blocked", f"{ROUTER} §2 把 D10 标给事业,未标给综合领域"),
|
||
("D2", "blocked", f"{ROUTER} §2 把 D2 标给财富,未标给综合领域"),
|
||
("AL", "sourced", f"{PDF} Arudha Lagna"),
|
||
("Karakamsha", "sourced", f"{JAIMINI} Karakamsha"),
|
||
("natural", "blocked", "references/ 未给出综合领域的一组自然代表星"),
|
||
("house:1", "sourced", f"{MAPPING} 第1宫"),
|
||
],
|
||
}
|
||
catalog = {}
|
||
for domain, rows in extra.items():
|
||
catalog[domain] = [
|
||
{"id": item_id, "status": status, "citation": citation}
|
||
for item_id, status, citation in ([*shared, *rows])
|
||
]
|
||
return catalog
|
||
|
||
|
||
def _shared_participation(view: dict, minute_ok: bool) -> list[dict[str, Any]]:
|
||
items = [
|
||
_sourced("sudarshana", f"{ROUTER} Sudarshana Chakra", _leaf(view, "worksheets.divisional_and_special_charts.sudarshana")),
|
||
_sourced("d9", f"{ROUTER} §2 D9 Navamsa", _leaf(view, "worksheets.divisional_and_special_charts.varga_full.D9_Navamsa")),
|
||
_sourced("bhava_chalit", f"{ROUTER} Full Bhava Chalit", _first(view, [
|
||
"worksheets.d1_rasi_bhava.bhava_chalit",
|
||
"worksheets.divisional_and_special_charts.bhava_chalit",
|
||
])),
|
||
_sourced("shadbala", f"{ROUTER} §2 Shadbala", _leaf(view, "worksheets.strengths_and_scores.shadbala")),
|
||
_sourced("bhava_bala", f"{ROUTER} §2 Shadbala", _leaf(view, "worksheets.strengths_and_scores.bhava_bala")),
|
||
_sourced("avastha", "references/deep-varga-avastha-execution-guide.md", _leaf(view, "worksheets.advanced_systems.avasthas")),
|
||
_sourced("vimsopaka", f"{DEEP} Vimsopaka", _leaf(view, "worksheets.strengths_and_scores.vimsopaka")),
|
||
_sourced("ashtakavarga", f"{ROUTER} §2 Ashtakavarga", _leaf(view, "worksheets.strengths_and_scores.ashtakavarga")),
|
||
_sourced("functional", f"{ROUTER} §2 Functional benefic/malefic", _leaf(view, "worksheets.strengths_and_scores.functional_benefic_malefic")),
|
||
_parashari_aspects(view),
|
||
_sourced("jaimini_aspects", f"{JAIMINI} Jaimini Drishti", _first(view, [
|
||
"worksheets.advanced_systems.jaimini.rashi_aspects",
|
||
"worksheets.advanced_systems.jaimini.drishti",
|
||
])),
|
||
_sourced("argala", "references/argala-complete-guide.md", _leaf(view, "worksheets.advanced_systems.argala")),
|
||
_sourced("dispositor_chain", f"{ROUTER} §2 dispositor chain", _leaf(view, "worksheets.advanced_systems.dispositor_chains")),
|
||
_sourced("vimshottari", VIM, _leaf(view, "worksheets.timing_and_predictive_systems.dasha.current_dasha.lord")),
|
||
_sourced("narayana", NARAYANA, _leaf(view, "worksheets.timing_and_predictive_systems.narayana_dasha.current_dasha.md.sign")),
|
||
_sourced("yogini", "references/alternative-dasha-systems.md", _leaf(view, "worksheets.timing_and_predictive_systems.yogini_dasha.current.planet")),
|
||
_sourced("transits", f"{ROUTER} §2 Transit", _leaf(view, "worksheets.timing_and_predictive_systems.transit_multi_reference")),
|
||
_sourced("double_transit", f"{CAREER} §2 Transit / Double Transit", _leaf(view, "worksheets.timing_and_predictive_systems.double_transit_pac")),
|
||
_sourced("yogas_hits", "references/event_judgment_skeleton.md", _first(view, [
|
||
"worksheets.advanced_systems.yoga.hits",
|
||
"worksheets.advanced_systems.yogas.hits",
|
||
])),
|
||
{"id": "kp", "status": "value", "value": "不参与", "citation": "决策 8:KP 不参与", "check": "label"},
|
||
]
|
||
del minute_ok
|
||
return items
|
||
|
||
|
||
def _varga_item(view: dict, item_id: str, citation: str, code: str, minute_ok: bool) -> dict[str, Any]:
|
||
if code in PRECISION_VARGA and not minute_ok:
|
||
return {"id": item_id, "status": "withheld", "value": PRECISION_SENTENCE, "citation": citation, "check": "withheld"}
|
||
_chart, path = _varga_chart(view, code)
|
||
if not path:
|
||
return {"id": item_id, "status": "gap", "path": f"varga.{code}", "citation": citation, "check": "path"}
|
||
return _sourced(item_id, citation, _leaf(view, path))
|
||
|
||
|
||
def _domain_participation(view: dict, domain: str, minute_ok: bool) -> list[dict[str, Any]]:
|
||
lagna = "worksheets.divisional_and_special_charts.special_lagnas"
|
||
annual = "worksheets.timing_and_predictive_systems"
|
||
if domain == "career":
|
||
return [
|
||
_varga_item(view, "D10", f"{ROUTER} §2 D10 for career", "D10", minute_ok),
|
||
_sourced("A10", f"{CAREER} §2 A10 / Karma Pada", _leaf(view, f"{lagna}.A10_Karma_Pada.sign")),
|
||
_sourced("AL", f"{PDF} Arudha Lagna", _leaf(view, f"{lagna}.Arudha_Lagna.sign")),
|
||
_sourced("AmK", f"{JAIMINI} Amatyakaraka", _karaka(view, "7", "Amatyakaraka")),
|
||
_sourced("Karakamsha", f"{CAREER} §2 Karakamsha", _leaf(view, "worksheets.advanced_systems.jaimini.karakamsha")),
|
||
_sourced("Ghati", f"{DEEP} Ghati Lagna 权力指标", _point(view, [f"{lagna}.Ghati_Lagna.sign"], precision=True, minute_ok=minute_ok)),
|
||
_blocked("Vighati", "references/ 未找到 Vighati 条目"),
|
||
_sourced("D1_D9_D10", f"{ROUTER} §2 D10 for career", _leaf(view, "worksheets.advanced_systems.inter_chart_linkage")),
|
||
]
|
||
if domain == "wealth":
|
||
return [
|
||
_varga_item(view, "D2", f"{WEALTH} §3 D2", "D2", minute_ok),
|
||
_varga_item(view, "D11", f"{WEALTH} §3 D11", "D11", minute_ok),
|
||
_sourced("Hora", f"{DEEP} Hora Lagna 财富指标", _leaf(view, f"{lagna}.Hora_Lagna.sign")),
|
||
_blocked("Indu", "references/ 未找到 Indu Lagna 财富条目"),
|
||
_sourced("Sree", "references/promise-assessment-templates.md Hora Lagna + Sree Lagna", _first(view, [
|
||
f"{lagna}.Sree_Lagna.sign",
|
||
f"{lagna}.Sri_Lagna.sign",
|
||
])),
|
||
_blocked("AL2", f"{WEALTH} §3 未列出从 AL 起第 2 宫"),
|
||
_blocked("AL11", f"{WEALTH} §3 未列出从 AL 起第 11 宫"),
|
||
]
|
||
if domain == "marriage":
|
||
return [
|
||
_varga_item(view, "D9_7", f"{MARRIAGE} §2 D9 7H / 7L", "D9", minute_ok),
|
||
_sourced("UL", f"{MARRIAGE} §2 UL", _leaf(view, f"{lagna}.Upapada_Lagna.sign")),
|
||
_blocked("UL2", f"{MARRIAGE} §2 未列出 UL 起第 2 宫"),
|
||
_blocked("A7", f"{MARRIAGE} §2 未列出 A7"),
|
||
_sourced("DK", f"{JAIMINI} Darakaraka", _karaka(view, "7", "Darakaraka")),
|
||
_sourced("Vivah", f"{MARRIAGE} §2 Vivah Saham", _first(view, [
|
||
f"{annual}.annual_tajika_pack.sahams.data.vivah_saham",
|
||
"worksheets.advanced_systems.sahams.vivah_saham",
|
||
])),
|
||
_sourced("Mars", f"{MARRIAGE} §2 Mars", _leaf(view, "core_chart.planets.Mars.sign")),
|
||
]
|
||
if domain == "health":
|
||
return [
|
||
_varga_item(view, "D6", f"{ROUTER} §7 D6", "D6", minute_ok),
|
||
_varga_item(view, "D8", f"{ROUTER} §7 D8", "D8", minute_ok),
|
||
_varga_item(view, "D30", f"{ROUTER} §7 D30", "D30", minute_ok),
|
||
_sourced("drekkana22", f"{SHODASA} 第22Drekkana与寿限", _first(view, [
|
||
"worksheets.divisional_and_special_charts.sensitive_points.drekkana_22",
|
||
"worksheets.divisional_and_special_charts.varga_full.D3_Drekkana",
|
||
])),
|
||
_blocked("navamsa64", "references/ 未找到第 64 个 Navamsa 条目"),
|
||
_sourced("Badhaka", BADHAKA, _first(view, [
|
||
"worksheets.advanced_systems.badhaka",
|
||
"worksheets.d1_rasi_bhava.badhaka",
|
||
])),
|
||
_blocked("Gulika", "references/prashna-complete-guide.md 只在问事章写 Gulika,未列入本命健康参与层"),
|
||
_blocked("Mandi", "references/prashna-complete-guide.md 只在问事章写 Mandi,未列入本命健康参与层"),
|
||
_blocked("Pranapada", "references/prashna-complete-guide.md 的 Pranapada 是问事替代计算,未列入本命健康参与层"),
|
||
]
|
||
if domain == "children":
|
||
return [
|
||
_varga_item(view, "D7", f"{ROUTER} §2 D7 for children", "D7", minute_ok),
|
||
_sourced("PK", f"{JAIMINI} Putrakaraka", _karaka(view, "7", "Putrakaraka")),
|
||
]
|
||
if domain == "parents":
|
||
return [
|
||
_varga_item(view, "D12", f"{ROUTER} §2 D12 for family/ancestral themes", "D12", minute_ok),
|
||
_sourced("MK", f"{JAIMINI} Matrikaraka", _karaka(view, "7", "Matrikaraka")),
|
||
_sourced("PiK", f"{JAIMINI} 可选PiK", _karaka(view, "8", "Pitrukaraka")),
|
||
]
|
||
if domain == "education":
|
||
return [
|
||
_varga_item(view, "D24", f"{ROUTER} §2 D24 for education", "D24", minute_ok),
|
||
_blocked("D5", "references/ 未找到把 D5 标成学业分盘的段落"),
|
||
]
|
||
if domain == "migration":
|
||
return [
|
||
_varga_item(view, "D4", f"{RECTIFY} 房产/搬迁 D4", "D4", minute_ok),
|
||
_blocked("D12", f"{ROUTER} §2 把 D12 标给家族,未标给迁居"),
|
||
]
|
||
if domain == "family":
|
||
return [
|
||
_blocked("D4", f"{RECTIFY} 把 D4 标给房产/搬迁,未标给家庭领域"),
|
||
_blocked("D7", f"{ROUTER} §2 把 D7 标给子女,未标给家庭领域"),
|
||
_varga_item(view, "D12", f"{ROUTER} §2 D12 for family/ancestral themes", "D12", minute_ok),
|
||
]
|
||
if domain == "annual":
|
||
return [
|
||
_sourced("tajika", VARSHAPHALA, _leaf(view, f"{annual}.tajika_yogas")),
|
||
_sourced("Muntha", f"{VARSHAPHALA} Muntha", _first(view, [
|
||
f"{annual}.annual_tajika_pack.muntha",
|
||
f"{annual}.tajika.muntha",
|
||
])),
|
||
_sourced("year_lord", f"{VARSHAPHALA} 年主星", _first(view, [
|
||
f"{annual}.annual_tajika_pack.year_lord",
|
||
f"{annual}.tajika.year_lord",
|
||
])),
|
||
_sourced("Mudda", f"{VARSHAPHALA} Mudda Dasha", _first(view, [
|
||
f"{annual}.annual_tajika_pack.mudda_dasha",
|
||
f"{annual}.tajika.mudda_dasha",
|
||
])),
|
||
_sourced("Sahams", VARSHAPHALA, _first(view, [
|
||
f"{annual}.annual_tajika_pack.sahams",
|
||
"worksheets.advanced_systems.sahams",
|
||
])),
|
||
]
|
||
if domain == "timing":
|
||
return [
|
||
_sourced("narayana_sub", NARAYANA, _leaf(view, f"{annual}.narayana_dasha.current_dasha.ad.sign")),
|
||
_sourced("year_chart", VARSHAPHALA, _first(view, [
|
||
f"{annual}.annual_tajika_pack",
|
||
f"{annual}.tajika",
|
||
])),
|
||
]
|
||
if domain == "general":
|
||
return [
|
||
_varga_item(view, "D1", f"{ROUTER} §2 D1 Rashi", "D1", minute_ok),
|
||
_varga_item(view, "D9", f"{ROUTER} §2 D9 Navamsa", "D9", minute_ok),
|
||
_blocked("D10", f"{ROUTER} §2 把 D10 标给事业,未标给综合领域"),
|
||
_blocked("D2", f"{ROUTER} §2 把 D2 标给财富,未标给综合领域"),
|
||
_sourced("AL", f"{PDF} Arudha Lagna", _leaf(view, f"{lagna}.Arudha_Lagna.sign")),
|
||
_sourced("Karakamsha", f"{JAIMINI} Karakamsha", _leaf(view, "worksheets.advanced_systems.jaimini.karakamsha")),
|
||
]
|
||
return []
|
||
|
||
|
||
def _factors(domain: str, timing: dict[str, Any], karakas: dict[str, dict[str, Any]]) -> list[dict[str, Any]]:
|
||
catalog = domain_source_catalog()[domain]
|
||
factors: list[dict[str, Any]] = []
|
||
for row in catalog:
|
||
item_id = row["id"]
|
||
if item_id.startswith("house:"):
|
||
factors.append({"id": item_id, "kind": "house", "house": int(item_id.split(":")[1]), "citation": row["citation"]})
|
||
elif item_id.startswith("natural:"):
|
||
factors.append({"id": item_id, "kind": "planet", "planet": item_id.split(":")[1], "citation": row["citation"], "role": "natural"})
|
||
elif item_id == "natural":
|
||
factors.append({"id": item_id, "kind": "blocked", "citation": row["citation"]})
|
||
elif item_id == "house":
|
||
factors.append({"id": item_id, "kind": "blocked", "citation": row["citation"]})
|
||
for item_id, role in (("AmK", "Amatyakaraka"), ("DK", "Darakaraka"), ("PK", "Putrakaraka"), ("MK", "Matrikaraka")):
|
||
leaf = karakas.get(role)
|
||
if leaf and leaf.get("status") == "value" and isinstance(leaf.get("value"), str):
|
||
if any(row["id"] == item_id for row in catalog):
|
||
factors.append({"id": f"jaimini:{item_id}", "kind": "planet", "planet": leaf["value"], "citation": f"{JAIMINI} {role}", "role": item_id})
|
||
elif any(row["id"] == item_id for row in catalog):
|
||
factors.append({"id": f"jaimini:{item_id}", "kind": "gap", "citation": f"{JAIMINI} {role}", "path": leaf.get("path") if leaf else None})
|
||
pik = karakas.get("Pitrukaraka")
|
||
if any(row["id"] == "PiK" for row in catalog):
|
||
if pik and pik.get("status") == "value" and isinstance(pik.get("value"), str):
|
||
factors.append({"id": "jaimini:PiK", "kind": "planet", "planet": pik["value"], "citation": f"{JAIMINI} 可选PiK", "role": "PiK"})
|
||
else:
|
||
factors.append({"id": "jaimini:PiK", "kind": "gap", "citation": f"{JAIMINI} 可选PiK", "path": (pik or {}).get("path")})
|
||
if not any(row["id"].startswith("jaimini") or row["id"] in {"AmK", "DK", "PK", "MK", "PiK"} for row in catalog):
|
||
factors.append({"id": "jaimini", "kind": "blocked", "citation": f"{JAIMINI} 未给出该领域的 Jaimini 代表星"})
|
||
for key, label in (("md", "Vimshottari MD"), ("ad", "Vimshottari AD"), ("pd", "Vimshottari PD")):
|
||
leaf = timing.get(key) or {}
|
||
if leaf.get("status") == "value" and isinstance(leaf.get("value"), str):
|
||
factors.append({"id": f"vimshottari:{key}", "kind": "planet", "planet": leaf["value"], "citation": VIM, "role": label})
|
||
else:
|
||
factors.append({"id": f"vimshottari:{key}", "kind": "gap", "citation": VIM, "path": leaf.get("path")})
|
||
sign = timing.get("narayana_sign") or {}
|
||
lord = timing.get("narayana_lord") or {}
|
||
factors.append({"id": "narayana:md", "kind": "sign", "citation": NARAYANA, "sign": sign, "lord": lord})
|
||
if lord.get("status") == "value" and isinstance(lord.get("value"), str):
|
||
factors.append({"id": "narayana:lord", "kind": "planet", "planet": lord["value"], "citation": NARAYANA, "role": "Narayana MD lord"})
|
||
else:
|
||
factors.append({"id": "narayana:lord", "kind": "gap", "citation": NARAYANA, "path": lord.get("path")})
|
||
return factors
|
||
|
||
|
||
def build_domain_facts(packet: dict, domain: str, *, minute_confirmed: bool) -> dict[str, Any]:
|
||
if domain not in PACKET_DOMAIN:
|
||
raise KeyError(domain)
|
||
view = read_full_data_domain(packet, PACKET_DOMAIN[domain])
|
||
varga = {
|
||
"career": "D10", "wealth": "D2", "marriage": "D9", "health": "D6",
|
||
"children": "D7", "parents": "D12", "education": "D24", "migration": "D4",
|
||
"family": "D4", "annual": "D1", "timing": "D9", "general": "D10",
|
||
}[domain]
|
||
planets = {name: _planet_record(view, name, varga, minute_confirmed) for name in PLANETS}
|
||
houses = {}
|
||
for row in domain_source_catalog()[domain]:
|
||
if row["id"].startswith("house:"):
|
||
number = int(row["id"].split(":")[1])
|
||
houses[str(number)] = _house_record(view, number)
|
||
timing = _timing(view)
|
||
karakas = {
|
||
"Amatyakaraka": _karaka(view, "7", "Amatyakaraka"),
|
||
"Darakaraka": _karaka(view, "7", "Darakaraka"),
|
||
"Putrakaraka": _karaka(view, "7", "Putrakaraka"),
|
||
"Matrikaraka": _karaka(view, "7", "Matrikaraka"),
|
||
"Pitrukaraka": _karaka(view, "8", "Pitrukaraka"),
|
||
}
|
||
participation = _shared_participation(view, minute_confirmed) + _domain_participation(view, domain, minute_confirmed)
|
||
facts = {
|
||
"schema": "consult-card-facts-v1",
|
||
"domain": domain,
|
||
"packet_domain": PACKET_DOMAIN[domain],
|
||
"header_note": RAHU_NOTE,
|
||
"precision_sentence": PRECISION_SENTENCE,
|
||
"minute_confirmed": minute_confirmed,
|
||
"domain_varga": varga,
|
||
"planets": planets,
|
||
"houses": houses,
|
||
"timing": timing,
|
||
"factors": _factors(domain, timing, karakas),
|
||
"participation": participation,
|
||
}
|
||
encoded = json.dumps(facts, ensure_ascii=False, default=str)
|
||
if "tajika_named_yogas" in encoded:
|
||
raise RuntimeError("consult card facts must not read tajika_named_yogas")
|
||
return facts
|
||
|
||
|
||
def build_consult_card_facts(
|
||
packet: dict,
|
||
domains: list[str],
|
||
*,
|
||
minute_confirmed: bool | None,
|
||
body: dict | None = None,
|
||
) -> dict[str, Any]:
|
||
confirmed = minute_confirmed_from_body(body) if minute_confirmed is None else minute_confirmed
|
||
rows = []
|
||
for domain in domains:
|
||
if domain not in PACKET_DOMAIN:
|
||
continue
|
||
rows.append(build_domain_facts(packet, domain, minute_confirmed=confirmed))
|
||
return {
|
||
"schema": "consult-card-facts-v1",
|
||
"minute_confirmed": confirmed,
|
||
"domains": rows,
|
||
"gaps": [] if rows else ["consult_domain_missing"],
|
||
}
|
||
|
||
|
||
def assert_fact_leaves(packet: dict, facts: dict[str, Any]) -> None:
|
||
"""Every copied leaf equals the packet. Derived selections are not leaves."""
|
||
view = read_full_data_domain(packet, facts["packet_domain"])
|
||
|
||
def check(item: Any) -> None:
|
||
if isinstance(item, list):
|
||
for entry in item:
|
||
check(entry)
|
||
return
|
||
if not isinstance(item, dict):
|
||
return
|
||
kind = item.get("check")
|
||
if kind == "path" and item.get("status") == "value":
|
||
assert dig(view, item["path"]) == item["value"], item["path"]
|
||
elif kind == "index" and item.get("status") == "value":
|
||
assert dig(view, item["path"])[item["index"]] == item["value"], item["path"]
|
||
elif kind == "nonempty" and item.get("status") == "value":
|
||
assert dig(view, item["path"]) not in (None, {}, [], ""), item["path"]
|
||
elif kind == "membership" and item.get("status") == "value":
|
||
assert item["member"] in dig(view, item["path"]), item["path"]
|
||
elif kind == "withheld":
|
||
assert item.get("value") == PRECISION_SENTENCE
|
||
for key, value in item.items():
|
||
if key in ("value", "path", "citation"):
|
||
continue
|
||
check(value)
|
||
|
||
check(facts)
|