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,108 @@
1
+ """Vedic chart constants: planets, rashis, nakshatras."""
2
+
3
+ import swisseph as swe
4
+
5
+ PLANET_IDS = {
6
+ "Sun": swe.SUN,
7
+ "Moon": swe.MOON,
8
+ "Mars": swe.MARS,
9
+ "Mercury": swe.MERCURY,
10
+ "Jupiter": swe.JUPITER,
11
+ "Venus": swe.VENUS,
12
+ "Saturn": swe.SATURN,
13
+ "Rahu": swe.MEAN_NODE,
14
+ "Ketu": None,
15
+ }
16
+
17
+ RASHIS = [
18
+ "Aries",
19
+ "Taurus",
20
+ "Gemini",
21
+ "Cancer",
22
+ "Leo",
23
+ "Virgo",
24
+ "Libra",
25
+ "Scorpio",
26
+ "Sagittarius",
27
+ "Capricorn",
28
+ "Aquarius",
29
+ "Pisces",
30
+ ]
31
+
32
+ RASHI_LORDS = [
33
+ "Mars",
34
+ "Venus",
35
+ "Mercury",
36
+ "Moon",
37
+ "Sun",
38
+ "Mercury",
39
+ "Venus",
40
+ "Mars",
41
+ "Jupiter",
42
+ "Saturn",
43
+ "Saturn",
44
+ "Jupiter",
45
+ ]
46
+
47
+ NAKSHATRAS = [
48
+ "Ashwini",
49
+ "Bharani",
50
+ "Krittika",
51
+ "Rohini",
52
+ "Mrigashira",
53
+ "Ardra",
54
+ "Punarvasu",
55
+ "Pushya",
56
+ "Ashlesha",
57
+ "Magha",
58
+ "Purva Phalguni",
59
+ "Uttara Phalguni",
60
+ "Hasta",
61
+ "Chitra",
62
+ "Swati",
63
+ "Vishakha",
64
+ "Anuradha",
65
+ "Jyeshtha",
66
+ "Mula",
67
+ "Purva Ashadha",
68
+ "Uttara Ashadha",
69
+ "Shravana",
70
+ "Dhanishta",
71
+ "Shatabhisha",
72
+ "Purva Bhadrapada",
73
+ "Uttara Bhadrapada",
74
+ "Revati",
75
+ ]
76
+
77
+ NAKSHATRA_LORDS = [
78
+ "Ketu",
79
+ "Venus",
80
+ "Sun",
81
+ "Moon",
82
+ "Mars",
83
+ "Rahu",
84
+ "Jupiter",
85
+ "Saturn",
86
+ "Mercury",
87
+ "Ketu",
88
+ "Venus",
89
+ "Sun",
90
+ "Moon",
91
+ "Mars",
92
+ "Rahu",
93
+ "Jupiter",
94
+ "Saturn",
95
+ "Mercury",
96
+ "Ketu",
97
+ "Venus",
98
+ "Sun",
99
+ "Moon",
100
+ "Mars",
101
+ "Rahu",
102
+ "Jupiter",
103
+ "Saturn",
104
+ "Mercury",
105
+ ]
106
+
107
+ # Legacy constants for backward compatibility; new code should use ayanamsa.py defaults.
108
+ CALC_FLAGS = swe.FLG_SWIEPH | swe.FLG_SPEED | swe.FLG_SIDEREAL
@@ -0,0 +1,262 @@
1
+ """Extended Dasha calculations: Pratyantar Dasha (3rd level) and Yogini Dasha."""
2
+
3
+ from datetime import datetime, timedelta
4
+
5
+ from kundali_lib.vedic.constants import NAKSHATRAS
6
+
7
+ # ── Vimshottari base data ────────────────────────────────────────────────────
8
+
9
+ VIMSHOTTARI_SEQUENCE = [
10
+ "Ketu",
11
+ "Venus",
12
+ "Sun",
13
+ "Moon",
14
+ "Mars",
15
+ "Rahu",
16
+ "Jupiter",
17
+ "Saturn",
18
+ "Mercury",
19
+ ]
20
+ VIMSHOTTARI_YEARS: dict[str, int] = {
21
+ "Ketu": 7,
22
+ "Venus": 20,
23
+ "Sun": 6,
24
+ "Moon": 10,
25
+ "Mars": 7,
26
+ "Rahu": 18,
27
+ "Jupiter": 16,
28
+ "Saturn": 19,
29
+ "Mercury": 17,
30
+ }
31
+ VIMSHOTTARI_TOTAL = 120 # years
32
+
33
+ NAKSHATRA_TO_DASHA_LORD: dict[str, str] = {
34
+ "Ashwini": "Ketu",
35
+ "Magha": "Ketu",
36
+ "Mula": "Ketu",
37
+ "Bharani": "Venus",
38
+ "Purva Phalguni": "Venus",
39
+ "Purva Ashadha": "Venus",
40
+ "Krittika": "Sun",
41
+ "Uttara Phalguni": "Sun",
42
+ "Uttara Ashadha": "Sun",
43
+ "Rohini": "Moon",
44
+ "Hasta": "Moon",
45
+ "Shravana": "Moon",
46
+ "Mrigashira": "Mars",
47
+ "Chitra": "Mars",
48
+ "Dhanishta": "Mars",
49
+ "Ardra": "Rahu",
50
+ "Swati": "Rahu",
51
+ "Shatabhisha": "Rahu",
52
+ "Punarvasu": "Jupiter",
53
+ "Vishakha": "Jupiter",
54
+ "Purva Bhadrapada": "Jupiter",
55
+ "Pushya": "Saturn",
56
+ "Anuradha": "Saturn",
57
+ "Uttara Bhadrapada": "Saturn",
58
+ "Ashlesha": "Mercury",
59
+ "Jyeshtha": "Mercury",
60
+ "Revati": "Mercury",
61
+ }
62
+
63
+ # ── Yogini Dasha data ────────────────────────────────────────────────────────
64
+
65
+ YOGINI_SEQUENCE = [
66
+ "Mangala",
67
+ "Pingala",
68
+ "Dhanya",
69
+ "Bhramari",
70
+ "Bhadrika",
71
+ "Ulka",
72
+ "Siddha",
73
+ "Sankata",
74
+ ]
75
+ YOGINI_PLANETS: dict[str, str] = {
76
+ "Mangala": "Moon",
77
+ "Pingala": "Sun",
78
+ "Dhanya": "Jupiter",
79
+ "Bhramari": "Mars",
80
+ "Bhadrika": "Mercury",
81
+ "Ulka": "Saturn",
82
+ "Siddha": "Venus",
83
+ "Sankata": "Rahu",
84
+ }
85
+ YOGINI_YEARS: dict[str, int] = {
86
+ "Mangala": 1,
87
+ "Pingala": 2,
88
+ "Dhanya": 3,
89
+ "Bhramari": 4,
90
+ "Bhadrika": 5,
91
+ "Ulka": 6,
92
+ "Siddha": 7,
93
+ "Sankata": 8,
94
+ }
95
+ YOGINI_TOTAL = 36 # years
96
+
97
+ _NAKSHATRA_SPAN = 360.0 / 27.0
98
+
99
+
100
+ # ── Private helpers ──────────────────────────────────────────────────────────
101
+
102
+
103
+ def _rotated(seq: list[str], start: str) -> list[str]:
104
+ """Return seq rotated so it begins at start."""
105
+ idx = seq.index(start)
106
+ return seq[idx:] + seq[:idx]
107
+
108
+
109
+ def _compute_pratyantars(
110
+ ad_start: datetime,
111
+ ad_lord: str,
112
+ ad_duration_years: float,
113
+ ) -> dict:
114
+ """Compute Pratyantar Dashas (3rd level) within one Antardasha.
115
+
116
+ PD sequence starts from the AD lord; PD duration = (pd_years/120) * ad_duration_years.
117
+ """
118
+ result: dict = {}
119
+ pd_cursor = ad_start
120
+
121
+ for pd_lord in _rotated(VIMSHOTTARI_SEQUENCE, ad_lord):
122
+ pd_years = (VIMSHOTTARI_YEARS[pd_lord] / VIMSHOTTARI_TOTAL) * ad_duration_years
123
+ pd_days = pd_years * 365.25
124
+ pd_end = pd_cursor + timedelta(days=pd_days)
125
+
126
+ result[pd_lord] = {
127
+ "start": pd_cursor.strftime("%d-%m-%Y"),
128
+ "end": pd_end.strftime("%d-%m-%Y"),
129
+ "duration_days": round(pd_days, 2),
130
+ }
131
+ pd_cursor = pd_end
132
+
133
+ return result
134
+
135
+
136
+ # ── Public functions ─────────────────────────────────────────────────────────
137
+
138
+
139
+ def get_pratyantar_dasha(
140
+ birth_dt: datetime,
141
+ moon_longitude: float,
142
+ moon_nakshatra: str,
143
+ ) -> dict:
144
+ """Calculate 3 levels of Vimshottari Dasha: Mahadasha → Antardasha → Pratyantar Dasha.
145
+
146
+ The first Mahadasha started before birth; its start date is computed by
147
+ extrapolating back by the elapsed fraction of the birth nakshatra.
148
+
149
+ Args:
150
+ birth_dt: Naive local birth datetime.
151
+ moon_longitude: Sidereal Moon longitude in degrees (0–360).
152
+ moon_nakshatra: Name of the birth nakshatra (e.g. "Ashwini").
153
+
154
+ Returns:
155
+ dict with keys ``birth_dasha_lord`` and ``mahadashas`` (fully nested).
156
+ """
157
+ birth_dasha_lord = NAKSHATRA_TO_DASHA_LORD[moon_nakshatra]
158
+
159
+ # Fraction of birth nakshatra already elapsed at the moment of birth
160
+ fraction_elapsed = (moon_longitude % _NAKSHATRA_SPAN) / _NAKSHATRA_SPAN
161
+
162
+ result_mds: dict = {}
163
+ current_md_end: datetime = birth_dt # running cursor; overwritten in loop
164
+
165
+ for i, md_lord in enumerate(_rotated(VIMSHOTTARI_SEQUENCE, birth_dasha_lord)):
166
+ md_full_years = VIMSHOTTARI_YEARS[md_lord]
167
+ md_full_days = md_full_years * 365.25
168
+
169
+ if i == 0:
170
+ # First MD started (fraction_elapsed * full_years) before birth
171
+ elapsed_days = fraction_elapsed * md_full_days
172
+ md_start = birth_dt - timedelta(days=elapsed_days)
173
+ # Duration available from birth onward
174
+ display_duration = round((1.0 - fraction_elapsed) * md_full_years, 4)
175
+ else:
176
+ md_start = current_md_end
177
+ display_duration = float(md_full_years)
178
+
179
+ md_end = md_start + timedelta(days=md_full_days)
180
+ current_md_end = md_end
181
+
182
+ # ── Antardasha sequence starts from this MD lord ──────────────────
183
+ result_ads: dict = {}
184
+ ad_cursor = md_start
185
+
186
+ for ad_lord in _rotated(VIMSHOTTARI_SEQUENCE, md_lord):
187
+ ad_full_years = (
188
+ VIMSHOTTARI_YEARS[ad_lord] / VIMSHOTTARI_TOTAL
189
+ ) * md_full_years
190
+ ad_full_days = ad_full_years * 365.25
191
+ ad_end = ad_cursor + timedelta(days=ad_full_days)
192
+
193
+ result_ads[ad_lord] = {
194
+ "start": ad_cursor.strftime("%d-%m-%Y"),
195
+ "end": ad_end.strftime("%d-%m-%Y"),
196
+ "duration_days": round(ad_full_days, 2),
197
+ "pratyantars": _compute_pratyantars(ad_cursor, ad_lord, ad_full_years),
198
+ }
199
+ ad_cursor = ad_end
200
+
201
+ result_mds[md_lord] = {
202
+ "start": md_start.strftime("%d-%m-%Y"),
203
+ "end": md_end.strftime("%d-%m-%Y"),
204
+ "duration_years": display_duration,
205
+ "antardashas": result_ads,
206
+ }
207
+
208
+ return {
209
+ "birth_dasha_lord": birth_dasha_lord,
210
+ "mahadashas": result_mds,
211
+ }
212
+
213
+
214
+ def get_yogini_dasha(
215
+ birth_dt: datetime,
216
+ moon_longitude: float,
217
+ moon_nakshatra: str,
218
+ ) -> dict:
219
+ """Calculate Yogini Dasha — an 8-planet, 36-year Vedic cycle.
220
+
221
+ The 27 nakshatras are grouped in sets of 3 (cycling through 8 yoginis).
222
+ The fraction elapsed in the birth nakshatra gives the remaining time in
223
+ the birth yogini period.
224
+
225
+ Args:
226
+ birth_dt: Naive local birth datetime.
227
+ moon_longitude: Sidereal Moon longitude in degrees (0–360).
228
+ moon_nakshatra: Name of the birth nakshatra.
229
+
230
+ Returns:
231
+ dict with keys ``birth_yogini`` and ``yogini_dashas``.
232
+ """
233
+ nak_idx = NAKSHATRAS.index(moon_nakshatra)
234
+ yogini_idx = (nak_idx // 3) % 8
235
+ birth_yogini = YOGINI_SEQUENCE[yogini_idx]
236
+
237
+ fraction_elapsed = (moon_longitude % _NAKSHATRA_SPAN) / _NAKSHATRA_SPAN
238
+ remaining_years = (1.0 - fraction_elapsed) * YOGINI_YEARS[birth_yogini]
239
+
240
+ result_dashas: dict = {}
241
+ current_date = birth_dt
242
+
243
+ for i in range(8):
244
+ j = (yogini_idx + i) % 8
245
+ yogini = YOGINI_SEQUENCE[j]
246
+ duration = remaining_years if i == 0 else float(YOGINI_YEARS[yogini])
247
+ end_date = current_date + timedelta(days=duration * 365.25)
248
+
249
+ result_dashas[yogini] = {
250
+ "yogini": yogini,
251
+ "planet": YOGINI_PLANETS[yogini],
252
+ "start": current_date.strftime("%d-%m-%Y"),
253
+ "end": end_date.strftime("%d-%m-%Y"),
254
+ # Remaining years for birth period, full int years for the rest
255
+ "duration_years": round(duration, 4) if i == 0 else YOGINI_YEARS[yogini],
256
+ }
257
+ current_date = end_date
258
+
259
+ return {
260
+ "birth_yogini": birth_yogini,
261
+ "yogini_dashas": result_dashas,
262
+ }