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,319 @@
|
|
|
1
|
+
"""Special planetary conditions: Vargottama, Jaimini Karakas, Dig Bala, Pushkara Navamsha."""
|
|
2
|
+
|
|
3
|
+
# ---------------------------------------------------------------------------
|
|
4
|
+
# Constants
|
|
5
|
+
# ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
RASHIS = [
|
|
8
|
+
"Aries",
|
|
9
|
+
"Taurus",
|
|
10
|
+
"Gemini",
|
|
11
|
+
"Cancer",
|
|
12
|
+
"Leo",
|
|
13
|
+
"Virgo",
|
|
14
|
+
"Libra",
|
|
15
|
+
"Scorpio",
|
|
16
|
+
"Sagittarius",
|
|
17
|
+
"Capricorn",
|
|
18
|
+
"Aquarius",
|
|
19
|
+
"Pisces",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
# Jaimini Chara Karaka planets (Rahu included as 8th)
|
|
23
|
+
KARAKA_PLANETS = [
|
|
24
|
+
"Sun",
|
|
25
|
+
"Moon",
|
|
26
|
+
"Mars",
|
|
27
|
+
"Mercury",
|
|
28
|
+
"Jupiter",
|
|
29
|
+
"Venus",
|
|
30
|
+
"Saturn",
|
|
31
|
+
"Rahu",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
KARAKA_NAMES = [
|
|
35
|
+
"Atmakaraka", # AK — highest degree
|
|
36
|
+
"Amatyakaraka", # AmK
|
|
37
|
+
"Bhratrukaraka", # BK
|
|
38
|
+
"Matrukaraka", # MK
|
|
39
|
+
"Putrakaraka", # PK
|
|
40
|
+
"Gnatikaraka", # GK
|
|
41
|
+
"Darakaraka", # DK — lowest degree (7th)
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# Dig Bala: planet → best house (1-based)
|
|
45
|
+
DIG_BALA_BEST = {
|
|
46
|
+
"Sun": 10,
|
|
47
|
+
"Mars": 10,
|
|
48
|
+
"Moon": 4,
|
|
49
|
+
"Venus": 4,
|
|
50
|
+
"Mercury": 1,
|
|
51
|
+
"Jupiter": 1,
|
|
52
|
+
"Saturn": 7,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
KENDRA_HOUSES = {1, 4, 7, 10}
|
|
56
|
+
|
|
57
|
+
# Pushkara Navamsha degree ranges per rashi (inclusive lower, exclusive upper)
|
|
58
|
+
# Source: classical texts — the benefic Navamsha within each sign
|
|
59
|
+
PUSHKARA_RANGES: dict[str, tuple[float, float]] = {
|
|
60
|
+
"Aries": (21.0, 23.2),
|
|
61
|
+
"Taurus": (10.0, 13.3),
|
|
62
|
+
"Gemini": (28.0, 30.0),
|
|
63
|
+
"Cancer": (25.0, 27.2),
|
|
64
|
+
"Leo": (22.0, 24.4),
|
|
65
|
+
"Virgo": (19.0, 21.7),
|
|
66
|
+
"Libra": (16.0, 18.9),
|
|
67
|
+
"Scorpio": (13.0, 16.0),
|
|
68
|
+
"Sagittarius": (10.0, 13.3),
|
|
69
|
+
"Capricorn": (7.0, 10.6),
|
|
70
|
+
"Aquarius": (4.0, 7.8),
|
|
71
|
+
"Pisces": (1.0, 4.4),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Helpers
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _get_planet(positions: list, name: str) -> dict | None:
|
|
81
|
+
for p in positions:
|
|
82
|
+
if p.get("name", "").lower() == name.lower():
|
|
83
|
+
return p
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _degree_in_sign(longitude: float) -> float:
|
|
88
|
+
"""Return the degrees elapsed within the current sign (0-30)."""
|
|
89
|
+
return longitude % 30
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _rashi_from_lon(lon: float) -> str:
|
|
93
|
+
return RASHIS[int(lon % 360 / 30)]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# 1. Vargottama
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def check_vargottama(planetary_positions: list, d9_positions: list) -> list:
|
|
102
|
+
"""Identify Vargottama planets (same rashi in D1 and D9).
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
planetary_positions : list
|
|
107
|
+
D1 (natal/rashi) planet dicts from build_chart().
|
|
108
|
+
d9_positions : list
|
|
109
|
+
D9 (Navamsa) planet dicts. Must contain at least ``name`` and
|
|
110
|
+
``rashi`` (or ``longitude``) fields.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
list of dict
|
|
115
|
+
Each entry: planet, d1_rashi, d9_rashi, is_vargottama.
|
|
116
|
+
"""
|
|
117
|
+
results: list[dict] = []
|
|
118
|
+
all_planet_names = {p["name"] for p in planetary_positions if "name" in p}
|
|
119
|
+
|
|
120
|
+
for pname in all_planet_names:
|
|
121
|
+
d1 = _get_planet(planetary_positions, pname)
|
|
122
|
+
d9 = _get_planet(d9_positions, pname)
|
|
123
|
+
|
|
124
|
+
d1_rashi = d1.get("rashi") if d1 else None
|
|
125
|
+
if d9 is not None:
|
|
126
|
+
d9_rashi = d9.get("rashi") or (
|
|
127
|
+
_rashi_from_lon(d9["longitude"]) if "longitude" in d9 else None
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
d9_rashi = None
|
|
131
|
+
|
|
132
|
+
is_vargottama = (
|
|
133
|
+
d1_rashi is not None and d9_rashi is not None and d1_rashi == d9_rashi
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
results.append(
|
|
137
|
+
{
|
|
138
|
+
"planet": pname,
|
|
139
|
+
"d1_rashi": d1_rashi,
|
|
140
|
+
"d9_rashi": d9_rashi,
|
|
141
|
+
"is_vargottama": is_vargottama,
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Stable ordering
|
|
146
|
+
results.sort(key=lambda x: x["planet"])
|
|
147
|
+
return results
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# 2. Jaimini Chara Karakas
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_jaimini_karakas(planetary_positions: list) -> dict:
|
|
156
|
+
"""Calculate the 7 Jaimini Chara Karakas from 8 significator planets.
|
|
157
|
+
|
|
158
|
+
Rahu's degree is reversed: effective_degree = 30 - degree_in_sign.
|
|
159
|
+
All 8 planets are sorted by effective degree (descending); the top 7
|
|
160
|
+
receive Karaka titles from Atmakaraka down to Darakaraka.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
planetary_positions : list
|
|
165
|
+
List of planet dicts from build_chart().
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
dict
|
|
170
|
+
Keys are Karaka names (Atmakaraka … Darakaraka); values are dicts
|
|
171
|
+
with ``planet``, ``degree_in_sign``, ``rashi``.
|
|
172
|
+
"""
|
|
173
|
+
planet_data: list[tuple[float, str, float, str]] = []
|
|
174
|
+
# (effective_degree, name, raw_degree_in_sign, rashi)
|
|
175
|
+
|
|
176
|
+
for pname in KARAKA_PLANETS:
|
|
177
|
+
p = _get_planet(planetary_positions, pname)
|
|
178
|
+
if p is None:
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
raw_deg = p.get("degree", None)
|
|
182
|
+
if raw_deg is None:
|
|
183
|
+
lon = p.get("longitude", 0.0)
|
|
184
|
+
raw_deg = _degree_in_sign(lon)
|
|
185
|
+
|
|
186
|
+
rashi = p.get("rashi") or _rashi_from_lon(p.get("longitude", 0.0))
|
|
187
|
+
|
|
188
|
+
if pname == "Rahu":
|
|
189
|
+
effective = 30.0 - (raw_deg % 30)
|
|
190
|
+
else:
|
|
191
|
+
effective = raw_deg % 30
|
|
192
|
+
|
|
193
|
+
planet_data.append((effective, pname, raw_deg % 30, rashi))
|
|
194
|
+
|
|
195
|
+
# Sort descending by effective degree; secondary sort by name for stability
|
|
196
|
+
planet_data.sort(key=lambda x: (-x[0], x[1]))
|
|
197
|
+
|
|
198
|
+
result: dict = {}
|
|
199
|
+
for rank, karaka_name in enumerate(KARAKA_NAMES):
|
|
200
|
+
if rank < len(planet_data):
|
|
201
|
+
eff, pname, raw_deg, rashi = planet_data[rank]
|
|
202
|
+
result[karaka_name] = {
|
|
203
|
+
"planet": pname,
|
|
204
|
+
"degree_in_sign": round(raw_deg, 4),
|
|
205
|
+
"rashi": rashi,
|
|
206
|
+
}
|
|
207
|
+
else:
|
|
208
|
+
result[karaka_name] = None
|
|
209
|
+
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
# 3. Dig Bala
|
|
215
|
+
# ---------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def get_dig_bala(planetary_positions: list) -> list:
|
|
219
|
+
"""Assess directional strength (Dig Bala) for each planet.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
planetary_positions : list
|
|
224
|
+
List of planet dicts from build_chart().
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
list of dict
|
|
229
|
+
Each entry: planet, house, best_house, dig_bala_strength.
|
|
230
|
+
Strength: "Full" (in best house), "Half" (any other kendra), "Weak" (other).
|
|
231
|
+
"""
|
|
232
|
+
results: list[dict] = []
|
|
233
|
+
|
|
234
|
+
for pname, best_house in DIG_BALA_BEST.items():
|
|
235
|
+
p = _get_planet(planetary_positions, pname)
|
|
236
|
+
if p is None:
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
house = p.get("house")
|
|
240
|
+
if house is None:
|
|
241
|
+
strength = "Unknown"
|
|
242
|
+
elif house == best_house:
|
|
243
|
+
strength = "Full"
|
|
244
|
+
elif house in KENDRA_HOUSES:
|
|
245
|
+
strength = "Half"
|
|
246
|
+
else:
|
|
247
|
+
strength = "Weak"
|
|
248
|
+
|
|
249
|
+
results.append(
|
|
250
|
+
{
|
|
251
|
+
"planet": pname,
|
|
252
|
+
"house": house,
|
|
253
|
+
"best_house": best_house,
|
|
254
|
+
"dig_bala_strength": strength,
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return results
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
# 4. Pushkara Navamsha
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def check_pushkara_navamsha(planetary_positions: list) -> list:
|
|
267
|
+
"""Identify planets occupying a Pushkara Navamsha degree.
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
planetary_positions : list
|
|
272
|
+
List of planet dicts from build_chart().
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
list of dict
|
|
277
|
+
Each entry: planet, rashi, degree_in_sign, is_pushkara.
|
|
278
|
+
"""
|
|
279
|
+
results: list[dict] = []
|
|
280
|
+
|
|
281
|
+
for p in planetary_positions:
|
|
282
|
+
pname = p.get("name", "Unknown")
|
|
283
|
+
rashi = p.get("rashi")
|
|
284
|
+
|
|
285
|
+
lon = p.get("longitude", None)
|
|
286
|
+
raw_deg = p.get("degree", None)
|
|
287
|
+
|
|
288
|
+
if raw_deg is None and lon is not None:
|
|
289
|
+
raw_deg = _degree_in_sign(float(lon))
|
|
290
|
+
elif raw_deg is not None:
|
|
291
|
+
raw_deg = float(raw_deg) % 30 # ensure within sign
|
|
292
|
+
|
|
293
|
+
if rashi is None and lon is not None:
|
|
294
|
+
rashi = _rashi_from_lon(float(lon))
|
|
295
|
+
|
|
296
|
+
if rashi is None or raw_deg is None:
|
|
297
|
+
results.append(
|
|
298
|
+
{
|
|
299
|
+
"planet": pname,
|
|
300
|
+
"rashi": rashi,
|
|
301
|
+
"degree_in_sign": raw_deg,
|
|
302
|
+
"is_pushkara": False,
|
|
303
|
+
}
|
|
304
|
+
)
|
|
305
|
+
continue
|
|
306
|
+
|
|
307
|
+
lo, hi = PUSHKARA_RANGES.get(rashi, (0.0, 0.0))
|
|
308
|
+
is_pushkara = lo <= raw_deg < hi
|
|
309
|
+
|
|
310
|
+
results.append(
|
|
311
|
+
{
|
|
312
|
+
"planet": pname,
|
|
313
|
+
"rashi": rashi,
|
|
314
|
+
"degree_in_sign": round(raw_deg, 4),
|
|
315
|
+
"is_pushkara": is_pushkara,
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
return results
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Sudarshana Chakra: Triple chart analysis from Lagna, Moon, and Sun simultaneously."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from kundali_lib.vedic.constants import RASHIS
|
|
6
|
+
|
|
7
|
+
# Houses considered auspicious / inauspicious in classical Vedic astrology
|
|
8
|
+
_TRIKONA = [1, 5, 9] # Trine houses — dharma, prosperity
|
|
9
|
+
_KENDRA = [1, 4, 7, 10] # Angular houses — strength, visibility
|
|
10
|
+
_DUSTHANA = [6, 8, 12] # Dusthana — suffering, obstacles, loss
|
|
11
|
+
_UPACHAYA = [3, 6, 10, 11] # Growth houses
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _house_from_reference(planet_rashi: str, reference_rashi: str) -> int:
|
|
15
|
+
"""Return the 1-based house number of planet_rashi counted from reference_rashi."""
|
|
16
|
+
ref_i = RASHIS.index(reference_rashi)
|
|
17
|
+
pla_i = RASHIS.index(planet_rashi)
|
|
18
|
+
return (pla_i - ref_i) % 12 + 1
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _build_view(
|
|
22
|
+
reference_label: str,
|
|
23
|
+
reference_rashi: str,
|
|
24
|
+
planetary_positions: list[dict],
|
|
25
|
+
) -> dict:
|
|
26
|
+
"""Build one of the three Sudarshana views (Lagna, Chandra, or Surya).
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
reference_label: Human-readable label, e.g. ``"Lagna in Leo"``.
|
|
30
|
+
reference_rashi: The sign that acts as House 1 for this view.
|
|
31
|
+
planetary_positions: From ``base_chart["planetary_positions"]``.
|
|
32
|
+
"""
|
|
33
|
+
positions: list[dict] = []
|
|
34
|
+
for p in planetary_positions:
|
|
35
|
+
house = _house_from_reference(p["rashi"], reference_rashi)
|
|
36
|
+
positions.append(
|
|
37
|
+
{
|
|
38
|
+
"planet": p["name"],
|
|
39
|
+
"rashi": p["rashi"],
|
|
40
|
+
"house_from_reference": house,
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"reference": reference_label,
|
|
46
|
+
"reference_rashi": reference_rashi,
|
|
47
|
+
"planetary_positions": positions,
|
|
48
|
+
"key_houses": {
|
|
49
|
+
"trikona": _TRIKONA,
|
|
50
|
+
"kendra": _KENDRA,
|
|
51
|
+
"dusthana": _DUSTHANA,
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _score_house(house: int) -> int:
|
|
57
|
+
"""Simple scoring: trikona/kendra positive, dusthana negative, rest neutral."""
|
|
58
|
+
if house in _TRIKONA or house in _KENDRA:
|
|
59
|
+
return 1
|
|
60
|
+
if house in _DUSTHANA:
|
|
61
|
+
return -1
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _consolidated_analysis(
|
|
66
|
+
lagna_view: dict,
|
|
67
|
+
chandra_view: dict,
|
|
68
|
+
surya_view: dict,
|
|
69
|
+
) -> dict:
|
|
70
|
+
"""Derive cross-view insights from all three Sudarshana perspectives."""
|
|
71
|
+
|
|
72
|
+
# Build per-house strength score across all three views
|
|
73
|
+
house_scores: dict[int, int] = {h: 0 for h in range(1, 13)}
|
|
74
|
+
|
|
75
|
+
for view in (lagna_view, chandra_view, surya_view):
|
|
76
|
+
for pos in view["planetary_positions"]:
|
|
77
|
+
h = pos["house_from_reference"]
|
|
78
|
+
house_scores[h] += _score_house(h)
|
|
79
|
+
|
|
80
|
+
strongest_house = max(house_scores, key=lambda h: house_scores[h])
|
|
81
|
+
weakest_house = min(house_scores, key=lambda h: house_scores[h])
|
|
82
|
+
|
|
83
|
+
# Per-planet consistency check
|
|
84
|
+
planet_scores: dict[str, int] = {}
|
|
85
|
+
for view in (lagna_view, chandra_view, surya_view):
|
|
86
|
+
for pos in view["planetary_positions"]:
|
|
87
|
+
pname = pos["planet"]
|
|
88
|
+
score = _score_house(pos["house_from_reference"])
|
|
89
|
+
planet_scores[pname] = planet_scores.get(pname, 0) + score
|
|
90
|
+
|
|
91
|
+
# Consistently well-placed: positive score in all 3 views (score == 3)
|
|
92
|
+
well_placed = [p for p, s in planet_scores.items() if s == 3]
|
|
93
|
+
# Consistently afflicted: negative score in all 3 views (score == -3)
|
|
94
|
+
afflicted = [p for p, s in planet_scores.items() if s == -3]
|
|
95
|
+
|
|
96
|
+
if well_placed and not afflicted:
|
|
97
|
+
summary = (
|
|
98
|
+
f"The Sudarshana Chakra shows strong harmony. "
|
|
99
|
+
f"{', '.join(well_placed)} "
|
|
100
|
+
f"{'are' if len(well_placed) > 1 else 'is'} well-placed across all three "
|
|
101
|
+
f"perspectives (Lagna, Moon, Sun), indicating consistent support in the "
|
|
102
|
+
f"significations of house {strongest_house}."
|
|
103
|
+
)
|
|
104
|
+
elif afflicted and not well_placed:
|
|
105
|
+
summary = (
|
|
106
|
+
f"Significant challenges indicated. "
|
|
107
|
+
f"{', '.join(afflicted)} "
|
|
108
|
+
f"{'are' if len(afflicted) > 1 else 'is'} afflicted across all three "
|
|
109
|
+
f"perspectives, requiring careful attention and remedial measures."
|
|
110
|
+
)
|
|
111
|
+
elif well_placed and afflicted:
|
|
112
|
+
summary = (
|
|
113
|
+
f"Mixed Sudarshana pattern. {', '.join(well_placed)} consistently strong; "
|
|
114
|
+
f"{', '.join(afflicted)} consistently under stress. Balance strengths against "
|
|
115
|
+
f"weaknesses in decision-making."
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
summary = (
|
|
119
|
+
"Balanced Sudarshana Chakra — no planet dominates strongly or weakly across "
|
|
120
|
+
"all three views. Results depend on active dashas and Gochara."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
"strongest_house": strongest_house,
|
|
125
|
+
"weakest_house": weakest_house,
|
|
126
|
+
"planets_consistently_well_placed": well_placed,
|
|
127
|
+
"planets_consistently_afflicted": afflicted,
|
|
128
|
+
"summary": summary,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _sudarshana_year_analysis(
|
|
133
|
+
birth_year: int,
|
|
134
|
+
current_year: int,
|
|
135
|
+
lagna_view: dict,
|
|
136
|
+
chandra_view: dict,
|
|
137
|
+
surya_view: dict,
|
|
138
|
+
) -> dict:
|
|
139
|
+
"""Compute the Sudarshana year and activated houses for each view.
|
|
140
|
+
|
|
141
|
+
The Sudarshana year cycles through the 12 houses annually.
|
|
142
|
+
Year 1 of life → House 1, Year 2 → House 2, etc. (cycles every 12 years).
|
|
143
|
+
"""
|
|
144
|
+
age = current_year - birth_year
|
|
145
|
+
if age < 0:
|
|
146
|
+
age = 0
|
|
147
|
+
year_house = (age % 12) + 1 # 1-based
|
|
148
|
+
|
|
149
|
+
def _planets_in_house(view: dict, house: int) -> list[str]:
|
|
150
|
+
return [
|
|
151
|
+
pos["planet"]
|
|
152
|
+
for pos in view["planetary_positions"]
|
|
153
|
+
if pos["house_from_reference"] == house
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
"age": age,
|
|
158
|
+
"sudarshana_year_number": age + 1,
|
|
159
|
+
"activated_house": year_house,
|
|
160
|
+
"lagna_view_activated_planets": _planets_in_house(lagna_view, year_house),
|
|
161
|
+
"chandra_view_activated_planets": _planets_in_house(chandra_view, year_house),
|
|
162
|
+
"surya_view_activated_planets": _planets_in_house(surya_view, year_house),
|
|
163
|
+
"note": (
|
|
164
|
+
f"Currently in Sudarshana year {age + 1} (House {year_house} activated). "
|
|
165
|
+
"The themes of this house are emphasised across all three chart perspectives."
|
|
166
|
+
),
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def get_sudarshana_chakra(
|
|
171
|
+
base_chart: dict,
|
|
172
|
+
current_year: int | None = None,
|
|
173
|
+
) -> dict:
|
|
174
|
+
"""Generate the full Sudarshana Chakra analysis.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
base_chart: Output of ``build_chart()``. Must contain
|
|
178
|
+
``planetary_positions``, ``ascendant``, ``moon_sign``,
|
|
179
|
+
``sun_sign``, and ``birth_details``.
|
|
180
|
+
current_year: The Gregorian year to use for the Sudarshana year
|
|
181
|
+
calculation. Defaults to the current year if omitted.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
A dict with ``lagna_view``, ``chandra_view``, ``surya_view``,
|
|
185
|
+
``consolidated_analysis``, and ``sudarshana_year``.
|
|
186
|
+
"""
|
|
187
|
+
if current_year is None:
|
|
188
|
+
current_year = datetime.utcnow().year
|
|
189
|
+
|
|
190
|
+
planetary_positions: list[dict] = base_chart["planetary_positions"]
|
|
191
|
+
lagna_rashi: str = base_chart["ascendant"]["rashi"]
|
|
192
|
+
moon_rashi: str = base_chart["moon_sign"]
|
|
193
|
+
sun_rashi: str = base_chart["sun_sign"]
|
|
194
|
+
|
|
195
|
+
# --- Three Views ---
|
|
196
|
+
lagna_view = _build_view(
|
|
197
|
+
reference_label=f"Lagna in {lagna_rashi}",
|
|
198
|
+
reference_rashi=lagna_rashi,
|
|
199
|
+
planetary_positions=planetary_positions,
|
|
200
|
+
)
|
|
201
|
+
chandra_view = _build_view(
|
|
202
|
+
reference_label=f"Chandra (Moon) in {moon_rashi}",
|
|
203
|
+
reference_rashi=moon_rashi,
|
|
204
|
+
planetary_positions=planetary_positions,
|
|
205
|
+
)
|
|
206
|
+
surya_view = _build_view(
|
|
207
|
+
reference_label=f"Surya (Sun) in {sun_rashi}",
|
|
208
|
+
reference_rashi=sun_rashi,
|
|
209
|
+
planetary_positions=planetary_positions,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# --- Consolidated Analysis ---
|
|
213
|
+
consolidated = _consolidated_analysis(lagna_view, chandra_view, surya_view)
|
|
214
|
+
|
|
215
|
+
# --- Sudarshana Year ---
|
|
216
|
+
birth_year_str = base_chart.get("birth_details", {}).get("datetime", "")
|
|
217
|
+
try:
|
|
218
|
+
birth_year = int(birth_year_str[:4])
|
|
219
|
+
except (ValueError, TypeError):
|
|
220
|
+
birth_year = current_year
|
|
221
|
+
|
|
222
|
+
sudarshana_year = _sudarshana_year_analysis(
|
|
223
|
+
birth_year, current_year, lagna_view, chandra_view, surya_view
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
"lagna_view": lagna_view,
|
|
228
|
+
"chandra_view": chandra_view,
|
|
229
|
+
"surya_view": surya_view,
|
|
230
|
+
"consolidated_analysis": consolidated,
|
|
231
|
+
"sudarshana_year": sudarshana_year,
|
|
232
|
+
}
|