kundali-chart-mcp 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/azure-function/function_app.py +93 -0
- package/azure-function/host.json +15 -0
- package/azure-function/kundali_bridge.py +952 -0
- package/azure-function/python/kundali_lib/__init__.py +1 -0
- package/azure-function/python/kundali_lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/__pycache__/ephemeris.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/__pycache__/geocoder.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/__pycache__/vedicastro_bridge.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/ephemeris.py +30 -0
- package/azure-function/python/kundali_lib/geocoder.py +82 -0
- package/azure-function/python/kundali_lib/vedic/__init__.py +1 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/arishta.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/ashtakavarga.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/avasthas.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/ayanamsa.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/bhava_chalit.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/char_dasha.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/chart.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/chart_types.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/compatibility.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/constants.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/dasha_extended.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/dasha_systems.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/doshas.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/gandanta.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/gochara.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/hora.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/houses.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/jaimini.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/kalachakra.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/kartari.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/kurmachakra.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/lunar_return.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/muhurta.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/nabhasha.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/nakshatra_details.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/panchanga.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/planets.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/shadbala.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/special_conditions.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/sudarshana.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/tajaka.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/upagrahas.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/varshaphal.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/yogas.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/zodiac.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/arishta.py +465 -0
- package/azure-function/python/kundali_lib/vedic/ashtakavarga.py +213 -0
- package/azure-function/python/kundali_lib/vedic/avasthas.py +292 -0
- package/azure-function/python/kundali_lib/vedic/ayanamsa.py +106 -0
- package/azure-function/python/kundali_lib/vedic/bhava_chalit.py +137 -0
- package/azure-function/python/kundali_lib/vedic/char_dasha.py +308 -0
- package/azure-function/python/kundali_lib/vedic/chart.py +126 -0
- package/azure-function/python/kundali_lib/vedic/chart_types.py +338 -0
- package/azure-function/python/kundali_lib/vedic/compatibility.py +705 -0
- package/azure-function/python/kundali_lib/vedic/constants.py +108 -0
- package/azure-function/python/kundali_lib/vedic/dasha_extended.py +262 -0
- package/azure-function/python/kundali_lib/vedic/dasha_systems.py +439 -0
- package/azure-function/python/kundali_lib/vedic/doshas.py +453 -0
- package/azure-function/python/kundali_lib/vedic/gandanta.py +213 -0
- package/azure-function/python/kundali_lib/vedic/gochara.py +277 -0
- package/azure-function/python/kundali_lib/vedic/hora.py +263 -0
- package/azure-function/python/kundali_lib/vedic/houses.py +30 -0
- package/azure-function/python/kundali_lib/vedic/jaimini.py +361 -0
- package/azure-function/python/kundali_lib/vedic/kalachakra.py +226 -0
- package/azure-function/python/kundali_lib/vedic/kartari.py +243 -0
- package/azure-function/python/kundali_lib/vedic/kurmachakra.py +383 -0
- package/azure-function/python/kundali_lib/vedic/lunar_return.py +402 -0
- package/azure-function/python/kundali_lib/vedic/muhurta.py +414 -0
- package/azure-function/python/kundali_lib/vedic/nabhasha.py +349 -0
- package/azure-function/python/kundali_lib/vedic/nakshatra_details.py +945 -0
- package/azure-function/python/kundali_lib/vedic/panchanga.py +297 -0
- package/azure-function/python/kundali_lib/vedic/planets.py +55 -0
- package/azure-function/python/kundali_lib/vedic/shadbala.py +500 -0
- package/azure-function/python/kundali_lib/vedic/special_conditions.py +319 -0
- package/azure-function/python/kundali_lib/vedic/sudarshana.py +232 -0
- package/azure-function/python/kundali_lib/vedic/tajaka.py +482 -0
- package/azure-function/python/kundali_lib/vedic/upagrahas.py +229 -0
- package/azure-function/python/kundali_lib/vedic/varshaphal.py +185 -0
- package/azure-function/python/kundali_lib/vedic/yogas.py +935 -0
- package/azure-function/python/kundali_lib/vedic/zodiac.py +42 -0
- package/azure-function/python/kundali_lib/vedicastro_bridge.py +198 -0
- package/azure-function/requirements.txt +9 -0
- package/index.js +747 -0
- package/kundali-chart-mcp.js +159 -0
- package/kundali_bridge.py +952 -0
- package/package.json +41 -0
- package/python/kundali_lib/__init__.py +1 -0
- package/python/kundali_lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/kundali_lib/__pycache__/ephemeris.cpython-313.pyc +0 -0
- package/python/kundali_lib/__pycache__/geocoder.cpython-313.pyc +0 -0
- package/python/kundali_lib/__pycache__/vedicastro_bridge.cpython-313.pyc +0 -0
- package/python/kundali_lib/ephemeris.py +30 -0
- package/python/kundali_lib/geocoder.py +82 -0
- package/python/kundali_lib/vedic/__init__.py +1 -0
- package/python/kundali_lib/vedic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/arishta.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/ashtakavarga.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/avasthas.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/ayanamsa.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/bhava_chalit.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/char_dasha.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/chart.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/chart_types.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/compatibility.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/constants.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/dasha_extended.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/dasha_systems.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/doshas.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/gandanta.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/gochara.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/hora.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/houses.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/jaimini.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/kalachakra.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/kartari.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/kurmachakra.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/lunar_return.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/muhurta.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/nabhasha.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/nakshatra_details.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/panchanga.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/planets.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/shadbala.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/special_conditions.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/sudarshana.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/tajaka.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/upagrahas.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/varshaphal.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/yogas.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/zodiac.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/arishta.py +465 -0
- package/python/kundali_lib/vedic/ashtakavarga.py +213 -0
- package/python/kundali_lib/vedic/avasthas.py +292 -0
- package/python/kundali_lib/vedic/ayanamsa.py +106 -0
- package/python/kundali_lib/vedic/bhava_chalit.py +137 -0
- package/python/kundali_lib/vedic/char_dasha.py +308 -0
- package/python/kundali_lib/vedic/chart.py +126 -0
- package/python/kundali_lib/vedic/chart_types.py +338 -0
- package/python/kundali_lib/vedic/compatibility.py +705 -0
- package/python/kundali_lib/vedic/constants.py +108 -0
- package/python/kundali_lib/vedic/dasha_extended.py +262 -0
- package/python/kundali_lib/vedic/dasha_systems.py +439 -0
- package/python/kundali_lib/vedic/doshas.py +453 -0
- package/python/kundali_lib/vedic/gandanta.py +213 -0
- package/python/kundali_lib/vedic/gochara.py +277 -0
- package/python/kundali_lib/vedic/hora.py +263 -0
- package/python/kundali_lib/vedic/houses.py +30 -0
- package/python/kundali_lib/vedic/jaimini.py +361 -0
- package/python/kundali_lib/vedic/kalachakra.py +226 -0
- package/python/kundali_lib/vedic/kartari.py +243 -0
- package/python/kundali_lib/vedic/kurmachakra.py +383 -0
- package/python/kundali_lib/vedic/lunar_return.py +402 -0
- package/python/kundali_lib/vedic/muhurta.py +414 -0
- package/python/kundali_lib/vedic/nabhasha.py +349 -0
- package/python/kundali_lib/vedic/nakshatra_details.py +945 -0
- package/python/kundali_lib/vedic/panchanga.py +297 -0
- package/python/kundali_lib/vedic/planets.py +55 -0
- package/python/kundali_lib/vedic/shadbala.py +500 -0
- package/python/kundali_lib/vedic/special_conditions.py +319 -0
- package/python/kundali_lib/vedic/sudarshana.py +232 -0
- package/python/kundali_lib/vedic/tajaka.py +482 -0
- package/python/kundali_lib/vedic/upagrahas.py +229 -0
- package/python/kundali_lib/vedic/varshaphal.py +185 -0
- package/python/kundali_lib/vedic/yogas.py +935 -0
- package/python/kundali_lib/vedic/zodiac.py +42 -0
- package/python/kundali_lib/vedicastro_bridge.py +198 -0
- package/remote-server.js +590 -0
- package/requirements.txt +8 -0
- package/setup.sh +218 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""Char Dasha (Jaimini): Sign-based dasha system — one of the most important in Jaimini astrology."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
|
|
5
|
+
from kundali_lib.vedic.constants import RASHIS
|
|
6
|
+
|
|
7
|
+
# ── Constants ─────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
MOVABLE_SIGNS = ["Aries", "Cancer", "Libra", "Capricorn"]
|
|
10
|
+
FIXED_SIGNS = ["Taurus", "Leo", "Scorpio", "Aquarius"]
|
|
11
|
+
DUAL_SIGNS = ["Gemini", "Virgo", "Sagittarius", "Pisces"]
|
|
12
|
+
|
|
13
|
+
SIGN_LORD: dict[str, str] = {
|
|
14
|
+
"Aries": "Mars",
|
|
15
|
+
"Taurus": "Venus",
|
|
16
|
+
"Gemini": "Mercury",
|
|
17
|
+
"Cancer": "Moon",
|
|
18
|
+
"Leo": "Sun",
|
|
19
|
+
"Virgo": "Mercury",
|
|
20
|
+
"Libra": "Venus",
|
|
21
|
+
"Scorpio": "Mars",
|
|
22
|
+
"Sagittarius": "Jupiter",
|
|
23
|
+
"Capricorn": "Saturn",
|
|
24
|
+
"Aquarius": "Saturn",
|
|
25
|
+
"Pisces": "Jupiter",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Planets eligible for Atmakaraka (7-karaka system, Rahu included)
|
|
29
|
+
_AK_PLANETS = {"Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu"}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── Private helpers ───────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _rotated_signs(start_sign: str) -> list:
|
|
36
|
+
"""Return RASHIS rotated so it begins at start_sign."""
|
|
37
|
+
idx = RASHIS.index(start_sign)
|
|
38
|
+
return RASHIS[idx:] + RASHIS[:idx]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _get_atmakaraka(planetary_positions: list) -> tuple:
|
|
42
|
+
"""Return (ak_planet_name, ak_sign, degree_in_sign).
|
|
43
|
+
|
|
44
|
+
Atmakaraka = planet with the highest degree within its sign.
|
|
45
|
+
Rahu's degree is inverted (30 − degree) because it moves retrograde.
|
|
46
|
+
"""
|
|
47
|
+
best_name = "Sun"
|
|
48
|
+
best_sign = "Aries"
|
|
49
|
+
best_raw_degree = 0.0
|
|
50
|
+
best_eff_degree = -1.0
|
|
51
|
+
|
|
52
|
+
for p in planetary_positions:
|
|
53
|
+
name = p.get("name", "")
|
|
54
|
+
if name not in _AK_PLANETS:
|
|
55
|
+
continue
|
|
56
|
+
raw_deg = p.get("degree", 0.0) # degree within sign (0–30)
|
|
57
|
+
eff_deg = (30.0 - raw_deg) if name == "Rahu" else raw_deg
|
|
58
|
+
|
|
59
|
+
if eff_deg > best_eff_degree:
|
|
60
|
+
best_eff_degree = eff_deg
|
|
61
|
+
best_name = name
|
|
62
|
+
best_sign = p.get("rashi", "Aries")
|
|
63
|
+
best_raw_degree = raw_deg
|
|
64
|
+
|
|
65
|
+
return best_name, best_sign, best_raw_degree
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _planet_sign_map(planetary_positions: list) -> dict[str, str]:
|
|
69
|
+
"""Return {planet_name: rashi} for all planets in the chart."""
|
|
70
|
+
return {p["name"]: p.get("rashi", "") for p in planetary_positions}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _sign_has_planet(planetary_positions: list) -> dict[str, bool]:
|
|
74
|
+
"""Return {rashi: True} for every sign occupied by at least one planet."""
|
|
75
|
+
occupied: dict[str, bool] = {}
|
|
76
|
+
for p in planetary_positions:
|
|
77
|
+
rashi = p.get("rashi", "")
|
|
78
|
+
if rashi:
|
|
79
|
+
occupied[rashi] = True
|
|
80
|
+
return occupied
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _char_years_for_sign(
|
|
84
|
+
sign: str,
|
|
85
|
+
sign_idx: int,
|
|
86
|
+
lord_sign: str,
|
|
87
|
+
occupied: dict[str, bool],
|
|
88
|
+
forward: bool,
|
|
89
|
+
) -> int:
|
|
90
|
+
"""Calculate Char Dasha years for one sign.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
sign: The sign being evaluated (e.g. "Aries").
|
|
94
|
+
sign_idx: 0-based index of sign in RASHIS (0 = Aries … 11 = Pisces).
|
|
95
|
+
lord_sign: The sign occupied by this sign's lord in the natal chart.
|
|
96
|
+
occupied: Mapping of which signs are occupied by planets.
|
|
97
|
+
forward: True → count forward (movable/dual/odd-Narayana); False → count backward.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Clamped integer years in [1, 12].
|
|
101
|
+
"""
|
|
102
|
+
lord_idx = RASHIS.index(lord_sign)
|
|
103
|
+
|
|
104
|
+
if forward:
|
|
105
|
+
distance = (lord_idx - sign_idx) % 12 + 1
|
|
106
|
+
count = 13 - distance # lord in same sign → distance=1 → count=12
|
|
107
|
+
else:
|
|
108
|
+
count = (sign_idx - lord_idx) % 12 + 1 # lord in same sign → 0%12+1=1
|
|
109
|
+
|
|
110
|
+
# Subtract 1 if the 7th sign from this sign has any planet
|
|
111
|
+
seventh_sign = RASHIS[(sign_idx + 6) % 12]
|
|
112
|
+
if occupied.get(seventh_sign, False):
|
|
113
|
+
count -= 1
|
|
114
|
+
|
|
115
|
+
return max(1, min(12, count))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _compute_char_dasha_years(planetary_positions: list) -> dict[str, int]:
|
|
119
|
+
"""Calculate Char Dasha years for each of the 12 signs.
|
|
120
|
+
|
|
121
|
+
Direction rules:
|
|
122
|
+
- Movable signs (Aries, Cancer, Libra, Capricorn): count forward
|
|
123
|
+
- Fixed signs (Taurus, Leo, Scorpio, Aquarius): count backward
|
|
124
|
+
- Dual signs (Gemini, Virgo, Sagittarius, Pisces): count forward
|
|
125
|
+
"""
|
|
126
|
+
pmap = _planet_sign_map(planetary_positions)
|
|
127
|
+
occupied = _sign_has_planet(planetary_positions)
|
|
128
|
+
sign_years: dict[str, int] = {}
|
|
129
|
+
|
|
130
|
+
for i, sign in enumerate(RASHIS):
|
|
131
|
+
lord = SIGN_LORD[sign]
|
|
132
|
+
lord_sign = pmap.get(lord)
|
|
133
|
+
|
|
134
|
+
if lord_sign is None:
|
|
135
|
+
# Lord not found — assign a neutral default
|
|
136
|
+
sign_years[sign] = 7
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
forward = sign in MOVABLE_SIGNS or sign in DUAL_SIGNS
|
|
140
|
+
sign_years[sign] = _char_years_for_sign(sign, i, lord_sign, occupied, forward)
|
|
141
|
+
|
|
142
|
+
return sign_years
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _compute_narayana_years(planetary_positions: list) -> dict[str, int]:
|
|
146
|
+
"""Calculate Narayana Dasha years for each of the 12 signs.
|
|
147
|
+
|
|
148
|
+
Direction rules:
|
|
149
|
+
- Odd signs (1-based: Aries, Gemini, Leo, Libra, Sagittarius, Aquarius): count forward
|
|
150
|
+
- Even signs (1-based: Taurus, Cancer, Virgo, Scorpio, Capricorn, Pisces): count backward
|
|
151
|
+
"""
|
|
152
|
+
pmap = _planet_sign_map(planetary_positions)
|
|
153
|
+
occupied = _sign_has_planet(planetary_positions)
|
|
154
|
+
sign_years: dict[str, int] = {}
|
|
155
|
+
|
|
156
|
+
for i, sign in enumerate(RASHIS):
|
|
157
|
+
lord = SIGN_LORD[sign]
|
|
158
|
+
lord_sign = pmap.get(lord)
|
|
159
|
+
|
|
160
|
+
if lord_sign is None:
|
|
161
|
+
sign_years[sign] = 7
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
# 0-based even index → 1-based odd sign → forward
|
|
165
|
+
forward = i % 2 == 0
|
|
166
|
+
sign_years[sign] = _char_years_for_sign(sign, i, lord_sign, occupied, forward)
|
|
167
|
+
|
|
168
|
+
return sign_years
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _build_sign_dashas(
|
|
172
|
+
start_sign: str,
|
|
173
|
+
fraction_elapsed: float,
|
|
174
|
+
sign_years: dict[str, int],
|
|
175
|
+
birth_dt: datetime,
|
|
176
|
+
) -> dict:
|
|
177
|
+
"""Build mahadasha entries for all 12 signs with nested antardashas.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
start_sign: First sign in the dasha sequence.
|
|
181
|
+
fraction_elapsed: Fraction of the first sign's period already elapsed at birth (0–1).
|
|
182
|
+
sign_years: {sign: years} map for all 12 signs.
|
|
183
|
+
birth_dt: Timezone-aware (or naive) birth datetime.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
dict: mahadashas structure keyed by sign name.
|
|
187
|
+
"""
|
|
188
|
+
total_years = sum(sign_years.values())
|
|
189
|
+
sign_seq = _rotated_signs(start_sign)
|
|
190
|
+
|
|
191
|
+
result: dict = {}
|
|
192
|
+
# The first sign's period started (fraction_elapsed * first_sign_years) before birth
|
|
193
|
+
first_sign_years = sign_years[start_sign]
|
|
194
|
+
md_cursor = birth_dt - timedelta(days=fraction_elapsed * first_sign_years * 365.25)
|
|
195
|
+
|
|
196
|
+
for i, md_sign in enumerate(sign_seq):
|
|
197
|
+
md_years = sign_years[md_sign]
|
|
198
|
+
md_days = md_years * 365.25
|
|
199
|
+
|
|
200
|
+
md_start = md_cursor
|
|
201
|
+
md_end = md_start + timedelta(days=md_days)
|
|
202
|
+
md_cursor = md_end
|
|
203
|
+
|
|
204
|
+
display_duration: float = (
|
|
205
|
+
round((1.0 - fraction_elapsed) * md_years, 4) if i == 0 else float(md_years)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# ── Antardashas: 12 sub-signs starting from md_sign, zodiacal order ──
|
|
209
|
+
result_ads: dict = {}
|
|
210
|
+
ad_cursor = md_start
|
|
211
|
+
for ad_sign in _rotated_signs(md_sign):
|
|
212
|
+
ad_years = (sign_years[ad_sign] / total_years) * md_years
|
|
213
|
+
ad_days = ad_years * 365.25
|
|
214
|
+
ad_end = ad_cursor + timedelta(days=ad_days)
|
|
215
|
+
|
|
216
|
+
result_ads[ad_sign] = {
|
|
217
|
+
"start": ad_cursor.strftime("%d-%m-%Y"),
|
|
218
|
+
"end": ad_end.strftime("%d-%m-%Y"),
|
|
219
|
+
}
|
|
220
|
+
ad_cursor = ad_end
|
|
221
|
+
|
|
222
|
+
result[md_sign] = {
|
|
223
|
+
"start": md_start.strftime("%d-%m-%Y"),
|
|
224
|
+
"end": md_end.strftime("%d-%m-%Y"),
|
|
225
|
+
"duration_years": display_duration,
|
|
226
|
+
"antardashas": result_ads,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# ── Public functions ──────────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def get_char_dasha(base_chart: dict, birth_dt: datetime) -> dict:
|
|
236
|
+
"""Calculate Char Dasha (Jaimini sign-based dasha system).
|
|
237
|
+
|
|
238
|
+
Starting sign = sign occupied by the Atmakaraka (planet with highest
|
|
239
|
+
degree within its sign in the natal chart). The sequence then proceeds
|
|
240
|
+
through all 12 signs in zodiacal order.
|
|
241
|
+
|
|
242
|
+
Sub-periods (antardashas) within each sign-dasha rotate through all
|
|
243
|
+
12 signs starting from the main sign:
|
|
244
|
+
antardasha_duration = (sub_sign_years / total_years) × main_sign_years
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
base_chart: Full birth chart dict from ``build_chart()``.
|
|
248
|
+
birth_dt: Timezone-aware birth datetime.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
dict with ``system``, ``atmakaraka``, ``atmakaraka_sign``,
|
|
252
|
+
``starting_sign``, ``sign_dasha_years``, and ``mahadashas``.
|
|
253
|
+
"""
|
|
254
|
+
positions: list = base_chart.get("planetary_positions", [])
|
|
255
|
+
|
|
256
|
+
ak_name, ak_sign, ak_degree = _get_atmakaraka(positions)
|
|
257
|
+
sign_years = _compute_char_dasha_years(positions)
|
|
258
|
+
|
|
259
|
+
# Fraction of the first sign's dasha elapsed: AK's degree within its sign / 30
|
|
260
|
+
fraction_elapsed = ak_degree / 30.0
|
|
261
|
+
|
|
262
|
+
mahadashas = _build_sign_dashas(ak_sign, fraction_elapsed, sign_years, birth_dt)
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
"system": "Char Dasha (Jaimini)",
|
|
266
|
+
"atmakaraka": ak_name,
|
|
267
|
+
"atmakaraka_sign": ak_sign,
|
|
268
|
+
"starting_sign": ak_sign,
|
|
269
|
+
"sign_dasha_years": sign_years,
|
|
270
|
+
"mahadashas": mahadashas,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def get_narayana_dasha(base_chart: dict, birth_dt: datetime) -> dict:
|
|
275
|
+
"""Calculate Narayana Dasha (sign-based dasha for worldly and outer circumstances).
|
|
276
|
+
|
|
277
|
+
Narayana Dasha uses the Lagna (Ascendant) sign as the starting sign rather
|
|
278
|
+
than the Atmakaraka's sign. Direction of counting for sign years depends on
|
|
279
|
+
whether the sign is odd (forward) or even (backward) in the natural zodiac.
|
|
280
|
+
|
|
281
|
+
Sub-periods follow the same proportional formula as Char Dasha.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
base_chart: Full birth chart dict from ``build_chart()``.
|
|
285
|
+
birth_dt: Timezone-aware birth datetime.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
dict with ``system``, ``lagna_sign``, ``starting_sign``,
|
|
289
|
+
``sign_dasha_years``, and ``mahadashas``.
|
|
290
|
+
"""
|
|
291
|
+
positions: list = base_chart.get("planetary_positions", [])
|
|
292
|
+
ascendant: dict = base_chart.get("ascendant", {})
|
|
293
|
+
|
|
294
|
+
lagna_sign: str = ascendant.get("rashi", "Aries")
|
|
295
|
+
# Degree of Lagna within its sign determines fraction elapsed in first period
|
|
296
|
+
lagna_degree: float = ascendant.get("degree", 0.0) # 0–30
|
|
297
|
+
fraction_elapsed = lagna_degree / 30.0
|
|
298
|
+
|
|
299
|
+
sign_years = _compute_narayana_years(positions)
|
|
300
|
+
mahadashas = _build_sign_dashas(lagna_sign, fraction_elapsed, sign_years, birth_dt)
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
"system": "Narayana Dasha",
|
|
304
|
+
"lagna_sign": lagna_sign,
|
|
305
|
+
"starting_sign": lagna_sign,
|
|
306
|
+
"sign_dasha_years": sign_years,
|
|
307
|
+
"mahadashas": mahadashas,
|
|
308
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Build full birth chart from datetime and location."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
import pytz
|
|
6
|
+
import swisseph as swe
|
|
7
|
+
|
|
8
|
+
from kundali_lib.vedic.ayanamsa import (
|
|
9
|
+
_SIDM_MAP,
|
|
10
|
+
DEFAULT_AYANAMSA,
|
|
11
|
+
DEFAULT_HOUSE_SYSTEM,
|
|
12
|
+
)
|
|
13
|
+
from kundali_lib.vedic.constants import PLANET_IDS
|
|
14
|
+
from kundali_lib.vedic.houses import (
|
|
15
|
+
calc_houses_sidereal_with_system,
|
|
16
|
+
houses_dict_and_degrees,
|
|
17
|
+
)
|
|
18
|
+
from kundali_lib.vedic.planets import calc_planet_position
|
|
19
|
+
from kundali_lib.vedic.zodiac import (
|
|
20
|
+
get_nakshatra,
|
|
21
|
+
get_nakshatra_lord,
|
|
22
|
+
get_rashi,
|
|
23
|
+
get_rashi_lord,
|
|
24
|
+
position_dict,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# PLANET_NAMES is iteration order for planets
|
|
28
|
+
PLANET_NAMES = list(PLANET_IDS.keys())
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _jd_ut(birth_dt: datetime, timezone: str) -> float:
|
|
32
|
+
if timezone:
|
|
33
|
+
tz = pytz.timezone(timezone)
|
|
34
|
+
local = tz.localize(birth_dt)
|
|
35
|
+
utc = local.astimezone(pytz.UTC)
|
|
36
|
+
else:
|
|
37
|
+
utc = pytz.UTC.localize(birth_dt)
|
|
38
|
+
h = utc.hour + utc.minute / 60.0 + utc.second / 3600.0
|
|
39
|
+
return swe.julday(utc.year, utc.month, utc.day, h)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _assign_houses(planetary_positions: list[dict], asc_lon: float) -> None:
|
|
43
|
+
asc_rashi_i = int(asc_lon / 30) % 12
|
|
44
|
+
for p in planetary_positions:
|
|
45
|
+
rashi_i = int(p["longitude"] / 30) % 12
|
|
46
|
+
p["house"] = (rashi_i - asc_rashi_i) % 12 + 1
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _ayanamsa_name(mode_key: str) -> str:
|
|
50
|
+
try:
|
|
51
|
+
const = _SIDM_MAP.get(mode_key, _SIDM_MAP["lahiri"])
|
|
52
|
+
name = (swe.get_ayanamsa_name(const) or mode_key.title()).strip()
|
|
53
|
+
return name or "Lahiri"
|
|
54
|
+
except Exception:
|
|
55
|
+
return "Lahiri"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def build_chart(
|
|
59
|
+
name: str,
|
|
60
|
+
birth_dt: datetime,
|
|
61
|
+
lat: float,
|
|
62
|
+
lon: float,
|
|
63
|
+
timezone: str,
|
|
64
|
+
ayanamsa_mode: str | None = None,
|
|
65
|
+
house_system: str | None = None,
|
|
66
|
+
) -> dict:
|
|
67
|
+
"""Full chart: planets, houses, ascendant, ayanamsa.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
ayanamsa_mode: Key like 'lahiri', 'raman', 'krishnamurti', etc. Default 'lahiri'.
|
|
71
|
+
house_system: Single-char code like 'P' (Placidus), 'K' (Koch), 'E' (Equal), etc. Default 'P'.
|
|
72
|
+
"""
|
|
73
|
+
swe.set_ephe_path(None)
|
|
74
|
+
mode_key = (ayanamsa_mode or DEFAULT_AYANAMSA).lower().replace("-", "_")
|
|
75
|
+
if mode_key not in _SIDM_MAP:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"Unsupported ayanamsa_mode '{ayanamsa_mode}'. Use list_ayanamsa_modes to see options."
|
|
78
|
+
)
|
|
79
|
+
swe.set_sid_mode(_SIDM_MAP[mode_key])
|
|
80
|
+
|
|
81
|
+
hs = (house_system or DEFAULT_HOUSE_SYSTEM).upper()[:1]
|
|
82
|
+
|
|
83
|
+
jd = _jd_ut(birth_dt, timezone)
|
|
84
|
+
planetary_positions = [calc_planet_position(pname, jd) for pname in PLANET_NAMES]
|
|
85
|
+
cusps, ascmc = calc_houses_sidereal_with_system(jd, lat, lon, hs)
|
|
86
|
+
asc_lon = cusps[0]
|
|
87
|
+
mc_lon = ascmc[1] if len(ascmc) > 1 else cusps[9]
|
|
88
|
+
|
|
89
|
+
_assign_houses(planetary_positions, asc_lon)
|
|
90
|
+
houses, house_cusp_degrees = houses_dict_and_degrees(cusps)
|
|
91
|
+
ascendant = {"longitude": asc_lon, **position_dict(asc_lon)}
|
|
92
|
+
moon_pos = next(p for p in planetary_positions if p["name"] == "Moon")
|
|
93
|
+
sun_pos = next(p for p in planetary_positions if p["name"] == "Sun")
|
|
94
|
+
ayanamsa = swe.get_ayanamsa_ut(jd)
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"name": name,
|
|
98
|
+
"birth_details": {
|
|
99
|
+
"datetime": birth_dt.isoformat(),
|
|
100
|
+
"latitude": lat,
|
|
101
|
+
"longitude": lon,
|
|
102
|
+
"timezone": timezone,
|
|
103
|
+
},
|
|
104
|
+
"planetary_positions": planetary_positions,
|
|
105
|
+
"houses": houses,
|
|
106
|
+
"house_cusp_degrees": house_cusp_degrees,
|
|
107
|
+
"ascendant": ascendant,
|
|
108
|
+
"mc_longitude": round(mc_lon, 4),
|
|
109
|
+
"moon_sign": moon_pos["rashi"],
|
|
110
|
+
"sun_sign": sun_pos["rashi"],
|
|
111
|
+
"ayanamsa": round(ayanamsa, 4),
|
|
112
|
+
"ayanamsa_name": _ayanamsa_name(mode_key),
|
|
113
|
+
"ayanamsa_mode": mode_key,
|
|
114
|
+
"house_system": hs,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_transit_positions(dt_utc: datetime) -> list[dict]:
|
|
119
|
+
"""Sidereal (Lahiri) planetary positions for a given UTC moment (e.g. 'now' for transits)."""
|
|
120
|
+
swe.set_ephe_path(None)
|
|
121
|
+
swe.set_sid_mode(_SIDM_MAP["lahiri"])
|
|
122
|
+
if dt_utc.tzinfo is None:
|
|
123
|
+
dt_utc = pytz.UTC.localize(dt_utc)
|
|
124
|
+
h = dt_utc.hour + dt_utc.minute / 60.0 + dt_utc.second / 3600.0
|
|
125
|
+
jd = swe.julday(dt_utc.year, dt_utc.month, dt_utc.day, h)
|
|
126
|
+
return [calc_planet_position(pname, jd) for pname in PLANET_NAMES]
|