f83db2fac1
- add external validation reports and open-source comparison references - integrate Jaimini arudha/graha pada, enhanced argala, and additional synastry kutas - update skill docs and capability matrices - add smoke tests for open-source integrations
160 lines
5.8 KiB
Python
160 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
名人案例批量回归测试 v1.2
|
|
验证引擎排盘正确性 + 解盘结论匹配度
|
|
修复:v1.1 使用的 sign_index/longitude 字段引擎不存在,改用 sign 字段
|
|
"""
|
|
import json, sys, os, subprocess
|
|
from datetime import datetime
|
|
|
|
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
ENGINE = os.path.join(REPO_ROOT, "scripts", "jyotish_engine.py")
|
|
CASES_FILE = os.path.join(REPO_ROOT, "tests", "celebrity_cases.json")
|
|
|
|
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
|
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
|
|
|
SIGNS_CN = ['白羊','金牛','双子','巨蟹','狮子','处女',
|
|
'天秤','天蝎','射手','摩羯','水瓶','双鱼']
|
|
|
|
EXALTATION = {'Sun':'Aries','Moon':'Taurus','Mars':'Capricorn','Mercury':'Virgo',
|
|
'Jupiter':'Cancer','Venus':'Pisces','Saturn':'Libra'}
|
|
DEBILITATION = {'Sun':'Libra','Moon':'Scorpio','Mars':'Cancer','Mercury':'Pisces',
|
|
'Jupiter':'Capricorn','Venus':'Virgo','Saturn':'Aries'}
|
|
|
|
|
|
def run_case(case):
|
|
cmd = [
|
|
sys.executable, ENGINE, "chart",
|
|
"--year", str(case["year"]), "--month", str(case["month"]),
|
|
"--day", str(case["day"]), "--hour", str(case["hour"]),
|
|
"--minute", str(case["minute"]), "--lat", str(case["lat"]),
|
|
"--lon", str(case["lon"]), "--tz", str(case["tz"]),
|
|
]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120, cwd=REPO_ROOT)
|
|
if result.returncode != 0:
|
|
return {"error": result.stderr[:500]}
|
|
try:
|
|
return json.loads(result.stdout)
|
|
except json.JSONDecodeError:
|
|
return {"error": f"JSON parse error: {result.stdout[:200]}"}
|
|
|
|
|
|
def get_sign_cn(sign_name):
|
|
return SIGNS_CN[SIGNS.index(sign_name)] if sign_name in SIGNS else sign_name
|
|
|
|
|
|
def get_sign_state(planet_name, sign_name):
|
|
"""检查行星庙旺落陷,返回格式化字符串"""
|
|
if sign_name == EXALTATION.get(planet_name, ''):
|
|
return '擢升'
|
|
if sign_name == DEBILITATION.get(planet_name, ''):
|
|
return '落陷'
|
|
return ''
|
|
|
|
|
|
def validate_case(case, chart_data):
|
|
"""与已知数据做比对 — 使用引擎实际的 sign 字段"""
|
|
checks = []
|
|
|
|
# 上升比对
|
|
known_lagna = case.get("known_lagna", "")
|
|
if known_lagna:
|
|
actual_lagna = chart_data.get("ascendant", {}).get("sign", "?")
|
|
match = "✅" if actual_lagna == known_lagna else "❌"
|
|
checks.append({"type": "上升", "expected": known_lagna, "actual": actual_lagna, "pass": match == "✅"})
|
|
|
|
# 太阳比对
|
|
known_sun = case.get("known_sun_sign", "")
|
|
if known_sun:
|
|
sun_sign = chart_data.get("planets", {}).get("Sun", {}).get("sign", "?")
|
|
match = "✅" if sun_sign == known_sun else "❌"
|
|
state = get_sign_state("Sun", sun_sign)
|
|
checks.append({"type": "太阳", "expected": known_sun, "actual": sun_sign, "pass": match == "✅"})
|
|
|
|
# 月亮比对
|
|
known_moon = case.get("known_moon_sign", "")
|
|
if known_moon:
|
|
moon_sign = chart_data.get("planets", {}).get("Moon", {}).get("sign", "?")
|
|
match = "✅" if moon_sign == known_moon else "❌"
|
|
checks.append({"type": "月亮", "expected": known_moon, "actual": moon_sign, "pass": match == "✅"})
|
|
|
|
passed = sum(1 for c in checks if c["pass"])
|
|
total = max(len(checks), 1)
|
|
return checks, passed, total
|
|
|
|
|
|
def run_all():
|
|
with open(CASES_FILE, "r", encoding="utf-8") as f:
|
|
cases = json.load(f)
|
|
|
|
print(f"{'='*100}")
|
|
print(f" 印度占星Skill · 名人案例回归测试 v1.1")
|
|
print(f" 案例数: {len(cases)}")
|
|
print(f" 运行时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
|
print(f"{'='*100}")
|
|
|
|
results = []
|
|
total_known = 0
|
|
total_passed = 0
|
|
|
|
for i, case in enumerate(cases, 1):
|
|
name = case["name"]
|
|
rating = case.get("file_match_rating", "")
|
|
conclusion = case.get("summary_conclusion", "")
|
|
|
|
data = run_case(case)
|
|
|
|
if "error" in data:
|
|
print(f"\n[{i:>2}/{len(cases)}] {name:<22} ❌ 引擎错误")
|
|
results.append({"id": case["id"], "name": name, "status": "error"})
|
|
continue
|
|
|
|
checks, p, t = validate_case(case, data)
|
|
total_known += t
|
|
total_passed += p
|
|
|
|
# 符号
|
|
if checks:
|
|
all_pass = all(c["pass"] for c in checks)
|
|
status = "✅" if all_pass else "⚠️"
|
|
else:
|
|
status = "✅"
|
|
|
|
print(f"\n[{i:>2}/{len(cases)}] {name:<22} {status} 匹配率={rating or 'N/A':>4}")
|
|
for c in checks:
|
|
state_str = get_sign_state(c["type"], c["actual"])
|
|
state_tag = f" ({state_str})" if state_str else ""
|
|
pass_str = "✅" if c["pass"] else "❌"
|
|
print(f" {c['type']}:预期{get_sign_cn(c['expected'])} 实际{get_sign_cn(c['actual'])}{state_tag} {pass_str}")
|
|
if conclusion:
|
|
print(f" 关键发现: {conclusion[:50]}")
|
|
|
|
# 汇总
|
|
pass_rate = f"{total_passed/total_known*100:.0f}%" if total_known > 0 else "N/A"
|
|
print(f"\n{'='*100}")
|
|
print(f" 汇总报告")
|
|
print(f"{'='*100}")
|
|
print(f" 总案例: {len(cases)}")
|
|
print(f" 已知比对项: {total_known} 项")
|
|
print(f" 通过: {total_passed} 项")
|
|
print(f" 匹配率: {pass_rate}")
|
|
|
|
report = {
|
|
"timestamp": datetime.now().isoformat(),
|
|
"total_cases": len(cases),
|
|
"total_known_checks": total_known,
|
|
"passed_checks": total_passed,
|
|
"pass_rate": pass_rate,
|
|
"results": results,
|
|
}
|
|
|
|
report_path = os.path.join(REPO_ROOT, "tests", "celebrity_regression_report.json")
|
|
with open(report_path, "w", encoding="utf-8") as f:
|
|
json.dump(report, f, ensure_ascii=False, indent=2)
|
|
print(f"\n 报告已保存: {report_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run_all()
|