Files
Jyotisha/tests/run_all.py
T
jesse-uxandGrok (agent loop) 5ac88e58f5 让大运接口走完整解读的同一套算法,不再返回编出来的周期。
Ashtottari 在适用时把出生前已经走过的部分算进时间线。

Co-Authored-By: Grok (agent loop)
2026-10-16 00:52:29 +08:00

806 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""印度占星Skill自动化测试运行器 v1.0"""
import sys, os, time
from datetime import datetime
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
sys.path.insert(0, PROJECT_ROOT)
sys.path.insert(0, os.path.join(PROJECT_ROOT, 'scripts'))
TESTS = []
def test(name):
def decorator(fn): TESTS.append((name, fn)); return fn
return decorator
# === 模块导入测试 ===
@test("ashtakavarga import")
def t1():
# BUG-1209: calc_sodhita_av (Sun/Mars/Saturn removal, mislabelled BPHS) was removed;
# the standard chain lives in ashtakavarga_shodhana. Was: imported calc_sodhita_av.
from ashtakavarga import calc_ashtakavarga, calc_prastara_av # noqa: F401
from ashtakavarga_shodhana import sodhita_chain # noqa: F401
@test("shadbala import")
def t2(): from shadbala import calc_shadbala
@test("jaimini import")
def t3(): from jaimini import _chara_dasha_duration_knrao, _resolve_chara_dasha_lord
@test("bhava_bala import")
def t4(): from bhava_bala import calc_bhava_bala
@test("kakshya import")
def t5(): from kakshya import calc_kakshya_scores, get_kakshya_lord
@test("kp_system import")
def t6(): from kp_system import get_kp_lords, calc_kp_analysis
@test("synastry import")
def t7(): from synastry import calc_ashtakoot
@test("prashna import")
def t8(): from prashna import calc_prashna_chart, get_kp_prashna_answer
@test("remedies import")
def t9(): from remedies import recommend_remedies, quick_remedy
@test("pancha_mahapurusha import")
def t10(): from pancha_mahapurusha import detect_pancha_mahapurusha
@test("sade_sati import")
def t11(): from sade_sati import calc_sade_sati_complete
@test("sudarshana_chakra import")
def t12(): from sudarshana_chakra import calc_sudarshana_chakra
@test("career_analysis import")
def t13(): from career_analysis import analyze_career
@test("relationship_analysis import")
def t14(): from relationship_analysis import analyze_relationship
@test("muhurtha_election import")
def t15(): from muhurtha_election import evaluate_muhurtha
@test("yoga_expansion import")
def t16(): from yoga_expansion import detect_all_yogas
@test("birth_time_rectifier import")
def t17(): from birth_time_rectifier import check_lagna_boundary, get_effective_accuracy
@test("conditional dasha modules import")
def t18():
# 原值:from conditional_dashas import calc_dwisaptati_dasha, check_dwisaptati_condition
# 新值:导入独立模块的 calculate_*
# 原因:conditional_dashas.py 是占位副本,已删除
from dwisaptatisama_dasha import calculate_dwisaptatisama_dasha
from dwadashottari_dasha import calculate_dwadashottari_dasha
assert callable(calculate_dwisaptatisama_dasha) and callable(calculate_dwadashottari_dasha)
@test("dasha registry import")
def t19():
# 原值:from extended_dashas import get_available_dashas, DASHA_REGISTRY
# 新值:dasha_registry.DASHA_REGISTRY_METADATA
# 原因:extended_dashas.py 占位注册表已删除,元数据原样搬到新模块
from dasha_registry import DASHA_REGISTRY_METADATA, available_dasha_rows
assert len(DASHA_REGISTRY_METADATA) == len(available_dasha_rows())
@test("chart_renderer import")
def t20(): from chart_renderer import render_south_indian_chart, render_html_report
# === 功能测试 ===
@test("PAV validation")
def t21():
from ashtakavarga import calc_prastara_av, SEVEN_PLANETS
planets = {p: {'sign': 'Aries', 'degree': i*40} for i,p in enumerate(SEVEN_PLANETS)}
pav = calc_prastara_av(planets, 0)
assert pav['all_valid'], "PAV validation failed"
@test("Shadbala invariant")
def t22():
from shadbala import calc_shadbala
p = {pn: {'sign': 'Aries', 'degree': i*40, 'house': i+1} for i,pn in enumerate(['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn'])}
result = calc_shadbala(p, 'Aries', 12, 15, 45, 0)
assert len(result['planets']) == 7
@test("KP sublord")
def t23():
from kp_system import get_kp_lords
r = get_kp_lords(15.5)
assert r['sub_lord'] and r['sub_sub_lord']
@test("Synastry score range")
def t24():
from synastry import calc_ashtakoot
r = calc_ashtakoot(15.5, 120)
assert 0 <= r['total_score'] <= 36
@test("Sade Sati detection")
def t25():
from sade_sati import calc_sade_sati
r = calc_sade_sati("Aries", "Aries", datetime.now())
assert r.get("active") == True
assert r['active'] and r['phase'] == 'peak'
@test("Remedies generation")
def t26():
from remedies import recommend_remedies
sb = {'Sun': {'total_rupas': 0.4}, 'Saturn': {'total_rupas': 0.3}}
r = recommend_remedies(sb, doshas=['Mangal Dosha'])
assert len(r['recommendations']['gems']) >= 1
@test("Muhurtha evaluation")
def t27():
from muhurtha_election import evaluate_muhurtha
r = evaluate_muhurtha('marriage', 3, 'Rohini', 4, 'Monday', 'Taurus')
assert r['verdict'] in ('大吉', '尚可', '中性', '不吉')
@test("Dasha count")
def t28():
# 原值:extended_dashas.get_available_dashas() >= 30
# 新值:DASHA_REGISTRY_METADATA 键数 >= 30
# 原因:清单改读新注册表,35 个键和顺序未改
from dasha_registry import DASHA_REGISTRY_METADATA
assert len(DASHA_REGISTRY_METADATA) >= 30, "Dasha count should be >= 30"
@test("Rectifier boundary check")
def t29():
from birth_time_rectifier import check_lagna_boundary
is_b, _ = check_lagna_boundary(1.5)
assert is_b
@test("PMC detection")
def t30():
from pancha_mahapurusha import detect_pancha_mahapurusha
p = {'Mars': {'sign': 'Capricorn', 'house': 4, 'degree': 280}}
r = detect_pancha_mahapurusha(p)
assert len(r) >= 1
# === v6.7.5 新增功能测试 ===
# BUG-1207 (astrologer ruling 甲5): combustion only reduces the yoga and
# retrograde does not affect it. Was: both asserted is_valid == False.
@test("PMC combustion reduces, does not cancel")
def t31():
from pancha_mahapurusha import detect_pancha_mahapurusha
p = {'Mars': {'sign': 'Capricorn', 'house': 4, 'degree': 280}}
r = detect_pancha_mahapurusha(p, sun_degree=283)
assert r[0]['is_valid'] == True and r[0]['strength'] == 'reduced', "combust Mars keeps PMC, reduced"
@test("PMC retrograde does not cancel")
def t32():
from pancha_mahapurusha import detect_pancha_mahapurusha
p = {'Jupiter': {'sign': 'Cancer', 'house': 1, 'degree': 95, 'retrograde': True}}
r = detect_pancha_mahapurusha(p)
assert r[0]['is_valid'] == True and r[0]['strength'] == 'full'
@test("Bhava Bala computation")
def t33():
from bhava_bala import calc_bhava_adhipathi_bala, calc_bhava_dig_bala
adhi = calc_bhava_adhipathi_bala(['Aries']*12, {'Mars': 500})
assert len(adhi) == 12
dig = calc_bhava_dig_bala(['Aries']*12, [15]*12)
assert len(dig) == 12
@test("Kakshya scoring")
def t34():
from kakshya import calc_kakshya_scores
p = {'Sun': {'sign': 'Aries', 'degree': 15.5}}
r = calc_kakshya_scores(p, 0)
assert 'Sun' in r['planets']
@test("Sudarshana convergence")
def t35():
from sudarshana_chakra import calc_sudarshana_chakra
p = {'Sun': 15.0, 'Moon': 112.0, 'Mars': 280.0}
r = calc_sudarshana_chakra(p, asc_lon=0.0)
assert 'convergence' in r
@test("Career analysis fields")
def t36():
from career_analysis import analyze_career
p = {'Sun': {'house': 10}, 'Saturn': {'house': 6}}
r = analyze_career(p, 'Aries')
assert len(r['fields']) >= 1
@test("Relationship Venus check")
def t37():
from relationship_analysis import analyze_relationship
p = {'Venus': {'sign': 'Taurus', 'house': 7}, 'Moon': {'house': 4}}
r = analyze_relationship(p, 'Aries')
assert 'assessment' in r
@test("Prashna chart creation")
def t38():
from prashna import calc_prashna_chart, get_kp_prashna_answer
from datetime import datetime
r = calc_prashna_chart(datetime.now(), {'Sun': {'sign': 'Aries'}}, 45.5)
assert r['asc_sign'] in ['Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
@test("Prashna KP answer")
def t39():
from prashna import get_kp_prashna_answer
r = get_kp_prashna_answer({'Mars': {'sign': 'Aries'}}, 'career', 15.5)
assert r['confidence'] in ('高', '中')
@test("Prashna Arudha")
def t40():
from prashna import detect_prashna_arudha
r = detect_prashna_arudha({'Mars': {'sign': 'Aries'}}, 15.5, 7)
assert r['arudha_house'] >= 1
@test("Conditional Dasha trigger")
def t41():
# 原值:check_dwisaptati_condition('Sun', 1) == True
# 新值:dwisaptatisama_applicability 在命主落 1 宫时 applicable 为 True
# 原因:占位条件函数已删除,独立模块用同一条「1 或 7 宫」规则
from dwisaptatisama_dasha import dwisaptatisama_applicability
result = dwisaptatisama_applicability(ascendant_longitude=0.0, lagna_lord_sign_index=0)
assert result["applicable"] is True
assert result["lagna_lord_house"] == 1
@test("Conditional Dasha calc")
def t42():
# 原值:calc_dwisaptati_dasha(...) 返回长度为 8 的占位列表
# 新值:标准出表档下 major 非空,且每段带 lord / years / start_date
# 原因:占位函数已删除,改为独立模块的真实行
from datetime import datetime
from dwisaptatisama_dasha import calculate_dwisaptatisama_dasha
result = calculate_dwisaptatisama_dasha({
"birth_datetime": datetime(1990, 6, 15),
"ascendant_longitude": 0.0,
"lagna_lord_sign_index": 0,
"moon_longitude": 15.5,
"standard_table_profile": "pl9_reports_all_dasha_tables_v1",
})
rows = result["major"]
assert rows
assert {"lord", "years", "start_date"} <= set(rows[0])
@test("Extended Dasha registry")
def t43():
# 原值:get_dasha_info('kalachakra')['type'] == 'nakshatra' 且可用键 >= 35
# 新值:元数据里 kalachakra 的 type 仍是 nakshatra,键数 >= 35
# 原因:元数据原样搬迁
from dasha_registry import DASHA_REGISTRY_METADATA
assert DASHA_REGISTRY_METADATA['kalachakra']['type'] == 'nakshatra'
assert len(DASHA_REGISTRY_METADATA) >= 35
@test("Yoga expansion detect")
def t44():
from yoga_expansion import detect_kemadruma, detect_graha_yuddha
r = detect_kemadruma({'Moon': {'sign': 'Aries'}})
assert isinstance(r, dict)
@test("Yoga graha_yuddha")
def t45():
from yoga_expansion import detect_graha_yuddha
r = detect_graha_yuddha({'Mars': {'degree': 50.0}, 'Jupiter': {'degree': 50.3}})
assert len(r) >= 1
@test("Yoga gandanta")
def t46():
from yoga_expansion import detect_gandanta
r = detect_gandanta({'Moon': {'degree': 118.5}}) # Cancer end
assert len(r) >= 0 # may or may not be gandanta
@test("Rectifier accuracy matrix")
def t47():
from birth_time_rectifier import get_effective_accuracy, get_enabled_vargas
acc = get_effective_accuracy('minute', 'hospital')
assert acc == 'minute'
v = get_enabled_vargas('minute')
assert v['D9'] == 'enabled'
@test("Tajika Yogas detect")
def t48():
from tajika import detect_tajika_yogas
r = detect_tajika_yogas({'Sun': {'sign': 'Aries', 'degree': 15}, 'Moon': {'sign': 'Aries', 'degree': 18}})
assert len(r) >= 1
@test("Tajika Sahams count")
def t49():
from tajika import calc_all_sahams
from datetime import datetime
r = calc_all_sahams({'Sun': 80, 'Moon': 105, 'Mars': 220, 'Mercury': 75, 'Jupiter': 310, 'Venus': 350, 'Saturn': 180, 'Rahu': 45, 'Ketu': 225}, 15.0, datetime(1990,6,15,12,0), lat=39.9042, lon=116.4074, tz=8)
assert len(r) >= 30
@test("Chart renderer SVG")
def t50():
from chart_renderer import render_south_indian_chart
svg = render_south_indian_chart({'Sun': 'Aries', 'Moon': 'Cancer'}, 'Aries')
assert '<svg' in svg and '</svg>' in svg
# ========================================================================
# v6.9.14: 扩展测试 — 50→475+ 覆盖核心计算模块
# ========================================================================
# ── Shadbala precision tests ──
@test("Shadbala digs to 2 decimal places")
def t51():
from shadbala import calc_shadbala
s = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
p = {}
for i,(pn,d) in enumerate([('Sun',15),('Moon',75),('Mars',220),('Mercury',55),('Jupiter',310),('Venus',350),('Saturn',180)]):
p[pn] = {'sign':s[int(d/30)%12],'degree':d,'house':i+1}
r = calc_shadbala(p, 'Aries', 12, 15, 75, 0)
for pn, d in r['planets'].items():
assert isinstance(d['total_rupas'], float), f"{pn} total_rupas should be float"
assert 0 < d['total_rupas'] < 20, f"{pn} rupas out of range"
@test("Shadbala absolute component invariant")
def t52():
from shadbala import calc_shadbala
s = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
p = {}
for i,(pn,d) in enumerate([('Sun',15),('Moon',75),('Mars',220),('Mercury',55),('Jupiter',310),('Venus',350),('Saturn',180)]):
p[pn] = {'sign':s[int(d/30)%12],'degree':d,'house':i+1}
r = calc_shadbala(p, 'Aries', 12, 15, 75, 0)
for pn, d in r['planets'].items():
component_sum = (
d['sthana_bala']['total'] + d['dig_bala'] + d['kala_bala']['total'] +
d['chesta_bala'] + d['naisargika_bala'] + d['drik_bala']
)
assert abs(d['total_virupas'] - component_sum) < 0.1, f"{pn} total should equal component sum"
# ── Yoga engine deep tests ──
@test("Yoga engine Raja detection")
def t53():
from yoga_engine import YogaEngine
engine = YogaEngine('references/yoga_rules.json')
p = {pn: {'sign': s, 'house': h, 'degree': 15} for pn, s, h in [
('Sun','Leo',5),('Moon','Cancer',4),('Mars','Scorpio',8),
('Mercury','Gemini',3),('Jupiter','Scorpio',8),('Venus','Libra',7),
('Saturn','Capricorn',10),
]}
r = engine.detect(p, 'Scorpio')
assert len(r) >= 1, "Should detect at least 1 yoga"
@test("Yoga engine rules loaded")
def t54():
from yoga_engine import YogaEngine
engine = YogaEngine('references/yoga_rules.json')
# Astrologer rulings 2026-10-03 (BUG-1205..1207): 23 rules became reference_only
# (kept, not evaluated; their yogas come from one main detector each).
# Was: len(engine.rules) >= 400.
total = len(engine.rules) + len(engine.reference_only_rule_ids)
assert total >= 400, f"Should have 400+ rules, got {total}"
@test("Yoga engine solar lunar detection")
def t55():
from yoga_engine import YogaEngine
engine = YogaEngine('references/yoga_rules.json')
p = {pn: {'sign': s, 'house': h, 'degree': 15} for pn, s, h in [
('Sun','Aries',1),('Moon','Pisces',12),('Mars','Taurus',2),
('Mercury','Gemini',3),('Jupiter','Sagittarius',9),
('Venus','Aquarius',11),('Saturn','Capricorn',10),
]}
r = engine.detect(p, 'Aries')
names = [str(y) for y in r]
assert any("solar" in str(y).lower() or "Veshi" in str(y) or "Yoga" in str(y) for y in r), f"Got {len(r)} yogas"
# ── Ashtakavarga extended tests ──
@test("PAV validation all valid")
def t56():
from ashtakavarga import calc_prastara_av
s = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
p = {}
for i,(pn,d) in enumerate([('Sun',15),('Moon',75),('Mars',220),('Mercury',55),('Jupiter',310),('Venus',350),('Saturn',180)]):
p[pn] = {'sign':s[int(d/30)%12],'degree':d}
r = calc_prastara_av(p, 0)
assert r['all_valid'] == True
@test("Sodhita less or equal to original")
def t57():
# BUG-1209: standard Trikona -> Ekadhipatya chain (was calc_sodhita_av, <= 8 per cell).
from ashtakavarga import calc_ashtakavarga
from ashtakavarga_shodhana import sodhita_chain
s = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
p = {}
for i,(pn,d) in enumerate([('Sun',15),('Moon',75),('Mars',220),('Mercury',55),('Jupiter',310),('Venus',350),('Saturn',180)]):
p[pn] = {'sign':s[int(d/30)%12],'degree':d}
av = calc_ashtakavarga(p, 0)
sodhita = sodhita_chain(av['bav'], p)
for pn in sodhita['sodhita_bav']:
for i in range(12):
assert sodhita['sodhita_bav'][pn][i] <= av['bav'][pn]['bindus'][i], f"Sodhita {pn}[{i}] should <= raw"
# ── Dasha system tests ──
@test("Vimshottari remaining years positive")
def t58():
from dasha_calculator_enhanced import calculate_precise_remaining_years
r = calculate_precise_remaining_years(75) # Moon at 75°
assert r['remaining_years'] > 0
assert r['lord'] in ['Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury']
@test("Vimshottari sequence 9 MDs")
def t59():
from dasha_calculator_enhanced import calculate_dasha_dates
from datetime import datetime
r = calculate_dasha_dates(datetime(1990,6,15), 75)
assert len(r) == 9, f"Should be 9 MD periods, got {len(r)}"
@test("Chara Dasha duration within range")
def t60():
from jaimini import calc_chara_dasha
from jaimini import SIGNS
longs = {'Sun': 15, 'Moon': 75, 'Mars': 220, 'Mercury': 55, 'Jupiter': 310, 'Venus': 350, 'Saturn': 180}
r = calc_chara_dasha(0, longs, 1990, 6, 15)
seq = r.get('dasha_sequence', r) if isinstance(r, dict) else r
durations = [e.get('duration_years', e.get('duration', 0)) for e in seq]
assert len(seq) >= 8, f"Should have 8+ periods, got {len(seq)}"
assert all(d > 0 for d in durations), f"All Chara Dasha durations should be positive: {durations}"
@test("Yogini Dasha 8 periods")
def t61():
# 原值:占位 calc_yogini_dasha 返回 8 段,lord 为女神名
# 新值:引擎模块 major 至少 8 段,且 yogini 名属于八位女神
# 原因:接口与全量解读改为同一模块,模块会按周期重复排
from datetime import datetime
from yogini_dasha import calculate_yogini_dasha
result = calculate_yogini_dasha({
"birth_datetime": datetime(1990, 6, 15),
"moon_longitude": 5 * (360 / 27),
"as_of": datetime(2026, 1, 1),
})
names = {row["yogini"] for row in result["major"]}
assert len(result["major"]) >= 8
assert names <= {"Mangala", "Pingala", "Dhanya", "Bhramari", "Bhadrika", "Ulka", "Siddha", "Sankata"}
assert len(names) == 8
@test("Kalachakra Dasha")
def t62():
# 原值:占位 calc_kalachakra_dasha 返回至少 8 段
# 新值:引擎模块 major 至少 8 段
# 原因:改为全量解读使用的同一函数
from datetime import datetime
from kalachakra_dasha import calculate_kalachakra_dasha
result = calculate_kalachakra_dasha({
"birth_datetime": datetime(1990, 6, 15),
"moon_nakshatra_index": 5,
"moon_pada": 1,
})
assert len(result["major"]) >= 8
# ── KP system tests ──
@test("KP sublord calculation")
def t63():
from kp_system import get_kp_lords
r = get_kp_lords(15.5)
assert r['nakshatra'] is not None
assert r['sub_lord'] is not None
@test("KP subsublord calculation")
def t64():
from kp_system import get_kp_lords
r = get_kp_lords(120.0)
assert r['sub_sub_lord'] is not None
@test("KP analysis returns houses")
def t65():
from kp_system import calc_kp_analysis
planets = {'Sun': {'sign': 'Aries', 'degree': 15.5, 'house': 1}}
r = calc_kp_analysis(planets, 'Aries')
assert len(r.get('houses', {})) >= 1
# ── Divisional charts tests ──
@test("D9 position correct for Aries")
def t66():
from divisional_charts_extended import DivisionalChartsCalculator
calc = DivisionalChartsCalculator()
r = calc._calculate_varga_position(15.0, 9)
assert 0 <= r < 360
@test("D81 nested")
def t67():
from divisional_charts_extended import DivisionalChartsCalculator
calc = DivisionalChartsCalculator()
r = calc._calculate_varga_position(15.0, 81)
assert 0 <= r < 360
@test("D108 nested")
def t68():
from divisional_charts_extended import DivisionalChartsCalculator
calc = DivisionalChartsCalculator()
r = calc._calculate_varga_position(15.0, 108)
assert 0 <= r < 360
# ── Synastry tests ──
@test("Synastry score in 0-36 range")
def t69():
from synastry import calc_ashtakoot
r = calc_ashtakoot(15.5, 120.0)
assert 0 <= r['total_score'] <= 36
@test("Synastry all 8 factors present")
def t70():
from synastry import calc_ashtakoot
r = calc_ashtakoot(15.5, 75.0)
expected = ['Varna','Vashya','Tara','Yoni','GrahaMaitri','Gana','Bhakoot','Nadi']
for e in expected:
assert e in r.get('scores', {}), f"Missing {e}"
# ── Divisional yoga tests ──
@test("Divisional yoga D9 conversion")
def t71():
from divisional_yoga import convert_to_varga, detect_varga_yogas
vp = convert_to_varga({'Sun': 15, 'Moon': 75}, 'D9')
assert 'Sun' in vp
assert vp['Sun']['sign'] in ['Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
@test("Divisional yoga detection runs")
def t72():
from divisional_yoga import detect_varga_yogas
p = {'Sun': 15, 'Moon': 75, 'Mars': 220}
r = detect_varga_yogas(p, 'D9')
assert isinstance(r, list)
# ── Transit trigger tests ──
@test("Transit trigger search produces results")
def t73():
from transit_trigger import search_transit_triggers
from datetime import datetime, timedelta
r = search_transit_triggers('Saturn', 15.0, datetime(2026,6,1), datetime(2026,12,31))
assert isinstance(r, list)
# ── Varshaphala tests ──
@test("Varshaphala report structure")
def t74():
from varshaphala import varshaphala_report
natal = {'Sun':{'sign':'Gemini','degree':22},'Moon':{'sign':'Pisces','degree':5}}
r = varshaphala_report(1990,6,15,12,39.9,116.4,8,natal,'Virgo',2026)
assert 'solar_return' in r
assert 'muntha' in r
assert 'predictions' in r
# ── Remedies tests ──
@test("Remedies weak planet detected")
def t75():
from remedies import recommend_remedies
r = recommend_remedies({'Sun':{'total_rupas':0.4},'Saturn':{'total_rupas':0.3}}, doshas=['Mangal Dosha'])
assert len(r['recommendations']['gems']) >= 1
@test("Remedies dosha coverage")
def t76():
from remedies import recommend_remedies
r = recommend_remedies({'Moon':{'total_rupas':0.6}}, doshas=['Kaal Sarp Dosha','Pitra Dosha'])
assert len(r['recommendations'].get('dosha_remedies',[])) >= 1
# ── Pancha Mahapurusha tests ──
@test("PMC Hamsa Yoga")
def t77():
from pancha_mahapurusha import detect_pancha_mahapurusha
p = {'Jupiter':{'sign':'Cancer','house':1,'degree':95}}
r = detect_pancha_mahapurusha(p)
names = [y.get("name","?") for y in r]
assert any("Hamsa" in n for n in names), f"Expected Hamsa in {names}"
@test("PMC Malavya Yoga")
def t78():
from pancha_mahapurusha import detect_pancha_mahapurusha
p = {'Venus':{'sign':'Pisces','house':7,'degree':345}}
r = detect_pancha_mahapurusha(p)
names = [y.get("name","?") for y in r]
assert any("Malavya" in n for n in names), f"Expected Malavya in {names}"
# ── Sade Sati tests ──
@test("Sade Sati peak detection")
def t79():
from sade_sati import calc_sade_sati
r = calc_sade_sati("Aries", "Aries", datetime.now())
assert r.get("active") == True
@test("Sade Sati inactive")
def t80():
from sade_sati import calc_sade_sati
r = calc_sade_sati('Aries', 'Leo')
assert r['active'] == False
# ── Birth time rectifier tests ──
@test("Rectifier confidence calculation")
def t81():
from birth_time_rectifier import calculate_confidence
r = calculate_confidence(8, 10, 'minute', False)
assert 80 <= r['confidence'] <= 100
# ── Career / Relationship engine tests ──
@test("Career analysis returns fields")
def t82():
from career_analysis import analyze_career
p = {'Sun':{'house':10,'sign':'Leo'},'Saturn':{'house':6},'Venus':{'house':7}}
r = analyze_career(p, 'Aries')
assert len(r.get('fields',[])) >= 1
@test("Career analysis has assessment")
def t83():
from career_analysis import analyze_career
p = {'Sun':{'house':10},'Moon':{'house':4}}
r = analyze_career(p, 'Aries')
assert 'assessment' in r
# ── Misconceptions tests ──
@test("Fallacy detected for debilitated Saturn")
def t84():
from misconceptions import check_for_fallacies
interp = {'planets': {'Saturn': {'dignity': 'debilitated', 'note': '土星落陷,事业会坏'}}}
r = check_for_fallacies(interp)
assert len(r) >= 1
@test("Fallacy detected for Ketu 10th")
def t85():
from misconceptions import check_for_fallacies
interp = {'planets': {'Ketu': {'house': 10, 'note': 'Ketu 10宫=事业毁灭'}}}
r = check_for_fallacies(interp)
assert len(r) >= 1
# ── Case validator tests ──
@test("Case validation for Saturn debilitated")
def t86():
from case_validator import validate_config
r = validate_config('Saturn', 'debilitated')
assert r['validated'] == True
# ── Muhurtha tests ──
@test("Muhurtha marriage evaluation")
def t87():
from muhurtha_election import evaluate_muhurtha
r = evaluate_muhurtha('marriage', 3, 'Rohini', 4, 'Monday', 'Taurus', {
'Moon': {'sign':'Cancer','house':5},
'Venus': {'sign':'Taurus','house':2}
})
assert 'verdict' in r
assert 'score' in r
@test("Muhurtha bad timing detection")
def t88():
from muhurtha_election import evaluate_muhurtha
r = evaluate_muhurtha('business', 4, 'Bharani', 5, 'Tuesday', 'Aries')
assert r['score'] < 8
# ── Chart renderer tests ──
@test("Chart renderer SVG has planets")
def t89():
from chart_renderer import render_south_indian_chart
svg = render_south_indian_chart({
'Sun': 'Aries','Moon': 'Cancer','Mars': 'Scorpio','Mercury': 'Taurus',
'Jupiter': 'Sagittarius','Venus': 'Libra','Saturn': 'Capricorn',
'Rahu': 'Pisces','Ketu': 'Virgo',
}, 'Aries')
found = [p for p in ["Moon","Mars","Jupiter"] if p in svg]
assert len(found) >= 2, f"Only found {found} in SVG"
# ── Multiple Dasha co-existence tests ──
@test("Dasha registry entries unique")
def t90():
# 原值:DASHA_REGISTRY == 35 且 DASHA_CALCULATORS >= 32
# 新值:元数据仍是 35;占位计算器字典删除
# 原因:那 32 个里大部分是不读盘的占位函数
from dasha_registry import DASHA_REGISTRY_METADATA
assert len(DASHA_REGISTRY_METADATA) == 35
assert len({row["name"] for row in DASHA_REGISTRY_METADATA.values()}) == 35
@test("Generic Dasha produces results")
def t91():
# 原值:calc_any_dasha('shodasottari') 占位结果长度 >= 8
# 新值:独立模块在标准出表档下 major >= 8
# 原因:占位通用函数已删除
from datetime import datetime
from shodashottari_dasha import calculate_shodashottari_dasha
result = calculate_shodashottari_dasha({
"birth_datetime": datetime(1990, 6, 15),
"ascendant_longitude": 10.0,
"sun_longitude": 80.0,
"moon_longitude": 15.5,
"standard_table_profile": "pl9_reports_all_dasha_tables_v1",
})
assert len(result["major"]) >= 8
# ── Tajika tests ──
@test("Tajika detection with close planets")
def t92():
from tajika import detect_tajika_yogas
r = detect_tajika_yogas({
'Sun': {'sign': 'Aries', 'degree': 15},
'Moon': {'sign': 'Aries', 'degree': 18},
})
assert len(r) >= 1
@test("Tajika vedha detection")
def t93():
from tajika import detect_vedha
r = detect_vedha({
'Sun': {'degree': 15}, 'Moon': {'degree': 20}, 'Saturn': {'degree': 17},
})
assert isinstance(r, list)
# ── Prashna tests ──
@test("Prashna KP answer has confidence")
def t94():
from prashna import get_kp_prashna_answer
r = get_kp_prashna_answer({'Mars': {'sign': 'Aries'}}, 'career', 15.5)
assert r['confidence'] in ('高', '中', '低')
# ── Edge case: missing planets ──
@test("Handles missing planets gracefully")
def t95():
from shadbala import calc_shadbala
r = calc_shadbala({'Sun': {'sign': 'Aries', 'degree': 15, 'house': 1}}, 'Aries', 12, 15, 0, 0)
assert 'Sun' in r['planets']
# ── Regression: yoga_expansion modules ──
@test("Yoga expansion Kemadruma check")
def t96():
from yoga_expansion import detect_kemadruma
r = detect_kemadruma({'Moon': {'sign': 'Leo'}})
assert isinstance(r, dict)
@test("Yoga expansion Graha Yuddha check")
def t97():
from yoga_expansion import detect_graha_yuddha
r = detect_graha_yuddha({'Mars': {'degree': 50}, 'Jupiter': {'degree': 50.3}})
assert len(r) >= 1
@test("Yoga expansion Gandanta check")
def t98():
from yoga_expansion import detect_gandanta
r = detect_gandanta({'Moon': {'degree': 118.5}})
assert isinstance(r, list)
# ── Config validation chain ──
@test("Full validation chain returns confidence")
def t99():
from case_validator import validate_interpretation
interp = {
'planets': {
'Saturn': {'dignity': 'debilitated', 'note': '土星落陷需注意'},
'Jupiter': {'house': 9, 'note': 'Jupiter在9宫有利'},
}
}
r = validate_interpretation(interp)
assert 'overall_confidence' in r
# ── Package integrity ──
@test("Package version is consistent")
def t100():
from jyotish_vedic import __version__
# 原值=6.9.16;新值=6.9.17。原因=网页普通对话写答案改读数据卡(全谱系计算不变,
# 完整结果留在回执 / 本轮技法 / 报告),SKILL.md「关联技法完整调取」与 0.0.1 同步
# (TASK-consult-evidence-card-20260927 T4)。
assert __version__ == '6.9.17'
# ── v6.9.11 Precision gate tests ──
@test("Transit uses Swiss Ephemeris")
def t101():
from transit_trigger import _get_transit_lon_precise
lon, source = _get_transit_lon_precise('Jupiter', datetime(2026, 1, 1), datetime(2026, 1, 1))
assert source == 'swiss_ephemeris_lahiri'
assert 0 <= lon < 360
@test("KP CSV oracle sample")
def t102():
from kp_system import get_kp_lords
r = get_kp_lords(1.0)
assert r['sign'] == 'Aries'
assert r['nakshatra_lord'] == 'Ketu'
assert r['sub_lord'] == 'Venus'
# === 运行 ===
if __name__ == '__main__':
passed = 0; failed = 0; start = time.time()
print(f"\n{'='*50}")
print(f"🔮 Jyotish Skill Test Suite v1.0 — {len(TESTS)} tests")
print(f"{'='*50}")
for name, fn in TESTS:
try:
fn()
passed += 1
print(f" ✅ {name}")
except Exception as e:
failed += 1
print(f" ❌ {name}: {str(e)[:60]}")
t = time.time() - start
print(f"\n{'='*50}")
print(f" Passed: {passed}/{len(TESTS)} ({passed/len(TESTS)*100:.0f}%)")
print(f" Failed: {failed}")
print(f" Time: {t:.1f}s")
sys.exit(0 if failed == 0 else 1)