Files
Jyotisha/scripts/vedastro_evidence_orchestrator.py
T
2026-07-02 20:12:43 +08:00

268 lines
11 KiB
Python

#!/usr/bin/env python3
"""Shared VedAstro evidence orchestration layer.
The orchestrator does not expose hundreds of VedAstro nodes to users. It picks
the smallest useful domain scan set for the current route and delegates the
actual external boundary to ``vedastro_service_adapter``.
"""
from __future__ import annotations
from datetime import datetime, timedelta
from typing import Any
try:
from scripts.vedastro_service_adapter import (
VEDASTRO_CALCULATION_COVERAGE,
run_official_full_snapshot_for_case,
run_range_scan_for_case,
)
except ModuleNotFoundError: # pragma: no cover - script execution path
from vedastro_service_adapter import (
VEDASTRO_CALCULATION_COVERAGE,
run_official_full_snapshot_for_case,
run_range_scan_for_case,
)
ROUTE_DOMAIN_MAP = {
"relationship": ["marriage"],
"marriage": ["marriage"],
"career": ["career"],
"finance": ["wealth"],
"wealth": ["wealth"],
"health": ["health"],
"education": ["education"],
"property": ["property"],
"children": ["children"],
"migration": ["migration"],
"prashna": ["prashna"],
"rectification": ["marriage", "career", "wealth"],
"timing": ["career", "marriage", "wealth"],
"general": ["career", "marriage", "wealth"],
"overview": ["career", "marriage", "wealth"],
}
ROUTE_THEME_REQUIREMENTS = {
"relationship": {
"route": "relationship",
"requires_dual_dasha": True,
"required_local_supplements": ["upapada_lagna", "darakaraka", "narayana_dasha", "functional_benefic_malefic"],
},
"marriage": {
"route": "relationship",
"requires_dual_dasha": True,
"required_local_supplements": ["upapada_lagna", "darakaraka", "narayana_dasha", "functional_benefic_malefic"],
},
"career": {
"route": "career",
"requires_dual_dasha": True,
"required_local_supplements": ["a10_karma_pada", "narayana_dasha", "functional_benefic_malefic"],
},
"finance": {
"route": "finance",
"requires_dual_dasha": True,
"required_local_supplements": ["wealth_structure_explainer", "narayana_dasha", "functional_benefic_malefic"],
},
"wealth": {
"route": "finance",
"requires_dual_dasha": True,
"required_local_supplements": ["wealth_structure_explainer", "narayana_dasha", "functional_benefic_malefic"],
},
"health": {
"route": "health",
"requires_dual_dasha": True,
"required_local_supplements": ["d30_health_axis", "sixth_eighth_twelfth_houses", "functional_benefic_malefic"],
},
"education": {
"route": "education",
"requires_dual_dasha": True,
"required_local_supplements": ["d24_learning_axis", "fifth_ninth_houses", "functional_benefic_malefic"],
},
"property": {
"route": "property",
"requires_dual_dasha": True,
"required_local_supplements": ["d4_property_axis", "fourth_house", "functional_benefic_malefic"],
},
"children": {
"route": "children",
"requires_dual_dasha": True,
"required_local_supplements": ["d7_children_axis", "fifth_house", "functional_benefic_malefic"],
},
"migration": {
"route": "migration",
"requires_dual_dasha": True,
"required_local_supplements": ["d4_d9_foreign_axis", "ninth_twelfth_houses", "functional_benefic_malefic"],
},
"prashna": {
"route": "prashna",
"requires_dual_dasha": False,
"required_local_supplements": ["prashna_chart", "question_context_required", "functional_benefic_malefic"],
},
"overview": {
"route": "overview",
"requires_dual_dasha": True,
"required_local_supplements": [],
},
"general": {
"route": "general",
"requires_dual_dasha": True,
"required_local_supplements": [],
},
}
def _default_window(reference_date: str | None, days: int = 180) -> tuple[str, str]:
raw = str(reference_date or datetime.utcnow().strftime("%Y-%m-%d"))[:10]
try:
start = datetime.strptime(raw, "%Y-%m-%d").date()
except ValueError:
return raw, raw
return start.isoformat(), (start + timedelta(days=days)).isoformat()
def _normalize_case(birth_payload: dict[str, Any]) -> dict[str, Any]:
return {
"year": birth_payload.get("year"),
"month": birth_payload.get("month"),
"day": birth_payload.get("day"),
"hour": birth_payload.get("hour"),
"minute": birth_payload.get("minute"),
"second": birth_payload.get("second", 0),
"lat": birth_payload.get("lat"),
"lon": birth_payload.get("lon"),
"tz": birth_payload.get("tz"),
"ayanamsa_policy": birth_payload.get("ayanamsa_policy")
or birth_payload.get("ayanamsa")
or "lahiri",
"node_policy": birth_payload.get("node_policy")
or birth_payload.get("node_mode")
or birth_payload.get("nodeMode")
or "mean",
}
def orchestrate_vedastro_evidence(
birth_payload: dict[str, Any],
*,
route: str = "general",
reference_date: str | None = None,
start_date: str | None = None,
end_date: str | None = None,
case_id: str = "vedastro_orchestrator",
) -> dict[str, Any]:
domains = ROUTE_DOMAIN_MAP.get(route, ROUTE_DOMAIN_MAP["general"])
window_start, window_end = (start_date, end_date) if start_date and end_date else _default_window(reference_date)
case = _normalize_case(birth_payload)
case["reference_date"] = str(reference_date or window_start)[:10]
case["themes"] = list(domains)
domain_reports: dict[str, Any] = {}
evidence_ledger: list[dict[str, Any]] = []
top_events_by_domain: dict[str, Any] = {}
daily_windows_by_domain: dict[str, list[dict[str, Any]]] = {}
top_daily_window_by_domain: dict[str, dict[str, Any]] = {}
domain_statuses: dict[str, Any] = {}
domain_event_counts: dict[str, int] = {}
available = False
first_reason = None
official_full_snapshot = run_official_full_snapshot_for_case(
case,
case_id=f"{case_id}_official_full_snapshot",
)
official_metadata = official_full_snapshot.get("source_metadata") if isinstance(official_full_snapshot, dict) else {}
if not isinstance(official_metadata, dict):
official_metadata = {}
full_catalog = official_metadata.get("official_full_capability_catalog")
if not isinstance(full_catalog, dict):
full_catalog = {}
full_catalog_domain_routing = full_catalog.get("domain_routing") if isinstance(full_catalog.get("domain_routing"), dict) else {}
full_catalog_dynamic_selection = full_catalog.get("dynamic_selection") if isinstance(full_catalog.get("dynamic_selection"), dict) else {}
official_section_statuses = official_full_snapshot.get("section_statuses") if isinstance(official_full_snapshot, dict) else {}
if not isinstance(official_section_statuses, dict):
official_section_statuses = {}
official_report_references = {
theme: selection.get("report_reference")
for theme, selection in full_catalog_dynamic_selection.items()
if isinstance(selection, dict) and isinstance(selection.get("report_reference"), dict)
}
theme_requirements = ROUTE_THEME_REQUIREMENTS.get(route, ROUTE_THEME_REQUIREMENTS["general"]).copy()
theme_requirements["domains"] = list(domains)
for domain in domains:
report = run_range_scan_for_case(
case,
domain,
str(window_start),
str(window_end),
case_id=f"{case_id}_{domain}",
)
domain_reports[domain] = report
domain_statuses[domain] = report.get("status")
domain_event_counts[domain] = int(report.get("event_count", 0) or 0)
available = available or bool(report.get("available"))
first_reason = first_reason or report.get("reason")
if isinstance(report.get("top_event"), dict):
top_events_by_domain[domain] = report["top_event"]
daily_windows = report.get("daily_windows")
if isinstance(daily_windows, list):
daily_windows_by_domain[domain] = daily_windows
top_daily_window = report.get("top_daily_window")
if isinstance(top_daily_window, dict):
top_daily_window_by_domain[domain] = top_daily_window
for event in report.get("evidence_ledger") or []:
if isinstance(event, dict):
evidence_ledger.append(event)
status = "ok" if any(item == "ok" for item in domain_statuses.values()) else next(iter(domain_statuses.values()), "blocked")
return {
"backend": "vedastro_service_adapter_candidate",
"available": available,
"status": status,
"operation": "range_scan",
"domain": domains[0] if len(domains) == 1 else "overview",
"route": route,
"event_count": len(evidence_ledger),
"top_event": next(iter(top_events_by_domain.values()), None),
"top_events_by_domain": top_events_by_domain,
"daily_windows_by_domain": daily_windows_by_domain,
"top_daily_window_by_domain": top_daily_window_by_domain,
"evidence_ledger": evidence_ledger,
"reason": None if status == "ok" else first_reason,
"official_full_snapshot": official_full_snapshot,
"domain_reports": domain_reports,
"source_metadata": {
"auto_ingested_by": "VedAstroEvidenceOrchestrator",
"strategy": "official_full_snapshot_first_then_route_scoped_range_scan",
"official_python_path": (
(official_full_snapshot.get("source_metadata") or {}).get("official_python_path")
if isinstance(official_full_snapshot, dict)
else None
),
"official_python_bundle_status": (
(official_metadata.get("official_python_bundle") or {}).get("status")
),
"official_full_capability_catalog_status": full_catalog.get("status"),
"official_full_capability_catalog_summary": full_catalog.get("summary") or {},
"official_full_capability_domain_routing": full_catalog_domain_routing,
"official_full_capability_dynamic_selection": full_catalog_dynamic_selection,
"official_report_references": official_report_references,
"official_section_statuses": official_section_statuses,
"theme_requirements": theme_requirements,
"node_coverage": {
"official_full_snapshot_first": True,
"official_full_capability_catalog_default": bool(full_catalog),
"official_full_capability_theme_routing": bool(full_catalog_domain_routing),
"official_full_capability_dynamic_selection": bool(full_catalog_dynamic_selection),
"strategy": "domain_scoped_range_scan",
"official_calculation_coverage": VEDASTRO_CALCULATION_COVERAGE,
"selected_domains": domains,
"not_user_exposed": True,
},
"route": route,
"scan_window": {"start_date": str(window_start), "end_date": str(window_end)},
"domain_statuses": domain_statuses,
"domain_event_counts": domain_event_counts,
},
}