Files
Jyotisha/scripts/analyze_yoga_errors.py
T
732642856 96a5d50667 v6.1.1: Fix aspect directions and tighten yoga rules
- annadana_yoga: Fix aspect direction (L2 aspects Jupiter/Mercury, not vice versa)
- parannabhojana_yoga: Fix aspect direction (L2 aspects debilitated planets)
- bandhubhisthyaktha: Tweak weak_or_bad to match PyJHora (house_strength <= 1)
- dharidhra: Remove unmapped method1/v145/v146, keep only v144/v147-v152

Results: Precision 94.42% -> 95.60%, F1 94.02% -> 94.35%
2026-06-07 13:09:08 +08:00

164 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""
自动化Yoga误差分析器
对Top FP/FN规则,提取具体星盘例子并比较中间计算结果
"""
import json, sys, os
from collections import defaultdict
SKILL_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(SKILL_DIR, 'scripts'))
from yoga_engine import YogaEngine, YogaContext
RULES_PATH = os.path.join(SKILL_DIR, 'references', 'yoga_rules.json')
POS_PATH = os.path.join(SKILL_DIR, 'references', 'planet_positions_60.json')
PYJ_PATH = os.path.join(SKILL_DIR, 'references', 'standard_test_charts.json')
REPORT_PATH = os.path.join(SKILL_DIR, 'references', 'validation_logic_report.json')
def main():
engine = YogaEngine(RULES_PATH)
with open(POS_PATH) as f:
pos_data = json.load(f)
with open(PYJ_PATH) as f:
pyj_data = json.load(f)
with open(REPORT_PATH) as f:
report = json.load(f)
pyj_charts = {c['name']: c for c in pyj_data['charts']}
charts_by_name = {c['name']: c for c in pos_data['charts']}
# 从false_positives和false_negatives列表中按规则分组
fp_by_rule = defaultdict(list)
fn_by_rule = defaultdict(list)
for item in report.get('false_positives', []):
fp_by_rule[item['rule_id']].append(item['chart'])
for item in report.get('false_negatives', []):
fn_by_rule[item['rule_id']].append(item['chart'])
# 合并errors,按总数量排序
all_errors = {}
for rid in set(list(fp_by_rule.keys()) + list(fn_by_rule.keys())):
all_errors[rid] = {
'fp': len(fp_by_rule.get(rid, [])),
'fn': len(fn_by_rule.get(rid, [])),
'fp_charts': fp_by_rule.get(rid, []),
'fn_charts': fn_by_rule.get(rid, [])
}
sorted_errors = sorted(all_errors.items(), key=lambda x: x[1]['fp'] + x[1]['fn'], reverse=True)
print("=" * 80)
print("Top Yoga Error Analysis")
print("=" * 80)
for rule_id, err in sorted_errors[:15]:
total = err['fp'] + err['fn']
if total == 0:
continue
rule = next((r for r in engine.rules if r['id'] == rule_id), None)
if not rule:
continue
print(f"\n{'='*80}")
print(f"Rule: {rule_id} ({rule.get('name', '')}) — FP:{err['fp']} FN:{err['fn']} Total:{total}")
print(f"{'='*80}")
if err['fn_charts']:
print(f"\n FN Examples (PyJHora=True, Skill=False):")
for chart_name in err['fn_charts'][:3]:
analyze_example(engine, charts_by_name, pyj_charts, chart_name, rule_id, 'FN')
if err['fp_charts']:
print(f"\n FP Examples (PyJHora=False, Skill=True):")
for chart_name in err['fp_charts'][:3]:
analyze_example(engine, charts_by_name, pyj_charts, chart_name, rule_id, 'FP')
def analyze_example(engine, charts_by_name, pyj_charts, chart_name, rule_id, error_type):
chart = charts_by_name.get(chart_name)
if not chart:
return
planets = chart['planets']
ascendant = chart['ascendant']
pyj_chart = pyj_charts.get(chart_name)
pyj_yogas = pyj_chart.get('expected_yogas', []) if pyj_chart else []
ctx = YogaContext(planets, ascendant)
print(f"\n [{error_type}] {chart_name} — Asc: {ascendant}")
# 根据规则id输出相关中间计算
if rule_id in ['kahala_yoga', 'bvr_kaahala_yoga']:
l1 = ctx.lord_of_house(1)
l4 = ctx.lord_of_house(4)
l9 = ctx.lord_of_house(9)
print(f" L1={l1} in H{ctx.house_of(l1)} (strong: {ctx.house_of(l1) in [1,4,5,7,9,10] if l1 else 'N/A'})")
print(f" L4={l4} in H{ctx.house_of(l4)}")
print(f" L9={l9} in H{ctx.house_of(l9)}")
if l4 and l9:
off = (ctx.house_of(l9) - ctx.house_of(l4)) % 12
print(f" offset(L4,L9)={off} (kendra: {off in [0,3,6,9]})")
elif rule_id == 'sankha_yoga':
l1 = ctx.lord_of_house(1)
l5 = ctx.lord_of_house(5)
l6 = ctx.lord_of_house(6)
l9 = ctx.lord_of_house(9)
l10 = ctx.lord_of_house(10)
print(f" L1={l1} in H{ctx.house_of(l1)} (strong: {ctx.house_of(l1) in [1,4,5,7,9,10] if l1 else 'N/A'})")
print(f" L5={l5} in H{ctx.house_of(l5)}")
print(f" L6={l6} in H{ctx.house_of(l6)}")
print(f" L9={l9} in H{ctx.house_of(l9)} (strong: {ctx.house_of(l9) in [1,4,5,7,9,10] if l9 else 'N/A'})")
print(f" L10={l10} in H{ctx.house_of(l10)}")
if l5 and l6:
off1 = (ctx.house_of(l6) - ctx.house_of(l5)) % 12
print(f" offset(L5,L6)={off1} (kendra: {off1 in [0,3,6,9]})")
if l1 and l10:
print(f" L1==L10 house: {ctx.house_of(l1) == ctx.house_of(l10)}")
print(f" L1 sign: {ctx.sign_of(l1)} (movable: {ctx.sign_of(l1) in ['Aries','Cancer','Libra','Capricorn'] if l1 else 'N/A'})")
elif rule_id == 'bvr_dharidhra_11_precise':
l1 = ctx.lord_of_house(1)
l2 = ctx.lord_of_house(2)
l11 = ctx.lord_of_house(11)
print(f" L2={l2} in H{ctx.house_of(l2)}")
print(f" L11={l11} in H{ctx.house_of(l11)}")
print(f" method1 (L2/L11 in 6/8/12): {ctx.house_of(l2) in [6,8,12] if l2 else False} / {ctx.house_of(l11) in [6,8,12] if l11 else False}")
elif rule_id == 'bvr_annadana_yoga':
l2 = ctx.lord_of_house(2)
l11 = ctx.lord_of_house(11)
print(f" L2={l2} in H{ctx.house_of(l2)}")
print(f" L11={l11} in H{ctx.house_of(l11)}")
elif rule_id == 'bvr_kapata_yoga':
l4 = ctx.lord_of_house(4)
print(f" L4={l4} in H{ctx.house_of(l4)}")
elif rule_id == 'bvr_nishkapata_precise':
l4 = ctx.lord_of_house(4)
print(f" L4={l4} in H{ctx.house_of(l4)}")
elif rule_id == 'bvr_thrikaala_gnana_yoga':
jup = ctx.house_of('Jupiter') if 'Jupiter' in planets else None
mer = ctx.house_of('Mercury') if 'Mercury' in planets else None
print(f" Jupiter in H{jup}")
print(f" Mercury in H{mer}")
else:
for h in range(1, 13):
p = ctx.lord_of_house(h)
if p:
print(f" L{h}={p} in H{ctx.house_of(p)}")
# 列出PyJHora期望的yoga(包含当前规则的变体)
matched = [y for y in pyj_yogas if rule_id.replace('bvr_', '') in y or rule_id in y]
print(f" PyJHora matched: {matched}")
if __name__ == '__main__':
main()