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.
Files changed (172) hide show
  1. package/README.md +67 -0
  2. package/azure-function/function_app.py +93 -0
  3. package/azure-function/host.json +15 -0
  4. package/azure-function/kundali_bridge.py +952 -0
  5. package/azure-function/python/kundali_lib/__init__.py +1 -0
  6. package/azure-function/python/kundali_lib/__pycache__/__init__.cpython-313.pyc +0 -0
  7. package/azure-function/python/kundali_lib/__pycache__/ephemeris.cpython-313.pyc +0 -0
  8. package/azure-function/python/kundali_lib/__pycache__/geocoder.cpython-313.pyc +0 -0
  9. package/azure-function/python/kundali_lib/__pycache__/vedicastro_bridge.cpython-313.pyc +0 -0
  10. package/azure-function/python/kundali_lib/ephemeris.py +30 -0
  11. package/azure-function/python/kundali_lib/geocoder.py +82 -0
  12. package/azure-function/python/kundali_lib/vedic/__init__.py +1 -0
  13. package/azure-function/python/kundali_lib/vedic/__pycache__/__init__.cpython-313.pyc +0 -0
  14. package/azure-function/python/kundali_lib/vedic/__pycache__/arishta.cpython-313.pyc +0 -0
  15. package/azure-function/python/kundali_lib/vedic/__pycache__/ashtakavarga.cpython-313.pyc +0 -0
  16. package/azure-function/python/kundali_lib/vedic/__pycache__/avasthas.cpython-313.pyc +0 -0
  17. package/azure-function/python/kundali_lib/vedic/__pycache__/ayanamsa.cpython-313.pyc +0 -0
  18. package/azure-function/python/kundali_lib/vedic/__pycache__/bhava_chalit.cpython-313.pyc +0 -0
  19. package/azure-function/python/kundali_lib/vedic/__pycache__/char_dasha.cpython-313.pyc +0 -0
  20. package/azure-function/python/kundali_lib/vedic/__pycache__/chart.cpython-313.pyc +0 -0
  21. package/azure-function/python/kundali_lib/vedic/__pycache__/chart_types.cpython-313.pyc +0 -0
  22. package/azure-function/python/kundali_lib/vedic/__pycache__/compatibility.cpython-313.pyc +0 -0
  23. package/azure-function/python/kundali_lib/vedic/__pycache__/constants.cpython-313.pyc +0 -0
  24. package/azure-function/python/kundali_lib/vedic/__pycache__/dasha_extended.cpython-313.pyc +0 -0
  25. package/azure-function/python/kundali_lib/vedic/__pycache__/dasha_systems.cpython-313.pyc +0 -0
  26. package/azure-function/python/kundali_lib/vedic/__pycache__/doshas.cpython-313.pyc +0 -0
  27. package/azure-function/python/kundali_lib/vedic/__pycache__/gandanta.cpython-313.pyc +0 -0
  28. package/azure-function/python/kundali_lib/vedic/__pycache__/gochara.cpython-313.pyc +0 -0
  29. package/azure-function/python/kundali_lib/vedic/__pycache__/hora.cpython-313.pyc +0 -0
  30. package/azure-function/python/kundali_lib/vedic/__pycache__/houses.cpython-313.pyc +0 -0
  31. package/azure-function/python/kundali_lib/vedic/__pycache__/jaimini.cpython-313.pyc +0 -0
  32. package/azure-function/python/kundali_lib/vedic/__pycache__/kalachakra.cpython-313.pyc +0 -0
  33. package/azure-function/python/kundali_lib/vedic/__pycache__/kartari.cpython-313.pyc +0 -0
  34. package/azure-function/python/kundali_lib/vedic/__pycache__/kurmachakra.cpython-313.pyc +0 -0
  35. package/azure-function/python/kundali_lib/vedic/__pycache__/lunar_return.cpython-313.pyc +0 -0
  36. package/azure-function/python/kundali_lib/vedic/__pycache__/muhurta.cpython-313.pyc +0 -0
  37. package/azure-function/python/kundali_lib/vedic/__pycache__/nabhasha.cpython-313.pyc +0 -0
  38. package/azure-function/python/kundali_lib/vedic/__pycache__/nakshatra_details.cpython-313.pyc +0 -0
  39. package/azure-function/python/kundali_lib/vedic/__pycache__/panchanga.cpython-313.pyc +0 -0
  40. package/azure-function/python/kundali_lib/vedic/__pycache__/planets.cpython-313.pyc +0 -0
  41. package/azure-function/python/kundali_lib/vedic/__pycache__/shadbala.cpython-313.pyc +0 -0
  42. package/azure-function/python/kundali_lib/vedic/__pycache__/special_conditions.cpython-313.pyc +0 -0
  43. package/azure-function/python/kundali_lib/vedic/__pycache__/sudarshana.cpython-313.pyc +0 -0
  44. package/azure-function/python/kundali_lib/vedic/__pycache__/tajaka.cpython-313.pyc +0 -0
  45. package/azure-function/python/kundali_lib/vedic/__pycache__/upagrahas.cpython-313.pyc +0 -0
  46. package/azure-function/python/kundali_lib/vedic/__pycache__/varshaphal.cpython-313.pyc +0 -0
  47. package/azure-function/python/kundali_lib/vedic/__pycache__/yogas.cpython-313.pyc +0 -0
  48. package/azure-function/python/kundali_lib/vedic/__pycache__/zodiac.cpython-313.pyc +0 -0
  49. package/azure-function/python/kundali_lib/vedic/arishta.py +465 -0
  50. package/azure-function/python/kundali_lib/vedic/ashtakavarga.py +213 -0
  51. package/azure-function/python/kundali_lib/vedic/avasthas.py +292 -0
  52. package/azure-function/python/kundali_lib/vedic/ayanamsa.py +106 -0
  53. package/azure-function/python/kundali_lib/vedic/bhava_chalit.py +137 -0
  54. package/azure-function/python/kundali_lib/vedic/char_dasha.py +308 -0
  55. package/azure-function/python/kundali_lib/vedic/chart.py +126 -0
  56. package/azure-function/python/kundali_lib/vedic/chart_types.py +338 -0
  57. package/azure-function/python/kundali_lib/vedic/compatibility.py +705 -0
  58. package/azure-function/python/kundali_lib/vedic/constants.py +108 -0
  59. package/azure-function/python/kundali_lib/vedic/dasha_extended.py +262 -0
  60. package/azure-function/python/kundali_lib/vedic/dasha_systems.py +439 -0
  61. package/azure-function/python/kundali_lib/vedic/doshas.py +453 -0
  62. package/azure-function/python/kundali_lib/vedic/gandanta.py +213 -0
  63. package/azure-function/python/kundali_lib/vedic/gochara.py +277 -0
  64. package/azure-function/python/kundali_lib/vedic/hora.py +263 -0
  65. package/azure-function/python/kundali_lib/vedic/houses.py +30 -0
  66. package/azure-function/python/kundali_lib/vedic/jaimini.py +361 -0
  67. package/azure-function/python/kundali_lib/vedic/kalachakra.py +226 -0
  68. package/azure-function/python/kundali_lib/vedic/kartari.py +243 -0
  69. package/azure-function/python/kundali_lib/vedic/kurmachakra.py +383 -0
  70. package/azure-function/python/kundali_lib/vedic/lunar_return.py +402 -0
  71. package/azure-function/python/kundali_lib/vedic/muhurta.py +414 -0
  72. package/azure-function/python/kundali_lib/vedic/nabhasha.py +349 -0
  73. package/azure-function/python/kundali_lib/vedic/nakshatra_details.py +945 -0
  74. package/azure-function/python/kundali_lib/vedic/panchanga.py +297 -0
  75. package/azure-function/python/kundali_lib/vedic/planets.py +55 -0
  76. package/azure-function/python/kundali_lib/vedic/shadbala.py +500 -0
  77. package/azure-function/python/kundali_lib/vedic/special_conditions.py +319 -0
  78. package/azure-function/python/kundali_lib/vedic/sudarshana.py +232 -0
  79. package/azure-function/python/kundali_lib/vedic/tajaka.py +482 -0
  80. package/azure-function/python/kundali_lib/vedic/upagrahas.py +229 -0
  81. package/azure-function/python/kundali_lib/vedic/varshaphal.py +185 -0
  82. package/azure-function/python/kundali_lib/vedic/yogas.py +935 -0
  83. package/azure-function/python/kundali_lib/vedic/zodiac.py +42 -0
  84. package/azure-function/python/kundali_lib/vedicastro_bridge.py +198 -0
  85. package/azure-function/requirements.txt +9 -0
  86. package/index.js +747 -0
  87. package/kundali-chart-mcp.js +159 -0
  88. package/kundali_bridge.py +952 -0
  89. package/package.json +41 -0
  90. package/python/kundali_lib/__init__.py +1 -0
  91. package/python/kundali_lib/__pycache__/__init__.cpython-313.pyc +0 -0
  92. package/python/kundali_lib/__pycache__/ephemeris.cpython-313.pyc +0 -0
  93. package/python/kundali_lib/__pycache__/geocoder.cpython-313.pyc +0 -0
  94. package/python/kundali_lib/__pycache__/vedicastro_bridge.cpython-313.pyc +0 -0
  95. package/python/kundali_lib/ephemeris.py +30 -0
  96. package/python/kundali_lib/geocoder.py +82 -0
  97. package/python/kundali_lib/vedic/__init__.py +1 -0
  98. package/python/kundali_lib/vedic/__pycache__/__init__.cpython-313.pyc +0 -0
  99. package/python/kundali_lib/vedic/__pycache__/arishta.cpython-313.pyc +0 -0
  100. package/python/kundali_lib/vedic/__pycache__/ashtakavarga.cpython-313.pyc +0 -0
  101. package/python/kundali_lib/vedic/__pycache__/avasthas.cpython-313.pyc +0 -0
  102. package/python/kundali_lib/vedic/__pycache__/ayanamsa.cpython-313.pyc +0 -0
  103. package/python/kundali_lib/vedic/__pycache__/bhava_chalit.cpython-313.pyc +0 -0
  104. package/python/kundali_lib/vedic/__pycache__/char_dasha.cpython-313.pyc +0 -0
  105. package/python/kundali_lib/vedic/__pycache__/chart.cpython-313.pyc +0 -0
  106. package/python/kundali_lib/vedic/__pycache__/chart_types.cpython-313.pyc +0 -0
  107. package/python/kundali_lib/vedic/__pycache__/compatibility.cpython-313.pyc +0 -0
  108. package/python/kundali_lib/vedic/__pycache__/constants.cpython-313.pyc +0 -0
  109. package/python/kundali_lib/vedic/__pycache__/dasha_extended.cpython-313.pyc +0 -0
  110. package/python/kundali_lib/vedic/__pycache__/dasha_systems.cpython-313.pyc +0 -0
  111. package/python/kundali_lib/vedic/__pycache__/doshas.cpython-313.pyc +0 -0
  112. package/python/kundali_lib/vedic/__pycache__/gandanta.cpython-313.pyc +0 -0
  113. package/python/kundali_lib/vedic/__pycache__/gochara.cpython-313.pyc +0 -0
  114. package/python/kundali_lib/vedic/__pycache__/hora.cpython-313.pyc +0 -0
  115. package/python/kundali_lib/vedic/__pycache__/houses.cpython-313.pyc +0 -0
  116. package/python/kundali_lib/vedic/__pycache__/jaimini.cpython-313.pyc +0 -0
  117. package/python/kundali_lib/vedic/__pycache__/kalachakra.cpython-313.pyc +0 -0
  118. package/python/kundali_lib/vedic/__pycache__/kartari.cpython-313.pyc +0 -0
  119. package/python/kundali_lib/vedic/__pycache__/kurmachakra.cpython-313.pyc +0 -0
  120. package/python/kundali_lib/vedic/__pycache__/lunar_return.cpython-313.pyc +0 -0
  121. package/python/kundali_lib/vedic/__pycache__/muhurta.cpython-313.pyc +0 -0
  122. package/python/kundali_lib/vedic/__pycache__/nabhasha.cpython-313.pyc +0 -0
  123. package/python/kundali_lib/vedic/__pycache__/nakshatra_details.cpython-313.pyc +0 -0
  124. package/python/kundali_lib/vedic/__pycache__/panchanga.cpython-313.pyc +0 -0
  125. package/python/kundali_lib/vedic/__pycache__/planets.cpython-313.pyc +0 -0
  126. package/python/kundali_lib/vedic/__pycache__/shadbala.cpython-313.pyc +0 -0
  127. package/python/kundali_lib/vedic/__pycache__/special_conditions.cpython-313.pyc +0 -0
  128. package/python/kundali_lib/vedic/__pycache__/sudarshana.cpython-313.pyc +0 -0
  129. package/python/kundali_lib/vedic/__pycache__/tajaka.cpython-313.pyc +0 -0
  130. package/python/kundali_lib/vedic/__pycache__/upagrahas.cpython-313.pyc +0 -0
  131. package/python/kundali_lib/vedic/__pycache__/varshaphal.cpython-313.pyc +0 -0
  132. package/python/kundali_lib/vedic/__pycache__/yogas.cpython-313.pyc +0 -0
  133. package/python/kundali_lib/vedic/__pycache__/zodiac.cpython-313.pyc +0 -0
  134. package/python/kundali_lib/vedic/arishta.py +465 -0
  135. package/python/kundali_lib/vedic/ashtakavarga.py +213 -0
  136. package/python/kundali_lib/vedic/avasthas.py +292 -0
  137. package/python/kundali_lib/vedic/ayanamsa.py +106 -0
  138. package/python/kundali_lib/vedic/bhava_chalit.py +137 -0
  139. package/python/kundali_lib/vedic/char_dasha.py +308 -0
  140. package/python/kundali_lib/vedic/chart.py +126 -0
  141. package/python/kundali_lib/vedic/chart_types.py +338 -0
  142. package/python/kundali_lib/vedic/compatibility.py +705 -0
  143. package/python/kundali_lib/vedic/constants.py +108 -0
  144. package/python/kundali_lib/vedic/dasha_extended.py +262 -0
  145. package/python/kundali_lib/vedic/dasha_systems.py +439 -0
  146. package/python/kundali_lib/vedic/doshas.py +453 -0
  147. package/python/kundali_lib/vedic/gandanta.py +213 -0
  148. package/python/kundali_lib/vedic/gochara.py +277 -0
  149. package/python/kundali_lib/vedic/hora.py +263 -0
  150. package/python/kundali_lib/vedic/houses.py +30 -0
  151. package/python/kundali_lib/vedic/jaimini.py +361 -0
  152. package/python/kundali_lib/vedic/kalachakra.py +226 -0
  153. package/python/kundali_lib/vedic/kartari.py +243 -0
  154. package/python/kundali_lib/vedic/kurmachakra.py +383 -0
  155. package/python/kundali_lib/vedic/lunar_return.py +402 -0
  156. package/python/kundali_lib/vedic/muhurta.py +414 -0
  157. package/python/kundali_lib/vedic/nabhasha.py +349 -0
  158. package/python/kundali_lib/vedic/nakshatra_details.py +945 -0
  159. package/python/kundali_lib/vedic/panchanga.py +297 -0
  160. package/python/kundali_lib/vedic/planets.py +55 -0
  161. package/python/kundali_lib/vedic/shadbala.py +500 -0
  162. package/python/kundali_lib/vedic/special_conditions.py +319 -0
  163. package/python/kundali_lib/vedic/sudarshana.py +232 -0
  164. package/python/kundali_lib/vedic/tajaka.py +482 -0
  165. package/python/kundali_lib/vedic/upagrahas.py +229 -0
  166. package/python/kundali_lib/vedic/varshaphal.py +185 -0
  167. package/python/kundali_lib/vedic/yogas.py +935 -0
  168. package/python/kundali_lib/vedic/zodiac.py +42 -0
  169. package/python/kundali_lib/vedicastro_bridge.py +198 -0
  170. package/remote-server.js +590 -0
  171. package/requirements.txt +8 -0
  172. package/setup.sh +218 -0
@@ -0,0 +1,292 @@
1
+ """Planetary Avasthas: 9 states of planets in a birth chart."""
2
+
3
+ # ---------------------------------------------------------------------------
4
+ # Avastha reference data
5
+ # ---------------------------------------------------------------------------
6
+
7
+ AVASTHAS = {
8
+ "Deepta": {
9
+ "condition": "Exaltation sign",
10
+ "strength": 1.0,
11
+ "nature": "Highly auspicious",
12
+ },
13
+ "Swastha": {
14
+ "condition": "Own sign (including Moolatrikona)",
15
+ "strength": 0.875,
16
+ "nature": "Auspicious",
17
+ },
18
+ "Mudita": {"condition": "Friend's sign", "strength": 0.75, "nature": "Pleasing"},
19
+ "Santha": {"condition": "Neutral sign", "strength": 0.5, "nature": "Neutral"},
20
+ "Dina": {"condition": "Enemy's sign", "strength": 0.375, "nature": "Distressed"},
21
+ "Dukhita": {
22
+ "condition": "Combust (within combustion threshold of Sun)",
23
+ "strength": 0.25,
24
+ "nature": "Pained",
25
+ },
26
+ "Vikala": {
27
+ "condition": "Defeated in Graha Yuddha (within 1° of another planet)",
28
+ "strength": 0.125,
29
+ "nature": "Crippled",
30
+ },
31
+ "Khala": {
32
+ "condition": "Debilitation sign",
33
+ "strength": 0.0,
34
+ "nature": "Wicked/Weak",
35
+ },
36
+ "Bhadra": {
37
+ "condition": "Retrograde in exaltation or own sign",
38
+ "strength": 1.0,
39
+ "nature": "Fortunate (exception)",
40
+ },
41
+ }
42
+
43
+ EXALTATION_SIGNS = {
44
+ "Sun": "Aries",
45
+ "Moon": "Taurus",
46
+ "Mars": "Capricorn",
47
+ "Mercury": "Virgo",
48
+ "Jupiter": "Cancer",
49
+ "Venus": "Pisces",
50
+ "Saturn": "Libra",
51
+ }
52
+
53
+ DEBILITATION_SIGNS = {
54
+ "Sun": "Libra",
55
+ "Moon": "Scorpio",
56
+ "Mars": "Cancer",
57
+ "Mercury": "Pisces",
58
+ "Jupiter": "Capricorn",
59
+ "Venus": "Virgo",
60
+ "Saturn": "Aries",
61
+ }
62
+
63
+ OWN_SIGNS = {
64
+ "Sun": ["Leo"],
65
+ "Moon": ["Cancer"],
66
+ "Mars": ["Aries", "Scorpio"],
67
+ "Mercury": ["Gemini", "Virgo"],
68
+ "Jupiter": ["Sagittarius", "Pisces"],
69
+ "Venus": ["Taurus", "Libra"],
70
+ "Saturn": ["Capricorn", "Aquarius"],
71
+ }
72
+
73
+ MOOLATRIKONA = {
74
+ "Sun": "Leo",
75
+ "Moon": "Taurus",
76
+ "Mars": "Aries",
77
+ "Mercury": "Virgo",
78
+ "Jupiter": "Sagittarius",
79
+ "Venus": "Libra",
80
+ "Saturn": "Aquarius",
81
+ }
82
+
83
+ # Degrees within which a planet is considered combust by the Sun
84
+ COMBUSTION_DEG = {
85
+ "Moon": 12,
86
+ "Mars": 17,
87
+ "Mercury": 14,
88
+ "Jupiter": 11,
89
+ "Venus": 10,
90
+ "Saturn": 15,
91
+ }
92
+
93
+ PLANET_FRIENDS = {
94
+ "Sun": ["Moon", "Mars", "Jupiter"],
95
+ "Moon": ["Sun", "Mercury"],
96
+ "Mars": ["Sun", "Moon", "Jupiter"],
97
+ "Mercury": ["Sun", "Venus"],
98
+ "Jupiter": ["Sun", "Moon", "Mars"],
99
+ "Venus": ["Mercury", "Saturn"],
100
+ "Saturn": ["Mercury", "Venus"],
101
+ }
102
+
103
+ PLANET_ENEMIES = {
104
+ "Sun": ["Venus", "Saturn"],
105
+ "Moon": [],
106
+ "Mars": ["Mercury"],
107
+ "Mercury": ["Moon"],
108
+ "Jupiter": ["Mercury", "Venus"],
109
+ "Venus": ["Sun", "Moon"],
110
+ "Saturn": ["Sun", "Moon", "Mars"],
111
+ }
112
+
113
+ # Planets for which we compute Avasthas (the 7 classical planets)
114
+ _CLASSICAL_PLANETS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]
115
+
116
+ # ---------------------------------------------------------------------------
117
+ # Baladi Avastha (age-based state by degree within sign)
118
+ # ---------------------------------------------------------------------------
119
+
120
+ _BALADI_THRESHOLDS = [
121
+ (6, "Bala", 0.25), # Infant 0–6°
122
+ (12, "Kumara", 0.50), # Young 6–12°
123
+ (18, "Yuva", 1.00), # Youth 12–18° (peak)
124
+ (24, "Vriddha", 0.50), # Old 18–24°
125
+ (30, "Mrita", 0.25), # Dead 24–30°
126
+ ]
127
+
128
+
129
+ def _get_baladi(degree_in_sign: float) -> tuple:
130
+ """Return (baladi_name, baladi_strength) for a degree within a sign (0–30)."""
131
+ for upper, name, strength in _BALADI_THRESHOLDS:
132
+ if degree_in_sign < upper:
133
+ return name, strength
134
+ return "Mrita", 0.25 # fallback for exactly 30°
135
+
136
+
137
+ def _angular_diff(lon1: float, lon2: float) -> float:
138
+ """Shortest angular distance between two longitudes (0–180)."""
139
+ diff = abs(lon1 - lon2) % 360
140
+ return diff if diff <= 180 else 360 - diff
141
+
142
+
143
+ # ---------------------------------------------------------------------------
144
+ # Main function
145
+ # ---------------------------------------------------------------------------
146
+
147
+
148
+ def get_avasthas(planetary_positions: list, ascendant: dict) -> list:
149
+ """Compute the Avastha (planetary state) for each classical planet.
150
+
151
+ Parameters
152
+ ----------
153
+ planetary_positions : list
154
+ Each item is a dict with at least:
155
+ ``name``, ``longitude``, ``rashi``, ``degree`` (= degree within sign),
156
+ ``house``, ``is_retrograde``.
157
+ ascendant : dict
158
+ Must contain at least ``longitude`` and ``rashi``.
159
+
160
+ Returns
161
+ -------
162
+ list of dict
163
+ One entry per classical planet (Sun–Saturn) found in
164
+ *planetary_positions*, each with keys:
165
+ planet, rashi, house, avastha, strength_ratio, nature,
166
+ condition_met, is_retrograde, baladi_avastha, baladi_strength.
167
+ """
168
+ # Build quick lookup by name
169
+ planet_map = {p["name"]: p for p in planetary_positions}
170
+
171
+ sun = planet_map.get("Sun")
172
+ sun_lon = sun["longitude"] if sun else None
173
+
174
+ results = []
175
+
176
+ for name in _CLASSICAL_PLANETS:
177
+ p = planet_map.get(name)
178
+ if p is None:
179
+ continue
180
+
181
+ rashi = p["rashi"]
182
+ lon = p["longitude"]
183
+ house = p["house"]
184
+ is_retro = p.get("is_retrograde", False)
185
+ # degree_in_sign: prefer "degree" key, else compute from longitude
186
+ deg_in_sign = p.get("degree", lon % 30)
187
+
188
+ # ------------------------------------------------------------------
189
+ # Determine Avastha (priority order)
190
+ # ------------------------------------------------------------------
191
+ avastha = None
192
+ condition_met = ""
193
+
194
+ # 1. Bhadra: retrograde AND (exaltation OR own sign)
195
+ if is_retro and (
196
+ EXALTATION_SIGNS.get(name) == rashi
197
+ or rashi in OWN_SIGNS.get(name, [])
198
+ or MOOLATRIKONA.get(name) == rashi
199
+ ):
200
+ avastha = "Bhadra"
201
+ condition_met = "Retrograde in exaltation/own sign"
202
+
203
+ # 2. Deepta: exaltation sign
204
+ elif EXALTATION_SIGNS.get(name) == rashi:
205
+ avastha = "Deepta"
206
+ condition_met = f"Exalted in {rashi}"
207
+
208
+ # 3. Khala: debilitation sign
209
+ elif DEBILITATION_SIGNS.get(name) == rashi:
210
+ avastha = "Khala"
211
+ condition_met = f"Debilitated in {rashi}"
212
+
213
+ # 4. Dukhita: combust (only applicable to non-Sun planets)
214
+ elif name != "Sun" and sun_lon is not None and name in COMBUSTION_DEG:
215
+ diff = _angular_diff(lon, sun_lon)
216
+ if diff <= COMBUSTION_DEG[name]:
217
+ avastha = "Dukhita"
218
+ condition_met = (
219
+ f"Combust — {diff:.1f}° from Sun "
220
+ f"(threshold {COMBUSTION_DEG[name]}°)"
221
+ )
222
+
223
+ # 5. Vikala: within 1° of any other planet (exclude Sun/Moon from trigger)
224
+ if avastha is None:
225
+ for other_name in _CLASSICAL_PLANETS:
226
+ if other_name == name or other_name in ("Sun", "Moon"):
227
+ continue
228
+ other = planet_map.get(other_name)
229
+ if other is None:
230
+ continue
231
+ diff = _angular_diff(lon, other["longitude"])
232
+ if diff <= 1.0:
233
+ avastha = "Vikala"
234
+ condition_met = f"Within 1° of {other_name} ({diff:.2f}°)"
235
+ break
236
+
237
+ if avastha is None:
238
+ # 6. Swastha: own sign or moolatrikona
239
+ if rashi in OWN_SIGNS.get(name, []) or MOOLATRIKONA.get(name) == rashi:
240
+ avastha = "Swastha"
241
+ condition_met = (
242
+ "Moolatrikona sign"
243
+ if MOOLATRIKONA.get(name) == rashi
244
+ else "Own sign"
245
+ )
246
+
247
+ # 7. Mudita: friend's sign
248
+ elif rashi in [
249
+ EXALTATION_SIGNS.get(f, "") or "" for f in PLANET_FRIENDS.get(name, [])
250
+ ] or any(
251
+ rashi in OWN_SIGNS.get(friend, [])
252
+ for friend in PLANET_FRIENDS.get(name, [])
253
+ ):
254
+ avastha = "Mudita"
255
+ condition_met = "Placed in a friend's sign"
256
+
257
+ # 8. Dina: enemy's sign
258
+ elif any(
259
+ rashi in OWN_SIGNS.get(enemy, [])
260
+ for enemy in PLANET_ENEMIES.get(name, [])
261
+ ):
262
+ avastha = "Dina"
263
+ condition_met = "Placed in an enemy's sign"
264
+
265
+ # 9. Santha: neutral (default)
266
+ else:
267
+ avastha = "Santha"
268
+ condition_met = "Neutral sign"
269
+
270
+ meta = AVASTHAS[avastha]
271
+
272
+ # ------------------------------------------------------------------
273
+ # Baladi Avastha
274
+ # ------------------------------------------------------------------
275
+ baladi_name, baladi_strength = _get_baladi(deg_in_sign)
276
+
277
+ results.append(
278
+ {
279
+ "planet": name,
280
+ "rashi": rashi,
281
+ "house": house,
282
+ "avastha": avastha,
283
+ "strength_ratio": meta["strength"],
284
+ "nature": meta["nature"],
285
+ "condition_met": condition_met,
286
+ "is_retrograde": is_retro,
287
+ "baladi_avastha": baladi_name,
288
+ "baladi_strength": baladi_strength,
289
+ }
290
+ )
291
+
292
+ return results
@@ -0,0 +1,106 @@
1
+ """Ayanamsa modes and house systems from Swiss Ephemeris."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import swisseph as swe
8
+
9
+ # Expose all predefined sidereal modes (ayanamsas) from Swiss Ephemeris.
10
+ AYANAMSA_MODES: dict[str, dict[str, Any]] = {}
11
+
12
+ # Map readable names to swisseph constants.
13
+ _SIDM_MAP = {
14
+ "lahiri": swe.SIDM_LAHIRI,
15
+ "raman": swe.SIDM_RAMAN,
16
+ "krishnamurti": swe.SIDM_KRISHNAMURTI,
17
+ "yukteshwar": swe.SIDM_YUKTESHWAR,
18
+ "fagan_bradley": swe.SIDM_FAGAN_BRADLEY,
19
+ "galactic_0sag": swe.SIDM_GALCENT_0SAG,
20
+ "galactic_mula": swe.SIDM_GALEQU_MULA,
21
+ "true_chitra": swe.SIDM_TRUE_CITRA,
22
+ "true_revati": swe.SIDM_TRUE_REVATI,
23
+ "true_pushya": swe.SIDM_TRUE_PUSHYA,
24
+ "b1950": swe.SIDM_B1950,
25
+ "j1900": swe.SIDM_J1900,
26
+ "j2000": swe.SIDM_J2000,
27
+ "aryabhata": swe.SIDM_ARYABHATA,
28
+ "aryabhata_522": swe.SIDM_ARYABHATA_522,
29
+ "suryasiddhanta": swe.SIDM_SURYASIDDHANTA,
30
+ "suryasiddhanta_msun": swe.SIDM_SURYASIDDHANTA_MSUN,
31
+ "galactic_iau1958": swe.SIDM_GALEQU_IAU1958,
32
+ "galactic_true": swe.SIDM_GALEQU_TRUE,
33
+ "galactic_fiorenza": swe.SIDM_GALEQU_FIORENZA,
34
+ "deluce": swe.SIDM_DELUCE,
35
+ "djwhal_khul": swe.SIDM_DJWHAL_KHUL,
36
+ "babyl_kugler1": swe.SIDM_BABYL_KUGLER1,
37
+ "babyl_kugler2": swe.SIDM_BABYL_KUGLER2,
38
+ "babyl_kugler3": swe.SIDM_BABYL_KUGLER3,
39
+ "babyl_huber": swe.SIDM_BABYL_HUBER,
40
+ "babyl_etpsc": swe.SIDM_BABYL_ETPSC,
41
+ "babyl_britton": swe.SIDM_BABYL_BRITTON,
42
+ "aldebaran_15tau": swe.SIDM_ALDEBARAN_15TAU,
43
+ "ss_citra": swe.SIDM_SS_CITRA,
44
+ "ss_revati": swe.SIDM_SS_REVATI,
45
+ "valems_moon": swe.SIDM_VALENS_MOON,
46
+ "hipparchos": swe.SIDM_HIPPARCHOS,
47
+ "galalign_mardyks": swe.SIDM_GALALIGN_MARDYKS,
48
+ "galcent_cochrane": swe.SIDM_GALCENT_COCHRANE,
49
+ "galcent_mula_wilhelm": swe.SIDM_GALCENT_MULA_WILHELM,
50
+ "galcent_rgibrand": swe.SIDM_GALCENT_RGILBRAND,
51
+ "true_sheoran": swe.SIDM_TRUE_SHEORAN,
52
+ "ushashashi": swe.SIDM_USHASHASHI,
53
+ "lahiri_1940": swe.SIDM_LAHIRI_1940,
54
+ "lahiri_icrc": swe.SIDM_LAHIRI_ICRC,
55
+ "lahiri_vp285": swe.SIDM_LAHIRI_VP285,
56
+ "krishnamurti_vp291": swe.SIDM_KRISHNAMURTI_VP291,
57
+ }
58
+
59
+ for name, const in _SIDM_MAP.items():
60
+ try:
61
+ label = swe.get_ayanamsa_name(const) or name.replace("_", " ").title()
62
+ except Exception:
63
+ label = name.replace("_", " ").title()
64
+ AYANAMSA_MODES[name] = {
65
+ "id": const,
66
+ "label": label,
67
+ "description": f"Swiss Ephemeris sidereal mode {name}.",
68
+ }
69
+
70
+ DEFAULT_AYANAMSA = "lahiri"
71
+
72
+ # House systems supported by Swiss Ephemeris.
73
+ HOUSE_SYSTEMS: dict[str, dict[str, Any]] = {
74
+ "P": {"name": "Placidus", "description": "Placidus house system (default)."},
75
+ "K": {"name": "Koch", "description": "Koch house system."},
76
+ "O": {"name": "Porphyrius", "description": "Porphyrius house system."},
77
+ "R": {"name": "Regiomontanus", "description": "Regiomontanus house system."},
78
+ "C": {"name": "Campanus", "description": "Campanus house system."},
79
+ "A": {"name": "Equal (0° Aries)", "description": "Equal houses from 0° Aries."},
80
+ "E": {"name": "Equal (Ascendant)", "description": "Equal houses from Ascendant."},
81
+ "V": {"name": "Vehlow", "description": "Vehlow equal houses."},
82
+ "X": {"name": "Axial/Aequalis", "description": "Axial system."},
83
+ "H": {"name": "Horizon", "description": "Horizon houses."},
84
+ "N": {"name": "Natural/Whole Sign", "description": "Whole sign houses."},
85
+ "B": {"name": "Pullen SD", "description": "Pullen sinodal delta."},
86
+ "G": {"name": "Gauquelin Sectors", "description": "Gauquelin sectors."},
87
+ "M": {"name": "Morinus", "description": "Morinus house system."},
88
+ "U": {"name": "Uranus/Krueger", "description": "Krugeian/Uranus houses."},
89
+ "T": {"name": "Topocentric", "description": "Topocentric house system."},
90
+ }
91
+
92
+ DEFAULT_HOUSE_SYSTEM = "P"
93
+
94
+
95
+ def list_ayanamsa_modes() -> dict[str, Any]:
96
+ return {
97
+ "default": DEFAULT_AYANAMSA,
98
+ "modes": AYANAMSA_MODES,
99
+ }
100
+
101
+
102
+ def list_house_systems() -> dict[str, Any]:
103
+ return {
104
+ "default": DEFAULT_HOUSE_SYSTEM,
105
+ "systems": HOUSE_SYSTEMS,
106
+ }
@@ -0,0 +1,137 @@
1
+ """Bhava Chalit Chart: house positions based on equal divisions from house cusps."""
2
+
3
+ from kundali_lib.vedic.constants import RASHIS
4
+
5
+ # Absolute longitude where each rashi begins (sidereal 0°)
6
+ RASHI_START: dict[str, float] = {rashi: i * 30.0 for i, rashi in enumerate(RASHIS)}
7
+
8
+
9
+ def _normalize(lon: float) -> float:
10
+ """Bring longitude into [0, 360)."""
11
+ return lon % 360.0
12
+
13
+
14
+ def _angular_distance(a: float, b: float) -> float:
15
+ """Shortest signed arc from a to b along the zodiac circle, result in (-180, 180]."""
16
+ diff = (b - a) % 360.0
17
+ if diff > 180.0:
18
+ diff -= 360.0
19
+ return diff
20
+
21
+
22
+ def _reconstruct_cusp_longitudes(base_chart: dict) -> list[float]:
23
+ """Convert (rashi, degree-within-sign) cusp data back to absolute longitudes.
24
+
25
+ ``base_chart["houses"]`` maps ``"House_1"`` … ``"House_12"`` to rashi names.
26
+ ``base_chart["house_cusp_degrees"]`` is a 12-element list of degrees-within-sign
27
+ (0–30) for each cusp, as produced by ``houses_dict_and_degrees()``.
28
+ """
29
+ houses: dict[str, str] = base_chart["houses"]
30
+ cusp_degrees: list[float] = base_chart["house_cusp_degrees"]
31
+ cusps_abs: list[float] = []
32
+ for i in range(12):
33
+ rashi = houses[f"House_{i + 1}"]
34
+ deg = cusp_degrees[i]
35
+ cusps_abs.append(_normalize(RASHI_START[rashi] + deg))
36
+ return cusps_abs
37
+
38
+
39
+ def _build_bhava_boundaries(cusps_abs: list[float]) -> list[tuple[float, float]]:
40
+ """Return 12 (start, end) boundary pairs using the midpoint method.
41
+
42
+ The boundary between house N and house N+1 is the midpoint of cusp_N and cusp_{N+1}
43
+ along the zodiac circle (handling wrap-around correctly).
44
+ """
45
+ boundaries: list[tuple[float, float]] = []
46
+ midpoints: list[float] = []
47
+ for i in range(12):
48
+ c_this = cusps_abs[i]
49
+ c_next = cusps_abs[(i + 1) % 12]
50
+ # Arc from c_this → c_next (forward along zodiac)
51
+ arc = (c_next - c_this) % 360.0
52
+ mid = _normalize(c_this + arc / 2.0)
53
+ midpoints.append(mid)
54
+
55
+ for i in range(12):
56
+ start = midpoints[(i - 1) % 12]
57
+ end = midpoints[i]
58
+ boundaries.append((start, end))
59
+
60
+ return boundaries
61
+
62
+
63
+ def _planet_bhava(planet_lon: float, boundaries: list[tuple[float, float]]) -> int:
64
+ """Return 1-based Bhava Chalit house number for a given planet longitude."""
65
+ for i, (start, end) in enumerate(boundaries):
66
+ # The span from start → end going forward along the zodiac
67
+ if start <= end:
68
+ if start <= planet_lon < end:
69
+ return i + 1
70
+ else:
71
+ # Wrap-around case (crosses 0°/360°)
72
+ if planet_lon >= start or planet_lon < end:
73
+ return i + 1
74
+ # Fallback — should not reach here with a well-formed chart
75
+ return 1
76
+
77
+
78
+ def get_bhava_chalit(base_chart: dict) -> dict:
79
+ """Compute the Bhava Chalit (Equal House from Cusps) chart.
80
+
81
+ Args:
82
+ base_chart: Output of ``build_chart()``. Must contain ``planetary_positions``,
83
+ ``houses``, ``house_cusp_degrees``, and ``ascendant``.
84
+
85
+ Returns:
86
+ A dict with absolute cusp longitudes, bhava boundaries, per-planet bhava
87
+ positions, and a list of planets whose house changed from the rashi chart.
88
+ """
89
+ cusps_abs = _reconstruct_cusp_longitudes(base_chart)
90
+ boundaries = _build_bhava_boundaries(cusps_abs)
91
+
92
+ # Build output for house cusps
93
+ house_cusps_absolute: dict[str, float] = {
94
+ f"House_{i + 1}": round(cusps_abs[i], 4) for i in range(12)
95
+ }
96
+
97
+ bhava_boundaries: dict[str, dict] = {}
98
+ for i, (start, end) in enumerate(boundaries):
99
+ bhava_boundaries[f"House_{i + 1}"] = {
100
+ "start": round(start, 4),
101
+ "end": round(end, 4),
102
+ }
103
+
104
+ planetary_bhava_positions: list[dict] = []
105
+ planets_that_changed: list[str] = []
106
+
107
+ for planet in base_chart["planetary_positions"]:
108
+ pname = planet["name"]
109
+ lon = planet["longitude"]
110
+ regular_house: int = planet["house"]
111
+ bhava_house = _planet_bhava(_normalize(lon), boundaries)
112
+ changed = bhava_house != regular_house
113
+
114
+ planetary_bhava_positions.append(
115
+ {
116
+ "planet": pname,
117
+ "longitude": round(lon, 4),
118
+ "regular_house": regular_house,
119
+ "bhava_chalit_house": bhava_house,
120
+ "house_changed": changed,
121
+ "rashi": planet["rashi"],
122
+ }
123
+ )
124
+
125
+ if changed:
126
+ planets_that_changed.append(pname)
127
+
128
+ return {
129
+ "house_cusps_absolute": house_cusps_absolute,
130
+ "bhava_boundaries": bhava_boundaries,
131
+ "planetary_bhava_positions": planetary_bhava_positions,
132
+ "ascendant_bhava": 1,
133
+ "planets_that_changed_house": planets_that_changed,
134
+ "note": (
135
+ "Bhava Chalit shows actual house influence. Use alongside Rashi chart."
136
+ ),
137
+ }