Clarify benchmark boundaries and improve annual workflow surfaces
This commit is contained in:
@@ -41,7 +41,7 @@ def tz_to_float(tz_str):
|
||||
minutes = int(parts[1]) if len(parts) > 1 else 0
|
||||
return sign * (hours + minutes / 60.0)
|
||||
|
||||
def _planet_dict_from_pyjhora_positions(positions, asc_sign):
|
||||
def _planet_dict_from_external_benchmark_positions(positions, asc_sign):
|
||||
"""Convert PyJHora planet positions to the skill validation schema."""
|
||||
names = {
|
||||
0: "Sun",
|
||||
@@ -123,17 +123,17 @@ def compute_yogas(chart):
|
||||
"d1": {
|
||||
"ascendant": SIGNS[d1_asc_sign],
|
||||
"ascendant_degree": d1_asc_degree,
|
||||
"planets": _planet_dict_from_pyjhora_positions(d1_positions, d1_asc_sign),
|
||||
"planets": _planet_dict_from_external_benchmark_positions(d1_positions, d1_asc_sign),
|
||||
},
|
||||
"d9": {
|
||||
"ascendant": SIGNS[d9_asc_sign],
|
||||
"ascendant_degree": d9_asc_degree,
|
||||
"planets": _planet_dict_from_pyjhora_positions(d9_positions, d9_asc_sign),
|
||||
"planets": _planet_dict_from_external_benchmark_positions(d9_positions, d9_asc_sign),
|
||||
},
|
||||
"d60": {
|
||||
"ascendant": SIGNS[d60_asc_sign],
|
||||
"ascendant_degree": d60_asc_degree,
|
||||
"planets": _planet_dict_from_pyjhora_positions(d60_positions, d60_asc_sign),
|
||||
"planets": _planet_dict_from_external_benchmark_positions(d60_positions, d60_asc_sign),
|
||||
},
|
||||
"panchanga": {
|
||||
"tithi": tithi_no,
|
||||
|
||||
@@ -101,8 +101,8 @@ def extract_skill_names(rules: list[dict]) -> tuple[set[str], dict[str, list[str
|
||||
return keys, reverse
|
||||
|
||||
|
||||
def extract_pyjhora_names(pyjhora_yoga_file: Path) -> set[str]:
|
||||
content = pyjhora_yoga_file.read_text(encoding="utf-8", errors="ignore")
|
||||
def extract_external_benchmark_names(external_benchmark_yoga_file: Path) -> set[str]:
|
||||
content = external_benchmark_yoga_file.read_text(encoding="utf-8", errors="ignore")
|
||||
funcs = re.findall(r"^def ([a-zA-Z_][a-zA-Z0-9_]*)\(", content, re.MULTILINE)
|
||||
names: set[str] = set()
|
||||
for fn in funcs:
|
||||
@@ -126,7 +126,7 @@ def extract_pyjhora_names(pyjhora_yoga_file: Path) -> set[str]:
|
||||
return names
|
||||
|
||||
|
||||
def find_pyjhora_yoga_file(explicit: str | None = None) -> Path | None:
|
||||
def find_external_benchmark_yoga_file(explicit: str | None = None) -> Path | None:
|
||||
if explicit:
|
||||
p = Path(explicit).expanduser().resolve()
|
||||
return p if p.exists() else None
|
||||
@@ -176,12 +176,12 @@ def main() -> int:
|
||||
|
||||
skill_keys, skill_reverse = extract_skill_names(rules)
|
||||
|
||||
pyjhora_file = find_pyjhora_yoga_file(args.pyjhora_yoga_file)
|
||||
external_benchmark_file = find_external_benchmark_yoga_file(args.external_benchmark_yoga_file)
|
||||
py_names: set[str] = set()
|
||||
missing: list[str] = []
|
||||
coverage_pct = None
|
||||
if pyjhora_file:
|
||||
py_names = extract_pyjhora_names(pyjhora_file)
|
||||
if external_benchmark_file:
|
||||
py_names = extract_external_benchmark_names(external_benchmark_file)
|
||||
missing = sorted([name for name in py_names if not covered(name, skill_keys)])
|
||||
coverage_pct = round((len(py_names) - len(missing)) / len(py_names) * 100, 2) if py_names else None
|
||||
|
||||
@@ -197,11 +197,11 @@ def main() -> int:
|
||||
"categories": dict(categories.most_common()),
|
||||
"strength_values": dict(strength_values),
|
||||
"skill_normalized_name_keys": len(skill_keys),
|
||||
"pyjhora_yoga_file": str(pyjhora_file) if pyjhora_file else None,
|
||||
"pyjhora_unique_yoga_names": len(py_names) if pyjhora_file else None,
|
||||
"matched_unique_yoga_names": (len(py_names) - len(missing)) if pyjhora_file else None,
|
||||
"external_benchmark_yoga_file": str(external_benchmark_file) if external_benchmark_file else None,
|
||||
"external_benchmark_unique_yoga_names": len(py_names) if external_benchmark_file else None,
|
||||
"matched_unique_yoga_names": (len(py_names) - len(missing)) if external_benchmark_file else None,
|
||||
"coverage_pct": coverage_pct,
|
||||
"missing_count": len(missing) if pyjhora_file else None,
|
||||
"missing_count": len(missing) if external_benchmark_file else None,
|
||||
"missing": missing,
|
||||
}
|
||||
|
||||
@@ -221,11 +221,11 @@ def main() -> int:
|
||||
print(f" {cat:16s} {count:3d}")
|
||||
|
||||
print("\nPyJHora 对比:")
|
||||
if not pyjhora_file:
|
||||
if not external_benchmark_file:
|
||||
print(" 未找到 PyJHora yoga.py;仅完成本地 JSON 统计。")
|
||||
print(" 可用 --pyjhora-yoga-file 指定路径。")
|
||||
else:
|
||||
print(f" yoga.py: {pyjhora_file}")
|
||||
print(f" yoga.py: {external_benchmark_file}")
|
||||
print(f" PyJHora 唯一 Yoga 名称: {len(py_names)}")
|
||||
print(f" 已匹配: {len(py_names) - len(missing)}")
|
||||
print(f" 疑似缺失: {len(missing)}")
|
||||
|
||||
@@ -74,7 +74,7 @@ CELEBRITY_CHARTS = [
|
||||
{"name": "Paramahansa Yogananda", "date": "1893-01-05", "time": "20:38", "tz": "+05:30", "lat": 27.0360, "lon": 88.2627, "city": "Gorakhpur, India"},
|
||||
]
|
||||
|
||||
def compute_pyjhora_yogas(chart):
|
||||
def compute_external_benchmark_yogas(chart):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[PYJHORA, HELPER],
|
||||
@@ -98,12 +98,12 @@ if __name__ == "__main__":
|
||||
}
|
||||
for chart in CELEBRITY_CHARTS:
|
||||
print(f" Computing {chart['name']}...", flush=True)
|
||||
yogas = compute_pyjhora_yogas(chart)
|
||||
yogas = compute_external_benchmark_yogas(chart)
|
||||
entry = dict(chart)
|
||||
entry["expected_yogas"] = yogas.get("yogas", [])
|
||||
if "context" in yogas:
|
||||
entry["context"] = yogas["context"]
|
||||
entry["pyjhora_raw"] = yogas
|
||||
entry["external_benchmark_raw"] = yogas
|
||||
output["charts"].append(entry)
|
||||
with open(outpath, "w") as f:
|
||||
json.dump(output, f, indent=2, ensure_ascii=False)
|
||||
|
||||
@@ -8,6 +8,7 @@ mkdir -p "$WB/references"
|
||||
mkdir -p "$WB/skills/jyotish-engine-modules"
|
||||
mkdir -p "$WB/skills/jyotish-full-reading-integration"
|
||||
|
||||
cp "$ROOT/AGENTS.md" "$WB/AGENTS.md"
|
||||
cp "$ROOT/SKILL.md" "$WB/SKILL.md"
|
||||
cp "$ROOT/references/technique_registry.json" "$WB/references/technique_registry.json"
|
||||
cp "$ROOT/references/quick-reference-guide.md" "$WB/references/quick-reference-guide.md"
|
||||
|
||||
@@ -353,7 +353,7 @@ def main():
|
||||
'chart': name,
|
||||
'rule_id': rid,
|
||||
'rule_name': rule_id_to_name.get(rid, '?'),
|
||||
'pyjhora_names': orig_names,
|
||||
'external_benchmark_names': orig_names,
|
||||
})
|
||||
|
||||
# ==== 输出报告 ====
|
||||
@@ -404,8 +404,8 @@ def main():
|
||||
"charts_tested": 60,
|
||||
"comparable_rules": len(comparable_rule_ids),
|
||||
"skill_total": total_skill_comp,
|
||||
"pyjhora_total": total_pyj_comp,
|
||||
"unmapped_pyjhora": total_unmapped_pyj,
|
||||
"external_benchmark_total": total_pyj_comp,
|
||||
"unmapped_external_benchmark": total_unmapped_pyj,
|
||||
"missing_mappings": {k: v for k, v in sorted(missing_mappings.items())},
|
||||
"agreements": total_agreements,
|
||||
"false_positives": total_false_positives,
|
||||
|
||||
@@ -175,7 +175,7 @@ def skill_detect_yogas(planets, asc):
|
||||
# ============================================================
|
||||
# PyJhora 接口封装
|
||||
# ============================================================
|
||||
def init_pyjhora():
|
||||
def init_external_benchmark():
|
||||
"""初始化 PyJhora,返回是否成功"""
|
||||
try:
|
||||
import jhora.horoscope.chart.yoga as py_yoga
|
||||
@@ -188,13 +188,13 @@ def init_pyjhora():
|
||||
return False
|
||||
|
||||
|
||||
def pyjhora_jd(year, month, day, hour_frac):
|
||||
def external_benchmark_jd(year, month, day, hour_frac):
|
||||
"""计算 Julian Day(与 PyJhora 一致)"""
|
||||
import swisseph as swe
|
||||
return swe.julday(year, month, day, hour_frac)
|
||||
|
||||
|
||||
def pyjhora_get_yogas(jd, lat, lon, tz, divisional_chart_factor=1):
|
||||
def external_benchmark_get_yogas(jd, lat, lon, tz, divisional_chart_factor=1):
|
||||
"""
|
||||
调用 PyJhora 获取 D1 宫盘的 Yoga 检测结果。
|
||||
返回:{yoga_function_name: {"name": ..., "desc": ..., "benefits": ...}}
|
||||
@@ -329,10 +329,10 @@ def _canonical_name(key: str) -> str:
|
||||
return CROSS_NAME_MAP.get(key, key)
|
||||
|
||||
|
||||
def get_pyjhora_yoga_keys(pyjhora_results: Dict[str, dict]) -> Set[str]:
|
||||
def get_external_benchmark_yoga_keys(external_benchmark_results: Dict[str, dict]) -> Set[str]:
|
||||
"""从 PyJhora 检测结果中提取归一化名称集合"""
|
||||
keys = set()
|
||||
for fname in pyjhora_results.keys():
|
||||
for fname in external_benchmark_results.keys():
|
||||
# 去掉 _from_jd_place 等后缀
|
||||
base = re.sub(r"_(from_jd_place|from_planet_positions|calculation|calc)$",
|
||||
"", fname)
|
||||
@@ -347,7 +347,7 @@ def get_pyjhora_yoga_keys(pyjhora_results: Dict[str, dict]) -> Set[str]:
|
||||
# ============================================================
|
||||
# 核心验证逻辑
|
||||
# ============================================================
|
||||
def validate_one_case(case: dict, run_pyjhora: bool = True) -> dict:
|
||||
def validate_one_case(case: dict, run_external_benchmark: bool = True) -> dict:
|
||||
"""
|
||||
验证单个测试用例。
|
||||
|
||||
@@ -363,10 +363,10 @@ def validate_one_case(case: dict, run_pyjhora: bool = True) -> dict:
|
||||
"birth": f"{case['year']}-{case['month']:02d}-{case['day']:02d} "
|
||||
f"{case.get('hour', 12):02d}:{case.get('minute', 0):02d}",
|
||||
"skill_yogas": [],
|
||||
"pyjhora_yogas": [],
|
||||
"external_benchmark_yogas": [],
|
||||
"matched": [],
|
||||
"skill_only": [], # false positive
|
||||
"pyjhora_only": [], # false negative
|
||||
"external_benchmark_only": [], # false negative
|
||||
"error": None,
|
||||
}
|
||||
|
||||
@@ -399,16 +399,16 @@ def validate_one_case(case: dict, run_pyjhora: bool = True) -> dict:
|
||||
return result
|
||||
|
||||
# --- PyJhora 检测 ---
|
||||
if run_pyjhora:
|
||||
if run_external_benchmark:
|
||||
try:
|
||||
jd_py = pyjhora_jd(year, month, day, hour + minute / 60.0)
|
||||
pyjhora_results = pyjhora_get_yogas(jd_py, lat, lon, tz,
|
||||
jd_py = external_benchmark_jd(year, month, day, hour + minute / 60.0)
|
||||
external_benchmark_results = external_benchmark_get_yogas(jd_py, lat, lon, tz,
|
||||
divisional_chart_factor=1)
|
||||
result['pyjhora_yogas'] = list(pyjhora_results.keys())
|
||||
result['pyjhora_count'] = len(pyjhora_results)
|
||||
result['pyjhora_details'] = []
|
||||
for fname, details in pyjhora_results.items():
|
||||
result['pyjhora_details'].append({
|
||||
result['external_benchmark_yogas'] = list(external_benchmark_results.keys())
|
||||
result['external_benchmark_count'] = len(external_benchmark_results)
|
||||
result['external_benchmark_details'] = []
|
||||
for fname, details in external_benchmark_results.items():
|
||||
result['external_benchmark_details'].append({
|
||||
'function': fname,
|
||||
'name': details.get('name', ''),
|
||||
'desc': details.get('desc', ''),
|
||||
@@ -419,11 +419,11 @@ def validate_one_case(case: dict, run_pyjhora: bool = True) -> dict:
|
||||
|
||||
# --- 对比 ---
|
||||
skill_keys = get_skill_yoga_keys(skill_yogas)
|
||||
pyjhora_keys = get_pyjhora_yoga_keys(pyjhora_results)
|
||||
external_benchmark_keys = get_external_benchmark_yoga_keys(external_benchmark_results)
|
||||
|
||||
result['matched'] = sorted(skill_keys & pyjhora_keys)
|
||||
result['skill_only'] = sorted(skill_keys - pyjhora_keys)
|
||||
result['pyjhora_only'] = sorted(pyjhora_keys - skill_keys)
|
||||
result['matched'] = sorted(skill_keys & external_benchmark_keys)
|
||||
result['skill_only'] = sorted(skill_keys - external_benchmark_keys)
|
||||
result['external_benchmark_only'] = sorted(external_benchmark_keys - skill_keys)
|
||||
|
||||
# --- 分类:功能性 Yoga vs 经典 Yoga ---
|
||||
cat_map = get_skill_rule_categories()
|
||||
@@ -439,17 +439,17 @@ def validate_one_case(case: dict, run_pyjhora: bool = True) -> dict:
|
||||
return result
|
||||
|
||||
|
||||
def run_validation(cases: List[dict], run_pyjhora: bool = True) -> dict:
|
||||
def run_validation(cases: List[dict], run_external_benchmark: bool = True) -> dict:
|
||||
"""运行批量验证"""
|
||||
report = {
|
||||
"total_cases": len(cases),
|
||||
"cases": [],
|
||||
"summary": {
|
||||
"total_skill_yogas": 0,
|
||||
"total_pyjhora_yogas": 0,
|
||||
"total_external_benchmark_yogas": 0,
|
||||
"total_matched": 0,
|
||||
"total_skill_only": 0,
|
||||
"total_pyjhora_only": 0,
|
||||
"total_external_benchmark_only": 0,
|
||||
"total_skill_only_functional": 0,
|
||||
"total_skill_only_classic": 0,
|
||||
}
|
||||
@@ -458,7 +458,7 @@ def run_validation(cases: List[dict], run_pyjhora: bool = True) -> dict:
|
||||
for case in cases:
|
||||
name = case.get('name', 'unknown')
|
||||
print(f"🔍 验证: {name} ({case['year']}-{case['month']:02d}-{case['day']:02d})")
|
||||
r = validate_one_case(case, run_pyjhora=run_pyjhora)
|
||||
r = validate_one_case(case, run_external_benchmark=run_external_benchmark)
|
||||
report['cases'].append(r)
|
||||
|
||||
if r['error']:
|
||||
@@ -466,21 +466,21 @@ def run_validation(cases: List[dict], run_pyjhora: bool = True) -> dict:
|
||||
continue
|
||||
|
||||
sc = r.get('skill_count', 0)
|
||||
pc = r.get('pyjhora_count', '?')
|
||||
pc = r.get('external_benchmark_count', '?')
|
||||
func_n = len(r.get('skill_only_functional', []))
|
||||
classic_n = len(r.get('skill_only_classic', []))
|
||||
print(f" Skill: {sc} | PyJhora: {pc}")
|
||||
print(f" 匹配: {len(r['matched'])} | "
|
||||
f"Skill独有: {len(r['skill_only'])} (功能性{func_n}, 经典{classic_n}) | "
|
||||
f"PyJhora独有: {len(r['pyjhora_only'])}")
|
||||
f"PyJhora独有: {len(r['external_benchmark_only'])}")
|
||||
|
||||
report['summary']['total_skill_yogas'] += sc
|
||||
if isinstance(pc, int):
|
||||
report['summary']['total_pyjhora_yogas'] += pc
|
||||
report['summary']['total_external_benchmark_yogas'] += pc
|
||||
report['summary']['total_matched'] += len(r['matched'])
|
||||
report['summary']['total_skill_only'] += len(r['skill_only'])
|
||||
if isinstance(pc, int):
|
||||
report['summary']['total_pyjhora_only'] += len(r['pyjhora_only'])
|
||||
report['summary']['total_external_benchmark_only'] += len(r['external_benchmark_only'])
|
||||
report['summary']['total_skill_only_functional'] += func_n
|
||||
report['summary']['total_skill_only_classic'] += classic_n
|
||||
|
||||
@@ -535,27 +535,27 @@ def print_report(report: dict):
|
||||
|
||||
summary = report['summary']
|
||||
total_skill = summary['total_skill_yogas']
|
||||
total_pyjhora = summary['total_pyjhora_yogas']
|
||||
total_external_benchmark = summary['total_external_benchmark_yogas']
|
||||
matched = summary['total_matched']
|
||||
skill_only = summary['total_skill_only']
|
||||
pyjhora_only = summary['total_pyjhora_only']
|
||||
external_benchmark_only = summary['total_external_benchmark_only']
|
||||
func_only = summary.get('total_skill_only_functional', 0)
|
||||
classic_only = summary.get('total_skill_only_classic', 0)
|
||||
|
||||
print(f"\n📊 汇总:")
|
||||
print(f" 测试用例数: {report['total_cases']}")
|
||||
print(f" Skill 检测总数: {total_skill}")
|
||||
print(f" PyJhora 检测总数: {total_pyjhora}")
|
||||
print(f" PyJhora 检测总数: {total_external_benchmark}")
|
||||
print(f" 匹配总数: {matched}")
|
||||
print(f" Skill 独有: {skill_only} (功能性{func_only}, 经典{classic_only})")
|
||||
print(f" PyJhora 独有 (skill 缺失): {pyjhora_only}")
|
||||
print(f" PyJhora 独有 (skill 缺失): {external_benchmark_only}")
|
||||
|
||||
if total_skill > 0:
|
||||
precision = matched / total_skill * 100
|
||||
print(f"\n 总体精确率 (Precision): {precision:.1f}%")
|
||||
|
||||
if total_pyjhora > 0:
|
||||
recall = matched / total_pyjhora * 100
|
||||
if total_external_benchmark > 0:
|
||||
recall = matched / total_external_benchmark * 100
|
||||
print(f" 总体召回率 (Recall): {recall:.1f}%")
|
||||
|
||||
# --- 核心经典 Yoga 准确率(排除 skill 特色功能性 Yoga)---
|
||||
@@ -564,21 +564,21 @@ def print_report(report: dict):
|
||||
classic_precision = matched / classic_skill_total * 100
|
||||
print(f"\n 🎯 核心经典 Yoga 精确率: {classic_precision:.1f}%")
|
||||
print(f" (排除 {func_only} 条功能性 Yoga 后: {matched}/{classic_skill_total})")
|
||||
if total_pyjhora > 0:
|
||||
classic_recall = matched / total_pyjhora * 100
|
||||
if total_external_benchmark > 0:
|
||||
classic_recall = matched / total_external_benchmark * 100
|
||||
print(f" 🎯 核心经典 Yoga 召回率: {classic_recall:.1f}%")
|
||||
|
||||
# --- 全局不匹配统计 ---
|
||||
print(f"\n🔍 全局不匹配分析:")
|
||||
all_skill_classic = set()
|
||||
all_skill_func = set()
|
||||
all_pyjhora_missing = set()
|
||||
all_external_benchmark_missing = set()
|
||||
for r in report['cases']:
|
||||
if r.get('error'):
|
||||
continue
|
||||
all_skill_classic.update(r.get('skill_only_classic', []))
|
||||
all_skill_func.update(r.get('skill_only_functional', []))
|
||||
all_pyjhora_missing.update(r.get('pyjhora_only', []))
|
||||
all_external_benchmark_missing.update(r.get('external_benchmark_only', []))
|
||||
|
||||
print(f" Skill 经典 Yoga 不匹配(可能误判): {len(all_skill_classic)} 种")
|
||||
if all_skill_classic:
|
||||
@@ -586,9 +586,9 @@ def print_report(report: dict):
|
||||
print(f" Skill 功能性 Yoga(PyJhora 无对应,属 skill 特色): {len(all_skill_func)} 种")
|
||||
if all_skill_func:
|
||||
print(f" {sorted(all_skill_func)[:15]}")
|
||||
print(f" PyJhora 有但 Skill 缺失的 Yoga: {len(all_pyjhora_missing)} 种")
|
||||
if all_pyjhora_missing:
|
||||
print(f" {sorted(all_pyjhora_missing)[:15]}")
|
||||
print(f" PyJhora 有但 Skill 缺失的 Yoga: {len(all_external_benchmark_missing)} 种")
|
||||
if all_external_benchmark_missing:
|
||||
print(f" {sorted(all_external_benchmark_missing)[:15]}")
|
||||
|
||||
# --- 改进建议 ---
|
||||
print(f"\n💡 改进建议:")
|
||||
@@ -599,13 +599,13 @@ def print_report(report: dict):
|
||||
if len(all_skill_classic) > 10:
|
||||
print(f" ... 等共 {len(all_skill_classic)} 种")
|
||||
|
||||
missing_top = sorted(all_pyjhora_missing)[:20]
|
||||
print(f"\n 2. 【规则补齐】以下 {len(all_pyjhora_missing)} 种 Yoga PyJhora 已实现但 skill 缺失,")
|
||||
missing_top = sorted(all_external_benchmark_missing)[:20]
|
||||
print(f"\n 2. 【规则补齐】以下 {len(all_external_benchmark_missing)} 种 Yoga PyJhora 已实现但 skill 缺失,")
|
||||
print(f" 建议按优先级补充(推荐先补充高频出现的):")
|
||||
for name in missing_top[:15]:
|
||||
print(f" - {name}")
|
||||
if len(all_pyjhora_missing) > 15:
|
||||
print(f" ... 等共 {len(all_pyjhora_missing)} 种")
|
||||
if len(all_external_benchmark_missing) > 15:
|
||||
print(f" ... 等共 {len(all_external_benchmark_missing)} 种")
|
||||
|
||||
print(f"\n 3. 【名称映射】当前 CROSS_NAME_MAP 已覆盖常见别名,")
|
||||
print(f" 如仍有新别名发现,请添加到映射表中。")
|
||||
@@ -617,20 +617,20 @@ def print_report(report: dict):
|
||||
print(f" ❌ {r['error'][:300]}")
|
||||
continue
|
||||
sc = r.get('skill_count', 0)
|
||||
pc = r.get('pyjhora_count', '?')
|
||||
pc = r.get('external_benchmark_count', '?')
|
||||
print(f" Skill ({sc}): {r['skill_yogas'][:5]}{'...' if len(r['skill_yogas']) > 5 else ''}")
|
||||
if 'pyjhora_yogas' in r:
|
||||
print(f" PyJhora ({pc}): {r['pyjhora_yogas'][:5]}{'...' if len(r['pyjhora_yogas']) > 5 else ''}")
|
||||
if 'external_benchmark_yogas' in r:
|
||||
print(f" PyJhora ({pc}): {r['external_benchmark_yogas'][:5]}{'...' if len(r['external_benchmark_yogas']) > 5 else ''}")
|
||||
func_n = len(r.get('skill_only_functional', []))
|
||||
cls_n = len(r.get('skill_only_classic', []))
|
||||
print(f" 匹配: {len(r['matched'])} | Skill独有: {len(r['skill_only'])}(功能{func_n},经典{cls_n}) | PyJhora独有: {len(r['pyjhora_only'])}")
|
||||
print(f" 匹配: {len(r['matched'])} | Skill独有: {len(r['skill_only'])}(功能{func_n},经典{cls_n}) | PyJhora独有: {len(r['external_benchmark_only'])}")
|
||||
|
||||
if r.get('skill_only_classic'):
|
||||
print(f" Skill 经典不匹配: {r['skill_only_classic'][:10]}")
|
||||
if r.get('skill_only_functional'):
|
||||
print(f" Skill 功能性: {r['skill_only_functional'][:10]}")
|
||||
if r.get('pyjhora_only'):
|
||||
print(f" PyJhora 独有: {r['pyjhora_only'][:10]}")
|
||||
if r.get('external_benchmark_only'):
|
||||
print(f" PyJhora 独有: {r['external_benchmark_only'][:10]}")
|
||||
|
||||
|
||||
def save_report(report: dict, output_file: str):
|
||||
@@ -653,8 +653,8 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
# 检查 PyJhora
|
||||
if not args.skip_pyjhora:
|
||||
if not init_pyjhora():
|
||||
if not args.skip_external_benchmark:
|
||||
if not init_external_benchmark():
|
||||
print("❌ PyJhora 不可用,请先安装: pip install pyjhora swisseph")
|
||||
print(" 提示:也可用 --skip-pyjhora 只测试 skill 侧")
|
||||
return 1
|
||||
@@ -672,7 +672,7 @@ def main():
|
||||
return 1
|
||||
|
||||
# 运行验证
|
||||
report = run_validation(cases, run_pyjhora=not args.skip_pyjhora)
|
||||
report = run_validation(cases, run_external_benchmark=not args.skip_external_benchmark)
|
||||
|
||||
# 输出报告
|
||||
print_report(report)
|
||||
|
||||
@@ -1298,7 +1298,7 @@ class YogaEngine:
|
||||
return False
|
||||
return offset(ctx.house_of(p), h) in (6, 3 if p == 'Mars' else -1, 7 if p == 'Mars' else -1, 4 if p == 'Jupiter' else -1, 8 if p == 'Jupiter' else -1, 2 if p == 'Saturn' else -1, 9 if p == 'Saturn' else -1)
|
||||
|
||||
def pyjhora_planets_aspecting_raasi(p, h):
|
||||
def external_benchmark_planets_aspecting_raasi(p, h):
|
||||
"""Replicate PyJHora house.planets_aspecting_the_raasi() behavior for source parity."""
|
||||
if p not in ctx.planets or h is None:
|
||||
return False
|
||||
@@ -1314,7 +1314,7 @@ class YogaEngine:
|
||||
]
|
||||
return target_rasi_idx in planet_ids_in_aspected_signs
|
||||
|
||||
def pyjhora_aspected_planets_of_raasi(h):
|
||||
def external_benchmark_aspected_planets_of_raasi(h):
|
||||
"""Replicate PyJHora house.aspected_planets_of_the_raasi(): planets whose rasi drishti hits a target house."""
|
||||
if h is None:
|
||||
return []
|
||||
@@ -1431,7 +1431,7 @@ class YogaEngine:
|
||||
occupants = ctx.planets_in_house(target)
|
||||
return bool(occupants) and all(p in BENEFICS for p in occupants)
|
||||
|
||||
def pyjhora_natural_benefics():
|
||||
def external_benchmark_natural_benefics():
|
||||
"""Replicate PyJHora yoga._get_natural_benefics(): Jupiter, Venus, plus benefic Mercury."""
|
||||
benefics = [p for p in ["Jupiter", "Venus"] if p in ctx.planets]
|
||||
mercury_house = ctx.house_of("Mercury")
|
||||
@@ -1492,8 +1492,8 @@ class YogaEngine:
|
||||
# v6.0.32: 同宫与相位检查(custom规则常用)
|
||||
"same_house": same_house, "aspect": aspect, "aspects_house": aspects_house,
|
||||
"graha_aspects_house": graha_aspects_house,
|
||||
"pyjhora_planets_aspecting_raasi": pyjhora_planets_aspecting_raasi,
|
||||
"pyjhora_aspected_planets_of_raasi": pyjhora_aspected_planets_of_raasi,
|
||||
"external_benchmark_planets_aspecting_raasi": external_benchmark_planets_aspecting_raasi,
|
||||
"external_benchmark_aspected_planets_of_raasi": external_benchmark_aspected_planets_of_raasi,
|
||||
"rasi_drishti_signs_from": rasi_drishti_signs_from,
|
||||
"rasi_aspects_house": rasi_aspects_house, "rasi_aspects": rasi_aspects,
|
||||
"rasi_aspected_by_planets": rasi_aspected_by_planets,
|
||||
@@ -1503,7 +1503,7 @@ class YogaEngine:
|
||||
"only_malefics_in_house": only_malefics_in_house,
|
||||
"house_has_benefic": house_has_benefic, "house_has_malefic": house_has_malefic,
|
||||
"house_sign": house_sign, "movable_house": movable_house,
|
||||
"pyjhora_natural_benefics": pyjhora_natural_benefics,
|
||||
"external_benchmark_natural_benefics": external_benchmark_natural_benefics,
|
||||
"d9_house_of": d9_house_of, "d9_sign_of": d9_sign_of,
|
||||
"d9_lord_of_house": d9_lord_of_house, "navamsa_dispositor": navamsa_dispositor,
|
||||
"tithi": tithi, "is_waning_moon": is_waning_moon,
|
||||
|
||||
Reference in New Issue
Block a user