Files
Jyotisha/tests/test_yogini_seed_profile.py
T
jesse-uxGrok (agent loop)Cursor
6eeb98c774 feat(dasha): Yogini 小运按比例单列,大运输出不变
完整解读仍只看大运。小运从该大运自己的 Yogini 起按年数比例排,出生前的段丢掉。

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 03:33:19 +08:00

134 lines
6.0 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.
from datetime import datetime, timedelta
import pytest
from scripts.yogini_dasha import YOGINI_SEQUENCE, TOTAL_CYCLE, calculate_yogini_dasha, yogini_antardashas
def test_yogini_classical_sequence_and_balance():
# Goel Classical chapter Table I: lord and years, not the older Maya/Jaya/Rati list.
assert [(row['name'], row['planet'], row['years']) for row in YOGINI_SEQUENCE] == [
('Mangala', 'Moon', 1),
('Pingala', 'Sun', 2),
('Dhanya', 'Jupiter', 3),
('Bhramari', 'Mars', 4),
('Bhadrika', 'Mercury', 5),
('Ulka', 'Saturn', 6),
('Siddha', 'Venus', 7),
('Sankata', 'Rahu', 8),
]
birth = datetime(2000, 1, 1, 12)
# Mid-Pushya (eighth nakshatra): remainder 3 is Dhanya, half of 3 years left.
result = calculate_yogini_dasha({
'birth_datetime': birth,
'moon_longitude': 100.0,
'as_of': '2000-01-02',
'dasha_year_days': 360,
})
assert result['starting_planet'] == 'Jupiter'
assert result['starting_yogini'] == 'Dhanya'
assert result['birth_balance_years'] == pytest.approx(1.5)
assert datetime.fromisoformat(result['major'][0]['end_date']) == birth + timedelta(days=540)
assert result['major'][0]['years'] == pytest.approx(1.5)
assert result['current']['planet'] == 'Jupiter'
assert result['major'][1]['planet'] == 'Mars'
assert all(a['end_date'] == b['start_date'] for a, b in zip(result['major'], result['major'][1:]))
def test_yogini_no_invented_lagna_fallback_or_missing_balance():
# 原值: lagna-only input started at Maya via legacy_lagna_rashi_compatibility
# 新值: status blocked,major 为空
# 原因: BUG-1262,没有月亮黄经就不能编一段从出生起算的满周期
result = calculate_yogini_dasha({
'lagna_rashi_index': 0,
'moon_nakshatra_index': 0,
'birth_datetime': datetime(2000, 1, 1),
})
assert result['status'] == 'blocked'
assert result['major'] == []
def test_yogini_explicit_reference_controls_current_cycle():
args = {'birth_datetime': '2000-01-01', 'moon_longitude': 0.0, 'dasha_year_days': 360}
first = calculate_yogini_dasha(dict(args, as_of='2000-01-02'))
later = calculate_yogini_dasha(dict(args, as_of='2040-01-01'))
assert first['current']['cycle_number'] == 1
assert later['current']['cycle_number'] == 2
# Ashwini start: (0 + 3) % 8 is Bhramari / Mars, and the balance is the whole period.
assert first['starting_planet'] == 'Mars'
assert first['birth_balance_years'] == pytest.approx(4.0)
def _sub_names(start_name):
start = next(index for index, item in enumerate(YOGINI_SEQUENCE) if item['name'] == start_name)
return [YOGINI_SEQUENCE[(start + offset) % 8]['name'] for offset in range(8)]
def test_yogini_antardasha_proportional_on_unclipped_seed():
# Moon at Ashwini 0°: Bhramari opens on the birth instant, so nothing is clipped.
birth = datetime(2000, 1, 1)
year_days = 360
result = calculate_yogini_dasha({
'birth_datetime': birth,
'moon_longitude': 0.0,
'as_of': '2000-01-02',
'dasha_year_days': year_days,
})
assert all('antardasha' not in row for row in result['major'])
major = result['major'][0]
assert major['unclipped_start_date'] == major['start_date']
subs = yogini_antardashas(major, birth_dt=birth, year_days=year_days)
assert [row['yogini'] for row in subs] == _sub_names('Bhramari')
assert all('unclipped_start_date' not in row for row in subs)
assert subs[0]['start_date'] == major['start_date']
assert subs[-1]['end_date'] == major['end_date']
assert all(left['end_date'] == right['start_date'] for left, right in zip(subs, subs[1:]))
for row, item in zip(subs, YOGINI_SEQUENCE[3:] + YOGINI_SEQUENCE[:3]):
assert row['level'] == 'antardasha'
assert row['planet'] == row['lord'] == item['planet']
assert row['years'] == pytest.approx(major['full_years'] * item['years'] / TOTAL_CYCLE)
visible = sum(
(datetime.fromisoformat(row['end_date']) - datetime.fromisoformat(row['start_date'])).total_seconds()
for row in subs
)
major_span = (
datetime.fromisoformat(major['end_date']) - datetime.fromisoformat(major['start_date'])
).total_seconds()
assert abs(visible - major_span) <= 86400
def test_yogini_antardasha_clips_the_birth_crossing_subperiod():
# Mid-Pushya is the same seed as test_yogini_classical_sequence_and_balance,
# shifted so the elapsed balance falls inside Siddha rather than on a boundary.
birth = datetime(2000, 1, 1, 12)
year_days = 360
moon = (7 + 0.6) * (360 / 27)
result = calculate_yogini_dasha({
'birth_datetime': birth,
'moon_longitude': moon,
'as_of': '2000-01-02',
'dasha_year_days': year_days,
})
first = result['major'][0]
later = result['major'][1]
first_subs = yogini_antardashas(first, birth_dt=birth, year_days=year_days)
later_subs = yogini_antardashas(later, birth_dt=birth, year_days=year_days)
assert first_subs[0]['yogini'] == 'Siddha'
assert first_subs[0]['start_date'] == first['start_date'] == birth.isoformat(timespec='seconds')
assert datetime.fromisoformat(first_subs[0]['unclipped_start_date']) < birth
assert [row['yogini'] for row in first_subs] == _sub_names('Dhanya')[-4:]
assert later_subs[0]['start_date'] == later['start_date']
assert [row['yogini'] for row in later_subs] == _sub_names(later['yogini'])
assert 'unclipped_start_date' not in later_subs[0]
for major, subs in ((first, first_subs), (later, later_subs)):
assert all(left['end_date'] == right['start_date'] for left, right in zip(subs, subs[1:]))
assert subs[-1]['end_date'] == major['end_date']
visible = sum(
(datetime.fromisoformat(row['end_date']) - datetime.fromisoformat(row['start_date'])).total_seconds()
for row in subs
)
major_span = (
datetime.fromisoformat(major['end_date']) - datetime.fromisoformat(major['start_date'])
).total_seconds()
assert abs(visible - major_span) == 0