feat: v3.6.0 全量同步 — 完整印度占星引擎+74篇参考资料+14子命令

## 核心升级(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:触发词/子命令表/工作流完整更新
This commit is contained in:
732642856
2026-04-24 22:36:57 +08:00
parent fb3a260eb5
commit 2c3392703d
52 changed files with 15062 additions and 722 deletions
+292
View File
@@ -0,0 +1,292 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
R1-R10 数学验证模块
对星盘计算结果执行 10 项硬性校验规则(来自 CNWU16 审计框架)
规则清单:
R1: SAV 总和 = 3377行星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