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,297 @@
1
+ """Panchanga: the 5 limbs of Vedic time (Tithi, Vara, Nakshatra, Yoga, Karana)."""
2
+
3
+ import math
4
+
5
+ # ---------------------------------------------------------------------------
6
+ # Constant tables
7
+ # ---------------------------------------------------------------------------
8
+
9
+ TITHI_NAMES = [
10
+ "Pratipada",
11
+ "Dwitiya",
12
+ "Tritiya",
13
+ "Chaturthi",
14
+ "Panchami",
15
+ "Shashthi",
16
+ "Saptami",
17
+ "Ashtami",
18
+ "Navami",
19
+ "Dashami",
20
+ "Ekadashi",
21
+ "Dwadashi",
22
+ "Trayodashi",
23
+ "Chaturdashi",
24
+ "Purnima",
25
+ # Krishna Paksha (indices 15-29 → tithis 16-30)
26
+ "Pratipada",
27
+ "Dwitiya",
28
+ "Tritiya",
29
+ "Chaturthi",
30
+ "Panchami",
31
+ "Shashthi",
32
+ "Saptami",
33
+ "Ashtami",
34
+ "Navami",
35
+ "Dashami",
36
+ "Ekadashi",
37
+ "Dwadashi",
38
+ "Trayodashi",
39
+ "Chaturdashi",
40
+ "Amavasya",
41
+ ]
42
+
43
+ VARA_NAMES = [
44
+ "Sunday",
45
+ "Monday",
46
+ "Tuesday",
47
+ "Wednesday",
48
+ "Thursday",
49
+ "Friday",
50
+ "Saturday",
51
+ ]
52
+ VARA_LORDS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]
53
+
54
+ NAKSHATRA_NAMES = [
55
+ "Ashwini",
56
+ "Bharani",
57
+ "Krittika",
58
+ "Rohini",
59
+ "Mrigashira",
60
+ "Ardra",
61
+ "Punarvasu",
62
+ "Pushya",
63
+ "Ashlesha",
64
+ "Magha",
65
+ "Purva Phalguni",
66
+ "Uttara Phalguni",
67
+ "Hasta",
68
+ "Chitra",
69
+ "Swati",
70
+ "Vishakha",
71
+ "Anuradha",
72
+ "Jyeshtha",
73
+ "Mula",
74
+ "Purva Ashadha",
75
+ "Uttara Ashadha",
76
+ "Shravana",
77
+ "Dhanishta",
78
+ "Shatabhisha",
79
+ "Purva Bhadrapada",
80
+ "Uttara Bhadrapada",
81
+ "Revati",
82
+ ]
83
+
84
+ # Ketu, Venus, Sun, Moon, Mars, Rahu, Jupiter, Saturn, Mercury (×3)
85
+ NAKSHATRA_LORDS = [
86
+ "Ketu",
87
+ "Venus",
88
+ "Sun",
89
+ "Moon",
90
+ "Mars",
91
+ "Rahu",
92
+ "Jupiter",
93
+ "Saturn",
94
+ "Mercury",
95
+ ] * 3
96
+
97
+ YOGA_NAMES = [
98
+ "Vishkumbha",
99
+ "Priti",
100
+ "Ayushman",
101
+ "Saubhagya",
102
+ "Shobhana",
103
+ "Atiganda",
104
+ "Sukarma",
105
+ "Dhriti",
106
+ "Shoola",
107
+ "Ganda",
108
+ "Vriddhi",
109
+ "Dhruva",
110
+ "Vyaghata",
111
+ "Harshana",
112
+ "Vajra",
113
+ "Siddhi",
114
+ "Vyatipata",
115
+ "Variyana",
116
+ "Parigha",
117
+ "Shiva",
118
+ "Siddha",
119
+ "Sadhya",
120
+ "Shubha",
121
+ "Shukla",
122
+ "Brahma",
123
+ "Indra",
124
+ "Vaidhriti",
125
+ ]
126
+
127
+ # 1-indexed set of inauspicious yoga numbers
128
+ INAUSPICIOUS_YOGAS = {1, 6, 9, 10, 13, 15, 17, 19, 27}
129
+
130
+ # Fixed karanas at positions 1, 58, 59, 60
131
+ FIXED_KARANAS = {1: "Kimstughna", 58: "Shakuni", 59: "Chatushpada", 60: "Naga"}
132
+ MOVABLE_KARANAS = ["Bava", "Balava", "Kaulava", "Taitila", "Garija", "Vanija", "Vishti"]
133
+
134
+
135
+ # ---------------------------------------------------------------------------
136
+ # Helper
137
+ # ---------------------------------------------------------------------------
138
+
139
+
140
+ def _normalize(lon: float) -> float:
141
+ """Bring longitude into [0, 360)."""
142
+ return lon % 360
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # Main function
147
+ # ---------------------------------------------------------------------------
148
+
149
+
150
+ def get_panchanga(sun_lon: float, moon_lon: float, julian_day: float) -> dict:
151
+ """Return the five limbs of Vedic time (Panchanga).
152
+
153
+ Parameters
154
+ ----------
155
+ sun_lon : float
156
+ Sidereal longitude of the Sun in degrees [0, 360).
157
+ moon_lon : float
158
+ Sidereal longitude of the Moon in degrees [0, 360).
159
+ julian_day : float
160
+ Julian Day Number for the moment of interest.
161
+
162
+ Returns
163
+ -------
164
+ dict
165
+ Keys: tithi, vara, nakshatra, yoga, karana — each a dict with
166
+ relevant sub-fields.
167
+ """
168
+ sun_lon = _normalize(sun_lon)
169
+ moon_lon = _normalize(moon_lon)
170
+
171
+ return {
172
+ "tithi": _calc_tithi(sun_lon, moon_lon),
173
+ "vara": _calc_vara(julian_day),
174
+ "nakshatra": _calc_nakshatra(moon_lon),
175
+ "yoga": _calc_yoga(sun_lon, moon_lon),
176
+ "karana": _calc_karana(sun_lon, moon_lon),
177
+ }
178
+
179
+
180
+ # ---------------------------------------------------------------------------
181
+ # Tithi
182
+ # ---------------------------------------------------------------------------
183
+
184
+
185
+ def _calc_tithi(sun_lon: float, moon_lon: float) -> dict:
186
+ diff = _normalize(moon_lon - sun_lon)
187
+ tithi_num = math.ceil(diff / 12)
188
+ if tithi_num == 0:
189
+ tithi_num = 30 # diff == 0 edge case
190
+
191
+ percentage_elapsed = ((diff % 12) / 12) * 100
192
+
193
+ if tithi_num <= 15:
194
+ paksha = "Shukla"
195
+ name = TITHI_NAMES[tithi_num - 1]
196
+ else:
197
+ paksha = "Krishna"
198
+ name = TITHI_NAMES[tithi_num - 1] # table has 30 entries
199
+
200
+ return {
201
+ "number": tithi_num,
202
+ "name": name,
203
+ "paksha": paksha,
204
+ "percentage_elapsed": round(percentage_elapsed, 2),
205
+ }
206
+
207
+
208
+ # ---------------------------------------------------------------------------
209
+ # Vara
210
+ # ---------------------------------------------------------------------------
211
+
212
+
213
+ def _calc_vara(julian_day: float) -> dict:
214
+ # JD 0.0 is Monday noon; adding 1.5 shifts so index 0 = Sunday
215
+ vara_index = int(julian_day + 1.5) % 7 # 0=Sunday … 6=Saturday
216
+ number = vara_index + 1 # 1=Sunday … 7=Saturday
217
+
218
+ return {
219
+ "number": number,
220
+ "name": VARA_NAMES[vara_index],
221
+ "lord": VARA_LORDS[vara_index],
222
+ }
223
+
224
+
225
+ # ---------------------------------------------------------------------------
226
+ # Nakshatra
227
+ # ---------------------------------------------------------------------------
228
+
229
+
230
+ def _calc_nakshatra(moon_lon: float) -> dict:
231
+ span = 360 / 27 # ≈ 13.333°
232
+ pada_span = 360 / 108 # ≈ 3.333°
233
+
234
+ nakshatra_index = int(moon_lon / span) # 0-26
235
+ nakshatra_num = nakshatra_index + 1 # 1-27
236
+
237
+ lon_within = moon_lon % span
238
+ pada = int(lon_within / pada_span) + 1 # 1-4
239
+ pada = min(pada, 4) # safety clamp
240
+
241
+ return {
242
+ "number": nakshatra_num,
243
+ "name": NAKSHATRA_NAMES[nakshatra_index],
244
+ "lord": NAKSHATRA_LORDS[nakshatra_index],
245
+ "pada": pada,
246
+ }
247
+
248
+
249
+ # ---------------------------------------------------------------------------
250
+ # Yoga
251
+ # ---------------------------------------------------------------------------
252
+
253
+
254
+ def _calc_yoga(sun_lon: float, moon_lon: float) -> dict:
255
+ combined = _normalize(sun_lon + moon_lon)
256
+ span = 360 / 27
257
+ yoga_index = int(combined / span) # 0-26
258
+ yoga_num = yoga_index + 1 # 1-27
259
+
260
+ if yoga_num in INAUSPICIOUS_YOGAS:
261
+ nature = "Inauspicious"
262
+ elif yoga_num in {3, 4, 5, 7, 8, 11, 12, 14, 16, 20, 21, 22, 23, 24, 25, 26}:
263
+ nature = "Auspicious"
264
+ else:
265
+ nature = "Neutral"
266
+
267
+ return {
268
+ "number": yoga_num,
269
+ "name": YOGA_NAMES[yoga_index],
270
+ "nature": nature,
271
+ }
272
+
273
+
274
+ # ---------------------------------------------------------------------------
275
+ # Karana
276
+ # ---------------------------------------------------------------------------
277
+
278
+
279
+ def _calc_karana(sun_lon: float, moon_lon: float) -> dict:
280
+ diff = _normalize(moon_lon - sun_lon)
281
+ karana_num = int(diff / 6) + 1 # 1-60
282
+ karana_num = max(1, min(karana_num, 60)) # safety clamp
283
+
284
+ if karana_num in FIXED_KARANAS:
285
+ name = FIXED_KARANAS[karana_num]
286
+ karana_type = "Fixed"
287
+ else:
288
+ # Movable karanas: positions 2-57 (index 0 in the 7-cycle = Bava)
289
+ movable_index = (karana_num - 2) % 7
290
+ name = MOVABLE_KARANAS[movable_index]
291
+ karana_type = "Movable"
292
+
293
+ return {
294
+ "number": karana_num,
295
+ "name": name,
296
+ "type": karana_type,
297
+ }
@@ -0,0 +1,55 @@
1
+ """Planetary position calculation (sidereal)."""
2
+
3
+ from typing import Optional
4
+
5
+ import swisseph as swe
6
+
7
+ from kundali_lib.vedic.constants import CALC_FLAGS, PLANET_IDS
8
+ from kundali_lib.vedic.zodiac import (
9
+ get_nakshatra,
10
+ get_nakshatra_lord,
11
+ get_pada,
12
+ get_rashi,
13
+ get_rashi_lord,
14
+ )
15
+
16
+
17
+ def calc_planet_position(planet_name: str, julian_day: float) -> dict:
18
+ """Single planet sidereal position. Ketu derived from Rahu."""
19
+ if planet_name == "Ketu":
20
+ rahu = calc_planet_position("Rahu", julian_day)
21
+ lon = (rahu["longitude"] + 180) % 360
22
+ return _planet_row("Ketu", lon, 0, False, -rahu.get("speed_longitude", 0))
23
+
24
+ pid = PLANET_IDS.get(planet_name)
25
+ if pid is None:
26
+ raise ValueError(f"Unknown planet: {planet_name}")
27
+
28
+ xx, _ = swe.calc_ut(julian_day, pid, CALC_FLAGS)
29
+ lon, speed = xx[0], xx[3]
30
+ retro = speed < 0
31
+
32
+ return _planet_row(planet_name, lon, speed, retro)
33
+
34
+
35
+ def _planet_row(
36
+ name: str,
37
+ longitude: float,
38
+ speed: float,
39
+ is_retro: bool,
40
+ speed_override: Optional[float] = None,
41
+ ) -> dict:
42
+ sp = speed_override if speed_override is not None else speed
43
+ return {
44
+ "name": name,
45
+ "longitude": longitude,
46
+ "rashi": get_rashi(longitude),
47
+ "rashi_lord": get_rashi_lord(longitude),
48
+ "degree": longitude % 30,
49
+ "nakshatra": get_nakshatra(longitude),
50
+ "nakshatra_lord": get_nakshatra_lord(longitude),
51
+ "pada": get_pada(longitude),
52
+ "house": 0,
53
+ "is_retrograde": is_retro,
54
+ "speed_longitude": round(sp, 4),
55
+ }