fix(report): unblock personal-full themes with real varga, transit, and karaka evidence
Independent Staging Quality Gate / validate (push) Successful in 9m15s
Independent Staging Quality Gate / publish (push) Successful in 1m50s

The extractor only read an obsolete varga object shape, never issued Transit receipts, and the consultation response omitted Chara Karakas, so career/marriage/timing/wealth all blocked. Supply the live engine shape, fail-closed receipts, and jaimini.chara_karakas without relaxing the evidence plan.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-02 18:03:23 +08:00
parent e37f8272ba
commit 7faf8555a5
9 changed files with 6077 additions and 32 deletions
@@ -0,0 +1,257 @@
#!/usr/bin/env python3
"""Capture trimmed consultation_workflow responses for report blocked-repairs fixtures.
Smoke birth only: 1993-06-15 10:30, lat 36.42 / lon 114.21 / tz 8.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
from consultation_plan_contract import PLAN_VERSION, _ROUTE_CONTRACTS # noqa: E402
from jyotish_api_server import JyotishAPIHandler # noqa: E402
BIRTH = {
"year": 1993,
"month": 6,
"day": 15,
"hour": 10,
"minute": 30,
"lat": 36.42,
"lon": 114.21,
"tz": 8,
"city": "Handan",
}
THEMES = ("career", "marriage", "timing", "wealth")
VARGA_KEEP = (
"D2_Hora", "D2",
"D9_Navamsa", "D9",
"D10_Dasamsa", "D10",
"D11_Rudramsa", "D11",
"D24_Chaturvimsamsa", "D24_Siddhamsa", "D24",
)
MODULE_KEEP = (
"varga_full",
"transits",
"jaimini",
"narayana_dasha",
"arudha_padas",
"yogas",
"ashtakavarga",
"dasha_sub_periods",
)
CONSUMER_KEEP = (
"route",
"core_status",
"available_layers",
"missing_route_layers",
"hard_blockers",
"answer_policy",
)
CHART_KEEP = ("success", "ascendant", "planets", "houses", "dasha")
def _handler() -> JyotishAPIHandler:
return JyotishAPIHandler.__new__(JyotishAPIHandler)
def _body(theme: str) -> dict:
contract = _ROUTE_CONTRACTS[theme]
return {
**BIRTH,
"question": f"请为个人报告计算 {theme} 主题证据",
"question_text": f"请为个人报告计算 {theme} 主题证据",
"theme": list(contract.themes),
"entry_mode": "direct_chart",
"plan_version": PLAN_VERSION,
"strict_workflow_route": contract.resolved_routes[0],
"required_layers": list(contract.required_layers),
"claim_boundary": contract.claim_boundary,
"plan_depth": "standard",
"requested_domains": list(contract.requested_domains),
"timing_horizon": "next_12_months" if theme == "timing" else None,
"precision_boundary": "server_evidence_required",
"required_evidence_categories": list(contract.required_evidence_categories),
"defer_optional_external_evidence": True,
}
def _trim_varga(varga_full: dict) -> dict:
trimmed = {}
for key in VARGA_KEEP:
value = varga_full.get(key)
if isinstance(value, dict):
trimmed[key] = value
if trimmed:
return trimmed
# Fall back to prefix match if the live keys drifted.
for key, value in varga_full.items():
if not isinstance(key, str) or not isinstance(value, dict):
continue
if any(key == keep or key.startswith(f"{keep.split('_')[0]}_") for keep in ("D2", "D9", "D10", "D11", "D24")):
if key.startswith(("D2", "D9", "D10", "D11", "D24")):
trimmed[key] = value
return trimmed
def _trim_workflow(raw: dict) -> dict:
chart = raw.get("chart") if isinstance(raw.get("chart"), dict) else {}
modules = chart.get("modules") if isinstance(chart.get("modules"), dict) else {}
kept_modules = {}
for name in MODULE_KEEP:
value = modules.get(name)
if name == "varga_full" and isinstance(value, dict):
kept_modules[name] = _trim_varga(value)
elif value is not None:
kept_modules[name] = value
kept_chart = {key: chart[key] for key in CHART_KEEP if key in chart}
if chart.get("success") is not None:
kept_chart["success"] = chart.get("success")
kept_chart["modules"] = kept_modules
consumer = raw.get("consumer_context") if isinstance(raw.get("consumer_context"), dict) else {}
machine = raw.get("machine_evidence_packet") if isinstance(raw.get("machine_evidence_packet"), dict) else {}
workflow = {
"success": bool(raw.get("success")),
"chart": kept_chart,
"consumer_context": {key: consumer[key] for key in CONSUMER_KEEP if key in consumer},
"machine_evidence_packet": {
"sections": machine.get("sections") if isinstance(machine.get("sections"), dict) else {},
},
}
return _compact_workflow(workflow)
def _pick(row: dict, keys: tuple[str, ...]) -> dict:
return {key: row[key] for key in keys if key in row}
def _compact_workflow(workflow: dict) -> dict:
"""Drop bulky unused fields after the extraction-layer keep list."""
chart = workflow["chart"]
planets = chart.get("planets")
if isinstance(planets, dict):
chart["planets"] = {
name: _pick(row, ("sign", "sign_idx", "sign_index", "lon", "degree", "degree_in_sign", "house", "retrograde"))
if isinstance(row, dict) else row
for name, row in planets.items()
}
houses = chart.get("houses")
if isinstance(houses, dict):
chart["houses"] = {
key: _pick(row, ("cusp_sign", "sign", "sign_idx", "lord")) if isinstance(row, dict) else row
for key, row in houses.items()
}
dasha = chart.get("dasha")
if isinstance(dasha, dict):
periods = [
_pick(item, ("lord", "start", "end"))
for item in (dasha.get("periods") or [])
if isinstance(item, dict)
]
compact_dasha = {}
if periods:
compact_dasha["periods"] = periods
compact_dasha["mahadashas"] = periods
for key in ("current_md", "start_date"):
if key in dasha:
compact_dasha[key] = dasha[key]
chart["dasha"] = compact_dasha
modules = chart["modules"]
transits = modules.get("transits")
if isinstance(transits, dict):
modules["transits"] = _pick(transits, ("status", "sade_sati", "search_period", "trigger_count", "boundary"))
sav = (modules.get("ashtakavarga") or {}).get("sav") if isinstance(modules.get("ashtakavarga"), dict) else None
if isinstance(sav, dict):
modules["ashtakavarga"] = {"sav": _pick(sav, ("scores", "total"))}
yogas = modules.get("yogas")
if isinstance(yogas, dict):
rows = [
_pick(item, ("name", "category", "hit", "planets"))
for item in (yogas.get("yogas") or [])
if isinstance(item, dict)
]
modules["yogas"] = {"status": yogas.get("status"), "count": yogas.get("count"), "yogas": rows}
nara = modules.get("narayana_dasha")
if isinstance(nara, dict):
modules["narayana_dasha"] = _pick(nara, ("current_dasha", "lagna_sign"))
arudha = modules.get("arudha_padas")
if isinstance(arudha, dict):
padas = arudha.get("padas") if isinstance(arudha.get("padas"), dict) else {}
modules["arudha_padas"] = {"padas": {key: padas[key] for key in ("A7", "A10", "UL") if key in padas}}
jaimini = modules.get("jaimini")
if isinstance(jaimini, dict):
compact_jaimini = {}
if isinstance(jaimini.get("arudha_padas"), dict):
padas = jaimini["arudha_padas"].get("padas") if isinstance(jaimini["arudha_padas"].get("padas"), dict) else {}
compact_jaimini["arudha_padas"] = {"padas": {key: padas[key] for key in ("A7", "A10", "UL") if key in padas}}
karakas = jaimini.get("chara_karakas")
if isinstance(karakas, dict):
compact_jaimini["chara_karakas"] = {
key: _pick(row, ("planet", "degree_in_sign", "rank", "domain", "cn_name")) if isinstance(row, dict) else row
for key, row in karakas.items()
}
modules["jaimini"] = compact_jaimini
sub = modules.get("dasha_sub_periods")
if isinstance(sub, dict) and isinstance(sub.get("current"), dict):
modules["dasha_sub_periods"] = {"current": sub["current"]}
sections = workflow["machine_evidence_packet"].get("sections")
if isinstance(sections, dict):
keep = {
"D1", "D2", "D4", "D6", "D7", "D9", "D10", "D11", "D12", "D24", "D30",
"A7", "A10", "UL", "dasha_boundaries", "narayana_dasha", "yogas", "ashtakavarga",
"planet_degrees", "house_degrees", "dasha_sub_periods",
}
workflow["machine_evidence_packet"]["sections"] = {key: value for key, value in sections.items() if key in keep}
return workflow
def main() -> int:
handler = _handler()
out_dir = ROOT / "frontend/tests/fixtures"
out_dir.mkdir(parents=True, exist_ok=True)
payload = {
"source": "scripts/jyotish_api_server.py _compute_consultation_workflow",
"smoke_birth": {**BIRTH, "note": "fictional capture identity; not a real person"},
"trim": {
"keep_varga_keys": list(VARGA_KEEP),
"keep_modules": list(MODULE_KEEP),
"drop": [
"ai_prompt_pack",
"vedastro",
"varga_spectrum",
"guided_topics",
"western_spectrum",
"technique_audit_table",
],
},
"themes": {},
}
for theme in THEMES:
raw = handler._compute_consultation_workflow(_body(theme))
payload["themes"][theme] = _trim_workflow(raw)
varga = payload["themes"][theme]["chart"]["modules"].get("varga_full") or {}
transits = payload["themes"][theme]["chart"]["modules"].get("transits") or {}
jaimini = payload["themes"][theme]["chart"]["modules"].get("jaimini") or {}
print(
theme,
"success=",
payload["themes"][theme]["success"],
"varga_keys=",
sorted(varga),
"transits=",
transits.get("status"),
"karakas=",
sorted((jaimini.get("chara_karakas") or {})),
)
dest = out_dir / "consultation-workflow-report-blocked-repairs-golden.json"
dest.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print("wrote", dest, "bytes", dest.stat().st_size)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+32
View File
@@ -1502,6 +1502,38 @@ def _attach_local_consultation_layers(handler, chart: dict, birth_payload: dict,
except Exception as exc:
diagnostics.append({'layer': 'arudha_padas', 'status': 'unavailable', 'reason': exc.__class__.__name__})
existing_jaimini = modules.get('jaimini') if isinstance(modules.get('jaimini'), dict) else {}
if planets and not isinstance(existing_jaimini.get('chara_karakas'), dict):
try:
jaimini = _load_local_module('jaimini')
planet_degs = {
name: lon % 30
for name, lon in _planet_longitudes(planets).items()
}
ck7 = jaimini.calc_chara_karaka_7(planet_degs) if planet_degs else {}
table = ck7.get('karaka_table') if isinstance(ck7, dict) else None
if isinstance(table, dict) and table:
short_names = {
'Atmakaraka': 'AK',
'Amatyakaraka': 'AmK',
'Bhratrikaraka': 'BK',
'Matrikaraka': 'MK',
'Putrakaraka': 'PK',
'Gnatikaraka': 'GK',
'Darakaraka': 'DK',
}
chara_karakas = {}
for full_name, row in table.items():
if not isinstance(row, dict):
continue
chara_karakas[full_name] = row
alias = short_names.get(full_name)
if alias:
chara_karakas[alias] = row
modules.setdefault('jaimini', {})['chara_karakas'] = chara_karakas
except Exception as exc:
diagnostics.append({'layer': 'chara_karakas', 'status': 'unavailable', 'reason': exc.__class__.__name__})
if planets and ascendant and not isinstance(modules.get('narayana_dasha'), dict):
try:
narayana = _load_local_module('narayana_dasha')