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,439 @@
1
+ """Additional Dasha systems: Ashtottari, Sookshma (4th level), Prana (5th level)."""
2
+
3
+ from datetime import datetime, timedelta
4
+
5
+ from kundali_lib.vedic.dasha_extended import (
6
+ NAKSHATRA_TO_DASHA_LORD,
7
+ VIMSHOTTARI_SEQUENCE,
8
+ VIMSHOTTARI_TOTAL,
9
+ VIMSHOTTARI_YEARS,
10
+ )
11
+
12
+ # ── Ashtottari constants ─────────────────────────────────────────────────────
13
+
14
+ ASHTOTTARI_SEQUENCE = [
15
+ "Sun",
16
+ "Moon",
17
+ "Mars",
18
+ "Mercury",
19
+ "Saturn",
20
+ "Jupiter",
21
+ "Rahu",
22
+ "Venus",
23
+ ]
24
+ ASHTOTTARI_YEARS: dict[str, int] = {
25
+ "Sun": 6,
26
+ "Moon": 15,
27
+ "Mars": 8,
28
+ "Mercury": 17,
29
+ "Saturn": 10,
30
+ "Jupiter": 19,
31
+ "Rahu": 12,
32
+ "Venus": 21,
33
+ }
34
+ ASHTOTTARI_TOTAL = 108
35
+
36
+ # Birth nakshatra → Ashtottari starting dasha
37
+ NAKSHATRA_ASHTOTTARI: dict[str, str] = {
38
+ "Ashwini": "Sun",
39
+ "Bharani": "Moon",
40
+ "Krittika": "Mars",
41
+ "Rohini": "Mercury",
42
+ "Mrigashira": "Saturn",
43
+ "Ardra": "Jupiter",
44
+ "Punarvasu": "Rahu",
45
+ "Pushya": "Venus",
46
+ "Ashlesha": "Sun",
47
+ "Magha": "Moon",
48
+ "Purva Phalguni": "Mars",
49
+ "Uttara Phalguni": "Mercury",
50
+ "Hasta": "Saturn",
51
+ "Chitra": "Jupiter",
52
+ "Swati": "Rahu",
53
+ "Vishakha": "Venus",
54
+ "Anuradha": "Sun",
55
+ "Jyeshtha": "Moon",
56
+ "Mula": "Mars",
57
+ "Purva Ashadha": "Mercury",
58
+ "Uttara Ashadha": "Saturn",
59
+ "Shravana": "Jupiter",
60
+ "Dhanishta": "Rahu",
61
+ "Shatabhisha": "Venus",
62
+ "Purva Bhadrapada": "Sun",
63
+ "Uttara Bhadrapada": "Moon",
64
+ "Revati": "Mars",
65
+ }
66
+
67
+ _NAKSHATRA_SPAN = 360.0 / 27.0
68
+
69
+
70
+ # ── Private helpers ──────────────────────────────────────────────────────────
71
+
72
+
73
+ def _rotated(seq: list, start: str) -> list:
74
+ """Return seq rotated so it begins at start."""
75
+ idx = seq.index(start)
76
+ return seq[idx:] + seq[:idx]
77
+
78
+
79
+ def _iter_pds(ad_lord: str, ad_start: datetime, ad_years: float):
80
+ """Yield (pd_lord, pd_start, pd_years) for each of the 9 PDs in one AD."""
81
+ pd_cursor = ad_start
82
+ for pd_lord in _rotated(VIMSHOTTARI_SEQUENCE, ad_lord):
83
+ pd_y = (VIMSHOTTARI_YEARS[pd_lord] / VIMSHOTTARI_TOTAL) * ad_years
84
+ pd_end = pd_cursor + timedelta(days=pd_y * 365.25)
85
+ yield pd_lord, pd_cursor, pd_y
86
+ pd_cursor = pd_end
87
+
88
+
89
+ def _iter_sds(pd_lord: str, pd_start: datetime, pd_years: float):
90
+ """Yield (sd_lord, sd_start, sd_end) for each of the 9 SDs in one PD.
91
+
92
+ SD duration = (sd_years / 120) * pd_duration_years.
93
+ """
94
+ sd_cursor = pd_start
95
+ for sd_lord in _rotated(VIMSHOTTARI_SEQUENCE, pd_lord):
96
+ sd_y = (VIMSHOTTARI_YEARS[sd_lord] / VIMSHOTTARI_TOTAL) * pd_years
97
+ sd_days = sd_y * 365.25
98
+ sd_end = sd_cursor + timedelta(days=sd_days)
99
+ yield sd_lord, sd_cursor, sd_end, sd_y
100
+ sd_cursor = sd_end
101
+
102
+
103
+ def _iter_prds(sd_lord: str, sd_start: datetime, sd_years: float):
104
+ """Yield (prd_lord, prd_start, prd_end, prd_days) for each PRD in one SD.
105
+
106
+ PRD duration = (prd_years / 120) * sd_duration_years.
107
+ """
108
+ prd_cursor = sd_start
109
+ for prd_lord in _rotated(VIMSHOTTARI_SEQUENCE, sd_lord):
110
+ prd_y = (VIMSHOTTARI_YEARS[prd_lord] / VIMSHOTTARI_TOTAL) * sd_years
111
+ prd_days = prd_y * 365.25
112
+ prd_end = prd_cursor + timedelta(days=prd_days)
113
+ yield prd_lord, prd_cursor, prd_end, prd_days
114
+ prd_cursor = prd_end
115
+
116
+
117
+ def _find_periods(
118
+ birth_dt: datetime,
119
+ moon_longitude: float,
120
+ moon_nakshatra: str,
121
+ now: datetime,
122
+ ) -> dict | None:
123
+ """Drill down MD → AD → PD and return context for current periods.
124
+
125
+ Returns a dict with keys: md_lord, ad_lord, ad_start, ad_years,
126
+ pd_lord, pd_start, pd_years — or None if not found within the 120-year span.
127
+ """
128
+ birth_dasha_lord = NAKSHATRA_TO_DASHA_LORD[moon_nakshatra]
129
+ fraction_elapsed = (moon_longitude % _NAKSHATRA_SPAN) / _NAKSHATRA_SPAN
130
+
131
+ md0_years = VIMSHOTTARI_YEARS[birth_dasha_lord]
132
+ md_cursor = birth_dt - timedelta(days=fraction_elapsed * md0_years * 365.25)
133
+
134
+ for md_lord in _rotated(VIMSHOTTARI_SEQUENCE, birth_dasha_lord):
135
+ md_years = VIMSHOTTARI_YEARS[md_lord]
136
+ md_start = md_cursor
137
+ md_end = md_start + timedelta(days=md_years * 365.25)
138
+ md_cursor = md_end
139
+
140
+ if not (md_start <= now < md_end):
141
+ continue
142
+
143
+ # Found the current MD — drill into ADs
144
+ ad_cursor = md_start
145
+ for ad_lord in _rotated(VIMSHOTTARI_SEQUENCE, md_lord):
146
+ ad_years = (VIMSHOTTARI_YEARS[ad_lord] / VIMSHOTTARI_TOTAL) * md_years
147
+ ad_start = ad_cursor
148
+ ad_end = ad_start + timedelta(days=ad_years * 365.25)
149
+ ad_cursor = ad_end
150
+
151
+ if not (ad_start <= now < ad_end):
152
+ continue
153
+
154
+ # Found the current AD — drill into PDs
155
+ pd_cursor = ad_start
156
+ for pd_lord in _rotated(VIMSHOTTARI_SEQUENCE, ad_lord):
157
+ pd_years = (VIMSHOTTARI_YEARS[pd_lord] / VIMSHOTTARI_TOTAL) * ad_years
158
+ pd_start = pd_cursor
159
+ pd_end = pd_start + timedelta(days=pd_years * 365.25)
160
+ pd_cursor = pd_end
161
+
162
+ if pd_start <= now < pd_end:
163
+ return {
164
+ "md_lord": md_lord,
165
+ "ad_lord": ad_lord,
166
+ "ad_start": ad_start,
167
+ "ad_years": ad_years,
168
+ "pd_lord": pd_lord,
169
+ "pd_start": pd_start,
170
+ "pd_years": pd_years,
171
+ }
172
+
173
+ return None
174
+
175
+
176
+ # ── Ashtottari Dasha ─────────────────────────────────────────────────────────
177
+
178
+
179
+ def get_ashtottari_dasha(
180
+ birth_dt: datetime,
181
+ moon_longitude: float,
182
+ moon_nakshatra: str,
183
+ ) -> dict:
184
+ """Calculate Ashtottari Dasha (108-year cycle).
185
+
186
+ Applicable when Rahu occupies certain positions; can be calculated for all charts.
187
+ Antardasha duration = (ad_years / 108) * md_years.
188
+
189
+ Args:
190
+ birth_dt: Naive local birth datetime.
191
+ moon_longitude: Sidereal Moon longitude in degrees (0–360).
192
+ moon_nakshatra: Name of the birth nakshatra (e.g. "Ashwini").
193
+
194
+ Returns:
195
+ dict with ``system``, ``total_years``, ``birth_dasha_lord``, and ``mahadashas``.
196
+ """
197
+ birth_dasha_lord = NAKSHATRA_ASHTOTTARI[moon_nakshatra]
198
+ fraction_elapsed = (moon_longitude % _NAKSHATRA_SPAN) / _NAKSHATRA_SPAN
199
+
200
+ result_mds: dict = {}
201
+ md_cursor: datetime | None = None
202
+
203
+ for i, md_lord in enumerate(_rotated(ASHTOTTARI_SEQUENCE, birth_dasha_lord)):
204
+ md_full_years = ASHTOTTARI_YEARS[md_lord]
205
+ md_full_days = md_full_years * 365.25
206
+
207
+ if i == 0:
208
+ elapsed_days = fraction_elapsed * md_full_days
209
+ md_start = birth_dt - timedelta(days=elapsed_days)
210
+ display_duration = round((1.0 - fraction_elapsed) * md_full_years, 4)
211
+ else:
212
+ md_start = md_cursor # type: ignore[assignment]
213
+ display_duration = float(md_full_years)
214
+
215
+ md_end = md_start + timedelta(days=md_full_days)
216
+ md_cursor = md_end
217
+
218
+ # ── Antardasha sequence starts from this MD lord ─────────────────
219
+ result_ads: dict = {}
220
+ ad_cursor = md_start
221
+
222
+ for ad_lord in _rotated(ASHTOTTARI_SEQUENCE, md_lord):
223
+ ad_years = (ASHTOTTARI_YEARS[ad_lord] / ASHTOTTARI_TOTAL) * md_full_years
224
+ ad_days = ad_years * 365.25
225
+ ad_end = ad_cursor + timedelta(days=ad_days)
226
+
227
+ result_ads[ad_lord] = {
228
+ "start": ad_cursor.strftime("%d-%m-%Y"),
229
+ "end": ad_end.strftime("%d-%m-%Y"),
230
+ "duration_days": round(ad_days, 2),
231
+ }
232
+ ad_cursor = ad_end
233
+
234
+ result_mds[md_lord] = {
235
+ "start": md_start.strftime("%d-%m-%Y"),
236
+ "end": md_end.strftime("%d-%m-%Y"),
237
+ "duration_years": display_duration,
238
+ "antardashas": result_ads,
239
+ }
240
+
241
+ return {
242
+ "system": "Ashtottari",
243
+ "total_years": ASHTOTTARI_TOTAL,
244
+ "birth_dasha_lord": birth_dasha_lord,
245
+ "mahadashas": result_mds,
246
+ }
247
+
248
+
249
+ # ── Sookshma Dasha (4th level) ────────────────────────────────────────────────
250
+
251
+
252
+ def get_sookshma_dasha(
253
+ birth_dt: datetime,
254
+ moon_longitude: float,
255
+ moon_nakshatra: str,
256
+ ) -> dict:
257
+ """Generate 4 levels of Vimshottari (MD → AD → PD → SD) and return the current state.
258
+
259
+ Finds the running period at all 4 levels (as of now) and returns the current
260
+ Sookshma Dasha plus the next 5 upcoming Sookshma periods.
261
+
262
+ SD duration = (sd_planet_years / 120) * pd_duration_years.
263
+
264
+ Args:
265
+ birth_dt: Naive local birth datetime.
266
+ moon_longitude: Sidereal Moon longitude in degrees (0–360).
267
+ moon_nakshatra: Name of the birth nakshatra.
268
+
269
+ Returns:
270
+ dict with current MD/AD/PD/SD labels, current SD start/end, and
271
+ ``upcoming_sookshma`` (list of next 5 SD periods).
272
+ """
273
+ now = datetime.now()
274
+ ctx = _find_periods(birth_dt, moon_longitude, moon_nakshatra, now)
275
+
276
+ if ctx is None:
277
+ return {"error": "Could not determine current dasha period."}
278
+
279
+ current_sd_lord: str | None = None
280
+ current_sd_start: datetime | None = None
281
+ current_sd_end: datetime | None = None
282
+ upcoming: list = []
283
+ found = False
284
+
285
+ for pd_lord, pd_start, pd_years in _iter_pds(
286
+ ctx["ad_lord"], ctx["ad_start"], ctx["ad_years"]
287
+ ):
288
+ # Skip PDs that ended before the current PD began
289
+ pd_end = pd_start + timedelta(days=pd_years * 365.25)
290
+ if pd_end <= ctx["pd_start"]:
291
+ continue
292
+
293
+ for sd_lord, sd_start, sd_end, _sd_y in _iter_sds(pd_lord, pd_start, pd_years):
294
+ if sd_start <= now < sd_end:
295
+ current_sd_lord = sd_lord
296
+ current_sd_start = sd_start
297
+ current_sd_end = sd_end
298
+ found = True
299
+ elif found and len(upcoming) < 5:
300
+ upcoming.append(
301
+ {
302
+ "planet": sd_lord,
303
+ "start": sd_start.strftime("%d-%m-%Y"),
304
+ "end": sd_end.strftime("%d-%m-%Y"),
305
+ }
306
+ )
307
+
308
+ if found and len(upcoming) >= 5:
309
+ break
310
+
311
+ return {
312
+ "current_mahadasha": ctx["md_lord"],
313
+ "current_antardasha": ctx["ad_lord"],
314
+ "current_pratyantar": ctx["pd_lord"],
315
+ "current_sookshma": current_sd_lord or "Unknown",
316
+ "current_sookshma_start": (
317
+ current_sd_start.strftime("%d-%m-%Y") if current_sd_start else ""
318
+ ),
319
+ "current_sookshma_end": (
320
+ current_sd_end.strftime("%d-%m-%Y") if current_sd_end else ""
321
+ ),
322
+ "upcoming_sookshma": upcoming,
323
+ }
324
+
325
+
326
+ # ── Prana Dasha (5th level) ───────────────────────────────────────────────────
327
+
328
+
329
+ def get_prana_dasha(
330
+ birth_dt: datetime,
331
+ moon_longitude: float,
332
+ moon_nakshatra: str,
333
+ ) -> dict:
334
+ """Generate 5 levels of Vimshottari (MD → AD → PD → SD → PRD) and return the current state.
335
+
336
+ Finds the running period at all 5 levels (as of now) and returns the current
337
+ Prana Dasha plus the next 5 upcoming Prana periods.
338
+
339
+ PRD duration = (prd_planet_years / 120) * sd_duration_years.
340
+
341
+ Args:
342
+ birth_dt: Naive local birth datetime.
343
+ moon_longitude: Sidereal Moon longitude in degrees (0–360).
344
+ moon_nakshatra: Name of the birth nakshatra.
345
+
346
+ Returns:
347
+ dict with current MD/AD/PD/SD/PRD labels, current PRD start/end/duration_days,
348
+ and ``upcoming_prana`` (list of next 5 PRD periods).
349
+ """
350
+ now = datetime.now()
351
+ ctx = _find_periods(birth_dt, moon_longitude, moon_nakshatra, now)
352
+
353
+ if ctx is None:
354
+ return {"error": "Could not determine current dasha period."}
355
+
356
+ current_sd_lord: str | None = None
357
+ current_prd_lord: str | None = None
358
+ current_prd_start: datetime | None = None
359
+ current_prd_end: datetime | None = None
360
+ current_prd_days: float | None = None
361
+ upcoming: list = []
362
+ found_sd = False
363
+ found_prd = False
364
+
365
+ for pd_lord, pd_start, pd_years in _iter_pds(
366
+ ctx["ad_lord"], ctx["ad_start"], ctx["ad_years"]
367
+ ):
368
+ pd_end = pd_start + timedelta(days=pd_years * 365.25)
369
+ if pd_end <= ctx["pd_start"]:
370
+ continue
371
+
372
+ for sd_lord, sd_start, sd_end, sd_y in _iter_sds(pd_lord, pd_start, pd_years):
373
+ # Skip SDs that ended before the current PD began
374
+ if sd_end <= ctx["pd_start"]:
375
+ continue
376
+
377
+ if sd_start <= now < sd_end and not found_sd:
378
+ current_sd_lord = sd_lord
379
+ found_sd = True
380
+
381
+ # Drill into PRDs within this SD
382
+ for prd_lord, prd_start, prd_end, prd_days in _iter_prds(
383
+ sd_lord, sd_start, sd_y
384
+ ):
385
+ if prd_start <= now < prd_end:
386
+ current_prd_lord = prd_lord
387
+ current_prd_start = prd_start
388
+ current_prd_end = prd_end
389
+ current_prd_days = prd_days
390
+ found_prd = True
391
+ elif found_prd and len(upcoming) < 5:
392
+ upcoming.append(
393
+ {
394
+ "planet": prd_lord,
395
+ "start": prd_start.strftime("%d-%m-%Y"),
396
+ "end": prd_end.strftime("%d-%m-%Y"),
397
+ }
398
+ )
399
+ if found_prd and len(upcoming) >= 5:
400
+ break
401
+
402
+ elif found_prd and len(upcoming) < 5:
403
+ # Collect PRDs from subsequent SDs to fill up upcoming list
404
+ for prd_lord, prd_start, prd_end, _prd_days in _iter_prds(
405
+ sd_lord, sd_start, sd_y
406
+ ):
407
+ upcoming.append(
408
+ {
409
+ "planet": prd_lord,
410
+ "start": prd_start.strftime("%d-%m-%Y"),
411
+ "end": prd_end.strftime("%d-%m-%Y"),
412
+ }
413
+ )
414
+ if len(upcoming) >= 5:
415
+ break
416
+
417
+ if found_prd and len(upcoming) >= 5:
418
+ break
419
+
420
+ if found_prd and len(upcoming) >= 5:
421
+ break
422
+
423
+ return {
424
+ "current_mahadasha": ctx["md_lord"],
425
+ "current_antardasha": ctx["ad_lord"],
426
+ "current_pratyantar": ctx["pd_lord"],
427
+ "current_sookshma": current_sd_lord or "Unknown",
428
+ "current_prana": current_prd_lord or "Unknown",
429
+ "current_prana_start": (
430
+ current_prd_start.strftime("%d-%m-%Y") if current_prd_start else ""
431
+ ),
432
+ "current_prana_end": (
433
+ current_prd_end.strftime("%d-%m-%Y") if current_prd_end else ""
434
+ ),
435
+ "current_prana_duration_days": round(current_prd_days, 2)
436
+ if current_prd_days
437
+ else 0.0,
438
+ "upcoming_prana": upcoming,
439
+ }