96a5d50667
- 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%
164 lines
6.5 KiB
Python
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()
|