2c3392703d
## 核心升级(v1.6.0 → v3.6.0) ### 计算引擎(scripts/) - jyotish_engine.py v3.6.0:14子命令统一引擎(chart/dasha/yoga/predict/varga/celebrity/db-stats/transit/shadbala/ashtakavarga/memory/validate/audit/report) - 基于 Swiss Ephemeris + Lahiri Ayanamsa 恒星黄道 - shadbala.py:Shadbala六重力量计算(Sthana/Dig/Kala/Chesta/Naisargika/Drik Bala) - ashtakavarga.py v2.0:BPHS完整8×8矩阵,SAV=337 - validate.py:R1-R10+R2b 数学验证(11项) - event_prediction_model.py:三层验证事件预测规则引擎 - hermes_memory_core.py + hermes_bridge.py:Hermes记忆系统 - report_builder.py:MD→HTML报告生成器(羊皮纸主题) ### 参考资料(references/)— 74篇 - 新增35篇:AI解盘工作流/Argala/Dasa Convergence/替代推运系统/条件Dasha/Shodasavarga十六分盘/古典文献翻译/专业发展路径等 - 修改8篇:PDF读取v3.0/Ashtakavarga v2.0/名人案例库v2.0/KP占星v2.0等 - 覆盖:行星/星座/宫位/Nakshatra/Yoga/Dasha/Transit/关系占星/年运盘/Jaimini/KP/Varshaphala/补救措施 ### 审计管线(P1-P12 + P3/P8/冲突仲裁) - P3仓库耦合:双宫掌管命运捆绑分析 - P8年龄状态:行星度数→执行模式判定 - 冲突仲裁3条规则:矛盾信号裁决 ### 其他 - CHANGELOG.md:完整版本历史 - .gitignore:排除缓存和数据库 - SKILL.md v3.6.0:触发词/子命令表/工作流完整更新
293 lines
11 KiB
Python
293 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
R1-R10 数学验证模块
|
||
对星盘计算结果执行 10 项硬性校验规则(来自 CNWU16 审计框架)
|
||
|
||
规则清单:
|
||
R1: SAV 总和 = 337(7行星BAV之和)
|
||
R2: 各 BAV 行常数(Sun=48, Moon=49, Mars=39, Mercury=54, Jupiter=56, Venus=52, Saturn=39, Lagna=49)
|
||
R2b: BAV列→SAV列校验(每个星座的7行星BAV之和 = 该星座SAV分数)
|
||
R3: 水星最大延伸角 ≤ 28°(不远离太阳)
|
||
R4: 金星最大延伸角 ≤ 47°(不远离太阳)
|
||
R5: Rahu-Ketu 严格 180° 对冲
|
||
R6: 逆行合法性(只有火星/木星/土星/金星/水星可能逆行,Rahu/Ketu永远逆行,太阳/月亮不逆行)
|
||
R7: Dasha 大运年限链总和 = 120 年
|
||
R8: 行星完整性(7行星 + Rahu + Ketu + Lagna 全部存在)
|
||
R9: 星座度数范围 [0, 30)
|
||
R10: 宫位连续性(1-12宫无断点)
|
||
"""
|
||
|
||
from typing import Dict, List, Tuple
|
||
import math
|
||
|
||
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
|
||
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
|
||
|
||
DASHA_YEARS = {
|
||
"Ketu": 7, "Venus": 20, "Sun": 6, "Moon": 10,
|
||
"Mars": 7, "Rahu": 18, "Jupiter": 16, "Saturn": 19, "Mercury": 17
|
||
}
|
||
|
||
EXPECTED_PLANETS = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu']
|
||
|
||
BAV_TOTALS = {
|
||
'Sun': 48, 'Moon': 49, 'Mars': 39, 'Mercury': 54,
|
||
'Jupiter': 56, 'Venus': 52, 'Saturn': 39, 'Lagna': 49
|
||
}
|
||
|
||
SEVEN_PLANETS = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']
|
||
|
||
|
||
def validate_chart(chart_data: Dict, ashtakavarga_data: Dict = None) -> Dict:
|
||
"""
|
||
执行 R1-R10 全部校验
|
||
|
||
Args:
|
||
chart_data: jyotish_engine chart 命令的完整输出
|
||
ashtakavarga_data: ashtakavarga 命令的输出(可选,用于 R1/R2)
|
||
|
||
Returns:
|
||
{
|
||
"valid": bool, # 全部通过
|
||
"total_checks": int,
|
||
"passed": int,
|
||
"failed": int,
|
||
"results": [{"rule": "R1", "name": "...", "passed": bool, "detail": "..."}, ...]
|
||
}
|
||
"""
|
||
results = []
|
||
planets = chart_data.get('planets', {})
|
||
asc = chart_data.get('ascendant', {})
|
||
|
||
# R1: SAV 总和 = 337
|
||
if ashtakavarga_data:
|
||
sav_total = ashtakavarga_data.get('sav', {}).get('total', 0)
|
||
sav_valid = ashtakavarga_data.get('sav', {}).get('valid', False)
|
||
results.append({
|
||
'rule': 'R1', 'name': 'SAV总和=337',
|
||
'passed': sav_valid,
|
||
'detail': f"SAV total = {sav_total}, expected 337"
|
||
})
|
||
else:
|
||
results.append({
|
||
'rule': 'R1', 'name': 'SAV总和=337',
|
||
'passed': None,
|
||
'detail': '未提供 ashtakavarga 数据,跳过'
|
||
})
|
||
|
||
# R2: 各 BAV 行常数
|
||
if ashtakavarga_data:
|
||
bav_results = ashtakavarga_data.get('bav', {})
|
||
all_bav_valid = ashtakavarga_data.get('all_bav_valid', False)
|
||
details = []
|
||
for pname in SEVEN_PLANETS + ['Lagna']:
|
||
if pname in bav_results:
|
||
actual = bav_results[pname].get('total', 0)
|
||
expected = BAV_TOTALS.get(pname, 0)
|
||
details.append(f"{pname}={actual}/{expected}")
|
||
results.append({
|
||
'rule': 'R2', 'name': 'BAV行常数校验',
|
||
'passed': all_bav_valid,
|
||
'detail': '; '.join(details) if details else '无BAV数据'
|
||
})
|
||
else:
|
||
results.append({
|
||
'rule': 'R2', 'name': 'BAV行常数校验',
|
||
'passed': None,
|
||
'detail': '未提供 ashtakavarga 数据,跳过'
|
||
})
|
||
|
||
# R2b: BAV列→SAV列校验(每个星座的7行星BAV之和 = 该星座SAV分数)
|
||
if ashtakavarga_data:
|
||
r2b_passed, r2b_detail = _check_bav_column_sav(ashtakavarga_data)
|
||
results.append({'rule': 'R2b', 'name': 'BAV列→SAV列校验', 'passed': r2b_passed, 'detail': r2b_detail})
|
||
else:
|
||
results.append({'rule': 'R2b', 'name': 'BAV列→SAV列校验', 'passed': None, 'detail': '未提供 ashtakavarga 数据,跳过'})
|
||
|
||
# R3: 水星最大延伸角 ≤ 28°
|
||
r3_passed, r3_detail = _check_elongation(planets, 'Mercury', 'Sun', 28)
|
||
results.append({'rule': 'R3', 'name': '水星延伸角≤28°', 'passed': r3_passed, 'detail': r3_detail})
|
||
|
||
# R4: 金星最大延伸角 ≤ 47°
|
||
r4_passed, r4_detail = _check_elongation(planets, 'Venus', 'Sun', 47)
|
||
results.append({'rule': 'R4', 'name': '金星延伸角≤47°', 'passed': r4_passed, 'detail': r4_detail})
|
||
|
||
# R5: Rahu-Ketu 严格 180° 对冲
|
||
r5_passed, r5_detail = _check_rahu_ketu_opposition(planets)
|
||
results.append({'rule': 'R5', 'name': 'Rahu-Ketu 180°对冲', 'passed': r5_passed, 'detail': r5_detail})
|
||
|
||
# R6: 逆行合法性
|
||
r6_passed, r6_detail = _check_retrograde_legitimacy(planets)
|
||
results.append({'rule': 'R6', 'name': '逆行合法性', 'passed': r6_passed, 'detail': r6_detail})
|
||
|
||
# R7: Dasha 大运年限链总和 = 120 年
|
||
dasha_sum = sum(DASHA_YEARS.values())
|
||
results.append({
|
||
'rule': 'R7', 'name': 'Dasha年限总和=120',
|
||
'passed': dasha_sum == 120,
|
||
'detail': f"Dasha year chain sum = {dasha_sum}"
|
||
})
|
||
|
||
# R8: 行星完整性
|
||
r8_passed, r8_detail = _check_planet_completeness(planets, asc)
|
||
results.append({'rule': 'R8', 'name': '行星完整性', 'passed': r8_passed, 'detail': r8_detail})
|
||
|
||
# R9: 星座度数范围 [0, 30)
|
||
r9_passed, r9_detail = _check_degree_range(planets)
|
||
results.append({'rule': 'R9', 'name': '星座度数范围', 'passed': r9_passed, 'detail': r9_detail})
|
||
|
||
# R10: 宫位连续性(1-12宫无断点)
|
||
r10_passed, r10_detail = _check_house_continuity(chart_data)
|
||
results.append({'rule': 'R10', 'name': '宫位连续性', 'passed': r10_passed, 'detail': r10_detail})
|
||
|
||
# 汇总
|
||
checked = [r for r in results if r['passed'] is not None]
|
||
passed_count = sum(1 for r in checked if r['passed'])
|
||
failed_count = sum(1 for r in checked if not r['passed'])
|
||
|
||
return {
|
||
'valid': failed_count == 0,
|
||
'total_checks': len(results),
|
||
'checked': len(checked),
|
||
'passed': passed_count,
|
||
'failed': failed_count,
|
||
'skipped': len(results) - len(checked),
|
||
'results': results,
|
||
}
|
||
|
||
|
||
def _check_elongation(planets: Dict, planet: str, reference: str, max_deg: float) -> Tuple[bool, str]:
|
||
"""检查行星与参考行星的延伸角"""
|
||
p1 = planets.get(planet, {})
|
||
p2 = planets.get(reference, {})
|
||
deg1 = p1.get('degree', None)
|
||
deg2 = p2.get('degree', None)
|
||
if deg1 is None or deg2 is None:
|
||
return None, f'{planet}或{reference}度数缺失'
|
||
elongation = abs(deg1 - deg2)
|
||
if elongation > 180:
|
||
elongation = 360 - elongation
|
||
ok = elongation <= max_deg
|
||
return ok, f"{planet}延伸角 = {elongation:.2f}°, 阈值 {max_deg}°"
|
||
|
||
|
||
def _check_rahu_ketu_opposition(planets: Dict) -> Tuple[bool, str]:
|
||
"""检查 Rahu-Ketu 严格 180° 对冲"""
|
||
rahu = planets.get('Rahu', {})
|
||
ketu = planets.get('Ketu', {})
|
||
rahu_deg = rahu.get('degree', None)
|
||
ketu_deg = ketu.get('degree', None)
|
||
if rahu_deg is None or ketu_deg is None:
|
||
return None, 'Rahu或Ketu度数缺失'
|
||
diff = abs(rahu_deg - ketu_deg)
|
||
deviation = abs(diff - 180)
|
||
ok = deviation < 0.01 # 允许极小数值误差
|
||
return ok, f"Rahu={rahu_deg:.4f}°, Ketu={ketu_deg:.4f}°, 偏差={deviation:.4f}°"
|
||
|
||
|
||
def _check_retrograde_legitimacy(planets: Dict) -> Tuple[bool, str]:
|
||
"""检查逆行合法性"""
|
||
# 太阳和月亮不应逆行
|
||
never_retro = ['Sun', 'Moon']
|
||
# Rahu/Ketu 永远逆行
|
||
always_retro = ['Rahu', 'Ketu']
|
||
# 火星/木星/土星/金星/水星 可能逆行也可能不逆行
|
||
can_retro = ['Mars', 'Jupiter', 'Saturn', 'Venus', 'Mercury']
|
||
|
||
issues = []
|
||
for pname in never_retro:
|
||
pd = planets.get(pname, {})
|
||
if pd.get('retrograde', False):
|
||
issues.append(f"{pname}不应逆行")
|
||
|
||
for pname in always_retro:
|
||
pd = planets.get(pname, {})
|
||
if not pd.get('retrograde', True):
|
||
issues.append(f"{pname}应永远逆行")
|
||
|
||
ok = len(issues) == 0
|
||
detail = '; '.join(issues) if issues else '所有行星逆行状态合法'
|
||
return ok, detail
|
||
|
||
|
||
def _check_planet_completeness(planets: Dict, asc: Dict) -> Tuple[bool, str]:
|
||
"""检查行星完整性"""
|
||
missing = []
|
||
for pname in EXPECTED_PLANETS:
|
||
if pname not in planets:
|
||
missing.append(pname)
|
||
if not asc:
|
||
missing.append('Ascendant(Lagna)')
|
||
ok = len(missing) == 0
|
||
detail = f"缺失: {', '.join(missing)}" if missing else f"完整:{len(EXPECTED_PLANETS)}行星 + Ascendant"
|
||
return ok, detail
|
||
|
||
|
||
def _check_degree_range(planets: Dict) -> Tuple[bool, str]:
|
||
"""检查星座度数范围"""
|
||
issues = []
|
||
for pname, pd in planets.items():
|
||
deg_in_sign = pd.get('degree_in_sign', None)
|
||
if deg_in_sign is not None:
|
||
if deg_in_sign < 0 or deg_in_sign >= 30:
|
||
issues.append(f"{pname}度数={deg_in_sign}° 超出[0,30)范围")
|
||
ok = len(issues) == 0
|
||
detail = '; '.join(issues) if issues else '所有行星度数在合法范围'
|
||
return ok, detail
|
||
|
||
|
||
def _check_house_continuity(chart_data: Dict) -> Tuple[bool, str]:
|
||
"""检查宫位连续性"""
|
||
houses = chart_data.get('houses', {})
|
||
house_nums = set()
|
||
for key in houses:
|
||
if key.startswith('house_'):
|
||
num = int(key.replace('house_', ''))
|
||
house_nums.add(num)
|
||
|
||
expected = set(range(1, 13))
|
||
missing = expected - house_nums
|
||
ok = len(missing) == 0
|
||
detail = f"缺失宫位: {sorted(missing)}" if missing else f"1-12宫完整({len(house_nums)}宫)"
|
||
return ok, detail
|
||
|
||
|
||
def _check_bav_column_sav(ashtakavarga_data: Dict) -> Tuple[bool, str]:
|
||
"""
|
||
R2b: BAV列→SAV列校验
|
||
每个星座的7行星BAV bindus之和必须等于该星座的SAV分数。
|
||
这验证了行总和(R2)和列总和的交叉一致性。
|
||
"""
|
||
bav_results = ashtakavarga_data.get('bav', {})
|
||
house_scores = ashtakavarga_data.get('house_scores', {})
|
||
sav_scores = ashtakavarga_data.get('sav', {})
|
||
|
||
issues = []
|
||
# 从每个7行星的 BAV bindus 列向量求和,对比 SAV house_scores
|
||
for sign_i in range(12):
|
||
# 计算该星座位置(第 sign_i 列)的 7行星 BAV 之和
|
||
col_sum = 0
|
||
for pname in SEVEN_PLANETS:
|
||
if pname in bav_results:
|
||
bindus = bav_results[pname].get('bindus', [])
|
||
if len(bindus) > sign_i:
|
||
col_sum += bindus[sign_i]
|
||
|
||
# 获取该星座/宫位的 SAV 分数
|
||
house_key = f'house_{sign_i + 1}'
|
||
hs = house_scores.get(house_key, {})
|
||
sav_score = hs.get('score', None)
|
||
|
||
if sav_score is not None and col_sum != sav_score:
|
||
issues.append(f"星座{SIGNS[sign_i]}列: BAV列和={col_sum}, SAV={sav_score}")
|
||
|
||
# 同时验证 SAV 逐宫总分是否与 house_scores 一致
|
||
if not issues:
|
||
ok = True
|
||
detail = "所有12星座 BAV列和 与 SAV分数一致 ✅"
|
||
else:
|
||
ok = False
|
||
detail = f"不一致: {'; '.join(issues)}"
|
||
return ok, detail
|