v6.1.2: Merge 4 hidden modules from session workspace

Added modules:
- ashtottari_dasha.py — 108-year cycle conditional dasha (BPHS Ch.19)
- yogini_dasha.py — 36-year cycle, 8 Yoginis dasha system
- kalachakra_dasha.py — Savya/Apasavya dual-mode dasha
- report_orchestrator.py — 5-theme narrative report generator with cross-validation engine
This commit is contained in:
732642856
2026-06-07 14:26:51 +08:00
parent 2a3359ea0f
commit b2faf72e83
4 changed files with 1919 additions and 0 deletions
+251
View File
@@ -0,0 +1,251 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ashtottari_dasha.py — Ashtottari Dasha System (108-Year Cycle)
Reference: Brihat Parashara Hora Shastra (BPHS), Chapter 19
Ashtottari Dasha is a conditional dasha system with a total cycle of 108 years.
It is applicable only when the Moon is in specific Nakshatras:
- Krishna Paksha (waning Moon): Rohini, Ardra, Pushya, Ashlesha, Magha, Revati
- Shukla Paksha (waxing Moon): Ashwini, Mrigashira, Punarvasu, Chitra, Shravana, Dhanishta
The 8 planetary lords and their year allotments:
1. Sun — 6 years
2. Moon — 15 years
3. Mars — 8 years
4. Mercury — 17 years
5. Saturn — 10 years
6. Jupiter — 19 years
7. Rahu — 12 years
8. Venus — 21 years
Total: 6 + 15 + 8 + 17 + 10 + 19 + 12 + 21 = 108 years.
The dasha sequence always runs: Sun → Moon → Mars → Mercury → Saturn → Jupiter → Rahu → Venus.
The starting lord is determined by the Moon's Nakshatra at birth.
"""
from datetime import datetime, timedelta
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
# Nakshatra indices (0-based): Ashwini=0, Bharani=1, ..., Revati=26
NAKSHATRAS = [
"Ashwini", "Bharani", "Krittika", "Rohini", "Mrigashira", "Ardra",
"Punarvasu", "Pushya", "Ashlesha", "Magha", "Purva Phalguni",
"Uttara Phalguni", "Hasta", "Chitra", "Swati", "Vishakha",
"Anuradha", "Jyeshtha", "Mula", "Purva Ashadha", "Uttara Ashadha",
"Shravana", "Dhanishta", "Shatabhisha", "Purva Bhadrapada",
"Uttara Bhadrapada", "Revati"
]
# Applicable Nakshatras for Ashtottari Dasha
SHUKLA_PAKSHA_NAKSHATRAS = {0, 4, 6, 13, 20, 22} # Ashwini, Mrigashira, Punarvasu, Chitra, Shravana, Dhanishta
KRISHNA_PAKSHA_NAKSHATRAS = {3, 5, 7, 8, 9, 26} # Rohini, Ardra, Pushya, Ashlesha, Magha, Revati
# Ashtottari planetary sequence and year allotments (BPHS Ch.19)
DASHA_SEQUENCE = [
{"planet": "Sun", "years": 6},
{"planet": "Moon", "years": 15},
{"planet": "Mars", "years": 8},
{"planet": "Mercury", "years": 17},
{"planet": "Saturn", "years": 10},
{"planet": "Jupiter", "years": 19},
{"planet": "Rahu", "years": 12},
{"planet": "Venus", "years": 21},
]
TOTAL_CYCLE = 108 # years
# Mapping from starting Nakshatra index to first dasha lord index in DASHA_SEQUENCE
# Per BPHS Ch.19:
# - Rohini (3), Ardra (5), Pushya (7), Ashlesha (8), Magha (9), Revati (26)
# → start from Sun (0)
# - Ashwini (0), Mrigashira (4), Punarvasu (6)
# → start from Mars (2)
# - Chitra (13), Shravana (20), Dhanishta (22)
# → start from Jupiter (5)
STARTING_LORD_MAP = {
3: 0, # Rohini -> Sun
5: 0, # Ardra -> Sun
7: 0, # Pushya -> Sun
8: 0, # Ashlesha -> Sun
9: 0, # Magha -> Sun
26: 0, # Revati -> Sun
0: 2, # Ashwini -> Mars
4: 2, # Mrigashira -> Mars
6: 2, # Punarvasu -> Mars
13: 5, # Chitra -> Jupiter
20: 5, # Shravana -> Jupiter
22: 5, # Dhanishta -> Jupiter
}
# ---------------------------------------------------------------------------
# Core Functions
# ---------------------------------------------------------------------------
def is_ashtottari_applicable(moon_nakshatra_index: int, is_shukla_paksha: bool) -> bool:
"""
Check whether Ashtottari Dasha is applicable for the given birth conditions.
Args:
moon_nakshatra_index: 0-based index of Moon's Nakshatra (0 = Ashwini).
is_shukla_paksha: True if birth is in Shukla Paksha (waxing Moon), else False.
Returns:
bool: True if Ashtottari Dasha applies.
"""
if is_shukla_paksha:
return moon_nakshatra_index in SHUKLA_PAKSHA_NAKSHATRAS
else:
return moon_nakshatra_index in KRISHNA_PAKSHA_NAKSHATRAS
def _build_major_periods(start_lord_idx: int, birth_date: datetime) -> list:
"""
Build the full list of major periods (Mahadashas) given the starting lord.
Returns a list of dicts with keys: planet, years, start_date, end_date.
"""
periods = []
seq_len = len(DASHA_SEQUENCE)
current_date = birth_date
for i in range(seq_len):
idx = (start_lord_idx + i) % seq_len
lord = DASHA_SEQUENCE[idx]
end_date = current_date + timedelta(days=lord["years"] * 365.25)
periods.append({
"planet": lord["planet"],
"years": lord["years"],
"start_date": current_date.isoformat(),
"end_date": end_date.isoformat(),
})
current_date = end_date
return periods
def calculate_ashtottari_dasha(birth_info: dict) -> dict:
"""
Calculate Ashtottari Dasha for a native.
Args:
birth_info: dict containing:
- "moon_nakshatra_index": int (0-26)
- "is_shukla_paksha": bool
- "birth_datetime": datetime object or ISO-format string
Returns:
dict with keys:
- "applicable": bool — whether this dasha system applies
- "major": list of major period dicts (planet, years, start/end dates)
- "current": dict — the currently running major period
- "total_cycle": int — 108
- "starting_planet": str — first dasha lord
"""
moon_idx = birth_info.get("moon_nakshatra_index")
is_shukla = birth_info.get("is_shukla_paksha", True)
birth_dt = birth_info.get("birth_datetime")
if isinstance(birth_dt, str):
birth_dt = datetime.fromisoformat(birth_dt)
if birth_dt is None:
birth_dt = datetime.now()
applicable = is_ashtottari_applicable(moon_idx, is_shukla)
if not applicable:
return {
"applicable": False,
"major": [],
"current": None,
"total_cycle": TOTAL_CYCLE,
"starting_planet": None,
"reason": f"Moon Nakshatra '{NAKSHATRAS[moon_idx]}' does not qualify for Ashtottari Dasha under {'Shukla' if is_shukla else 'Krishna'} Paksha.",
}
start_lord_idx = STARTING_LORD_MAP.get(moon_idx, 0)
starting_planet = DASHA_SEQUENCE[start_lord_idx]["planet"]
major_periods = _build_major_periods(start_lord_idx, birth_dt)
# Determine current period
now = datetime.now()
current_period = None
for p in major_periods:
p_start = datetime.fromisoformat(p["start_date"])
p_end = datetime.fromisoformat(p["end_date"])
if p_start <= now < p_end:
current_period = p.copy()
current_period["elapsed_years"] = (now - p_start).days / 365.25
current_period["remaining_years"] = (p_end - now).days / 365.25
break
return {
"applicable": True,
"major": major_periods,
"current": current_period,
"total_cycle": TOTAL_CYCLE,
"starting_planet": starting_planet,
}
# ---------------------------------------------------------------------------
# Self-test
# ---------------------------------------------------------------------------
if __name__ == "__main__":
# Example 1: Applicable case — Moon in Rohini, Krishna Paksha
test_birth_1 = {
"moon_nakshatra_index": 3, # Rohini
"is_shukla_paksha": False, # Krishna Paksha
"birth_datetime": datetime(1990, 6, 15, 10, 30),
}
result_1 = calculate_ashtottari_dasha(test_birth_1)
print("=" * 60)
print("Test 1: Moon in Rohini, Krishna Paksha")
print("=" * 60)
print(f"Applicable: {result_1['applicable']}")
print(f"Starting Planet: {result_1['starting_planet']}")
print(f"Total Cycle: {result_1['total_cycle']} years")
print("Major Periods:")
for p in result_1["major"]:
print(f" {p['planet']:10s} | {p['years']:2d} years | {p['start_date'][:10]}{p['end_date'][:10]}")
print(f"Current Period: {result_1['current']}")
print()
# Example 2: Applicable case — Moon in Ashwini, Shukla Paksha
test_birth_2 = {
"moon_nakshatra_index": 0, # Ashwini
"is_shukla_paksha": True, # Shukla Paksha
"birth_datetime": datetime(1985, 3, 20, 8, 0),
}
result_2 = calculate_ashtottari_dasha(test_birth_2)
print("=" * 60)
print("Test 2: Moon in Ashwini, Shukla Paksha")
print("=" * 60)
print(f"Applicable: {result_2['applicable']}")
print(f"Starting Planet: {result_2['starting_planet']}")
print(f"Total Cycle: {result_2['total_cycle']} years")
for p in result_2["major"]:
print(f" {p['planet']:10s} | {p['years']:2d} years | {p['start_date'][:10]}{p['end_date'][:10]}")
print(f"Current Period: {result_2['current']}")
print()
# Example 3: Non-applicable case — Moon in Bharani, Shukla Paksha
test_birth_3 = {
"moon_nakshatra_index": 1, # Bharani
"is_shukla_paksha": True,
"birth_datetime": datetime(2000, 1, 1, 0, 0),
}
result_3 = calculate_ashtottari_dasha(test_birth_3)
print("=" * 60)
print("Test 3: Moon in Bharani, Shukla Paksha (Non-applicable)")
print("=" * 60)
print(f"Applicable: {result_3['applicable']}")
print(f"Reason: {result_3['reason']}")
+301
View File
@@ -0,0 +1,301 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
kalachakra_dasha.py — Kalachakra Dasha System (Variable Cycle)
The Kalachakra Dasha is one of the most intricate Nakshatra-based dasha systems.
It is based on the Moon's Nakshatra and its Pada (quarter) at birth.
Key Concepts:
- 27 Nakshatras × 4 Padas = 108 Padas.
- Each Pada maps to a specific Rashi (Zodiac sign).
- Two directional modes:
* Savya (clockwise / forward)
* Apasavya (anti-clockwise / backward)
- 9 lords (Navatara-like) govern the periods in a fixed sequence.
Nakshatra Grouping (per standard texts):
Savya (forward) Nakshatras:
Ashwini, Bharani, Krittika, Ashlesha, Magha, Moola,
Purva Ashadha, Uttara Ashadha, Shravana, Dhanishta, Shatabhisha
Apasavya (backward) Nakshatras:
Rohini, Mrigashira, Ardra, Punarvasu, Pushya, Chitra,
Swati, Vishakha, Anuradha, Jyeshtha, Purva Bhadrapada,
Uttara Bhadrapada, Revati
(Note: Some authorities include Abhijit; this implementation uses the 27-Nakshatra scheme.)
9-Lord Sequence:
The 9 dasha lords correspond to the Navagraha sequence used in Kalachakra:
1. Ketu 2. Venus 3. Sun 4. Moon 5. Mars 6. Mercury 7. Jupiter 8. Saturn 9. Rahu
Their year allotments (one common traditional set):
Ketu: 7, Venus: 20, Sun: 6, Moon: 10, Mars: 7,
Mercury: 18, Jupiter: 16, Saturn: 19, Rahu: 17
Total: 7+20+6+10+7+18+16+19+17 = 120 years
However, Kalachakra is more commonly implemented with Rashi-based years.
This module uses a simplified Rashi-year model (see _RASHI_YEARS below).
Rashi Dasha Sequence and Years (simplified standard model):
Savya mode (forward through zodiac):
Capricorn 7 → Aquarius 8 → Pisces 9 → Aries 10 →
Taurus 11 → Gemini 12 → Cancer 13 → Leo 14 →
Virgo 15 → Libra 16 → Scorpio 17 → Sagittarius 18
Total = 150 years
Apasavya mode (backward through zodiac):
Cancer 13 → Gemini 12 → Taurus 11 → Aries 10 →
Pisces 9 → Aquarius 8 → Capricorn 7 → Sagittarius 18 →
Scorpio 17 → Libra 16 → Virgo 15 → Leo 14
Total = 150 years
Each of the 9 lords rules one consecutive Rashi in the sequence.
The starting point is determined by Moon's Nakshatra Pada.
Reference: Brihat Parashara Hora Shastra and various classical commentaries on Kalachakra.
"""
from datetime import datetime, timedelta
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
NAKSHATRAS = [
"Ashwini", "Bharani", "Krittika", "Rohini", "Mrigashira", "Ardra",
"Punarvasu", "Pushya", "Ashlesha", "Magha", "Purva Phalguni",
"Uttara Phalguni", "Hasta", "Chitra", "Swati", "Vishakha",
"Anuradha", "Jyeshtha", "Mula", "Purva Ashadha", "Uttara Ashadha",
"Shravana", "Dhanishta", "Shatabhisha", "Purva Bhadrapada",
"Uttara Bhadrapada", "Revati"
]
RASHIS = [
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"
]
# 9 Kalachakra lords in order
KALA_LORDS = [
"Ketu", "Venus", "Sun", "Moon", "Mars",
"Mercury", "Jupiter", "Saturn", "Rahu"
]
# Savya Rashi sequence (starting from Capricorn, forward)
SAVYA_RASHI_SEQ = [9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8]
# Apasavya Rashi sequence (starting from Cancer, backward)
APASAVYA_RASHI_SEQ = [3, 2, 1, 0, 11, 10, 9, 8, 7, 6, 5, 4]
# Rashi year allotments (index 0-11)
# Savya: Cap 7, Aqua 8, Pis 9, Ari 10, Tau 11, Gem 12, Can 13, Leo 14, Vir 15, Lib 16, Sco 17, Sag 18
# Apasavya: Can 13, Gem 12, Tau 11, Ari 10, Pis 9, Aqua 8, Cap 7, Sag 18, Sco 17, Lib 16, Vir 15, Leo 14
_RASHI_YEARS_SAVYA = {
9: 7, 10: 8, 11: 9, 0: 10, 1: 11, 2: 12,
3: 13, 4: 14, 5: 15, 6: 16, 7: 17, 8: 18
}
_RASHI_YEARS_APASAVYA = {
3: 13, 2: 12, 1: 11, 0: 10, 11: 9, 10: 8,
9: 7, 8: 18, 7: 17, 6: 16, 5: 15, 4: 14
}
# Nakshatra → mode mapping (Savya = True, Apasavya = False)
# Per standard grouping
SAVYA_NAKSHATRAS = {0, 1, 2, 8, 9, 18, 19, 20, 21, 22, 23}
APASAVYA_NAKSHATRAS = {3, 4, 5, 6, 7, 13, 14, 15, 16, 17, 24, 25, 26}
# Pada → starting Rashi offset within the sequence (simplified model)
# For Savya: Pada 1 → Cap(0), Pada 2 → Aqu(1), Pada 3 → Pis(2), Pada 4 → Ari(3)
# For Apasavya: Pada 1 → Can(0), Pada 2 → Gem(1), Pada 3 → Tau(2), Pada 4 → Ari(3)
# The actual classical mapping is more nuanced; this is a practical approximation.
PADA_OFFSET_MAP = {1: 0, 2: 1, 3: 2, 4: 3}
# ---------------------------------------------------------------------------
# Helper Functions
# ---------------------------------------------------------------------------
def _get_mode(nakshatra_index: int) -> str:
"""Return 'savya' or 'apasavya' based on Nakshatra."""
if nakshatra_index in SAVYA_NAKSHATRAS:
return "savya"
elif nakshatra_index in APASAVYA_NAKSHATRAS:
return "apasavya"
else:
# Default fallback (should not happen with valid 0-26 input)
return "savya"
def _build_major_periods(mode: str, start_offset: int, birth_date: datetime) -> list:
"""
Build major periods for Kalachakra Dasha.
Args:
mode: 'savya' or 'apasavya'.
start_offset: offset into the 12-sign Rashi sequence.
birth_date: datetime of birth.
Returns:
list of period dicts.
"""
if mode == "savya":
rashi_seq = SAVYA_RASHI_SEQ
years_map = _RASHI_YEARS_SAVYA
else:
rashi_seq = APASAVYA_RASHI_SEQ
years_map = _RASHI_YEARS_APASAVYA
periods = []
current_date = birth_date
seq_len = len(rashi_seq)
for i in range(seq_len):
seq_idx = (start_offset + i) % seq_len
rashi_idx = rashi_seq[seq_idx]
lord_idx = i % 9 # 9 lords cycle through 12 rashis
lord = KALA_LORDS[lord_idx]
years = years_map[rashi_idx]
end_date = current_date + timedelta(days=years * 365.25)
periods.append({
"lord": lord,
"rashi": RASHIS[rashi_idx],
"rashi_index": rashi_idx,
"years": years,
"start_date": current_date.isoformat(),
"end_date": end_date.isoformat(),
})
current_date = end_date
return periods
# ---------------------------------------------------------------------------
# Core Functions
# ---------------------------------------------------------------------------
def calculate_kalachakra_dasha(birth_info: dict) -> dict:
"""
Calculate Kalachakra Dasha for a native.
Args:
birth_info: dict containing:
- "moon_nakshatra_index": int (0-26)
- "moon_pada": int (1-4)
- "birth_datetime": datetime object or ISO-format string (optional)
Returns:
dict with keys:
- "mode": str — 'savya' or 'apasavya'
- "major": list of major period dicts
- "current": dict — the currently running major period
- "total_cycle": int — sum of years in one full cycle
- "starting_lord": str
- "starting_rashi": str
"""
nakshatra_idx = birth_info.get("moon_nakshatra_index", 0)
pada = birth_info.get("moon_pada", 1)
birth_dt = birth_info.get("birth_datetime")
if isinstance(birth_dt, str):
birth_dt = datetime.fromisoformat(birth_dt)
if birth_dt is None:
birth_dt = datetime.now()
# Clamp pada to 1-4
if not 1 <= pada <= 4:
pada = ((pada - 1) % 4) + 1
mode = _get_mode(nakshatra_idx)
start_offset = PADA_OFFSET_MAP.get(pada, 0)
major_periods = _build_major_periods(mode, start_offset, birth_dt)
total_cycle = sum(p["years"] for p in major_periods)
starting_lord = major_periods[0]["lord"] if major_periods else None
starting_rashi = major_periods[0]["rashi"] if major_periods else None
# Determine current period
now = datetime.now()
current_period = None
for p in major_periods:
p_start = datetime.fromisoformat(p["start_date"])
p_end = datetime.fromisoformat(p["end_date"])
if p_start <= now < p_end:
current_period = p.copy()
current_period["elapsed_years"] = (now - p_start).days / 365.25
current_period["remaining_years"] = (p_end - now).days / 365.25
break
return {
"mode": mode,
"major": major_periods,
"current": current_period,
"total_cycle": total_cycle,
"starting_lord": starting_lord,
"starting_rashi": starting_rashi,
}
# ---------------------------------------------------------------------------
# Self-test
# ---------------------------------------------------------------------------
if __name__ == "__main__":
# Example 1: Savya mode — Moon in Ashwini (Savya), Pada 1
test_birth_1 = {
"moon_nakshatra_index": 0, # Ashwini (Savya)
"moon_pada": 1,
"birth_datetime": datetime(1990, 6, 15, 10, 30),
}
result_1 = calculate_kalachakra_dasha(test_birth_1)
print("=" * 70)
print("Test 1: Moon in Ashwini (Savya), Pada 1")
print("=" * 70)
print(f"Mode : {result_1['mode']}")
print(f"Starting Lord : {result_1['starting_lord']}")
print(f"Starting Rashi : {result_1['starting_rashi']}")
print(f"Total Cycle : {result_1['total_cycle']} years")
print("Major Periods:")
for p in result_1["major"]:
print(f" {p['lord']:8s} | {p['rashi']:12s} | {p['years']:2d} years | {p['start_date'][:10]}{p['end_date'][:10]}")
print(f"Current Period : {result_1['current']}")
print()
# Example 2: Apasavya mode — Moon in Rohini (Apasavya), Pada 2
test_birth_2 = {
"moon_nakshatra_index": 3, # Rohini (Apasavya)
"moon_pada": 2,
"birth_datetime": datetime(1985, 3, 20, 8, 0),
}
result_2 = calculate_kalachakra_dasha(test_birth_2)
print("=" * 70)
print("Test 2: Moon in Rohini (Apasavya), Pada 2")
print("=" * 70)
print(f"Mode : {result_2['mode']}")
print(f"Starting Lord : {result_2['starting_lord']}")
print(f"Starting Rashi : {result_2['starting_rashi']}")
print(f"Total Cycle : {result_2['total_cycle']} years")
for p in result_2["major"]:
print(f" {p['lord']:8s} | {p['rashi']:12s} | {p['years']:2d} years | {p['start_date'][:10]}{p['end_date'][:10]}")
print(f"Current Period : {result_2['current']}")
print()
# Example 3: Savya mode — Moon in Shravana (Savya), Pada 4
test_birth_3 = {
"moon_nakshatra_index": 21, # Shravana (Savya)
"moon_pada": 4,
"birth_datetime": datetime(2000, 1, 1, 0, 0),
}
result_3 = calculate_kalachakra_dasha(test_birth_3)
print("=" * 70)
print("Test 3: Moon in Shravana (Savya), Pada 4")
print("=" * 70)
print(f"Mode : {result_3['mode']}")
print(f"Starting Lord : {result_3['starting_lord']}")
print(f"Starting Rashi : {result_3['starting_rashi']}")
print(f"Total Cycle : {result_3['total_cycle']} years")
for p in result_3["major"]:
print(f" {p['lord']:8s} | {p['rashi']:12s} | {p['years']:2d} years | {p['start_date'][:10]}{p['end_date'][:10]}")
File diff suppressed because it is too large Load Diff
+209
View File
@@ -0,0 +1,209 @@
#!/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.
The 8 Yoginis, their year allotments, and associated planets:
Order | Yogini | Years | Planet
------|-----------|-------|--------
1 | Maya | 1 | Sun
2 | Bhadrika | 2 | Moon
3 | Jaya | 3 | Mars
4 | Rati | 4 | Mercury
5 | Mangala | 5 | Jupiter
6 | Pingala | 6 | Saturn
7 | Dhanya | 7 | Venus
8 | Bhramari | 8 | Rahu
Total: 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 = 36 years.
Starting Yogini:
The starting Yogini is determined by the Ascendant (Lagna) Rashi.
Per standard texts:
- Aries / Leo / Sagittarius / Gemini / Libra / Aquarius
→ start from Maya (1)
- Taurus / Virgo / Capricorn / Cancer / Scorpio / Pisces
→ start from Bhadrika (2)
Alternative (more common) rule based on Lagna degree modulo 8:
Lagna Nakshatra Pada determines the starting Yogini.
For simplicity, this implementation uses the Ascendant Rashi rule above.
Reference: Various classical and modern Jyotish texts on Yogini Dasha.
"""
from datetime import datetime, timedelta
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
# 12 Rashis (Zodiac signs)
RASHIS = [
"Aries", "Taurus", "Gemini", "Cancer",
"Leo", "Virgo", "Libra", "Scorpio",
"Sagittarius", "Capricorn", "Aquarius", "Pisces"
]
# Yogini sequence: fixed order, never changes
YOGINI_SEQUENCE = [
{"name": "Maya", "years": 1, "planet": "Sun"},
{"name": "Bhadrika", "years": 2, "planet": "Moon"},
{"name": "Jaya", "years": 3, "planet": "Mars"},
{"name": "Rati", "years": 4, "planet": "Mercury"},
{"name": "Mangala", "years": 5, "planet": "Jupiter"},
{"name": "Pingala", "years": 6, "planet": "Saturn"},
{"name": "Dhanya", "years": 7, "planet": "Venus"},
{"name": "Bhramari", "years": 8, "planet": "Rahu"},
]
TOTAL_CYCLE = 36 # years
# Lagna Rashi to starting Yogini index mapping
# Odd Rashis (0,4,8,2,6,10) → start from Maya (0)
# Even Rashis (1,5,9,3,7,11) → start from Bhadrika (1)
def _get_start_yogini_index(lagna_rashi_index: int) -> int:
"""Return starting Yogini index based on Ascendant Rashi."""
if lagna_rashi_index % 2 == 0:
return 0 # Maya
else:
return 1 # Bhadrika
# ---------------------------------------------------------------------------
# Core Functions
# ---------------------------------------------------------------------------
def _build_major_periods(start_idx: int, birth_date: datetime) -> list:
"""
Build the full list of major Yogini periods.
Returns a list of dicts with keys: yogini, planet, years, start_date, end_date.
"""
periods = []
seq_len = len(YOGINI_SEQUENCE)
current_date = birth_date
for i in range(seq_len):
idx = (start_idx + i) % seq_len
yogini = YOGINI_SEQUENCE[idx]
end_date = current_date + timedelta(days=yogini["years"] * 365.25)
periods.append({
"yogini": yogini["name"],
"planet": yogini["planet"],
"years": yogini["years"],
"start_date": current_date.isoformat(),
"end_date": end_date.isoformat(),
})
current_date = end_date
return periods
def calculate_yogini_dasha(birth_info: dict) -> dict:
"""
Calculate Yogini Dasha for a native.
Args:
birth_info: dict containing:
- "lagna_rashi_index": int (0 = Aries, ..., 11 = Pisces)
- "birth_datetime": datetime object or ISO-format string (optional)
Returns:
dict with keys:
- "major": list of major period dicts
- "current": dict — the currently running major period
- "total_cycle": int — 36
- "starting_yogini": str — first Yogini name
- "starting_planet": str — planet of first Yogini
"""
lagna_idx = birth_info.get("lagna_rashi_index", 0)
birth_dt = birth_info.get("birth_datetime")
if isinstance(birth_dt, str):
birth_dt = datetime.fromisoformat(birth_dt)
if birth_dt is None:
birth_dt = datetime.now()
start_idx = _get_start_yogini_index(lagna_idx)
starting_yogini = YOGINI_SEQUENCE[start_idx]["name"]
starting_planet = YOGINI_SEQUENCE[start_idx]["planet"]
major_periods = _build_major_periods(start_idx, birth_dt)
# Determine current period
now = datetime.now()
current_period = None
for p in major_periods:
p_start = datetime.fromisoformat(p["start_date"])
p_end = datetime.fromisoformat(p["end_date"])
if p_start <= now < p_end:
current_period = p.copy()
current_period["elapsed_years"] = (now - p_start).days / 365.25
current_period["remaining_years"] = (p_end - now).days / 365.25
break
return {
"major": major_periods,
"current": current_period,
"total_cycle": TOTAL_CYCLE,
"starting_yogini": starting_yogini,
"starting_planet": starting_planet,
}
# ---------------------------------------------------------------------------
# Self-test
# ---------------------------------------------------------------------------
if __name__ == "__main__":
# Example 1: Ascendant in Aries (odd sign) → starts from Maya
test_birth_1 = {
"lagna_rashi_index": 0, # Aries
"birth_datetime": datetime(1990, 6, 15, 10, 30),
}
result_1 = calculate_yogini_dasha(test_birth_1)
print("=" * 60)
print("Test 1: Ascendant in Aries (odd sign)")
print("=" * 60)
print(f"Starting Yogini : {result_1['starting_yogini']} ({result_1['starting_planet']})")
print(f"Total Cycle : {result_1['total_cycle']} years")
print("Major Periods:")
for p in result_1["major"]:
print(f" {p['yogini']:10s} | {p['planet']:8s} | {p['years']} years | {p['start_date'][:10]}{p['end_date'][:10]}")
print(f"Current Period : {result_1['current']}")
print()
# Example 2: Ascendant in Taurus (even sign) → starts from Bhadrika
test_birth_2 = {
"lagna_rashi_index": 1, # Taurus
"birth_datetime": datetime(1985, 3, 20, 8, 0),
}
result_2 = calculate_yogini_dasha(test_birth_2)
print("=" * 60)
print("Test 2: Ascendant in Taurus (even sign)")
print("=" * 60)
print(f"Starting Yogini : {result_2['starting_yogini']} ({result_2['starting_planet']})")
print(f"Total Cycle : {result_2['total_cycle']} years")
for p in result_2["major"]:
print(f" {p['yogini']:10s} | {p['planet']:8s} | {p['years']} years | {p['start_date'][:10]}{p['end_date'][:10]}")
print(f"Current Period : {result_2['current']}")
print()
# Example 3: Ascendant in Scorpio (even sign)
test_birth_3 = {
"lagna_rashi_index": 7, # Scorpio
"birth_datetime": datetime(2000, 1, 1, 0, 0),
}
result_3 = calculate_yogini_dasha(test_birth_3)
print("=" * 60)
print("Test 3: Ascendant in Scorpio (even sign)")
print("=" * 60)
print(f"Starting Yogini : {result_3['starting_yogini']} ({result_3['starting_planet']})")
for p in result_3["major"]:
print(f" {p['yogini']:10s} | {p['planet']:8s} | {p['years']} years | {p['start_date'][:10]}{p['end_date'][:10]}")