Add official VedAstro MCP bridge and routing assets
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render the fastest practical VedAstro integration checklist.
|
||||
|
||||
This artifact turns the parity matrix plus live official catalog snapshot into
|
||||
an execution-friendly view:
|
||||
|
||||
- what should go through official MCP
|
||||
- what should go through the official Python bridge
|
||||
- what should go through the REST adapter
|
||||
- what should stay local because the repo is already stronger there
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from scripts import vedastro_parity_matrix
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_JSON_PATH = ROOT / "docs" / "research" / "vedastro_fast_path_checklist_latest.json"
|
||||
DEFAULT_MARKDOWN_PATH = ROOT / "docs" / "research" / "vedastro_fast_path_checklist_latest.md"
|
||||
CATALOG_PATH = ROOT / "scratch" / "local" / "vedastro_adapter" / "method_catalog_snapshot.json"
|
||||
|
||||
LANE_TITLES = {
|
||||
"official_mcp": "Direct Official MCP",
|
||||
"official_python_bridge": "Official Python Bridge",
|
||||
"rest_adapter": "REST Adapter",
|
||||
"local_native_preferred": "Local Native Preferred",
|
||||
"hybrid_router": "Hybrid Router",
|
||||
"external_evidence_only": "External Evidence Only",
|
||||
}
|
||||
|
||||
|
||||
def _load_catalog_summary() -> dict[str, Any]:
|
||||
if not CATALOG_PATH.exists():
|
||||
return {
|
||||
"available": False,
|
||||
"path": str(CATALOG_PATH),
|
||||
"tag_count": 0,
|
||||
"method_count": 0,
|
||||
}
|
||||
payload = json.loads(CATALOG_PATH.read_text(encoding="utf-8"))
|
||||
summary = payload.get("summary") or {}
|
||||
return {
|
||||
"available": True,
|
||||
"path": str(CATALOG_PATH),
|
||||
"tag_count": int(summary.get("tag_count", 0) or 0),
|
||||
"method_count": int(summary.get("method_count", 0) or 0),
|
||||
}
|
||||
|
||||
|
||||
def build_checklist() -> dict[str, Any]:
|
||||
matrix = vedastro_parity_matrix.build_matrix()
|
||||
catalog = _load_catalog_summary()
|
||||
|
||||
grouped: dict[str, list[dict[str, Any]]] = {lane: [] for lane in LANE_TITLES}
|
||||
for row in matrix["rows"]:
|
||||
grouped.setdefault(row["fastest_path_lane"], []).append(
|
||||
{
|
||||
"capability": row["vedastro_capability"],
|
||||
"priority": row["priority"],
|
||||
"recommended_path": row["recommended_path"],
|
||||
"local_status": row["local_status"],
|
||||
"adjudicator_use": row["adjudicator_use"],
|
||||
"route_notes": row["route_notes"],
|
||||
"gap_notes": row["gap_notes"],
|
||||
"local_assets": row["local_assets"],
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"scope": "vedastro_fast_path_checklist",
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"summary": {
|
||||
"row_count": len(matrix["rows"]),
|
||||
"local_registry_technique_count": matrix["summary"].get("local_registry_technique_count"),
|
||||
"catalog_tag_count": catalog["tag_count"],
|
||||
"catalog_method_count": catalog["method_count"],
|
||||
},
|
||||
"boundary": {
|
||||
"do_not_clone_vedastro": True,
|
||||
"keep_local_adjudicator_authoritative": True,
|
||||
"external_outputs_default_to_secondary_context": True,
|
||||
},
|
||||
"official_catalog": catalog,
|
||||
"lanes": grouped,
|
||||
}
|
||||
|
||||
|
||||
def render_markdown(checklist: dict[str, Any]) -> str:
|
||||
summary = checklist["summary"]
|
||||
catalog = checklist["official_catalog"]
|
||||
lines = [
|
||||
"# VedAstro 596+ Nodes Fast-Path Checklist",
|
||||
"",
|
||||
f"- Generated: `{checklist['generated_at']}`",
|
||||
f"- Local registry techniques: `{summary['local_registry_technique_count']}`",
|
||||
f"- Official catalog tags: `{summary['catalog_tag_count']}`",
|
||||
f"- Official catalog methods/events: `{summary['catalog_method_count']}`",
|
||||
"",
|
||||
"## Boundary",
|
||||
"",
|
||||
"- Do not clone VedAstro wholesale into local code.",
|
||||
"- Keep the local adjudicator as the final reasoning layer.",
|
||||
"- Default all external outputs to secondary evidence unless explicitly promoted by local tests and contracts.",
|
||||
"",
|
||||
"## Lane Breakdown",
|
||||
"",
|
||||
]
|
||||
|
||||
for lane, title in LANE_TITLES.items():
|
||||
rows = checklist["lanes"].get(lane) or []
|
||||
lines.append(f"### {title}")
|
||||
lines.append("")
|
||||
if not rows:
|
||||
lines.append("- None.")
|
||||
lines.append("")
|
||||
continue
|
||||
for row in rows:
|
||||
assets = ", ".join(row["local_assets"]) if row["local_assets"] else "-"
|
||||
lines.append(
|
||||
f"- `{row['capability']}` [{row['priority']}]"
|
||||
f" - local=`{row['local_status']}`, adjudicator=`{row['adjudicator_use']}`"
|
||||
f"; assets: {assets}"
|
||||
)
|
||||
lines.append(f" route: {row['route_notes']}")
|
||||
lines.append(f" gap: {row['gap_notes']}")
|
||||
lines.append("")
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"## Immediate Execution Order",
|
||||
"",
|
||||
"1. Use `Direct Official MCP` where an official MCP surface is available to agents.",
|
||||
"2. Use `Official Python Bridge` for broad calculator access that does not need browser/session orchestration.",
|
||||
"3. Use `REST Adapter` for range scans and endpoint-style external evidence.",
|
||||
"4. Keep `Local Native Preferred` rows local; do not waste time rebuilding what is already stronger here.",
|
||||
"5. Use `Hybrid Router` only where parity or oracle closure still matters more than raw breadth.",
|
||||
"",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def write_outputs(
|
||||
checklist: dict[str, Any],
|
||||
*,
|
||||
json_path: Path = DEFAULT_JSON_PATH,
|
||||
markdown_path: Path = DEFAULT_MARKDOWN_PATH,
|
||||
) -> None:
|
||||
json_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
markdown_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
json_path.write_text(json.dumps(checklist, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
markdown_path.write_text(render_markdown(checklist) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--format", choices=["json", "markdown"], default="json")
|
||||
parser.add_argument("--write", action="store_true")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
checklist = build_checklist()
|
||||
if args.write:
|
||||
write_outputs(checklist)
|
||||
if args.format == "json":
|
||||
print(json.dumps(checklist, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(render_markdown(checklist))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Sync a lightweight VedAstro method catalog snapshot.
|
||||
|
||||
This script intentionally starts small. It can pull the official event-tag
|
||||
catalog from VedAstro's public API or write a stubbed snapshot in tests. The
|
||||
result is a local JSON snapshot suitable for later MCP/Python/REST routing.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib import request
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_OUTPUT = ROOT / "scratch" / "local" / "vedastro_adapter" / "method_catalog_snapshot.json"
|
||||
OFFICIAL_TAG_CATALOG_URL = "https://api.vedastro.org/api/Calculate/GetAllEventDataGroupedByTag"
|
||||
STUB_ENV = "VEDASTRO_METHOD_CATALOG_STUB"
|
||||
|
||||
|
||||
def schema() -> dict[str, Any]:
|
||||
return {
|
||||
"sync": "vedastro_method_catalog_sync",
|
||||
"scope": "official_vedastro_method_catalog",
|
||||
"operations": ["sync_tags", "write_snapshot"],
|
||||
"sources": {
|
||||
"official_tag_catalog": OFFICIAL_TAG_CATALOG_URL,
|
||||
},
|
||||
"output_contract": ["source", "summary", "tag_groups"],
|
||||
}
|
||||
|
||||
|
||||
def _load_stubbed_catalog() -> dict[str, Any] | None:
|
||||
raw = os.environ.get(STUB_ENV, "").strip()
|
||||
if not raw:
|
||||
return None
|
||||
return json.loads(raw)
|
||||
|
||||
|
||||
def _fetch_official_tag_catalog() -> dict[str, Any]:
|
||||
with request.urlopen(OFFICIAL_TAG_CATALOG_URL, timeout=60) as resp:
|
||||
payload = json.loads(resp.read().decode("utf-8"))
|
||||
groups = ((payload.get("Payload") or {}).get("GetAllEventDataGroupedByTag") or {})
|
||||
return {
|
||||
"source": "official_tag_catalog",
|
||||
"tag_groups": groups,
|
||||
}
|
||||
|
||||
|
||||
def build_catalog() -> dict[str, Any]:
|
||||
catalog = _load_stubbed_catalog() or _fetch_official_tag_catalog()
|
||||
tag_groups = catalog.get("tag_groups") or {}
|
||||
method_count = 0
|
||||
for events in tag_groups.values():
|
||||
if isinstance(events, list):
|
||||
method_count += len(events)
|
||||
catalog["summary"] = {
|
||||
"tag_count": len(tag_groups),
|
||||
"method_count": method_count,
|
||||
}
|
||||
return catalog
|
||||
|
||||
|
||||
def write_snapshot(output_path: Path) -> dict[str, Any]:
|
||||
catalog = build_catalog()
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text(json.dumps(catalog, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8")
|
||||
return {
|
||||
"status": "ok",
|
||||
"source": catalog.get("source"),
|
||||
"summary": catalog.get("summary"),
|
||||
"output_path": str(output_path),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="VedAstro method catalog sync")
|
||||
parser.add_argument("--print-schema", action="store_true")
|
||||
parser.add_argument("--write", action="store_true")
|
||||
parser.add_argument("--output", default=str(DEFAULT_OUTPUT))
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.print_schema:
|
||||
result = schema()
|
||||
elif args.write:
|
||||
result = write_snapshot(Path(args.output))
|
||||
else:
|
||||
result = build_catalog()
|
||||
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Thin bridge to the official public VedAstro MCP endpoint.
|
||||
|
||||
This bridge is intentionally narrow. It proves that the official public MCP
|
||||
surface is reachable and callable from the local workspace without letting
|
||||
external responses silently override local adjudication.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_ENDPOINT = "https://mcp.vedastro.org/api/mcp/public"
|
||||
DEFAULT_PROTOCOL_VERSION = "2025-06-18"
|
||||
DEFAULT_TIMEOUT_SECONDS = 60
|
||||
|
||||
|
||||
def _post_json(endpoint: str, payload: dict[str, Any], timeout: float = DEFAULT_TIMEOUT_SECONDS) -> tuple[dict[str, Any], dict[str, str]]:
|
||||
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
endpoint,
|
||||
data=body,
|
||||
method="POST",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json, text/event-stream",
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
raw = resp.read().decode("utf-8")
|
||||
headers = {key.lower(): value for key, value in resp.headers.items()}
|
||||
return json.loads(raw), headers
|
||||
|
||||
|
||||
def _initialize(endpoint: str) -> dict[str, Any]:
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"protocolVersion": DEFAULT_PROTOCOL_VERSION,
|
||||
"capabilities": {},
|
||||
"clientInfo": {
|
||||
"name": "jyotish-local-bridge",
|
||||
"version": "1.0.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
result, headers = _post_json(endpoint, payload)
|
||||
session_id = headers.get("mcp-session-id") or headers.get("x-mcp-session-id")
|
||||
return {
|
||||
"endpoint": endpoint,
|
||||
"available": True,
|
||||
"status": "ok",
|
||||
"operation": "initialize",
|
||||
"session_id": session_id,
|
||||
"result": result.get("result") or result,
|
||||
"source": "official_public_mcp",
|
||||
}
|
||||
|
||||
|
||||
def _tools_list(endpoint: str) -> dict[str, Any]:
|
||||
init = _initialize(endpoint)
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/list",
|
||||
"params": {},
|
||||
}
|
||||
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json, text/event-stream",
|
||||
}
|
||||
if init.get("session_id"):
|
||||
headers["Mcp-Session-Id"] = str(init["session_id"])
|
||||
req = urllib.request.Request(endpoint, data=body, method="POST", headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=DEFAULT_TIMEOUT_SECONDS) as resp:
|
||||
raw = resp.read().decode("utf-8")
|
||||
response_headers = {key.lower(): value for key, value in resp.headers.items()}
|
||||
result = json.loads(raw)
|
||||
tools = (result.get("result") or {}).get("tools") or []
|
||||
return {
|
||||
"endpoint": endpoint,
|
||||
"available": True,
|
||||
"status": "ok",
|
||||
"operation": "tools_list",
|
||||
"session_id": init.get("session_id") or response_headers.get("mcp-session-id"),
|
||||
"tool_count": len(tools),
|
||||
"tool_names": [tool.get("name") for tool in tools if isinstance(tool, dict) and tool.get("name")],
|
||||
"result": result.get("result") or result,
|
||||
"source": "official_public_mcp",
|
||||
}
|
||||
|
||||
|
||||
def schema() -> dict[str, Any]:
|
||||
return {
|
||||
"bridge": "vedastro_official_mcp_bridge",
|
||||
"role": "official_public_mcp_thin_bridge",
|
||||
"endpoint": DEFAULT_ENDPOINT,
|
||||
"operations": ["initialize", "tools_list"],
|
||||
"response_contract": [
|
||||
"endpoint",
|
||||
"available",
|
||||
"status",
|
||||
"operation",
|
||||
"result",
|
||||
"source",
|
||||
],
|
||||
"boundaries": [
|
||||
"Use for official MCP reachability and tool discovery.",
|
||||
"Do not let raw MCP results directly override local score/dominant_label/payout_label.",
|
||||
"Promote only through explicit local contracts and tests.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _error_result(endpoint: str, operation: str, exc: Exception) -> dict[str, Any]:
|
||||
reason = str(exc)
|
||||
if isinstance(exc, urllib.error.HTTPError):
|
||||
reason = f"http_{exc.code}: {reason}"
|
||||
return {
|
||||
"endpoint": endpoint,
|
||||
"available": False,
|
||||
"status": "mcp_request_failed",
|
||||
"operation": operation,
|
||||
"reason": reason,
|
||||
"source": "official_public_mcp",
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Official public VedAstro MCP bridge")
|
||||
parser.add_argument("--print-schema", action="store_true")
|
||||
parser.add_argument("--endpoint", default=DEFAULT_ENDPOINT)
|
||||
parser.add_argument("--operation", choices=["initialize", "tools_list"], default="tools_list")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.print_schema:
|
||||
result = schema()
|
||||
else:
|
||||
try:
|
||||
if args.operation == "initialize":
|
||||
result = _initialize(args.endpoint)
|
||||
else:
|
||||
result = _tools_list(args.endpoint)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
result = _error_result(args.endpoint, args.operation, exc)
|
||||
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -29,6 +29,15 @@ ALLOWED_RECOMMENDED_PATHS = {
|
||||
"hybrid_local_plus_vedastro",
|
||||
}
|
||||
|
||||
ALLOWED_FASTEST_PATH_LANES = {
|
||||
"local_native_preferred",
|
||||
"official_mcp",
|
||||
"official_python_bridge",
|
||||
"rest_adapter",
|
||||
"hybrid_router",
|
||||
"external_evidence_only",
|
||||
}
|
||||
|
||||
|
||||
VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
{
|
||||
@@ -38,10 +47,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["transit_trigger", "dasha", "narayana_dasha", "vedastro_service_adapter.range_scan"],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "hybrid_local_plus_vedastro",
|
||||
"fastest_path_lane": "rest_adapter",
|
||||
"priority": "P0",
|
||||
"license_boundary": "external_service_or_clean_room_only",
|
||||
"adjudicator_use": "oracle_only",
|
||||
"gap_notes": "Local timing modules exist, but a high-frequency day/hour event graph is not yet a local productized radar.",
|
||||
"route_notes": "Use official REST adapter first for range scans; keep local adjudicator as the reasoning layer.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Ayanamsa Selection",
|
||||
@@ -50,10 +61,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["ayanamsa_utils", "ephemeris_adapter_contract"],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "hybrid_local_plus_vedastro",
|
||||
"fastest_path_lane": "hybrid_router",
|
||||
"priority": "P0",
|
||||
"license_boundary": "external_service_or_clean_room_only",
|
||||
"adjudicator_use": "oracle_only",
|
||||
"gap_notes": "Local Lahiri path is usable, but broad ayanamsa parity and public comparison artifacts remain incomplete.",
|
||||
"route_notes": "Prefer local ayanamsa controls for production and use VedAstro only as parity/oracle evidence.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "D1-D60 Divisional Charts",
|
||||
@@ -62,10 +75,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["varga", "varga_full", "shodasavarga", "divisional_charts_extended"],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "local_native",
|
||||
"fastest_path_lane": "local_native_preferred",
|
||||
"priority": "P0",
|
||||
"license_boundary": "local_native_or_clean_room",
|
||||
"adjudicator_use": "primary",
|
||||
"gap_notes": "Local varga coverage is strong; keep VedAstro/PyJHora as benchmark evidence rather than replacing local math.",
|
||||
"route_notes": "Do not pay external-call cost here; local engine is already the primary route.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Ashtakavarga",
|
||||
@@ -74,10 +89,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["ashtakavarga", "ashtakavarga_pav", "ashtakavarga_sodhita", "finance_ashtakavarga_bridge"],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "hybrid_local_plus_vedastro",
|
||||
"fastest_path_lane": "official_python_bridge",
|
||||
"priority": "P0",
|
||||
"license_boundary": "local_native_or_external_oracle",
|
||||
"adjudicator_use": "secondary",
|
||||
"gap_notes": "SAV/BAV is in production; PAV/Sodhita/Kakshya bridges need continued regression before being dominant labels.",
|
||||
"route_notes": "Use the Python bridge for broad calculator access while local strict workflows stay authoritative.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Shadbala",
|
||||
@@ -86,10 +103,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["shadbala", "shadbala_advanced", "shadbala_component_cap", "oracle_shadbala_queue"],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "hybrid_local_plus_vedastro",
|
||||
"fastest_path_lane": "official_python_bridge",
|
||||
"priority": "P0",
|
||||
"license_boundary": "local_native_or_external_oracle",
|
||||
"adjudicator_use": "secondary",
|
||||
"gap_notes": "Local component-aware cap exists; absolute external oracle closure is still incomplete.",
|
||||
"route_notes": "Broad strength calculators are easiest through the Python bridge, then folded back as oracle evidence.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Jaimini / Chara Dasha",
|
||||
@@ -98,10 +117,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["jaimini.py", "AK", "DK", "UL", "Chara Dasha", "Jaimini marriage bridge v1"],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "local_native",
|
||||
"fastest_path_lane": "local_native_preferred",
|
||||
"priority": "P0",
|
||||
"license_boundary": "local_native_mit_attribution_for_reused_parts",
|
||||
"adjudicator_use": "secondary",
|
||||
"gap_notes": "Core Jaimini exists; mission/career/marriage quality adjudicator folding is not yet exhaustive.",
|
||||
"route_notes": "Keep Jaimini native; external engines help only as spot-check evidence.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Synastry / Ashtakoot",
|
||||
@@ -110,10 +131,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["synastry.py", "ashtakoot.py", "36-point Ashtakoot", "16-factor compatibility"],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "local_native",
|
||||
"fastest_path_lane": "local_native_preferred",
|
||||
"priority": "P0",
|
||||
"license_boundary": "local_native_mit_attribution_for_reused_parts",
|
||||
"adjudicator_use": "secondary",
|
||||
"gap_notes": "Matching modules exist and API-backed; relationship adjudicator still needs a formal bridge.",
|
||||
"route_notes": "Local matching is already stronger than a thin external call unless you need an oracle comparison.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Tajika Annual",
|
||||
@@ -122,10 +145,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["tajika.py", "varshaphala.py", "sahams", "solar_return"],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "hybrid_local_plus_vedastro",
|
||||
"fastest_path_lane": "official_python_bridge",
|
||||
"priority": "P0",
|
||||
"license_boundary": "local_native_or_external_oracle",
|
||||
"adjudicator_use": "secondary",
|
||||
"gap_notes": "Annual modules exist; yearly career/wealth/month windows need stronger strict-workflow integration.",
|
||||
"route_notes": "Use Python bridge for breadth, but keep year-chart reasoning and labels local.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Prashna / Horary",
|
||||
@@ -134,10 +159,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["prashna.py", "kp_system.py", "upagraha_gulika_maandi", "sphuta_trisphuta_family"],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "local_native",
|
||||
"fastest_path_lane": "local_native_preferred",
|
||||
"priority": "P0",
|
||||
"license_boundary": "local_native_or_clean_room",
|
||||
"adjudicator_use": "secondary",
|
||||
"gap_notes": "Horary modules exist but are not yet a first-class question adjudicator route.",
|
||||
"route_notes": "No need to outsource core horary math while local modules already exist.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Report Rendering",
|
||||
@@ -146,22 +173,32 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["report_artifact API", "report_builder.py", "chart_renderer.py", "jyotish-app export"],
|
||||
"can_call_vedastro": False,
|
||||
"recommended_path": "new_local_impl",
|
||||
"fastest_path_lane": "local_native_preferred",
|
||||
"priority": "P0",
|
||||
"license_boundary": "local_native",
|
||||
"adjudicator_use": "not_used",
|
||||
"gap_notes": "HTML/PDF artifact path exists; polished SVG/PDF chart rendering and cloud-scale report production are not finished.",
|
||||
"route_notes": "Rendering is a local product concern, not a VedAstro dependency.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "MCP / API Surface",
|
||||
"category": "service_surface",
|
||||
"domains": ["event", "relationship", "wealth", "career"],
|
||||
"local_assets": ["mcp_server.py", "jyotish_api_server.py", "strict workflows", "vedastro_service_adapter.py"],
|
||||
"local_assets": [
|
||||
"mcp_server.py",
|
||||
"jyotish_api_server.py",
|
||||
"strict workflows",
|
||||
"vedastro_service_adapter.py",
|
||||
"vedastro_official_mcp_bridge.py",
|
||||
],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "hybrid_local_plus_vedastro",
|
||||
"fastest_path_lane": "official_mcp",
|
||||
"priority": "P0",
|
||||
"license_boundary": "external_service_or_local_native",
|
||||
"adjudicator_use": "primary",
|
||||
"gap_notes": "Local API/MCP surfaces exist; VedAstro live adapter needs endpoint-backed smoke tests and provenance capture.",
|
||||
"gap_notes": "Local API/MCP surfaces exist and the official public MCP bridge is live; REST adapter official endpoint smoke still depends on configured endpoint-backed execution.",
|
||||
"route_notes": "If official MCP is available, that is the fastest direct agent path; otherwise fall back to local REST adapter.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Numerology / Non-Jyotish Tools",
|
||||
@@ -170,10 +207,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": [],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "external_evidence_only",
|
||||
"fastest_path_lane": "external_evidence_only",
|
||||
"priority": "P2",
|
||||
"license_boundary": "external_service_only",
|
||||
"adjudicator_use": "not_used",
|
||||
"gap_notes": "Adjacent product feature; not required for Jyotish adjudicator depth.",
|
||||
"route_notes": "Do not implement locally unless it becomes product-critical.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Birth Time ML / Rectification Assistant",
|
||||
@@ -182,10 +221,12 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"local_assets": ["birth_time_rectifier.py", "rectification_gate", "jyotish-app rectification"],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "hybrid_local_plus_vedastro",
|
||||
"fastest_path_lane": "rest_adapter",
|
||||
"priority": "P1",
|
||||
"license_boundary": "external_service_or_local_native",
|
||||
"adjudicator_use": "secondary",
|
||||
"gap_notes": "Local rectification exists; ML parity with VedAstro-style service behavior is not established.",
|
||||
"route_notes": "Use VedAstro externally only as supporting evidence; local rectification gate stays in control.",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -232,10 +273,12 @@ def _build_row(seed: dict[str, Any], audit: dict[str, Any]) -> dict[str, Any]:
|
||||
"local_assets": list(seed["local_assets"]),
|
||||
"can_call_vedastro": bool(seed["can_call_vedastro"]),
|
||||
"recommended_path": recommended_path,
|
||||
"fastest_path_lane": seed["fastest_path_lane"],
|
||||
"priority": seed["priority"],
|
||||
"license_boundary": seed["license_boundary"],
|
||||
"adjudicator_use": seed["adjudicator_use"],
|
||||
"gap_notes": seed["gap_notes"],
|
||||
"route_notes": seed["route_notes"],
|
||||
}
|
||||
|
||||
|
||||
@@ -246,10 +289,12 @@ def build_matrix(audit: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
|
||||
status_counts: dict[str, int] = {}
|
||||
path_counts: dict[str, int] = {}
|
||||
lane_counts: dict[str, int] = {}
|
||||
priority_counts: dict[str, int] = {}
|
||||
for row in rows:
|
||||
status_counts[row["local_status"]] = status_counts.get(row["local_status"], 0) + 1
|
||||
path_counts[row["recommended_path"]] = path_counts.get(row["recommended_path"], 0) + 1
|
||||
lane_counts[row["fastest_path_lane"]] = lane_counts.get(row["fastest_path_lane"], 0) + 1
|
||||
priority_counts[row["priority"]] = priority_counts.get(row["priority"], 0) + 1
|
||||
|
||||
return {
|
||||
@@ -261,6 +306,7 @@ def build_matrix(audit: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
"p0_count": priority_counts.get("P0", 0),
|
||||
"status_counts": status_counts,
|
||||
"recommended_path_counts": path_counts,
|
||||
"fastest_path_lane_counts": lane_counts,
|
||||
"local_registry_technique_count": audit.get("technique_count"),
|
||||
},
|
||||
"boundary": {
|
||||
@@ -290,16 +336,17 @@ def render_markdown(matrix: dict[str, Any]) -> str:
|
||||
"",
|
||||
f"- Local status counts: `{json.dumps(summary['status_counts'], ensure_ascii=False, sort_keys=True)}`",
|
||||
f"- Recommended path counts: `{json.dumps(summary['recommended_path_counts'], ensure_ascii=False, sort_keys=True)}`",
|
||||
f"- Fastest path lane counts: `{json.dumps(summary['fastest_path_lane_counts'], ensure_ascii=False, sort_keys=True)}`",
|
||||
"",
|
||||
"## Matrix",
|
||||
"",
|
||||
"| VedAstro capability | Category | Local status | Path | Priority | Adjudicator use | Local assets | Gap notes |",
|
||||
"|---|---|---:|---|---:|---|---|---|",
|
||||
"| VedAstro capability | Category | Local status | Path | Fastest lane | Priority | Adjudicator use | Local assets | Gap notes |",
|
||||
"|---|---|---:|---|---|---:|---|---|---|",
|
||||
]
|
||||
for row in matrix["rows"]:
|
||||
assets = ", ".join(row["local_assets"]) if row["local_assets"] else "-"
|
||||
lines.append(
|
||||
"| {vedastro_capability} | {category} | {local_status} | {recommended_path} | {priority} | {adjudicator_use} | {assets} | {gap_notes} |".format(
|
||||
"| {vedastro_capability} | {category} | {local_status} | {recommended_path} | {fastest_path_lane} | {priority} | {adjudicator_use} | {assets} | {gap_notes} |".format(
|
||||
assets=assets,
|
||||
**row,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,480 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Thin bridge for the official VedAstro Python package.
|
||||
|
||||
This bridge exists to accelerate broad method coverage through the official
|
||||
Python SDK while keeping the local adjudicator and REST adapter boundaries
|
||||
separate. It stays deliberately thin:
|
||||
|
||||
- discover the real VedAstro runtime (current interpreter or local venv)
|
||||
- route generic method calls into the official Python surface
|
||||
- support a tiny typed-parameter contract for enums and Time/GeoLocation
|
||||
- fail in a controlled way when the package is unavailable
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PACKAGE_NAME = "VedAstro"
|
||||
MODULE_CANDIDATES = ("vedastro", "VedAstro")
|
||||
FORCE_UNAVAILABLE_ENV = "VEDASTRO_PYTHON_FORCE_UNAVAILABLE"
|
||||
STUB_RESULT_ENV = "VEDASTRO_PYTHON_BRIDGE_STUB_RESULT"
|
||||
PYTHON_BIN_ENV = "VEDASTRO_PYTHON_BIN"
|
||||
DEFAULT_VENV_PYTHONS = (
|
||||
ROOT / "venv_vedastro" / "bin" / "python3.11",
|
||||
ROOT / "venv_vedastro" / "bin" / "python3",
|
||||
ROOT / "venv_vedastro" / "bin" / "python",
|
||||
)
|
||||
|
||||
CHILD_RUNNER = r"""
|
||||
import contextlib
|
||||
import enum
|
||||
import importlib
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
|
||||
MODULE_CANDIDATES = ("vedastro", "VedAstro")
|
||||
|
||||
|
||||
def _offset_to_string(offset):
|
||||
if isinstance(offset, str):
|
||||
raw = offset.strip()
|
||||
if not raw:
|
||||
return "+00:00"
|
||||
if raw[0] in "+-" and ":" in raw:
|
||||
return raw
|
||||
try:
|
||||
offset = float(raw)
|
||||
except ValueError:
|
||||
return raw
|
||||
|
||||
if isinstance(offset, (int, float)):
|
||||
sign = "+" if offset >= 0 else "-"
|
||||
absolute = abs(float(offset))
|
||||
hours = int(absolute)
|
||||
minutes = int(round((absolute - hours) * 60))
|
||||
if minutes == 60:
|
||||
hours += 1
|
||||
minutes = 0
|
||||
return f"{sign}{hours:02d}:{minutes:02d}"
|
||||
|
||||
return "+00:00"
|
||||
|
||||
|
||||
def _import_module():
|
||||
for name in MODULE_CANDIDATES:
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
try:
|
||||
return name, importlib.import_module(name)
|
||||
except ModuleNotFoundError:
|
||||
continue
|
||||
return None, None
|
||||
|
||||
|
||||
def _coerce(module, value):
|
||||
if isinstance(value, list):
|
||||
return [_coerce(module, item) for item in value]
|
||||
if isinstance(value, dict):
|
||||
enum_name = value.get("__vedastro_enum__")
|
||||
if enum_name:
|
||||
enum_type = getattr(module, enum_name)
|
||||
return getattr(enum_type, value["value"])
|
||||
|
||||
type_name = value.get("__vedastro_type__")
|
||||
if type_name == "GeoLocation":
|
||||
return module.GeoLocation(
|
||||
value["location_name"],
|
||||
value["longitude"],
|
||||
value["latitude"],
|
||||
)
|
||||
if type_name == "Time":
|
||||
geolocation = _coerce(module, value["geolocation"]) if value.get("geolocation") else None
|
||||
if value.get("time_string") is not None:
|
||||
return module.Time(value["time_string"], geolocation)
|
||||
time_string = (
|
||||
f"{int(value['hour']):02d}:{int(value['minute']):02d} "
|
||||
f"{int(value['day']):02d}/{int(value['month']):02d}/{int(value['year'])} "
|
||||
f"{_offset_to_string(value.get('offset', '+00:00'))}"
|
||||
)
|
||||
return module.Time(time_string, geolocation)
|
||||
|
||||
if type_name:
|
||||
target = getattr(module, type_name)
|
||||
args = [_coerce(module, item) for item in value.get("args", [])]
|
||||
kwargs = {
|
||||
key: _coerce(module, item)
|
||||
for key, item in value.get("kwargs", {}).items()
|
||||
}
|
||||
if args or kwargs:
|
||||
return target(*args, **kwargs)
|
||||
payload = {
|
||||
key: _coerce(module, item)
|
||||
for key, item in value.items()
|
||||
if not key.startswith("__")
|
||||
}
|
||||
return target(**payload)
|
||||
|
||||
return {
|
||||
key: _coerce(module, item)
|
||||
for key, item in value.items()
|
||||
}
|
||||
return value
|
||||
|
||||
|
||||
def _serialize(value):
|
||||
if value is None or isinstance(value, (str, int, float, bool)):
|
||||
return value
|
||||
if isinstance(value, enum.Enum):
|
||||
return {
|
||||
"type": type(value).__name__,
|
||||
"name": value.name,
|
||||
"value": value.value,
|
||||
}
|
||||
if isinstance(value, (list, tuple, set)):
|
||||
return [_serialize(item) for item in value]
|
||||
if isinstance(value, dict):
|
||||
return {str(key): _serialize(item) for key, item in value.items()}
|
||||
if hasattr(value, "to_json"):
|
||||
try:
|
||||
payload = value.to_json()
|
||||
if isinstance(payload, str):
|
||||
try:
|
||||
return json.loads(payload)
|
||||
except json.JSONDecodeError:
|
||||
return payload
|
||||
return _serialize(payload)
|
||||
except Exception:
|
||||
pass
|
||||
if hasattr(value, "__dict__"):
|
||||
return {
|
||||
key: _serialize(item)
|
||||
for key, item in vars(value).items()
|
||||
if not key.startswith("_")
|
||||
}
|
||||
return str(value)
|
||||
|
||||
|
||||
def _resolve_callable(module, method_name):
|
||||
parts = [part for part in method_name.split(".") if part]
|
||||
if not parts:
|
||||
raise AttributeError("empty_method_name")
|
||||
if len(parts) == 1:
|
||||
return getattr(module.Calculate, parts[0])
|
||||
|
||||
current = module
|
||||
for part in parts:
|
||||
current = getattr(current, part)
|
||||
return current
|
||||
|
||||
|
||||
def main():
|
||||
method = sys.argv[1]
|
||||
params = json.loads(sys.argv[2])
|
||||
module_name, module = _import_module()
|
||||
if module is None:
|
||||
print(json.dumps({
|
||||
"available": False,
|
||||
"status": "python_package_not_installed",
|
||||
"method": method,
|
||||
"source": "vedastro_python_bridge_child",
|
||||
}))
|
||||
return
|
||||
|
||||
call_target = _resolve_callable(module, method)
|
||||
|
||||
args = []
|
||||
kwargs = {}
|
||||
if isinstance(params, dict) and ("args" in params or "kwargs" in params):
|
||||
args = [_coerce(module, item) for item in params.get("args", [])]
|
||||
kwargs = {
|
||||
key: _coerce(module, item)
|
||||
for key, item in params.get("kwargs", {}).items()
|
||||
}
|
||||
elif isinstance(params, dict):
|
||||
kwargs = {
|
||||
key: _coerce(module, item)
|
||||
for key, item in params.items()
|
||||
}
|
||||
elif isinstance(params, list):
|
||||
args = [_coerce(module, item) for item in params]
|
||||
elif params is not None:
|
||||
args = [_coerce(module, params)]
|
||||
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
result = call_target(*args, **kwargs)
|
||||
|
||||
print(json.dumps({
|
||||
"available": True,
|
||||
"status": "ok",
|
||||
"method": method,
|
||||
"module_name": module_name,
|
||||
"result": _serialize(result),
|
||||
"source": "vedastro_python_bridge",
|
||||
}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
"""
|
||||
|
||||
|
||||
def _package_available() -> bool:
|
||||
if os.environ.get(FORCE_UNAVAILABLE_ENV, "").strip().lower() in {"1", "true", "yes"}:
|
||||
return False
|
||||
return _discover_module_name() is not None
|
||||
|
||||
|
||||
def _discover_module_name() -> str | None:
|
||||
for candidate in MODULE_CANDIDATES:
|
||||
if importlib.util.find_spec(candidate) is not None:
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _candidate_python_bins() -> list[str]:
|
||||
bins: list[str] = []
|
||||
|
||||
configured = os.environ.get(PYTHON_BIN_ENV, "").strip()
|
||||
if configured:
|
||||
bins.append(configured)
|
||||
|
||||
for candidate in DEFAULT_VENV_PYTHONS:
|
||||
if candidate.exists():
|
||||
bins.append(str(candidate))
|
||||
|
||||
bins.append(sys.executable)
|
||||
|
||||
seen: set[str] = set()
|
||||
ordered: list[str] = []
|
||||
for item in bins:
|
||||
if item and item not in seen:
|
||||
ordered.append(item)
|
||||
seen.add(item)
|
||||
return ordered
|
||||
|
||||
|
||||
def _python_supports_vedastro(python_bin: str) -> bool:
|
||||
probe = (
|
||||
"import importlib.util; "
|
||||
"mods=('vedastro','VedAstro'); "
|
||||
"raise SystemExit(0 if any(importlib.util.find_spec(m) for m in mods) else 1)"
|
||||
)
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
[python_bin, "-c", probe],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=20,
|
||||
)
|
||||
except OSError:
|
||||
return False
|
||||
return completed.returncode == 0
|
||||
|
||||
|
||||
def _select_python_bin() -> str | None:
|
||||
for python_bin in _candidate_python_bins():
|
||||
if _python_supports_vedastro(python_bin):
|
||||
return python_bin
|
||||
return None
|
||||
|
||||
|
||||
def _call_via_child_python(python_bin: str, method: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||
child_env = os.environ.copy()
|
||||
completed = subprocess.run(
|
||||
[python_bin, "-c", CHILD_RUNNER, method, json.dumps(params, ensure_ascii=False)],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=180,
|
||||
env=child_env,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
return {
|
||||
"available": False,
|
||||
"status": "bridge_runtime_error",
|
||||
"method": method,
|
||||
"python_bin": python_bin,
|
||||
"stderr": (completed.stderr or "").strip(),
|
||||
"stdout_excerpt": (completed.stdout or "").strip()[:500],
|
||||
"source": "vedastro_python_bridge",
|
||||
}
|
||||
|
||||
try:
|
||||
payload = json.loads(completed.stdout)
|
||||
except json.JSONDecodeError:
|
||||
return {
|
||||
"available": False,
|
||||
"status": "bridge_invalid_json",
|
||||
"method": method,
|
||||
"python_bin": python_bin,
|
||||
"stdout_excerpt": (completed.stdout or "").strip()[:500],
|
||||
"stderr": (completed.stderr or "").strip()[:500],
|
||||
"source": "vedastro_python_bridge",
|
||||
}
|
||||
|
||||
payload["python_bin"] = python_bin
|
||||
return payload
|
||||
|
||||
|
||||
def schema() -> dict[str, Any]:
|
||||
return {
|
||||
"bridge": "vedastro_python_bridge",
|
||||
"package_name": PACKAGE_NAME,
|
||||
"module_candidates": list(MODULE_CANDIDATES),
|
||||
"intended_role": "python_sdk_bulk_calculation_bridge",
|
||||
"operations": ["call_method"],
|
||||
"request_contract": ["method", "params_json"],
|
||||
"typed_param_contract": {
|
||||
"enum": {"__vedastro_enum__": "PlanetName", "value": "Sun"},
|
||||
"geo": {
|
||||
"__vedastro_type__": "GeoLocation",
|
||||
"location_name": "REDACTED_PLACE",
|
||||
"longitude": 114.46,
|
||||
"latitude": 36.6,
|
||||
},
|
||||
"time": {
|
||||
"__vedastro_type__": "Time",
|
||||
"year": REDACTED_YEAR,
|
||||
"month": 4,
|
||||
"day": 17,
|
||||
"hour": 14,
|
||||
"minute": 49,
|
||||
"offset": 8,
|
||||
"geolocation": {
|
||||
"__vedastro_type__": "GeoLocation",
|
||||
"location_name": "REDACTED_PLACE",
|
||||
"longitude": 114.46,
|
||||
"latitude": 36.6,
|
||||
},
|
||||
},
|
||||
},
|
||||
"response_contract": ["available", "status", "method", "result", "source", "python_bin"],
|
||||
"boundaries": [
|
||||
"Generic method routing only; local adjudicator remains authoritative.",
|
||||
"Do not treat Python SDK output as score/dominant label override by default.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _missing_package_result(method: str) -> dict[str, Any]:
|
||||
return {
|
||||
"available": False,
|
||||
"status": "python_package_not_installed",
|
||||
"method": method,
|
||||
"reason": (
|
||||
f"Official Python package {PACKAGE_NAME} is not installed in the current runtime "
|
||||
"or detected project venv."
|
||||
),
|
||||
"source": "vedastro_python_bridge",
|
||||
}
|
||||
|
||||
|
||||
def call_method(method: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||
stub = os.environ.get(STUB_RESULT_ENV, "").strip()
|
||||
if stub:
|
||||
payload = json.loads(stub)
|
||||
return {
|
||||
"available": True,
|
||||
"status": "ok",
|
||||
"method": payload.get("method") or method,
|
||||
"result": payload.get("result"),
|
||||
"source": "stubbed_bridge_result",
|
||||
}
|
||||
|
||||
if os.environ.get(FORCE_UNAVAILABLE_ENV, "").strip().lower() in {"1", "true", "yes"}:
|
||||
return _missing_package_result(method)
|
||||
|
||||
python_bin = _select_python_bin()
|
||||
if not python_bin:
|
||||
return _missing_package_result(method)
|
||||
return _call_via_child_python(python_bin, method, params)
|
||||
|
||||
|
||||
def call_high_value(method_key: str, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
if method_key == "event_tag_catalog":
|
||||
return call_method("GetAllEventDataGroupedByTag", {})
|
||||
|
||||
if method_key == "planet_longitude":
|
||||
planet = payload["planet"]
|
||||
time = payload["time"]
|
||||
return call_method(
|
||||
"PlanetNirayanaLongitude",
|
||||
{
|
||||
"args": [
|
||||
{"__vedastro_enum__": "PlanetName", "value": planet},
|
||||
time,
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
if method_key == "vimshottari_snapshot":
|
||||
birth_time = payload["birth_time"]
|
||||
check_time = payload["check_time"]
|
||||
levels = int(payload.get("levels", 3))
|
||||
return call_method("DasaAtTime", {"args": [birth_time, check_time, levels]})
|
||||
|
||||
if method_key == "chara_dasha_snapshot":
|
||||
birth_time = payload["birth_time"]
|
||||
check_time = payload["check_time"]
|
||||
return call_method("GetCharaDasaAtTime", {"args": [birth_time, check_time]})
|
||||
|
||||
return {
|
||||
"available": False,
|
||||
"status": "unsupported_high_value_method",
|
||||
"method": method_key,
|
||||
"source": "vedastro_python_bridge",
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="VedAstro Python bridge")
|
||||
parser.add_argument("--print-schema", action="store_true")
|
||||
parser.add_argument("--method", default="")
|
||||
parser.add_argument("--params-json", default="{}")
|
||||
parser.add_argument("--high-value", default="")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.print_schema:
|
||||
result = schema()
|
||||
result["high_value_methods"] = {
|
||||
"event_tag_catalog": {
|
||||
"maps_to": "GetAllEventDataGroupedByTag",
|
||||
"request_contract": [],
|
||||
},
|
||||
"planet_longitude": {
|
||||
"maps_to": "PlanetNirayanaLongitude",
|
||||
"request_contract": ["planet", "time"],
|
||||
},
|
||||
"vimshottari_snapshot": {
|
||||
"maps_to": "DasaAtTime",
|
||||
"request_contract": ["birth_time", "check_time", "levels?"],
|
||||
},
|
||||
"chara_dasha_snapshot": {
|
||||
"maps_to": "GetCharaDasaAtTime",
|
||||
"request_contract": ["birth_time", "check_time"],
|
||||
},
|
||||
}
|
||||
elif args.high_value:
|
||||
params = json.loads(args.params_json or "{}")
|
||||
result = call_high_value(args.high_value, params)
|
||||
else:
|
||||
params = json.loads(args.params_json or "{}")
|
||||
result = call_method(args.method, params)
|
||||
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user