Files
Jyotisha/scripts/ephemeris_backend_probe.py
2026-07-20 11:02:41 +08:00

128 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""Probe Jyotish ephemeris backend readiness without network access.
This script is intentionally read-only. It turns the ephemeris roadmap into a
repeatable check: what is available now, what is only a benchmark, and what is
not ready to replace the current Swiss Ephemeris path.
"""
from __future__ import annotations
import importlib.util
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
def _exists(*parts: str) -> bool:
return (ROOT.joinpath(*parts)).exists()
def _read_text(*parts: str) -> str:
path = ROOT.joinpath(*parts)
if not path.exists():
return ""
return path.read_text(encoding="utf-8", errors="ignore")
def _python_module_status(module_name: str) -> dict[str, Any]:
spec = importlib.util.find_spec(module_name)
return {
"module": module_name,
"available": spec is not None,
"origin": getattr(spec, "origin", None) if spec else None,
}
def build_probe() -> dict[str, Any]:
product_gap = _read_text("docs", "research", "product_gap_matrix_2026_06_22.md")
open_source_scan = _read_text("docs", "research", "open_source_scan_2026_06_22.md")
swisseph_python_module = _python_module_status("swisseph")
swisseph_python_available = swisseph_python_module["available"] and "import swisseph as swe" in _read_text(
"scripts", "jyotish_api_server.py"
)
vedastro_local = _exists("references", "open_source_sources", "VedicAstro")
vedastro_scan = "VedAstro/VedAstro" in open_source_scan or "VedAstro/VedAstro" in product_gap
external_benchmark_scan = "naturalstupid/PyJHora" in open_source_scan or "PyJHora" in product_gap
xalen_scan = "xalen-ephemeris" in product_gap
candidate_backends: dict[str, dict[str, Any]] = {
"swisseph_python": {
"available": bool(swisseph_python_available),
"replacement_readiness": "primary",
"license_posture": "current production path; keep Swiss Ephemeris license/data boundary explicit",
"evidence": [
"scripts/jyotish_api_server.py imports swisseph",
swisseph_python_module,
],
"next_step": "keep as canonical longitude source until another backend passes parity cases",
},
"xalen_ephemeris": {
"available": False,
"replacement_readiness": "spike_only",
"license_posture": "Apache-2.0 candidate from vedika-io/xalen-ephemeris; no local adapter yet",
"evidence": [
"tracked in product gap matrix" if xalen_scan else "not found in local docs",
"no local references/open_source_sources/xalen mirror detected",
],
"next_step": "add an isolated Rust/CLI parity spike before exposing as selectable runtime backend",
},
"vedastro": {
"available": bool(vedastro_local or vedastro_scan),
"replacement_readiness": "service_adapter_candidate",
"license_posture": "MIT product/API benchmark; C# stack must stay behind a service/API boundary if reused",
"evidence": [
"references/open_source_sources/VedicAstro" if vedastro_local else "no local mirror detected by probe",
"tracked in open-source scan" if vedastro_scan else "not tracked in current scan",
],
"next_step": "reuse API/OpenAPI/product lessons; implement a service-boundary adapter contract before any runtime exposure",
},
"external_benchmark_benchmark": {
"available": bool(external_benchmark_scan or _python_module_status("jhora")["available"]),
"replacement_readiness": "benchmark_only",
"license_posture": "AGPL benchmark/oracle only unless downstream license posture changes",
"evidence": [
"tracked as naturalstupid/PyJHora in open-source scan" if external_benchmark_scan else "not tracked in current scan",
_python_module_status("jhora"),
],
"next_step": "use public examples and expected outputs for parity tests; do not copy AGPL implementation code",
},
}
valid = candidate_backends["swisseph_python"]["available"] or candidate_backends["swisseph_wasm"]["available"]
return {
"valid": bool(valid),
"candidate_backends": candidate_backends,
"license_posture": {
"direct_reuse": ["MIT", "Apache-2.0"],
"benchmark_only": ["AGPL", "GPL", "unknown/no-license"],
"current_boundary": "Swiss Ephemeris remains the production ephemeris source until parity and license checks pass.",
},
"replacement_readiness": {
"primary": ["swisseph_python"],
"spike_only": ["xalen_ephemeris"],
"service_adapter_candidate": ["vedastro"],
"benchmark_only": ["external_benchmark_benchmark"],
},
"recommendation": [
"Do not replace SwissEph in core calculations yet.",
"Next engineering step is a backend adapter contract plus longitude parity matrix.",
"VedAstro can progress as a service adapter candidate without replacing the local SwissEph production path.",
"Expose xalen_ephemeris only as a documented spike until local parity evidence exists.",
],
}
def main() -> int:
result = build_probe()
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
return 0 if result["valid"] else 1
if __name__ == "__main__":
raise SystemExit(main())