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,361 @@
1
+ """Jaimini astrology: Chara Karakas, Jaimini aspects, and Atmakaraka analysis."""
2
+
3
+ # ---------------------------------------------------------------------------
4
+ # Constant tables
5
+ # ---------------------------------------------------------------------------
6
+
7
+ KARAKA_NAMES = [
8
+ "Atmakaraka",
9
+ "Amatyakaraka",
10
+ "Bhratrukaraka",
11
+ "Matrukaraka",
12
+ "Putrakaraka",
13
+ "Gnatikaraka",
14
+ "Darakaraka",
15
+ ]
16
+
17
+ # Planets used for Chara Karaka calculation (7-karaka system)
18
+ KARAKA_PLANETS = [
19
+ "Sun",
20
+ "Moon",
21
+ "Mars",
22
+ "Mercury",
23
+ "Jupiter",
24
+ "Venus",
25
+ "Saturn",
26
+ "Rahu",
27
+ ]
28
+
29
+ # Sign type classification
30
+ MOVABLE_SIGNS = ["Aries", "Cancer", "Libra", "Capricorn"]
31
+ FIXED_SIGNS = ["Taurus", "Leo", "Scorpio", "Aquarius"]
32
+ DUAL_SIGNS = ["Gemini", "Virgo", "Sagittarius", "Pisces"]
33
+
34
+ # Jaimini aspects: each sign's aspect list (all signs it aspects)
35
+ # Movable aspects Fixed (except adjacent); Fixed aspects Movable (except adjacent);
36
+ # Dual aspects all other Dual signs.
37
+ _JAIMINI_ASPECTS: dict = {
38
+ "Aries": ["Leo", "Scorpio", "Aquarius"],
39
+ "Taurus": ["Cancer", "Libra", "Capricorn"],
40
+ "Gemini": ["Virgo", "Sagittarius", "Pisces"],
41
+ "Cancer": ["Taurus", "Leo", "Scorpio"],
42
+ "Leo": ["Aries", "Cancer", "Libra"],
43
+ "Virgo": ["Gemini", "Sagittarius", "Pisces"],
44
+ "Libra": ["Taurus", "Leo", "Scorpio"],
45
+ "Scorpio": ["Aries", "Cancer", "Libra"],
46
+ "Sagittarius": ["Gemini", "Virgo", "Pisces"],
47
+ "Capricorn": ["Taurus", "Leo", "Scorpio"],
48
+ "Aquarius": ["Aries", "Cancer", "Libra"],
49
+ "Pisces": ["Gemini", "Virgo", "Sagittarius"],
50
+ }
51
+
52
+ # Navamsha (D9) start rashi for each D1 sign index (0=Aries…11=Pisces)
53
+ # Fire signs (Aries=0, Leo=4, Sagittarius=8) start from Aries (0)
54
+ # Earth signs (Taurus=1, Virgo=5, Capricorn=9) start from Capricorn (9)
55
+ # Air signs (Gemini=2, Libra=6, Aquarius=10) start from Libra (6)
56
+ # Water signs(Cancer=3, Scorpio=7, Pisces=11) start from Cancer (3)
57
+ _NAVAMSHA_START = {
58
+ 0: 0,
59
+ 1: 9,
60
+ 2: 6,
61
+ 3: 3,
62
+ 4: 0,
63
+ 5: 9,
64
+ 6: 6,
65
+ 7: 3,
66
+ 8: 0,
67
+ 9: 9,
68
+ 10: 6,
69
+ 11: 3,
70
+ }
71
+
72
+ RASHIS = [
73
+ "Aries",
74
+ "Taurus",
75
+ "Gemini",
76
+ "Cancer",
77
+ "Leo",
78
+ "Virgo",
79
+ "Libra",
80
+ "Scorpio",
81
+ "Sagittarius",
82
+ "Capricorn",
83
+ "Aquarius",
84
+ "Pisces",
85
+ ]
86
+
87
+ # Atmakaraka interpretations keyed by planet
88
+ _AK_INTERPRETATIONS = {
89
+ "Sun": (
90
+ "Sun as Atmakaraka indicates a soul driven by authority, recognition, and leadership. "
91
+ "The native is here to cultivate ego-transcendence through positions of power. "
92
+ "Lessons around pride, father figures, and governance are central to the life path."
93
+ ),
94
+ "Moon": (
95
+ "Moon as Atmakaraka points to a soul whose deepest lessons revolve around the mind, "
96
+ "emotions, and nurturing. The native is drawn to healing, home, and the cycles of life. "
97
+ "Emotional mastery and compassion are the primary spiritual aims."
98
+ ),
99
+ "Mars": (
100
+ "Mars as Atmakaraka signifies a warrior soul whose journey involves courage, discipline, "
101
+ "and the right use of energy. Past-life themes of conflict and protection emerge. "
102
+ "The native must channel ambition into dharmic action."
103
+ ),
104
+ "Mercury": (
105
+ "Mercury as Atmakaraka indicates an intellectual soul whose path centers on communication, "
106
+ "discernment, and adaptability. The native is here to master the spoken and written word "
107
+ "and to use intelligence in service of truth."
108
+ ),
109
+ "Jupiter": (
110
+ "Jupiter as Atmakaraka reflects a soul with strong dharmic inclinations toward wisdom, "
111
+ "teaching, and spiritual guidance. The native is here to expand consciousness, share "
112
+ "knowledge, and uphold righteousness."
113
+ ),
114
+ "Venus": (
115
+ "Venus as Atmakaraka suggests a soul whose evolution is tied to relationships, beauty, "
116
+ "and creative expression. Themes of love, pleasure, and aesthetic refinement recur. "
117
+ "The lesson is learning to love without attachment."
118
+ ),
119
+ "Saturn": (
120
+ "Saturn as Atmakaraka is the mark of a soul undergoing deep purification through "
121
+ "discipline, hardship, and service. The native must cultivate patience, humility, "
122
+ "and endurance to fulfill a karmic contract of dutiful responsibility."
123
+ ),
124
+ "Rahu": (
125
+ "Rahu as Atmakaraka (rare) indicates an intensely karmic soul carrying unfulfilled "
126
+ "desires across lifetimes. The native's path involves confronting obsessions and "
127
+ "illusions in order to achieve liberation through transcending material cravings."
128
+ ),
129
+ }
130
+
131
+ _DK_INTERPRETATIONS = {
132
+ "Sun": "Sun as Darakaraka suggests a partner who is authoritative, proud, and leadership-oriented.",
133
+ "Moon": "Moon as Darakaraka indicates a partner who is nurturing, emotional, and family-focused.",
134
+ "Mars": "Mars as Darakaraka points to a partner who is energetic, assertive, and action-driven.",
135
+ "Mercury": "Mercury as Darakaraka suggests a communicative, intellectual, and versatile partner.",
136
+ "Jupiter": "Jupiter as Darakaraka indicates a wise, generous, and dharmic partner.",
137
+ "Venus": "Venus as Darakaraka (uncommon) points to a highly artistic, charming partner.",
138
+ "Saturn": "Saturn as Darakaraka suggests a disciplined, serious, or much older/younger partner.",
139
+ "Rahu": "Rahu as Darakaraka indicates an unconventional, foreign, or karmic partner relationship.",
140
+ }
141
+
142
+
143
+ # ---------------------------------------------------------------------------
144
+ # Helper: navamsha sign from absolute sidereal longitude
145
+ # ---------------------------------------------------------------------------
146
+
147
+
148
+ def _navamsha_rashi(lon: float) -> str:
149
+ lon = lon % 360
150
+ rashi_idx = int(lon / 30)
151
+ degree_in_sign = lon - rashi_idx * 30
152
+ nav_num = min(int(degree_in_sign / (10.0 / 3.0)), 8)
153
+ d9_idx = (_NAVAMSHA_START[rashi_idx] + nav_num) % 12
154
+ return RASHIS[d9_idx]
155
+
156
+
157
+ def _sign_type(sign: str) -> str:
158
+ if sign in MOVABLE_SIGNS:
159
+ return "Movable"
160
+ if sign in FIXED_SIGNS:
161
+ return "Fixed"
162
+ if sign in DUAL_SIGNS:
163
+ return "Dual"
164
+ return "Unknown"
165
+
166
+
167
+ # ---------------------------------------------------------------------------
168
+ # Chara Karakas
169
+ # ---------------------------------------------------------------------------
170
+
171
+
172
+ def get_jaimini_karakas(planetary_positions: list) -> dict:
173
+ """
174
+ Determine the seven Chara Karakas using the Jaimini 7-karaka system.
175
+
176
+ Parameters
177
+ ----------
178
+ planetary_positions : list
179
+ List of planet dicts from ``build_chart()``.
180
+
181
+ Returns
182
+ -------
183
+ dict
184
+ ``karakas`` mapping karaka name → planet details,
185
+ ``atmakaraka_analysis``, and ``darakaraka_analysis``.
186
+ """
187
+ # Collect degree-in-sign for each karaka planet
188
+ planet_degrees: list[tuple[str, float, str, int]] = []
189
+
190
+ pos_map = {p["name"]: p for p in planetary_positions}
191
+
192
+ for planet in KARAKA_PLANETS:
193
+ p = pos_map.get(planet)
194
+ if p is None:
195
+ continue
196
+
197
+ degree_in_sign = p["degree"] % 30 # degree within the sign
198
+
199
+ # Rahu moves backwards → invert degree for ranking
200
+ if planet == "Rahu":
201
+ degree_in_sign = 30.0 - degree_in_sign
202
+
203
+ planet_degrees.append(
204
+ (planet, degree_in_sign, p.get("rashi", ""), p.get("house", 0))
205
+ )
206
+
207
+ # Sort descending by degree_in_sign
208
+ planet_degrees.sort(key=lambda x: x[1], reverse=True)
209
+
210
+ # Assign karakas
211
+ karakas: dict = {}
212
+ for i, karaka_name in enumerate(KARAKA_NAMES):
213
+ if i >= len(planet_degrees):
214
+ break
215
+ planet_name, deg, rashi, house = planet_degrees[i]
216
+ karakas[karaka_name] = {
217
+ "karaka": karaka_name,
218
+ "planet": planet_name,
219
+ "degree_in_sign": round(deg, 3),
220
+ "rashi": rashi,
221
+ "house": house,
222
+ }
223
+
224
+ # Interpretations
225
+ ak_planet = karakas.get("Atmakaraka", {}).get("planet", "Sun")
226
+ dk_planet = karakas.get("Darakaraka", {}).get("planet", "Venus")
227
+
228
+ ak_analysis = _AK_INTERPRETATIONS.get(
229
+ ak_planet,
230
+ f"{ak_planet} as Atmakaraka shapes the soul's core lessons in this lifetime.",
231
+ )
232
+ dk_analysis = _DK_INTERPRETATIONS.get(
233
+ dk_planet,
234
+ f"{dk_planet} as Darakaraka describes the nature of the destined partner.",
235
+ )
236
+
237
+ return {
238
+ "karakas": karakas,
239
+ "atmakaraka_analysis": ak_analysis,
240
+ "darakaraka_analysis": dk_analysis,
241
+ }
242
+
243
+
244
+ # ---------------------------------------------------------------------------
245
+ # Jaimini Aspects
246
+ # ---------------------------------------------------------------------------
247
+
248
+
249
+ def get_jaimini_aspects(base_chart: dict) -> dict:
250
+ """
251
+ Calculate Jaimini sign-based aspects for each planet.
252
+
253
+ Parameters
254
+ ----------
255
+ base_chart : dict
256
+ Output of ``build_chart()``.
257
+
258
+ Returns
259
+ -------
260
+ dict
261
+ Keyed by planet name; each value contains sign, sign_type,
262
+ aspected_signs, and aspected_by.
263
+ """
264
+ positions: list = base_chart.get("planetary_positions", [])
265
+
266
+ # Build rashi → planets mapping
267
+ sign_to_planets: dict[str, list[str]] = {}
268
+ planet_sign: dict[str, str] = {}
269
+
270
+ for p in positions:
271
+ rashi = p.get("rashi", "")
272
+ name = p["name"]
273
+ planet_sign[name] = rashi
274
+ sign_to_planets.setdefault(rashi, []).append(name)
275
+
276
+ result: dict = {}
277
+
278
+ for p in positions:
279
+ name = p["name"]
280
+ sign = p.get("rashi", "")
281
+ aspected_signs = _JAIMINI_ASPECTS.get(sign, [])
282
+
283
+ # Which planets aspect this planet's sign?
284
+ aspected_by: list[str] = []
285
+ for other_sign, aspects in _JAIMINI_ASPECTS.items():
286
+ if sign in aspects:
287
+ aspected_by.extend(sign_to_planets.get(other_sign, []))
288
+
289
+ result[name] = {
290
+ "planet": name,
291
+ "sign": sign,
292
+ "sign_type": _sign_type(sign),
293
+ "aspected_signs": aspected_signs,
294
+ "aspected_by": [x for x in aspected_by if x != name],
295
+ }
296
+
297
+ return result
298
+
299
+
300
+ # ---------------------------------------------------------------------------
301
+ # Karakamsha
302
+ # ---------------------------------------------------------------------------
303
+
304
+
305
+ def get_karakamsha(planetary_positions: list, d9_positions: list) -> dict:
306
+ """
307
+ Find the Karakamsha — the sign occupied by the Atmakaraka in the Navamsha (D9).
308
+
309
+ Parameters
310
+ ----------
311
+ planetary_positions : list
312
+ D1 (birth chart) planet list from ``build_chart()``.
313
+ d9_positions : list
314
+ D9 (Navamsha) planet list. If not available, D9 sign is computed
315
+ from the Atmakaraka's D1 longitude.
316
+
317
+ Returns
318
+ -------
319
+ dict
320
+ Atmakaraka details, Karakamsha sign, Swamsha flag, and interpretation.
321
+ """
322
+ karaka_data = get_jaimini_karakas(planetary_positions)
323
+ ak_info = karaka_data["karakas"].get("Atmakaraka", {})
324
+ ak_planet = ak_info.get("planet", "Sun")
325
+ ak_d1_sign = ak_info.get("rashi", "")
326
+
327
+ # Try to get D9 sign from d9_positions if provided
328
+ ak_d9_sign = ""
329
+ if d9_positions:
330
+ d9_map = {p["name"]: p for p in d9_positions}
331
+ ak_d9_sign = d9_map.get(ak_planet, {}).get("rashi", "")
332
+
333
+ # Fallback: compute D9 sign from D1 longitude
334
+ if not ak_d9_sign:
335
+ pos_map = {p["name"]: p for p in planetary_positions}
336
+ ak_lon = pos_map.get(ak_planet, {}).get("longitude", 0.0)
337
+ ak_d9_sign = _navamsha_rashi(ak_lon)
338
+
339
+ swamsha = ak_d1_sign == ak_d9_sign
340
+
341
+ if swamsha:
342
+ interp = (
343
+ f"Swamsha — {ak_planet} occupies the same sign ({ak_d1_sign}) in both D1 and D9. "
344
+ "This is exceptionally powerful, conferring spiritual depth, self-mastery, and "
345
+ "heightened manifestation of the Atmakaraka's qualities throughout the life."
346
+ )
347
+ else:
348
+ interp = (
349
+ f"Karakamsha falls in {ak_d9_sign}. This sign and its lord become a supplementary "
350
+ f"Lagna for predicting career, spirituality, and the fruits of past-life deeds. "
351
+ f"The {ak_d9_sign} energy colors how the soul's purpose ({ak_planet} as AK) "
352
+ "expresses in the outer world."
353
+ )
354
+
355
+ return {
356
+ "atmakaraka": ak_planet,
357
+ "atmakaraka_d1_sign": ak_d1_sign,
358
+ "atmakaraka_d9_sign": ak_d9_sign,
359
+ "swamsha": swamsha,
360
+ "interpretation": interp,
361
+ }
@@ -0,0 +1,226 @@
1
+ """Kalachakra Dasha: the 9-cycle, 112-year sign-based dasha system.
2
+
3
+ One of the most powerful and complex dasha systems in Vedic astrology.
4
+ Based on birth nakshatra pada position within the 9-group Kalachakra wheel.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime, timedelta
10
+ from typing import Any
11
+
12
+ # ── Constants ─────────────────────────────────────────────────────────────────
13
+
14
+ RASHIS = [
15
+ "Aries",
16
+ "Taurus",
17
+ "Gemini",
18
+ "Cancer",
19
+ "Leo",
20
+ "Virgo",
21
+ "Libra",
22
+ "Scorpio",
23
+ "Sagittarius",
24
+ "Capricorn",
25
+ "Aquarius",
26
+ "Pisces",
27
+ ]
28
+
29
+ # Standard Kalachakra dasha years per sign
30
+ KALACHAKRA_YEARS: dict[str, int] = {
31
+ "Aries": 7,
32
+ "Taurus": 16,
33
+ "Gemini": 9,
34
+ "Cancer": 21,
35
+ "Leo": 5,
36
+ "Virgo": 9,
37
+ "Libra": 16,
38
+ "Scorpio": 7,
39
+ "Sagittarius": 10,
40
+ "Capricorn": 4,
41
+ "Aquarius": 4,
42
+ "Pisces": 4,
43
+ }
44
+ KALACHAKRA_TOTAL = 112 # years per full cycle
45
+
46
+ # Savya (forward) sign sequence
47
+ _SAVYA = RASHIS[:] # Aries → Pisces
48
+
49
+ # Apasavya (reverse) sign sequence
50
+ _APASAVYA = list(reversed(RASHIS)) # Pisces → Aries
51
+
52
+ # The 9 groups, each containing 12 sign slots mapped by pada index 1-12 within group.
53
+ # Groups alternate Savya / Apasavya.
54
+ # Pada index within group (1-based) selects the sign.
55
+ _GROUPS: list[list[str]] = [
56
+ _SAVYA, # Group 1 (padas 1-12) : Aries…Pisces
57
+ _APASAVYA, # Group 2 (padas 13-24) : Pisces…Aries
58
+ _SAVYA, # Group 3 (padas 25-36)
59
+ _APASAVYA, # Group 4 (padas 37-48)
60
+ _SAVYA, # Group 5 (padas 49-60)
61
+ _APASAVYA, # Group 6 (padas 61-72)
62
+ _SAVYA, # Group 7 (padas 73-84)
63
+ _APASAVYA, # Group 8 (padas 85-96)
64
+ _SAVYA, # Group 9 (padas 97-108)
65
+ ]
66
+
67
+ # 27 nakshatras in order
68
+ NAKSHATRAS = [
69
+ "Ashwini",
70
+ "Bharani",
71
+ "Krittika",
72
+ "Rohini",
73
+ "Mrigashira",
74
+ "Ardra",
75
+ "Punarvasu",
76
+ "Pushya",
77
+ "Ashlesha",
78
+ "Magha",
79
+ "Purva Phalguni",
80
+ "Uttara Phalguni",
81
+ "Hasta",
82
+ "Chitra",
83
+ "Swati",
84
+ "Vishakha",
85
+ "Anuradha",
86
+ "Jyeshtha",
87
+ "Mula",
88
+ "Purva Ashadha",
89
+ "Uttara Ashadha",
90
+ "Shravana",
91
+ "Dhanishta",
92
+ "Shatabhisha",
93
+ "Purva Bhadrapada",
94
+ "Uttara Bhadrapada",
95
+ "Revati",
96
+ ]
97
+
98
+
99
+ def _pada_global_index(moon_lon: float) -> tuple[int, int, float]:
100
+ """Return (global_pada 1-108, pada_within_nakshatra 1-4, fraction_elapsed 0-1)."""
101
+ nakshatra_span = 360.0 / 27
102
+ pada_span = nakshatra_span / 4
103
+
104
+ nak_idx = int(moon_lon / nakshatra_span) # 0-26
105
+ pos_in_nak = moon_lon % nakshatra_span
106
+ pada_in_nak = int(pos_in_nak / pada_span) # 0-3 within nakshatra
107
+ fraction = (pos_in_nak % pada_span) / pada_span # 0-1 elapsed within current pada
108
+
109
+ global_pada = nak_idx * 4 + pada_in_nak + 1 # 1-108
110
+ return global_pada, pada_in_nak + 1, fraction
111
+
112
+
113
+ def _starting_sign_and_sequence(global_pada: int) -> tuple[str, list[str]]:
114
+ """Return (starting_sign, full_sequence_of_12_signs_for_this_group)."""
115
+ group_idx = (global_pada - 1) // 12 # 0-8
116
+ pos_in_group = (global_pada - 1) % 12 # 0-11
117
+
118
+ group_sequence = _GROUPS[group_idx]
119
+ starting_sign = group_sequence[pos_in_group]
120
+
121
+ # Dasha sequence from starting_sign wraps within the group sequence
122
+ sequence = group_sequence[pos_in_group:] + group_sequence[:pos_in_group]
123
+ return starting_sign, sequence
124
+
125
+
126
+ def _fmt(dt: datetime) -> str:
127
+ return dt.strftime("%d-%m-%Y")
128
+
129
+
130
+ def _days(years: float) -> float:
131
+ return years * 365.25
132
+
133
+
134
+ # ── Public API ─────────────────────────────────────────────────────────────────
135
+
136
+
137
+ def get_kalachakra_dasha(
138
+ birth_dt: datetime, moon_longitude: float, moon_nakshatra: str
139
+ ) -> dict[str, Any]:
140
+ """Calculate Kalachakra Dasha for a birth chart.
141
+
142
+ Parameters
143
+ ----------
144
+ birth_dt : timezone-aware birth datetime
145
+ moon_longitude: sidereal Moon longitude (0-360)
146
+ moon_nakshatra: Moon's nakshatra name string
147
+ """
148
+ global_pada, pada_in_nak, fraction_elapsed = _pada_global_index(moon_longitude)
149
+ starting_sign, sign_sequence = _starting_sign_and_sequence(global_pada)
150
+
151
+ # Remaining time in birth (starting) sign's dasha
152
+ birth_sign_years = KALACHAKRA_YEARS[starting_sign]
153
+ remaining_years = (1.0 - fraction_elapsed) * birth_sign_years
154
+ remaining_days = _days(remaining_years)
155
+
156
+ # First MD ends at birth + remaining days
157
+ current_end = birth_dt + timedelta(days=remaining_days)
158
+
159
+ mahadashas: dict[str, Any] = {}
160
+ current_start = birth_dt
161
+
162
+ for i, sign in enumerate(sign_sequence):
163
+ years = KALACHAKRA_YEARS[sign]
164
+ if i == 0:
165
+ # birth sign — only remaining portion
166
+ md_days = remaining_days
167
+ else:
168
+ md_days = _days(years)
169
+
170
+ md_end = current_start + timedelta(days=md_days)
171
+
172
+ # ── Antardashas within this MD ────────────────────────────────────
173
+ # Sub-signs rotate starting from the same sign, same direction as group
174
+ # Duration: (ad_sign_years / KALACHAKRA_TOTAL) * md_duration_years
175
+ antardashas: dict[str, Any] = {}
176
+ ad_start = current_start
177
+ for j, ad_sign in enumerate(sign_sequence):
178
+ ad_years_full = KALACHAKRA_YEARS[ad_sign]
179
+ if i == 0 and j == 0:
180
+ # first sub within first MD — only remaining fraction of AD too
181
+ ad_proportion = 1.0 - fraction_elapsed
182
+ ad_days = _days(
183
+ (ad_proportion * ad_years_full * remaining_years) / birth_sign_years
184
+ )
185
+ else:
186
+ ad_days = _days(
187
+ (ad_years_full / KALACHAKRA_TOTAL)
188
+ * (years if i > 0 else remaining_years)
189
+ )
190
+ ad_end = ad_start + timedelta(days=ad_days)
191
+ antardashas[ad_sign] = {
192
+ "start": _fmt(ad_start),
193
+ "end": _fmt(ad_end),
194
+ "duration_days": round(ad_days, 1),
195
+ }
196
+ ad_start = ad_end
197
+
198
+ mahadashas[sign] = {
199
+ "start": _fmt(current_start),
200
+ "end": _fmt(md_end),
201
+ "duration_years": round(remaining_years if i == 0 else years, 2),
202
+ "antardashas": antardashas,
203
+ }
204
+ current_start = md_end
205
+
206
+ # After one full 112-year cycle, it repeats — annotate
207
+ second_cycle_start = current_start
208
+ second_cycle_note = (
209
+ f"Second cycle starts {_fmt(second_cycle_start)} (repeats from {starting_sign})"
210
+ )
211
+
212
+ return {
213
+ "system": "Kalachakra Dasha",
214
+ "total_years_per_cycle": KALACHAKRA_TOTAL,
215
+ "birth_nakshatra": moon_nakshatra,
216
+ "birth_pada": pada_in_nak,
217
+ "global_pada_number": global_pada,
218
+ "kalachakra_group": ((global_pada - 1) // 12) + 1,
219
+ "group_direction": "Savya (forward)"
220
+ if ((global_pada - 1) // 12) % 2 == 0
221
+ else "Apasavya (reverse)",
222
+ "starting_sign": starting_sign,
223
+ "sign_sequence": sign_sequence,
224
+ "mahadashas": mahadashas,
225
+ "second_cycle_note": second_cycle_note,
226
+ }