Add Jyotish registry audit and CI foundation
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"version": "v1",
|
||||
"cases": [
|
||||
{
|
||||
"id": "smoke_full_reading_beijing_1990",
|
||||
"description": "A deterministic smoke case that ensures full-reading exposes the engineering-critical modules added in v6.0.2.",
|
||||
"input": {
|
||||
"year": 1990,
|
||||
"month": 1,
|
||||
"day": 1,
|
||||
"hour": 12,
|
||||
"minute": 0,
|
||||
"lat": 39.9,
|
||||
"lon": 116.4,
|
||||
"tz": 8,
|
||||
"today": "2026-06-03"
|
||||
},
|
||||
"expected_output_paths": [
|
||||
"modules.chart",
|
||||
"modules.dasha",
|
||||
"modules.special_lagnas.Arudha_Lagna",
|
||||
"modules.special_lagnas.A10_Karma_Pada",
|
||||
"modules.special_lagnas.Upapada_Lagna",
|
||||
"modules.varga_full.D9_Navamsa",
|
||||
"modules.vargottama",
|
||||
"modules.pushkara",
|
||||
"modules.dasha_sandhi",
|
||||
"modules.shadbala",
|
||||
"modules.ashtakavarga",
|
||||
"modules.validation"
|
||||
],
|
||||
"max_errors": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Golden case regression runner for Jyotish full-reading output contracts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any, Dict, List
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ENGINE = os.path.join(ROOT_DIR, "scripts", "jyotish_engine.py")
|
||||
DEFAULT_CASES = os.path.join(ROOT_DIR, "tests", "golden", "golden_cases.json")
|
||||
|
||||
|
||||
def get_path(data: Dict[str, Any], dotted_path: str) -> Any:
|
||||
cur: Any = data
|
||||
for part in dotted_path.split("."):
|
||||
if not isinstance(cur, dict) or part not in cur:
|
||||
raise KeyError(dotted_path)
|
||||
cur = cur[part]
|
||||
return cur
|
||||
|
||||
|
||||
def run_full_reading(py: str, case: Dict[str, Any]) -> Dict[str, Any]:
|
||||
inp = case["input"]
|
||||
cmd = [
|
||||
py,
|
||||
ENGINE,
|
||||
"full-reading",
|
||||
"--year", str(inp["year"]),
|
||||
"--month", str(inp["month"]),
|
||||
"--day", str(inp["day"]),
|
||||
"--hour", str(inp["hour"]),
|
||||
"--minute", str(inp["minute"]),
|
||||
"--lat", str(inp["lat"]),
|
||||
"--lon", str(inp["lon"]),
|
||||
"--tz", str(inp["tz"]),
|
||||
]
|
||||
if inp.get("today"):
|
||||
cmd.extend(["--today", inp["today"]])
|
||||
out = subprocess.check_output(cmd, text=True)
|
||||
return json.loads(out)
|
||||
|
||||
|
||||
def run_cases(py: str, cases_path: str) -> Dict[str, Any]:
|
||||
with open(cases_path, "r", encoding="utf-8") as f:
|
||||
suite = json.load(f)
|
||||
results: List[Dict[str, Any]] = []
|
||||
for case in suite.get("cases", []):
|
||||
case_result = {"id": case.get("id"), "passed": True, "failures": []}
|
||||
try:
|
||||
data = run_full_reading(py, case)
|
||||
errors = data.get("errors", [])
|
||||
if len(errors) > case.get("max_errors", 0):
|
||||
case_result["passed"] = False
|
||||
case_result["failures"].append(f"too many errors: {len(errors)} > {case.get('max_errors', 0)}; {errors[:5]}")
|
||||
for path in case.get("expected_output_paths", []):
|
||||
try:
|
||||
value = get_path(data, path)
|
||||
if value is None or value == {} or value == []:
|
||||
case_result["passed"] = False
|
||||
case_result["failures"].append(f"empty output path: {path}")
|
||||
except KeyError:
|
||||
case_result["passed"] = False
|
||||
case_result["failures"].append(f"missing output path: {path}")
|
||||
except Exception as exc:
|
||||
case_result["passed"] = False
|
||||
case_result["failures"].append(str(exc))
|
||||
results.append(case_result)
|
||||
failed = [r for r in results if not r["passed"]]
|
||||
return {"valid": len(failed) == 0, "total": len(results), "failed": len(failed), "results": results}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Run Jyotish golden case regressions")
|
||||
parser.add_argument("--python", default=sys.executable, help="Python executable used to run jyotish_engine.py")
|
||||
parser.add_argument("--cases", default=DEFAULT_CASES, help="Path to golden_cases.json")
|
||||
args = parser.parse_args()
|
||||
result = run_cases(args.python, args.cases)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0 if result["valid"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+2
-2
@@ -247,8 +247,8 @@ class TestYogaDetection(unittest.TestCase):
|
||||
|
||||
def test_no_yoga_false_positive(self):
|
||||
"""测试不满足条件时不产生 Yoga"""
|
||||
# 行星分散,不满足任何 Yoga
|
||||
args = self._make_yoga_args('Aries', 'Sun:Taurus:2,Moon:Gemini:3')
|
||||
# 行星分散,且避免落入 Sun/Moon 两侧或扩展型简化 Yoga 条件
|
||||
args = self._make_yoga_args('Aries', 'Sun:Taurus:2,Mars:Leo:5')
|
||||
result = cmd_yoga(args)
|
||||
yogas = result.get('yogas', [])
|
||||
self.assertEqual(len(yogas), 0, "Should not detect any Yoga with scattered planets")
|
||||
|
||||
Reference in New Issue
Block a user