821 lines
34 KiB
Python
821 lines
34 KiB
Python
#!/usr/bin/env python3
|
|
"""Run selected official VedAstro Python capabilities through the shared bridge."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
PYTHON = sys.executable
|
|
BRIDGE = ROOT / "scripts" / "vedastro_python_bridge.py"
|
|
STUB_ENV = "VEDASTRO_OFFICIAL_CAPABILITY_RUNNER_STUB"
|
|
CATALOG_STUB_ENV = "VEDASTRO_OFFICIAL_CAPABILITY_CATALOG_STUB"
|
|
PLANETS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu", "Ascendant"]
|
|
HOUSES = [f"House{i}" for i in range(1, 13)]
|
|
DEFAULT_SIGIL_SAMPLE_LIMIT = int(os.environ.get("VEDASTRO_FULL_CATALOG_SAMPLE_LIMIT", "0") or 0)
|
|
DOMAIN_ORDER = ["career", "marriage", "wealth", "rectification", "timing", "general"]
|
|
DEFAULT_DYNAMIC_THEMES = ["career", "marriage", "wealth", "rectification", "timing"]
|
|
POLICY_BUCKETS = {
|
|
"needs_user_context": "needs_user_context_methods",
|
|
"needs_user_text": "needs_user_text_methods",
|
|
"needs_rectification_profile": "needs_rectification_profile_methods",
|
|
"blocked": "blocked_methods",
|
|
}
|
|
|
|
|
|
def _method_words(method: str) -> set[str]:
|
|
return {item.lower() for item in re.findall(r"[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)|\d+", method)}
|
|
|
|
|
|
def _domain_routing_for_method(method: str, capability: dict[str, Any], parameter_strategy: str) -> dict[str, Any]:
|
|
method_lower = method.lower()
|
|
words = _method_words(method)
|
|
text = " ".join(
|
|
str(value or "")
|
|
for value in (
|
|
method,
|
|
capability.get("signature"),
|
|
capability.get("bucket"),
|
|
" ".join(str(name) for name in capability.get("parameter_names") or []),
|
|
)
|
|
).lower()
|
|
if method_lower == "getalleventdatagroupedbytag" or "groupedbytag" in text:
|
|
return {
|
|
"domains": ["general"],
|
|
"execution_policy": "auto" if parameter_strategy != "unsupported_signature" else "blocked",
|
|
"priority": "low",
|
|
}
|
|
domains: set[str] = set()
|
|
priority = "low"
|
|
is_dasha_timing = (
|
|
"dasa" in words
|
|
or "dasha" in words
|
|
or method_lower.startswith(("dasaat", "dashaat", "getdasaat", "getdashaat"))
|
|
)
|
|
|
|
if is_dasha_timing or any(token in text for token in ("event", "search", "timing", "transit", "gochara")):
|
|
domains.update({"career", "marriage", "wealth", "rectification", "timing"})
|
|
priority = "high"
|
|
if any(token in text for token in ("marriage", "spouse", "match", "compat", "relationship", "ashtakoot", "kuta")):
|
|
domains.add("marriage")
|
|
priority = "high" if any(token in text for token in ("marriage", "match", "compat")) else priority
|
|
if any(token in text for token in ("career", "profession", "job", "work", "tenth", "house10", "house 10")):
|
|
domains.add("career")
|
|
priority = "high"
|
|
if any(token in text for token in ("wealth", "money", "finance", "income", "gain", "house2", "house11", "ashtakvarga")):
|
|
domains.add("wealth")
|
|
priority = "high" if priority == "low" else priority
|
|
if any(token in text for token in ("birth", "rectification", "appearance", "body", "height", "shape", "complexion")):
|
|
domains.add("rectification")
|
|
if priority == "low":
|
|
priority = "medium"
|
|
if any(token in text for token in ("planet", "house", "strength", "bala", "longitude", "rasi", "navamsa", "varga")):
|
|
domains.update({"career", "marriage", "wealth"})
|
|
if priority == "low":
|
|
priority = "medium"
|
|
|
|
if not domains:
|
|
domains.add("general")
|
|
|
|
if parameter_strategy in {"requires_user_context", "requires_user_text", "requires_rectification_profile"}:
|
|
execution_policy = {
|
|
"requires_user_context": "needs_user_context",
|
|
"requires_user_text": "needs_user_text",
|
|
"requires_rectification_profile": "needs_rectification_profile",
|
|
}[parameter_strategy]
|
|
elif parameter_strategy == "unsupported_signature":
|
|
execution_policy = "blocked"
|
|
else:
|
|
execution_policy = "auto"
|
|
|
|
ordered_domains = [domain for domain in DOMAIN_ORDER if domain in domains]
|
|
return {
|
|
"domains": ordered_domains,
|
|
"execution_policy": execution_policy,
|
|
"priority": priority,
|
|
}
|
|
|
|
|
|
def _build_domain_routing(method_statuses: dict[str, Any]) -> dict[str, Any]:
|
|
routing: dict[str, dict[str, Any]] = {}
|
|
for method, status in method_statuses.items():
|
|
if not isinstance(status, dict):
|
|
continue
|
|
for domain in status.get("domains") or ["general"]:
|
|
row = routing.setdefault(
|
|
domain,
|
|
{
|
|
"method_count": 0,
|
|
"auto_method_count": 0,
|
|
"needs_user_context_count": 0,
|
|
"needs_user_text_count": 0,
|
|
"blocked_method_count": 0,
|
|
"high_priority_methods": [],
|
|
},
|
|
)
|
|
row["method_count"] += 1
|
|
policy = status.get("execution_policy")
|
|
if policy == "auto":
|
|
row["auto_method_count"] += 1
|
|
elif policy == "needs_user_context":
|
|
row["needs_user_context_count"] += 1
|
|
elif policy == "needs_user_text":
|
|
row["needs_user_text_count"] += 1
|
|
elif policy in {"blocked", "needs_rectification_profile"}:
|
|
row["blocked_method_count"] += 1
|
|
if policy == "auto" and status.get("priority") == "high" and method not in row["high_priority_methods"]:
|
|
row["high_priority_methods"].append(method)
|
|
for row in routing.values():
|
|
row["high_priority_methods"] = row["high_priority_methods"][:24]
|
|
return routing
|
|
|
|
|
|
def _requested_dynamic_themes(payload: dict[str, Any]) -> list[str]:
|
|
raw = payload.get("themes") or payload.get("theme") or DEFAULT_DYNAMIC_THEMES
|
|
if isinstance(raw, str):
|
|
values = [raw]
|
|
elif isinstance(raw, list):
|
|
values = raw
|
|
else:
|
|
values = DEFAULT_DYNAMIC_THEMES
|
|
aliases = {
|
|
"relationship": "marriage",
|
|
"relationships": "marriage",
|
|
"finance": "wealth",
|
|
"money": "wealth",
|
|
"birth_time": "rectification",
|
|
"birth-time": "rectification",
|
|
"birthtime": "rectification",
|
|
"event": "timing",
|
|
"events": "timing",
|
|
"事业": "career",
|
|
"婚恋": "marriage",
|
|
"婚姻": "marriage",
|
|
"财富": "wealth",
|
|
"校时": "rectification",
|
|
"应期": "timing",
|
|
}
|
|
themes: list[str] = []
|
|
for value in values:
|
|
key = aliases.get(str(value).strip().lower(), str(value).strip().lower())
|
|
if key in DOMAIN_ORDER and key not in themes:
|
|
themes.append(key)
|
|
return themes or list(DEFAULT_DYNAMIC_THEMES)
|
|
|
|
|
|
def _method_priority_score(method: str, status: dict[str, Any], theme: str) -> tuple[int, str]:
|
|
policy = str(status.get("execution_policy") or "")
|
|
priority = str(status.get("priority") or "low")
|
|
score = 0
|
|
if policy == "auto":
|
|
score += 100
|
|
elif policy == "needs_user_context":
|
|
score += 60
|
|
elif policy in {"needs_user_text", "needs_rectification_profile"}:
|
|
score += 45
|
|
else:
|
|
score += 10
|
|
if priority == "high":
|
|
score += 40
|
|
elif priority == "medium":
|
|
score += 20
|
|
if status.get("status") == "ok":
|
|
score += 12
|
|
if status.get("executed") is True:
|
|
score += 8
|
|
if theme in status.get("domains", []):
|
|
score += 5
|
|
method_lower = method.lower()
|
|
if any(token in method_lower for token in ("searchevents", "eventsatrange", "eventsattime", "geteventtiming")):
|
|
score += 10
|
|
if theme == "career" and any(token in method_lower for token in ("dashamamsha", "profession", "career", "tenth")):
|
|
score += 8
|
|
if theme == "marriage" and any(token in method_lower for token in ("match", "marriage", "spouse", "ashtakoot")):
|
|
score += 8
|
|
if theme == "wealth" and any(token in method_lower for token in ("wealth", "money", "income", "gain", "ashtakvarga")):
|
|
score += 8
|
|
if theme == "rectification" and any(token in method_lower for token in ("birth", "appearance", "body")):
|
|
score += 8
|
|
if theme == "timing" and any(token in method_lower for token in ("dasa", "dasha", "event", "transit")):
|
|
score += 8
|
|
return (-score, method)
|
|
|
|
|
|
def _capability_reference(method: str, status: dict[str, Any], theme: str) -> dict[str, Any]:
|
|
return {
|
|
"citation_id": f"vedastro:{theme}:{method}",
|
|
"method": method,
|
|
"status": status.get("status"),
|
|
"execution_policy": status.get("execution_policy"),
|
|
"priority": status.get("priority"),
|
|
"domains": status.get("domains") or [],
|
|
"bucket": status.get("bucket"),
|
|
"signature": status.get("signature"),
|
|
"parameter_names": status.get("parameter_names") or [],
|
|
"executed": bool(status.get("executed")),
|
|
"source": "official_full_capability_catalog",
|
|
}
|
|
|
|
|
|
def _build_dynamic_selection(
|
|
method_statuses: dict[str, Any],
|
|
domain_routing: dict[str, Any],
|
|
requested_themes: list[str],
|
|
*,
|
|
limit: int = 12,
|
|
) -> dict[str, Any]:
|
|
selection: dict[str, Any] = {}
|
|
for theme in requested_themes:
|
|
candidates = [
|
|
(method, status)
|
|
for method, status in method_statuses.items()
|
|
if isinstance(status, dict) and theme in (status.get("domains") or [])
|
|
]
|
|
candidates.sort(key=lambda item: _method_priority_score(item[0], item[1], theme))
|
|
selected = [
|
|
_capability_reference(method, status, theme)
|
|
for method, status in candidates
|
|
if status.get("execution_policy") == "auto"
|
|
][:limit]
|
|
policy_groups: dict[str, list[dict[str, Any]]] = {
|
|
"needs_user_context_methods": [],
|
|
"needs_user_text_methods": [],
|
|
"needs_rectification_profile_methods": [],
|
|
"blocked_methods": [],
|
|
}
|
|
for method, status in candidates:
|
|
bucket_name = POLICY_BUCKETS.get(str(status.get("execution_policy") or ""))
|
|
if bucket_name and len(policy_groups[bucket_name]) < limit:
|
|
policy_groups[bucket_name].append(_capability_reference(method, status, theme))
|
|
citation_ids = [item["citation_id"] for item in selected]
|
|
selection[theme] = {
|
|
"requested_theme": theme,
|
|
"selection_policy": "official_catalog_theme_top_n",
|
|
"domain_summary": domain_routing.get(theme) or {},
|
|
"selected_methods": selected,
|
|
**policy_groups,
|
|
"report_reference": {
|
|
"theme": theme,
|
|
"citation_ids": citation_ids,
|
|
"auto_count": len(selected),
|
|
"needs_user_context_count": len(policy_groups["needs_user_context_methods"]),
|
|
"needs_user_text_count": len(policy_groups["needs_user_text_methods"]),
|
|
"needs_rectification_profile_count": len(policy_groups["needs_rectification_profile_methods"]),
|
|
"blocked_count": len(policy_groups["blocked_methods"]),
|
|
"boundary": "Citations identify official VedAstro capability evidence used or requested by the workflow; skipped or context-dependent methods are not treated as executed evidence.",
|
|
},
|
|
}
|
|
return selection
|
|
|
|
|
|
def schema() -> dict[str, Any]:
|
|
return {
|
|
"runner": "vedastro_official_capability_runner",
|
|
"primary_source": "vedastro_python_bridge",
|
|
"operations": ["run_bucket", "run_selected_methods", "run_snapshot_bundle", "run_full_capability_catalog"],
|
|
"request_contract": ["methods_json", "birth_json", "bundle?"],
|
|
"response_contract": ["summary", "results", "result?"],
|
|
}
|
|
|
|
|
|
def _normalize_tz(value: Any) -> str:
|
|
if isinstance(value, str):
|
|
return value
|
|
if value is None:
|
|
return "+00:00"
|
|
sign = "+" if float(value) >= 0 else "-"
|
|
absolute = abs(float(value))
|
|
hours = int(absolute)
|
|
minutes = int(round((absolute - hours) * 60))
|
|
return f"{sign}{hours:02d}:{minutes:02d}"
|
|
|
|
|
|
def _bridge_time(case: dict[str, Any], date_text: str, *, hour: int | None = None, minute: int | None = None) -> dict[str, Any]:
|
|
year, month, day = str(date_text).split("-")
|
|
return {
|
|
"__vedastro_type__": "Time",
|
|
"year": int(year),
|
|
"month": int(month),
|
|
"day": int(day),
|
|
"hour": int(case.get("hour", 0) if hour is None else hour),
|
|
"minute": int(case.get("minute", 0) if minute is None else minute),
|
|
"offset": _normalize_tz(case.get("tz")),
|
|
"geolocation": {
|
|
"__vedastro_type__": "GeoLocation",
|
|
"location_name": "UserLocation",
|
|
"longitude": case.get("lon"),
|
|
"latitude": case.get("lat"),
|
|
},
|
|
}
|
|
|
|
|
|
def _reference_date(case: dict[str, Any]) -> str:
|
|
for key in ("reference_date", "today", "transit_date", "current_date"):
|
|
value = case.get(key)
|
|
if value:
|
|
return str(value)[:10]
|
|
return datetime.utcnow().strftime("%Y-%m-%d")
|
|
|
|
|
|
def _build_method_payload(method: str, case: dict[str, Any]) -> dict[str, Any] | None:
|
|
birth_date = f"{int(case['year']):04d}-{int(case['month']):02d}-{int(case['day']):02d}"
|
|
birth_time = _bridge_time(case, birth_date)
|
|
ref_date = _reference_date(case)
|
|
check_time = _bridge_time(case, ref_date)
|
|
start_of_year = f"{ref_date[:4]}-01-01"
|
|
end_of_year = f"{ref_date[:4]}-12-31"
|
|
|
|
if method == "GetAllEventDataGroupedByTag":
|
|
return {}
|
|
if method == "DasaAtTime":
|
|
return {"args": [birth_time, check_time, 3]}
|
|
if method == "GetCharaDasaAtTime":
|
|
return {"args": [birth_time, check_time]}
|
|
if method == "DasaAtRange":
|
|
return {"args": [birth_time, _bridge_time(case, start_of_year, hour=0, minute=0), _bridge_time(case, end_of_year, hour=23, minute=59), 3, 100]}
|
|
if method in {"AllPlanetStrength", "AshtakvargaLifeMap"}:
|
|
return {"args": [birth_time]}
|
|
if method == "AllPlanetData":
|
|
return {"args": [{"__vedastro_enum__": "PlanetName", "value": "Sun"}, birth_time]}
|
|
if method == "AllHouseData":
|
|
return {"args": [{"__vedastro_enum__": "HouseName", "value": "House1"}, birth_time]}
|
|
return None
|
|
|
|
|
|
def _method_payload_for_instance(method: str, case: dict[str, Any], identity: str | None = None) -> dict[str, Any] | None:
|
|
payload = _build_method_payload(method, case)
|
|
if payload is None:
|
|
return None
|
|
if method == "AllPlanetData" and identity:
|
|
return {"args": [{"__vedastro_enum__": "PlanetName", "value": str(identity)}, payload["args"][1]]}
|
|
if method == "AllHouseData" and identity:
|
|
return {"args": [{"__vedastro_enum__": "HouseName", "value": str(identity)}, payload["args"][1]]}
|
|
return payload
|
|
|
|
|
|
def _call_bridge(method: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
completed = subprocess.run(
|
|
[PYTHON, str(BRIDGE), "--method", method, "--params-json", json.dumps(payload, ensure_ascii=False)],
|
|
cwd=ROOT,
|
|
text=True,
|
|
capture_output=True,
|
|
timeout=240,
|
|
check=False,
|
|
env=os.environ.copy(),
|
|
)
|
|
if completed.returncode != 0:
|
|
return {"available": False, "status": "bridge_runtime_error", "stderr": (completed.stderr or "").strip()}
|
|
return json.loads(completed.stdout)
|
|
|
|
|
|
def _list_official_capabilities() -> dict[str, Any]:
|
|
stub_raw = os.environ.get(CATALOG_STUB_ENV, "").strip()
|
|
if stub_raw:
|
|
payload = json.loads(stub_raw)
|
|
payload.setdefault("source", "stubbed_official_capability_catalog")
|
|
return payload
|
|
|
|
completed = subprocess.run(
|
|
[PYTHON, str(BRIDGE), "--list-capabilities"],
|
|
cwd=ROOT,
|
|
text=True,
|
|
capture_output=True,
|
|
timeout=240,
|
|
check=False,
|
|
env=os.environ.copy(),
|
|
)
|
|
if completed.returncode != 0:
|
|
return {
|
|
"available": False,
|
|
"status": "bridge_runtime_error",
|
|
"capabilities": [],
|
|
"buckets": {},
|
|
"stderr": (completed.stderr or "").strip(),
|
|
"source": "vedastro_official_capability_runner",
|
|
}
|
|
try:
|
|
return json.loads(completed.stdout)
|
|
except json.JSONDecodeError:
|
|
return {
|
|
"available": False,
|
|
"status": "bridge_invalid_json",
|
|
"capabilities": [],
|
|
"buckets": {},
|
|
"stdout_excerpt": (completed.stdout or "").strip()[:500],
|
|
"source": "vedastro_official_capability_runner",
|
|
}
|
|
|
|
|
|
def run_selected_methods(methods: list[str], birth_payload: dict[str, Any]) -> dict[str, Any]:
|
|
stub_raw = os.environ.get(STUB_ENV, "").strip()
|
|
stub_map = json.loads(stub_raw) if stub_raw else {}
|
|
results: dict[str, Any] = {}
|
|
ok_count = 0
|
|
skipped_count = 0
|
|
for method in methods:
|
|
if method in stub_map:
|
|
results[method] = stub_map[method]
|
|
else:
|
|
payload = _build_method_payload(method, birth_payload)
|
|
if payload is None:
|
|
results[method] = {"available": False, "status": "unsupported_signature"}
|
|
skipped_count += 1
|
|
continue
|
|
results[method] = _call_bridge(method, payload)
|
|
if results[method].get("status") == "ok":
|
|
ok_count += 1
|
|
|
|
return {
|
|
"runner": "vedastro_official_capability_runner",
|
|
"primary_source": "vedastro_python_bridge",
|
|
"summary": {
|
|
"requested_method_count": len(methods),
|
|
"executed_method_count": len(results),
|
|
"ok_count": ok_count,
|
|
"skipped_count": skipped_count,
|
|
},
|
|
"results": results,
|
|
}
|
|
|
|
|
|
def _sample_identity_for_method(method: str, parameter_names: list[str]) -> Any:
|
|
lowered = [name.lower() for name in parameter_names]
|
|
if "planetname" in lowered or "inputplanet" in lowered or "planet" in lowered:
|
|
return "Sun"
|
|
if "housename" in lowered:
|
|
return "House1"
|
|
if "housenumber" in lowered or "inputhousenumber" in lowered:
|
|
return 1
|
|
if "inputhouse" in lowered or "house" in lowered:
|
|
return "House1"
|
|
if "zodiacname" in lowered or "signname" in lowered or "inputsign" in lowered or "zodiacsign" in lowered:
|
|
return "Aries"
|
|
if "constellation" in lowered:
|
|
return "Aswini"
|
|
if "divisionalno" in lowered:
|
|
return 9
|
|
if "longitude" in lowered or "longitudedeg" in lowered or "totaldegrees" in lowered:
|
|
return 3.5
|
|
return None
|
|
|
|
|
|
def _full_catalog_method_payload(method: str, capability: dict[str, Any], case: dict[str, Any]) -> tuple[dict[str, Any] | None, str]:
|
|
parameter_names = [str(name) for name in capability.get("parameter_names") or []]
|
|
lowered = [name.lower() for name in parameter_names]
|
|
bucket = str(capability.get("bucket") or "")
|
|
birth_date = f"{int(case['year']):04d}-{int(case['month']):02d}-{int(case['day']):02d}"
|
|
birth_time = _bridge_time(case, birth_date)
|
|
ref_date = _reference_date(case)
|
|
check_time = _bridge_time(case, ref_date)
|
|
start_of_year = _bridge_time(case, f"{ref_date[:4]}-01-01", hour=0, minute=0)
|
|
end_of_year = _bridge_time(case, f"{ref_date[:4]}-12-31", hour=23, minute=59)
|
|
|
|
if not parameter_names:
|
|
return {}, "auto_zero_arg"
|
|
if bucket in {"time_only", "(inputTime)", "(queryTime)", "(time1)"} or lowered in (["time"], ["inputtime"], ["querytime"], ["time1"]):
|
|
key = parameter_names[0]
|
|
return {"kwargs": {key: check_time}}, "auto_time_only"
|
|
if bucket == "birth_time_only" or lowered == ["birthtime"]:
|
|
return {"kwargs": {parameter_names[0]: birth_time}}, "auto_birth_time_only"
|
|
if bucket == "planet_time" or lowered == ["planetname", "time"]:
|
|
return {"args": [{"__vedastro_enum__": "PlanetName", "value": "Sun"}, birth_time]}, "auto_planet_time"
|
|
if bucket == "planet_alias_time" or lowered == ["planet", "time"]:
|
|
return {"args": [{"__vedastro_enum__": "PlanetName", "value": "Sun"}, birth_time]}, "auto_planet_time"
|
|
if bucket == "house_name_time" or lowered == ["housename", "time"]:
|
|
return {"args": [{"__vedastro_enum__": "HouseName", "value": "House1"}, birth_time]}, "auto_house_name_time"
|
|
if bucket == "house_number_time" or lowered == ["housenumber", "time"]:
|
|
return {"args": [1, birth_time]}, "auto_house_number_time"
|
|
if bucket == "dasha_at_time" or lowered == ["birthtime", "checktime", "levels"]:
|
|
return {"args": [birth_time, check_time, 3]}, "auto_dasha_at_time"
|
|
if bucket == "dasha_at_range" or lowered == ["birthtime", "starttime", "endtime", "levels", "precisionhours"]:
|
|
return {"args": [birth_time, start_of_year, end_of_year, 3, 100]}, "auto_dasha_at_range"
|
|
|
|
if lowered == ["birthtime", "checktime"]:
|
|
return {"args": [birth_time, check_time]}, "auto_birth_check_time"
|
|
if lowered == ["birthtime", "levels"]:
|
|
return {"args": [birth_time, 3]}, "auto_birth_levels"
|
|
if lowered == ["birthtime", "scanyear"]:
|
|
return {"args": [birth_time, int(ref_date[:4])]}, "auto_birth_scan_year"
|
|
if lowered == ["birthtime", "querytime"]:
|
|
return {"args": [birth_time, check_time]}, "auto_birth_query_time"
|
|
if lowered == ["birthtime", "sortbyweight"]:
|
|
return {"args": [birth_time, False]}, "auto_birth_bool"
|
|
if lowered == ["birthtime", "filtertags", "sortbyweight"]:
|
|
return {"args": [birth_time, [], False]}, "auto_birth_filter_tags"
|
|
if lowered == ["birthtime", "starttime", "endtime", "eventtaglist", "precisionhours"]:
|
|
return {"args": [birth_time, start_of_year, end_of_year, ["Marriage"], 100]}, "auto_events_range"
|
|
if lowered == ["birthtime", "checktime", "eventtaglist"]:
|
|
return {"args": [birth_time, check_time, ["Marriage"]]}, "auto_events_time"
|
|
if lowered == ["birthtime", "attime", "eventtaglist"]:
|
|
return {"args": [birth_time, check_time, ["Marriage"]]}, "auto_search_events"
|
|
|
|
if len(parameter_names) == 1:
|
|
sample = _sample_identity_for_method(method, parameter_names)
|
|
if sample is not None:
|
|
return {"args": [sample]}, "auto_single_sample"
|
|
if len(parameter_names) == 2 and any(name in lowered for name in ("time", "inputtime", "birthtime")):
|
|
sample = _sample_identity_for_method(method, parameter_names)
|
|
if sample is not None:
|
|
args = []
|
|
for name in lowered:
|
|
if name in {"time", "inputtime", "birthtime"}:
|
|
args.append(birth_time)
|
|
else:
|
|
args.append(sample)
|
|
return {"args": args}, "auto_two_arg_sample"
|
|
|
|
if any(name in lowered for name in ("malebirthtime", "femalebirthtime", "partnerbirthtime", "personb", "personbirthtime")):
|
|
return None, "requires_user_context"
|
|
if any(name in lowered for name in ("bodyheight", "bodyshape", "hair", "lips", "nose", "complexion", "faceshape", "constitution", "personality")):
|
|
return None, "requires_rectification_profile"
|
|
if any(name in lowered for name in ("rawtextdata", "birthdatarawtext", "inputtext", "textinput", "query", "fullname", "personfullname", "address", "locationname", "ipaddress")):
|
|
return None, "requires_user_text"
|
|
return None, "unsupported_signature"
|
|
|
|
|
|
def run_full_capability_catalog(birth_payload: dict[str, Any]) -> dict[str, Any]:
|
|
catalog = _list_official_capabilities()
|
|
capabilities = catalog.get("capabilities") if isinstance(catalog.get("capabilities"), list) else []
|
|
buckets = catalog.get("buckets") if isinstance(catalog.get("buckets"), dict) else {}
|
|
stub_raw = os.environ.get(STUB_ENV, "").strip()
|
|
stub_map = json.loads(stub_raw) if stub_raw else {}
|
|
method_statuses: dict[str, Any] = {}
|
|
bucket_statuses: dict[str, dict[str, int]] = {}
|
|
executed_count = 0
|
|
ok_count = 0
|
|
unsupported_count = 0
|
|
blocked_count = 0
|
|
sample_limit = max(0, int(os.environ.get("VEDASTRO_FULL_CATALOG_SAMPLE_LIMIT", str(DEFAULT_SIGIL_SAMPLE_LIMIT)) or 0))
|
|
|
|
for capability in capabilities:
|
|
method = str(capability.get("method") or "")
|
|
if not method:
|
|
continue
|
|
bucket = str(capability.get("bucket") or "unknown")
|
|
payload, strategy = _full_catalog_method_payload(method, capability, birth_payload)
|
|
routing_meta = _domain_routing_for_method(method, capability, strategy)
|
|
bucket_row = bucket_statuses.setdefault(bucket, {"total": 0, "executed": 0, "ok": 0, "unsupported": 0, "blocked": 0})
|
|
bucket_row["total"] += 1
|
|
|
|
if payload is None:
|
|
unsupported_count += 1
|
|
bucket_row["unsupported"] += 1
|
|
method_statuses[method] = {
|
|
"status": strategy,
|
|
"bucket": bucket,
|
|
"signature": capability.get("signature"),
|
|
"parameter_names": capability.get("parameter_names") or [],
|
|
"executed": False,
|
|
**routing_meta,
|
|
}
|
|
continue
|
|
|
|
if executed_count >= sample_limit and method not in stub_map:
|
|
blocked_count += 1
|
|
bucket_row["blocked"] += 1
|
|
method_statuses[method] = {
|
|
"status": "skipped_by_sample_limit",
|
|
"bucket": bucket,
|
|
"signature": capability.get("signature"),
|
|
"parameter_names": capability.get("parameter_names") or [],
|
|
"executed": False,
|
|
"parameter_strategy": strategy,
|
|
**routing_meta,
|
|
}
|
|
continue
|
|
|
|
if method in stub_map:
|
|
report = stub_map[method]
|
|
else:
|
|
report = _call_bridge(method, payload)
|
|
status = str(report.get("status") or "blocked")
|
|
executed_count += 1
|
|
bucket_row["executed"] += 1
|
|
if status == "ok":
|
|
ok_count += 1
|
|
bucket_row["ok"] += 1
|
|
else:
|
|
blocked_count += 1
|
|
bucket_row["blocked"] += 1
|
|
method_statuses[method] = {
|
|
"status": status,
|
|
"bucket": bucket,
|
|
"signature": capability.get("signature"),
|
|
"parameter_names": capability.get("parameter_names") or [],
|
|
"executed": True,
|
|
"parameter_strategy": strategy,
|
|
"available": bool(report.get("available")),
|
|
"source": report.get("source"),
|
|
**routing_meta,
|
|
}
|
|
|
|
overall_status = "blocked"
|
|
if capabilities:
|
|
overall_status = "ok" if unsupported_count == 0 and blocked_count == 0 else "partial"
|
|
domain_routing = _build_domain_routing(method_statuses)
|
|
requested_themes = _requested_dynamic_themes(birth_payload)
|
|
dynamic_selection = _build_dynamic_selection(method_statuses, domain_routing, requested_themes)
|
|
|
|
return {
|
|
"runner": "vedastro_official_capability_runner",
|
|
"primary_source": "vedastro_python_bridge",
|
|
"bundle": "official_full_capability_catalog",
|
|
"available": bool(capabilities),
|
|
"status": overall_status,
|
|
"summary": {
|
|
"catalog_method_count": len(capabilities),
|
|
"official_callable_count": sum(1 for item in capabilities if item.get("callable")),
|
|
"signature_bucket_count": len(buckets),
|
|
"executed_method_count": executed_count,
|
|
"ok_method_count": ok_count,
|
|
"unsupported_method_count": unsupported_count,
|
|
"blocked_method_count": blocked_count,
|
|
"sample_limit": sample_limit,
|
|
"domain_routing_count": len(domain_routing),
|
|
"dynamic_selection_theme_count": len(dynamic_selection),
|
|
},
|
|
"coverage": {
|
|
"source_mode": "official_full_capability_catalog",
|
|
"catalog_source": catalog.get("source") or "vedastro_python_bridge",
|
|
"python_bin": catalog.get("python_bin"),
|
|
"bucket_count": len(buckets),
|
|
"safe_sampling": True,
|
|
"not_user_exposed": True,
|
|
"lightweight_domain_mapping": True,
|
|
"dynamic_theme_selection": True,
|
|
},
|
|
"domain_routing": domain_routing,
|
|
"dynamic_selection": dynamic_selection,
|
|
"bucket_statuses": bucket_statuses,
|
|
"method_statuses": method_statuses,
|
|
}
|
|
|
|
|
|
def run_snapshot_bundle(bundle: str, birth_payload: dict[str, Any]) -> dict[str, Any]:
|
|
if bundle == "official_full_capability_catalog":
|
|
return run_full_capability_catalog(birth_payload)
|
|
|
|
if bundle != "official_full_snapshot":
|
|
return {
|
|
"runner": "vedastro_official_capability_runner",
|
|
"bundle": bundle,
|
|
"available": False,
|
|
"status": "unsupported_bundle",
|
|
"reason": f"Unsupported bundle: {bundle}",
|
|
}
|
|
|
|
stub_raw = os.environ.get(STUB_ENV, "").strip()
|
|
stub_map = json.loads(stub_raw) if stub_raw else {}
|
|
stub_mode = bool(stub_raw)
|
|
snapshot_sections: dict[str, Any] = {}
|
|
section_statuses: dict[str, str] = {}
|
|
coverage_sections: list[str] = []
|
|
ok_count = 0
|
|
skipped_count = 0
|
|
|
|
chart_core: dict[str, Any] = {}
|
|
planet_statuses: dict[str, str] = {}
|
|
for planet in PLANETS:
|
|
stub_key = f"AllPlanetData:{planet}"
|
|
if stub_key in stub_map:
|
|
report = stub_map[stub_key]
|
|
elif stub_mode:
|
|
report = {"available": False, "status": "stub_not_provided"}
|
|
else:
|
|
payload = _method_payload_for_instance("AllPlanetData", birth_payload, planet)
|
|
report = {"available": False, "status": "unsupported_signature"} if payload is None else _call_bridge("AllPlanetData", payload)
|
|
status = str(report.get("status") or "blocked")
|
|
planet_statuses[planet] = "ok" if status == "ok" else status
|
|
if status == "ok":
|
|
ok_count += 1
|
|
else:
|
|
skipped_count += 1
|
|
chart_core[planet] = {
|
|
"Status": "Pass" if status == "ok" else "Fail",
|
|
"Payload": {"AllPlanetData": report.get("result")} if status == "ok" else report,
|
|
}
|
|
if chart_core:
|
|
snapshot_sections["chart_core"] = chart_core
|
|
section_statuses["chart_core"] = "ok" if all(value == "ok" for value in planet_statuses.values()) else "partial"
|
|
section_statuses["chart_core_fanout"] = planet_statuses
|
|
if section_statuses["chart_core"] == "ok":
|
|
coverage_sections.append("chart_core")
|
|
|
|
house_core: dict[str, Any] = {}
|
|
house_statuses: dict[str, str] = {}
|
|
for house in HOUSES:
|
|
stub_key = f"AllHouseData:{house}"
|
|
if stub_key in stub_map:
|
|
report = stub_map[stub_key]
|
|
elif stub_mode:
|
|
report = {"available": False, "status": "stub_not_provided"}
|
|
else:
|
|
payload = _method_payload_for_instance("AllHouseData", birth_payload, house)
|
|
report = {"available": False, "status": "unsupported_signature"} if payload is None else _call_bridge("AllHouseData", payload)
|
|
status = str(report.get("status") or "blocked")
|
|
house_statuses[house] = "ok" if status == "ok" else status
|
|
if status == "ok":
|
|
ok_count += 1
|
|
else:
|
|
skipped_count += 1
|
|
house_core[house] = {
|
|
"Status": "Pass" if status == "ok" else "Fail",
|
|
"Payload": {"AllHouseData": report.get("result")} if status == "ok" else report,
|
|
}
|
|
if house_core:
|
|
snapshot_sections["house_core"] = house_core
|
|
section_statuses["house_core"] = "ok" if all(value == "ok" for value in house_statuses.values()) else "partial"
|
|
section_statuses["house_core_fanout"] = house_statuses
|
|
if section_statuses["house_core"] == "ok":
|
|
coverage_sections.append("house_core")
|
|
|
|
scalar_methods = [
|
|
("dasha_all", "DasaAtRange", "DasaAtRange"),
|
|
("vimshottari_now", "DasaAtTime", "DasaAtTime"),
|
|
("chara_dasha_now", "GetCharaDasaAtTime", "GetCharaDasaAtTime"),
|
|
("shadbala", "AllPlanetStrength", "AllPlanetStrength"),
|
|
("ashtakavarga", "AshtakvargaLifeMap", "AshtakvargaLifeMap"),
|
|
]
|
|
for section_name, method, payload_key in scalar_methods:
|
|
if method in stub_map:
|
|
report = stub_map[method]
|
|
elif stub_mode:
|
|
report = {"available": False, "status": "stub_not_provided"}
|
|
else:
|
|
payload = _method_payload_for_instance(method, birth_payload)
|
|
report = {"available": False, "status": "unsupported_signature"} if payload is None else _call_bridge(method, payload)
|
|
status = str(report.get("status") or "blocked")
|
|
section_statuses[section_name] = "ok" if status == "ok" else status
|
|
if status == "ok":
|
|
ok_count += 1
|
|
coverage_sections.append(section_name)
|
|
else:
|
|
skipped_count += 1
|
|
snapshot_sections[section_name] = {
|
|
"Status": "Pass" if status == "ok" else "Fail",
|
|
"Payload": {payload_key: report.get("result")} if status == "ok" else report,
|
|
}
|
|
|
|
overall_status = "blocked"
|
|
if coverage_sections:
|
|
overall_status = "ok" if len(coverage_sections) == 7 else "partial"
|
|
|
|
return {
|
|
"runner": "vedastro_official_capability_runner",
|
|
"primary_source": "vedastro_python_bridge",
|
|
"bundle": bundle,
|
|
"available": bool(coverage_sections),
|
|
"status": overall_status,
|
|
"summary": {
|
|
"requested_method_count": len(PLANETS) + len(HOUSES) + len(scalar_methods),
|
|
"executed_method_count": len(PLANETS) + len(HOUSES) + len(scalar_methods),
|
|
"ok_count": ok_count,
|
|
"skipped_count": skipped_count,
|
|
},
|
|
"result": {
|
|
"snapshot_sections": snapshot_sections,
|
|
"section_statuses": section_statuses,
|
|
"coverage": {
|
|
"source_mode": "official_capability_runner_bundle",
|
|
"filled_sections": coverage_sections,
|
|
"planet_count": len(chart_core),
|
|
"house_count": len(house_core),
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--print-schema", action="store_true")
|
|
parser.add_argument("--methods-json", default="[]")
|
|
parser.add_argument("--birth-json", default="{}")
|
|
parser.add_argument("--bundle", default="")
|
|
args = parser.parse_args()
|
|
|
|
if args.print_schema:
|
|
result = schema()
|
|
elif args.bundle:
|
|
birth_payload = json.loads(args.birth_json or "{}")
|
|
result = run_snapshot_bundle(args.bundle, birth_payload)
|
|
else:
|
|
methods = json.loads(args.methods_json or "[]")
|
|
birth_payload = json.loads(args.birth_json or "{}")
|
|
result = run_selected_methods(methods, birth_payload)
|
|
|
|
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|