#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ yogini_dasha.py — Yogini Dasha System (36-Year Cycle) The Yogini Dasha is a Nakshatra-based dasha system with a total cycle of 36 years. It consists of 8 Yoginis (female planetary deities), each ruling a specific number of years. The sequence is fixed and cycles continuously. Order follows Goel, Classical chapter, Table I. The first period is the remaining birth balance of that Yogini, not a full period counted from birth. Seed is ``(zero_based_moon_nakshatra + 3) mod 8``. Moon longitude is required. Order | Yogini | Years | Planet ------|-----------|-------|-------- 1 | Mangala | 1 | Moon 2 | Pingala | 2 | Sun 3 | Dhanya | 3 | Jupiter 4 | Bhramari | 4 | Mars 5 | Bhadrika | 5 | Mercury 6 | Ulka | 6 | Saturn 7 | Siddha | 7 | Venus 8 | Sankata | 8 | Rahu Total: 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 = 36 years. """ from datetime import datetime, timedelta # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- # 12 Rashis (Zodiac signs) RASHIS = [ "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces" ] # Goel Classical chapter Table I. Do not start a full period at birth. YOGINI_SEQUENCE = [ {"name": "Mangala", "years": 1, "planet": "Moon"}, {"name": "Pingala", "years": 2, "planet": "Sun"}, {"name": "Dhanya", "years": 3, "planet": "Jupiter"}, {"name": "Bhramari", "years": 4, "planet": "Mars"}, {"name": "Bhadrika", "years": 5, "planet": "Mercury"}, {"name": "Ulka", "years": 6, "planet": "Saturn"}, {"name": "Siddha", "years": 7, "planet": "Venus"}, {"name": "Sankata", "years": 8, "planet": "Rahu"}, ] TOTAL_CYCLE = 36 # years def calculate_yogini_dasha(birth_info: dict) -> dict: """Yogini from the Moon's nakshatra, with the remaining birth balance. birth_info: birth_datetime: datetime or ISO string moon_longitude: sidereal degrees, required as_of / today: observation instant; report callers should pass it dasha_year_days: fixed-day year, default 365.25 """ return _calculate_moon_balanced_yogini(birth_info) def _calculate_moon_balanced_yogini(birth_info): """Goel Table I order, angular birth balance, explicit fixed-day year.""" birth_dt = birth_info.get("birth_datetime") if isinstance(birth_dt, str): birth_dt = datetime.fromisoformat(birth_dt) if birth_dt is None or birth_info.get('moon_longitude') is None: return { 'status': 'blocked', 'reason': 'birth_datetime_and_moon_longitude_required', 'major': [], 'current': None, } moon = float(birth_info['moon_longitude']) % 360 nak_length = 360 / 27 nak = int(moon / nak_length) fraction = (moon % nak_length) / nak_length start_idx = (nak + 3) % 8 seed_method = 'moon_nakshatra_plus_3_mod_8_with_angular_balance' starting_yogini = YOGINI_SEQUENCE[start_idx]["name"] starting_planet = YOGINI_SEQUENCE[start_idx]["planet"] year_days = float(birth_info.get('dasha_year_days', 365.25)) if year_days <= 0: raise ValueError('dasha_year_days must be positive') now = birth_info.get('as_of') or birth_info.get('today') or datetime.now() if isinstance(now, str): now = datetime.fromisoformat(now) first_years = YOGINI_SEQUENCE[start_idx]['years'] cursor = birth_dt - timedelta(days=fraction * first_years * year_days) major_periods = [] cycles = max(3, int(max(0, (now - birth_dt).total_seconds()) / (86400 * year_days * 36)) + 2) current_period = None for i in range(8 * cycles): item = YOGINI_SEQUENCE[(start_idx + i) % 8] end = cursor + timedelta(days=item['years'] * year_days) visible_start = max(cursor, birth_dt) row = { 'yogini': item['name'], 'planet': item['planet'], 'full_years': item['years'], 'years': (end - visible_start).total_seconds() / (86400 * year_days), 'start_date': visible_start.isoformat(timespec='seconds'), 'end_date': end.isoformat(timespec='seconds'), 'unclipped_start_date': cursor.isoformat(timespec='seconds'), 'cycle_number': i // 8 + 1, } major_periods.append(row) if visible_start <= now < end: current_period = dict(row) cursor = end return { "major": major_periods, "current": current_period, "total_cycle": TOTAL_CYCLE, "starting_yogini": starting_yogini, "starting_planet": starting_planet, "seed_method": seed_method, "reference_date": now.isoformat(timespec='seconds'), "dasha_year_days": year_days, "birth_balance_years": first_years * (1 - fraction), "source": "BPHS Yogini; Goel Table I; angular balance and fixed-day year", }