268 lines
11 KiB
Python
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,
|
|
},
|
|
}
|