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,705 @@
1
+ """Ashtakoot Milan: 36-point Vedic marriage compatibility scoring."""
2
+
3
+ # ---------------------------------------------------------------------------
4
+ # Reference data
5
+ # ---------------------------------------------------------------------------
6
+
7
+ RASHIS = [
8
+ "Aries",
9
+ "Taurus",
10
+ "Gemini",
11
+ "Cancer",
12
+ "Leo",
13
+ "Virgo",
14
+ "Libra",
15
+ "Scorpio",
16
+ "Sagittarius",
17
+ "Capricorn",
18
+ "Aquarius",
19
+ "Pisces",
20
+ ]
21
+
22
+ RASHI_LORDS = {
23
+ "Aries": "Mars",
24
+ "Taurus": "Venus",
25
+ "Gemini": "Mercury",
26
+ "Cancer": "Moon",
27
+ "Leo": "Sun",
28
+ "Virgo": "Mercury",
29
+ "Libra": "Venus",
30
+ "Scorpio": "Mars",
31
+ "Sagittarius": "Jupiter",
32
+ "Capricorn": "Saturn",
33
+ "Aquarius": "Saturn",
34
+ "Pisces": "Jupiter",
35
+ }
36
+
37
+ NAKSHATRAS = [
38
+ "Ashwini",
39
+ "Bharani",
40
+ "Krittika",
41
+ "Rohini",
42
+ "Mrigashira",
43
+ "Ardra",
44
+ "Punarvasu",
45
+ "Pushya",
46
+ "Ashlesha",
47
+ "Magha",
48
+ "Purva Phalguni",
49
+ "Uttara Phalguni",
50
+ "Hasta",
51
+ "Chitra",
52
+ "Swati",
53
+ "Vishakha",
54
+ "Anuradha",
55
+ "Jyeshtha",
56
+ "Mula",
57
+ "Purva Ashadha",
58
+ "Uttara Ashadha",
59
+ "Shravana",
60
+ "Dhanishta",
61
+ "Shatabhisha",
62
+ "Purva Bhadrapada",
63
+ "Uttara Bhadrapada",
64
+ "Revati",
65
+ ]
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # Varna
69
+ # ---------------------------------------------------------------------------
70
+
71
+ VARNA = {
72
+ "Cancer": "Brahmin",
73
+ "Scorpio": "Brahmin",
74
+ "Pisces": "Brahmin",
75
+ "Aries": "Kshatriya",
76
+ "Leo": "Kshatriya",
77
+ "Sagittarius": "Kshatriya",
78
+ "Taurus": "Vaishya",
79
+ "Virgo": "Vaishya",
80
+ "Capricorn": "Vaishya",
81
+ "Gemini": "Shudra",
82
+ "Libra": "Shudra",
83
+ "Aquarius": "Shudra",
84
+ }
85
+
86
+ VARNA_RANK = {"Brahmin": 4, "Kshatriya": 3, "Vaishya": 2, "Shudra": 1}
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Vashya
90
+ # ---------------------------------------------------------------------------
91
+
92
+ VASHYA = {
93
+ "Aries": "Chatushpada",
94
+ "Taurus": "Chatushpada",
95
+ "Gemini": "Nara",
96
+ "Cancer": "Jalachara",
97
+ "Leo": "Vanachara",
98
+ "Virgo": "Nara",
99
+ "Libra": "Nara",
100
+ "Scorpio": "Keeta",
101
+ "Sagittarius": "Nara",
102
+ "Capricorn": "Jalachara",
103
+ "Aquarius": "Nara",
104
+ "Pisces": "Jalachara",
105
+ }
106
+
107
+ # Vashya compatible pairs (one-way control): score 1
108
+ _VASHYA_COMPATIBLE = {
109
+ ("Chatushpada", "Nara"),
110
+ ("Nara", "Chatushpada"),
111
+ }
112
+
113
+ # ---------------------------------------------------------------------------
114
+ # Yoni
115
+ # ---------------------------------------------------------------------------
116
+
117
+ YONI = {
118
+ "Ashwini": "Horse",
119
+ "Bharani": "Elephant",
120
+ "Krittika": "Sheep",
121
+ "Rohini": "Serpent",
122
+ "Mrigashira": "Serpent",
123
+ "Ardra": "Dog",
124
+ "Punarvasu": "Cat",
125
+ "Pushya": "Sheep",
126
+ "Ashlesha": "Cat",
127
+ "Magha": "Rat",
128
+ "Purva Phalguni": "Rat",
129
+ "Uttara Phalguni": "Cow",
130
+ "Hasta": "Buffalo",
131
+ "Chitra": "Tiger",
132
+ "Swati": "Buffalo",
133
+ "Vishakha": "Tiger",
134
+ "Anuradha": "Deer",
135
+ "Jyeshtha": "Deer",
136
+ "Mula": "Dog",
137
+ "Purva Ashadha": "Monkey",
138
+ "Uttara Ashadha": "Mongoose",
139
+ "Shravana": "Monkey",
140
+ "Dhanishta": "Lion",
141
+ "Shatabhisha": "Horse",
142
+ "Purva Bhadrapada": "Lion",
143
+ "Uttara Bhadrapada": "Cow",
144
+ "Revati": "Elephant",
145
+ }
146
+
147
+ _ENEMY_YONI_PAIRS = {
148
+ frozenset({"Horse", "Buffalo"}),
149
+ frozenset({"Elephant", "Lion"}),
150
+ frozenset({"Sheep", "Monkey"}),
151
+ frozenset({"Serpent", "Mongoose"}),
152
+ frozenset({"Dog", "Deer"}),
153
+ frozenset({"Cat", "Rat"}),
154
+ frozenset({"Cow", "Tiger"}),
155
+ }
156
+
157
+ # ---------------------------------------------------------------------------
158
+ # Graha Maitri
159
+ # ---------------------------------------------------------------------------
160
+
161
+ PLANET_FRIENDS = {
162
+ "Sun": {
163
+ "friends": ["Moon", "Mars", "Jupiter"],
164
+ "enemies": ["Venus", "Saturn"],
165
+ "neutral": ["Mercury"],
166
+ },
167
+ "Moon": {
168
+ "friends": ["Sun", "Mercury"],
169
+ "enemies": [],
170
+ "neutral": ["Mars", "Jupiter", "Venus", "Saturn"],
171
+ },
172
+ "Mars": {
173
+ "friends": ["Sun", "Moon", "Jupiter"],
174
+ "enemies": ["Mercury"],
175
+ "neutral": ["Venus", "Saturn"],
176
+ },
177
+ "Mercury": {
178
+ "friends": ["Sun", "Venus"],
179
+ "enemies": ["Moon"],
180
+ "neutral": ["Mars", "Jupiter", "Saturn"],
181
+ },
182
+ "Jupiter": {
183
+ "friends": ["Sun", "Moon", "Mars"],
184
+ "enemies": ["Mercury", "Venus"],
185
+ "neutral": ["Saturn"],
186
+ },
187
+ "Venus": {
188
+ "friends": ["Mercury", "Saturn"],
189
+ "enemies": ["Sun", "Moon"],
190
+ "neutral": ["Mars", "Jupiter"],
191
+ },
192
+ "Saturn": {
193
+ "friends": ["Mercury", "Venus"],
194
+ "enemies": ["Sun", "Moon", "Mars"],
195
+ "neutral": ["Jupiter"],
196
+ },
197
+ }
198
+
199
+
200
+ def _planet_relationship(p1: str, p2: str) -> str:
201
+ """Return 'friend', 'enemy', or 'neutral' from p1's perspective of p2."""
202
+ if p1 not in PLANET_FRIENDS:
203
+ return "neutral"
204
+ data = PLANET_FRIENDS[p1]
205
+ if p2 in data["friends"]:
206
+ return "friend"
207
+ if p2 in data["enemies"]:
208
+ return "enemy"
209
+ return "neutral"
210
+
211
+
212
+ # ---------------------------------------------------------------------------
213
+ # Gana
214
+ # ---------------------------------------------------------------------------
215
+
216
+ GANA = {
217
+ "Ashwini": "Deva",
218
+ "Mrigashira": "Deva",
219
+ "Punarvasu": "Deva",
220
+ "Pushya": "Deva",
221
+ "Hasta": "Deva",
222
+ "Swati": "Deva",
223
+ "Anuradha": "Deva",
224
+ "Shravana": "Deva",
225
+ "Revati": "Deva",
226
+ "Bharani": "Manushya",
227
+ "Rohini": "Manushya",
228
+ "Ardra": "Manushya",
229
+ "Purva Phalguni": "Manushya",
230
+ "Uttara Phalguni": "Manushya",
231
+ "Purva Ashadha": "Manushya",
232
+ "Uttara Ashadha": "Manushya",
233
+ "Purva Bhadrapada": "Manushya",
234
+ "Uttara Bhadrapada": "Manushya",
235
+ "Krittika": "Rakshasa",
236
+ "Ashlesha": "Rakshasa",
237
+ "Magha": "Rakshasa",
238
+ "Chitra": "Rakshasa",
239
+ "Vishakha": "Rakshasa",
240
+ "Jyeshtha": "Rakshasa",
241
+ "Mula": "Rakshasa",
242
+ "Dhanishta": "Rakshasa",
243
+ "Shatabhisha": "Rakshasa",
244
+ }
245
+
246
+ # Gana score table: (boy_gana, girl_gana) -> score
247
+ _GANA_SCORES = {
248
+ ("Deva", "Deva"): 6,
249
+ ("Manushya", "Manushya"): 6,
250
+ ("Rakshasa", "Rakshasa"): 6,
251
+ ("Deva", "Manushya"): 5,
252
+ ("Manushya", "Deva"): 5,
253
+ ("Deva", "Rakshasa"): 1,
254
+ ("Rakshasa", "Deva"): 0,
255
+ ("Manushya", "Rakshasa"): 0,
256
+ ("Rakshasa", "Manushya"): 3,
257
+ }
258
+
259
+ # ---------------------------------------------------------------------------
260
+ # Nadi
261
+ # ---------------------------------------------------------------------------
262
+
263
+ NADI = {
264
+ "Ashwini": "Aadi",
265
+ "Ardra": "Aadi",
266
+ "Punarvasu": "Aadi",
267
+ "Uttara Phalguni": "Aadi",
268
+ "Hasta": "Aadi",
269
+ "Jyeshtha": "Aadi",
270
+ "Mula": "Aadi",
271
+ "Shatabhisha": "Aadi",
272
+ "Purva Bhadrapada": "Aadi",
273
+ "Bharani": "Madhya",
274
+ "Mrigashira": "Madhya",
275
+ "Pushya": "Madhya",
276
+ "Purva Phalguni": "Madhya",
277
+ "Chitra": "Madhya",
278
+ "Anuradha": "Madhya",
279
+ "Purva Ashadha": "Madhya",
280
+ "Dhanishta": "Madhya",
281
+ "Uttara Bhadrapada": "Madhya",
282
+ "Krittika": "Antya",
283
+ "Rohini": "Antya",
284
+ "Ashlesha": "Antya",
285
+ "Magha": "Antya",
286
+ "Swati": "Antya",
287
+ "Vishakha": "Antya",
288
+ "Uttara Ashadha": "Antya",
289
+ "Shravana": "Antya",
290
+ "Revati": "Antya",
291
+ }
292
+
293
+ # ---------------------------------------------------------------------------
294
+ # Internal helpers
295
+ # ---------------------------------------------------------------------------
296
+
297
+
298
+ def _get_planet(positions: list, name: str) -> dict | None:
299
+ for p in positions:
300
+ if p["name"] == name:
301
+ return p
302
+ return None
303
+
304
+
305
+ def _moon_data(chart: dict) -> tuple:
306
+ """Return (moon_rashi, moon_nakshatra) from a chart."""
307
+ positions = chart.get("planetary_positions", [])
308
+ moon = _get_planet(positions, "Moon")
309
+ if moon:
310
+ rashi = moon.get("rashi") or chart.get("moon_sign", "")
311
+ nakshatra = moon.get("nakshatra", "")
312
+ return rashi, nakshatra
313
+ return chart.get("moon_sign", ""), ""
314
+
315
+
316
+ def _rashi_index(rashi: str) -> int:
317
+ try:
318
+ return RASHIS.index(rashi)
319
+ except ValueError:
320
+ return 0
321
+
322
+
323
+ def _nakshatra_index(nakshatra: str) -> int:
324
+ try:
325
+ return NAKSHATRAS.index(nakshatra)
326
+ except ValueError:
327
+ return 0
328
+
329
+
330
+ def _rashi_from(base: str, n: int) -> str:
331
+ """Return the rashi that is n positions from base (1-indexed)."""
332
+ idx = (_rashi_index(base) + n - 1) % 12
333
+ return RASHIS[idx]
334
+
335
+
336
+ def _count_rashi_from(base: str, target: str) -> int:
337
+ """Count how many rashis target is from base (1-indexed, 1-12)."""
338
+ return (_rashi_index(target) - _rashi_index(base)) % 12 + 1
339
+
340
+
341
+ # ---------------------------------------------------------------------------
342
+ # Koota scoring functions
343
+ # ---------------------------------------------------------------------------
344
+
345
+
346
+ def _score_varna(boy_rashi: str, girl_rashi: str) -> tuple:
347
+ boy_varna = VARNA.get(boy_rashi, "Shudra")
348
+ girl_varna = VARNA.get(girl_rashi, "Shudra")
349
+ boy_rank = VARNA_RANK.get(boy_varna, 1)
350
+ girl_rank = VARNA_RANK.get(girl_varna, 1)
351
+ score = 1.0 if boy_rank >= girl_rank else 0.0
352
+ desc = (
353
+ f"Boy: {boy_varna} ({boy_rashi}), Girl: {girl_varna} ({girl_rashi}). "
354
+ f"{'Compatible' if score == 1 else 'Incompatible'}: boy's social class "
355
+ f"{'matches or exceeds' if score == 1 else 'is lower than'} girl's."
356
+ )
357
+ return score, desc
358
+
359
+
360
+ def _score_vashya(boy_rashi: str, girl_rashi: str) -> tuple:
361
+ boy_group = VASHYA.get(boy_rashi, "Nara")
362
+ girl_group = VASHYA.get(girl_rashi, "Nara")
363
+ if boy_group == girl_group:
364
+ score = 2.0
365
+ compatibility = "same Vashya group — ideal"
366
+ elif (boy_group, girl_group) in _VASHYA_COMPATIBLE or (
367
+ girl_group,
368
+ boy_group,
369
+ ) in _VASHYA_COMPATIBLE:
370
+ score = 1.0
371
+ compatibility = "partially compatible Vashya groups"
372
+ else:
373
+ score = 0.0
374
+ compatibility = "incompatible Vashya groups"
375
+ desc = (
376
+ f"Boy: {boy_group} ({boy_rashi}), Girl: {girl_group} ({girl_rashi}). "
377
+ f"{compatibility.capitalize()}."
378
+ )
379
+ return score, desc
380
+
381
+
382
+ def _score_tara(boy_nak: str, girl_nak: str) -> tuple:
383
+ boy_idx = _nakshatra_index(boy_nak)
384
+ girl_idx = _nakshatra_index(girl_nak)
385
+
386
+ def tara_number(from_idx: int, to_idx: int) -> int:
387
+ count = (to_idx - from_idx) % 27 + 1 # 1-27
388
+ return ((count - 1) % 9) + 1 # 1-9
389
+
390
+ AUSPICIOUS_TARAS = {1, 4, 7}
391
+ INAUSPICIOUS_TARAS = {3, 6, 9}
392
+
393
+ t_boy_to_girl = tara_number(boy_idx, girl_idx)
394
+ t_girl_to_boy = tara_number(girl_idx, boy_idx)
395
+
396
+ def tara_category(t: int) -> str:
397
+ if t in AUSPICIOUS_TARAS:
398
+ return "auspicious"
399
+ if t in INAUSPICIOUS_TARAS:
400
+ return "inauspicious"
401
+ return "neutral"
402
+
403
+ cat_btg = tara_category(t_boy_to_girl)
404
+ cat_gtb = tara_category(t_girl_to_boy)
405
+
406
+ if cat_btg == "auspicious" and cat_gtb == "auspicious":
407
+ score = 3.0
408
+ elif cat_btg == "inauspicious" and cat_gtb == "inauspicious":
409
+ score = 0.0
410
+ else:
411
+ score = 1.5
412
+
413
+ desc = (
414
+ f"Boy→Girl tara: {t_boy_to_girl} ({cat_btg}), "
415
+ f"Girl→Boy tara: {t_girl_to_boy} ({cat_gtb})."
416
+ )
417
+ return score, desc
418
+
419
+
420
+ def _score_yoni(boy_nak: str, girl_nak: str) -> tuple:
421
+ boy_yoni = YONI.get(boy_nak, "")
422
+ girl_yoni = YONI.get(girl_nak, "")
423
+
424
+ if boy_yoni == girl_yoni:
425
+ score = 4.0
426
+ compat = "same animal — ideal match"
427
+ elif frozenset({boy_yoni, girl_yoni}) in _ENEMY_YONI_PAIRS:
428
+ score = 0.0
429
+ compat = "enemy animals — incompatible"
430
+ else:
431
+ score = 2.0
432
+ compat = "neutral animal pairing"
433
+
434
+ desc = (
435
+ f"Boy: {boy_yoni} ({boy_nak}), Girl: {girl_yoni} ({girl_nak}). "
436
+ f"{compat.capitalize()}."
437
+ )
438
+ return score, desc
439
+
440
+
441
+ def _score_graha_maitri(boy_rashi: str, girl_rashi: str) -> tuple:
442
+ boy_lord = RASHI_LORDS.get(boy_rashi, "")
443
+ girl_lord = RASHI_LORDS.get(girl_rashi, "")
444
+
445
+ rel_boy_to_girl = _planet_relationship(boy_lord, girl_lord)
446
+ rel_girl_to_boy = _planet_relationship(girl_lord, boy_lord)
447
+
448
+ scoring_map = {
449
+ ("friend", "friend"): 5.0,
450
+ ("friend", "neutral"): 4.0,
451
+ ("neutral", "friend"): 4.0,
452
+ ("neutral", "neutral"): 3.0,
453
+ ("friend", "enemy"): 1.0,
454
+ ("enemy", "friend"): 1.0,
455
+ ("neutral", "enemy"): 1.0,
456
+ ("enemy", "neutral"): 1.0,
457
+ ("enemy", "enemy"): 0.0,
458
+ }
459
+ score = scoring_map.get((rel_boy_to_girl, rel_girl_to_boy), 3.0)
460
+
461
+ desc = (
462
+ f"Boy's lord: {boy_lord} ({boy_rashi}), Girl's lord: {girl_lord} ({girl_rashi}). "
463
+ f"{boy_lord} views {girl_lord} as {rel_boy_to_girl}; "
464
+ f"{girl_lord} views {boy_lord} as {rel_girl_to_boy}."
465
+ )
466
+ return score, desc
467
+
468
+
469
+ def _score_gana(boy_nak: str, girl_nak: str) -> tuple:
470
+ boy_gana = GANA.get(boy_nak, "Manushya")
471
+ girl_gana = GANA.get(girl_nak, "Manushya")
472
+ score = float(_GANA_SCORES.get((boy_gana, girl_gana), 0))
473
+ desc = (
474
+ f"Boy: {boy_gana} ({boy_nak}), Girl: {girl_gana} ({girl_nak}). "
475
+ f"Score {int(score)}/6."
476
+ )
477
+ return score, desc
478
+
479
+
480
+ def _score_bhakoot(boy_rashi: str, girl_rashi: str) -> tuple:
481
+ girl_from_boy = _count_rashi_from(boy_rashi, girl_rashi) # 1-12
482
+ boy_from_girl = _count_rashi_from(girl_rashi, boy_rashi) # 1-12
483
+
484
+ # 6-8 Dosha: one is 6th from the other (the other is always 8th)
485
+ dosha_68 = girl_from_boy in {6, 8}
486
+ # 2-12 Dosha: one is 2nd from the other (the other is always 12th)
487
+ dosha_212 = girl_from_boy in {2, 12}
488
+
489
+ if dosha_68 or dosha_212:
490
+ score = 0.0
491
+ dosha_name = "6-8 (Shashtashtama)" if dosha_68 else "2-12 (Dvir-Dvadasha)"
492
+ compat = f"Bhakoot dosha present ({dosha_name}) — inauspicious"
493
+ else:
494
+ score = 7.0
495
+ compat = "No Bhakoot dosha — auspicious"
496
+
497
+ desc = (
498
+ f"Girl's rashi is {girl_from_boy}th from boy's ({boy_rashi}→{girl_rashi}); "
499
+ f"boy's is {boy_from_girl}th from girl's. {compat}."
500
+ )
501
+ return score, desc
502
+
503
+
504
+ def _score_nadi(boy_nak: str, girl_nak: str) -> tuple:
505
+ boy_nadi = NADI.get(boy_nak, "")
506
+ girl_nadi = NADI.get(girl_nak, "")
507
+
508
+ if boy_nadi and girl_nadi and boy_nadi == girl_nadi:
509
+ score = 0.0
510
+ desc = (
511
+ f"Both have {boy_nadi} Nadi ({boy_nak}, {girl_nak}). "
512
+ "Nadi Dosha present — same nadi is highly inauspicious for health and progeny."
513
+ )
514
+ else:
515
+ score = 8.0
516
+ desc = (
517
+ f"Boy: {boy_nadi} Nadi ({boy_nak}), Girl: {girl_nadi} Nadi ({girl_nak}). "
518
+ "Different nadis — auspicious, no Nadi Dosha."
519
+ )
520
+ return score, desc
521
+
522
+
523
+ # ---------------------------------------------------------------------------
524
+ # Mangal Dosha
525
+ # ---------------------------------------------------------------------------
526
+
527
+
528
+ def _has_mangal_dosha(chart: dict) -> bool:
529
+ """Mars in houses 1, 2, 4, 7, 8, or 12 from Lagna = Mangal Dosha."""
530
+ positions = chart.get("planetary_positions", [])
531
+ for p in positions:
532
+ if p["name"] == "Mars":
533
+ return p.get("house") in {1, 2, 4, 7, 8, 12}
534
+ return False
535
+
536
+
537
+ # ---------------------------------------------------------------------------
538
+ # Verdict helpers
539
+ # ---------------------------------------------------------------------------
540
+
541
+
542
+ def _verdict(total: float) -> str:
543
+ if total >= 31:
544
+ return "Excellent"
545
+ if total >= 26:
546
+ return "Good"
547
+ if total >= 21:
548
+ return "Average"
549
+ if total >= 16:
550
+ return "Below Average"
551
+ return "Poor"
552
+
553
+
554
+ def _recommendation(
555
+ total: float,
556
+ nadi_dosha: bool,
557
+ bhakoot_dosha: bool,
558
+ boy_mangal: bool,
559
+ girl_mangal: bool,
560
+ ) -> str:
561
+ parts = []
562
+
563
+ if total >= 31:
564
+ parts.append("This is an excellent match with very strong compatibility.")
565
+ elif total >= 26:
566
+ parts.append("This is a good match with solid compatibility.")
567
+ elif total >= 21:
568
+ parts.append("This is an average match; the union can work with mutual effort.")
569
+ elif total >= 16:
570
+ parts.append(
571
+ "Compatibility is below average; careful consideration is advised."
572
+ )
573
+ else:
574
+ parts.append(
575
+ "Compatibility is poor; consultation with a Jyotishi is strongly advised."
576
+ )
577
+
578
+ if nadi_dosha:
579
+ parts.append(
580
+ "Nadi Dosha is present, which may affect health and progeny — remedies are recommended."
581
+ )
582
+ if bhakoot_dosha:
583
+ parts.append(
584
+ "Bhakoot Dosha is present, which may cause emotional friction — remedies can help."
585
+ )
586
+ if boy_mangal and girl_mangal:
587
+ parts.append(
588
+ "Both partners have Mangal Dosha, which cancels each other out — compatible in this regard."
589
+ )
590
+ elif boy_mangal or girl_mangal:
591
+ parts.append(
592
+ "One partner has Mangal Dosha while the other does not — remedies or a Mangalik partner are advised."
593
+ )
594
+
595
+ return " ".join(parts)
596
+
597
+
598
+ # ---------------------------------------------------------------------------
599
+ # Main public function
600
+ # ---------------------------------------------------------------------------
601
+
602
+
603
+ def get_ashtakoot_milan(chart1: dict, chart2: dict) -> dict:
604
+ """
605
+ Compute Ashtakoot Milan (36-point) compatibility between two birth charts.
606
+
607
+ Args:
608
+ chart1: Boy's birth chart from build_chart().
609
+ chart2: Girl's birth chart from build_chart().
610
+
611
+ Returns:
612
+ Full compatibility report dict.
613
+ """
614
+ boy_rashi, boy_nak = _moon_data(chart1)
615
+ girl_rashi, girl_nak = _moon_data(chart2)
616
+
617
+ # Score all 8 kootas
618
+ varna_score, varna_desc = _score_varna(boy_rashi, girl_rashi)
619
+ vashya_score, vashya_desc = _score_vashya(boy_rashi, girl_rashi)
620
+ tara_score, tara_desc = _score_tara(boy_nak, girl_nak)
621
+ yoni_score, yoni_desc = _score_yoni(boy_nak, girl_nak)
622
+ gm_score, gm_desc = _score_graha_maitri(boy_rashi, girl_rashi)
623
+ gana_score, gana_desc = _score_gana(boy_nak, girl_nak)
624
+ bhakoot_score, bhakoot_desc = _score_bhakoot(boy_rashi, girl_rashi)
625
+ nadi_score, nadi_desc = _score_nadi(boy_nak, girl_nak)
626
+
627
+ total = (
628
+ varna_score
629
+ + vashya_score
630
+ + tara_score
631
+ + yoni_score
632
+ + gm_score
633
+ + gana_score
634
+ + bhakoot_score
635
+ + nadi_score
636
+ )
637
+
638
+ nadi_dosha = nadi_score == 0.0
639
+ bhakoot_dosha = bhakoot_score == 0.0
640
+ boy_mangal = _has_mangal_dosha(chart1)
641
+ girl_mangal = _has_mangal_dosha(chart2)
642
+
643
+ # Mangal Dosha cancels when both have it
644
+ mangal_compatible = not (boy_mangal ^ girl_mangal)
645
+
646
+ return {
647
+ "total_score": round(total, 1),
648
+ "max_score": 36,
649
+ "percentage": round(total / 36 * 100, 1),
650
+ "verdict": _verdict(total),
651
+ "recommendation": _recommendation(
652
+ total, nadi_dosha, bhakoot_dosha, boy_mangal, girl_mangal
653
+ ),
654
+ "kootas": {
655
+ "varna": {
656
+ "score": varna_score,
657
+ "max": 1,
658
+ "description": varna_desc,
659
+ },
660
+ "vashya": {
661
+ "score": vashya_score,
662
+ "max": 2,
663
+ "description": vashya_desc,
664
+ },
665
+ "tara": {
666
+ "score": tara_score,
667
+ "max": 3,
668
+ "description": tara_desc,
669
+ },
670
+ "yoni": {
671
+ "score": yoni_score,
672
+ "max": 4,
673
+ "description": yoni_desc,
674
+ },
675
+ "graha_maitri": {
676
+ "score": gm_score,
677
+ "max": 5,
678
+ "description": gm_desc,
679
+ },
680
+ "gana": {
681
+ "score": gana_score,
682
+ "max": 6,
683
+ "description": gana_desc,
684
+ },
685
+ "bhakoot": {
686
+ "score": bhakoot_score,
687
+ "max": 7,
688
+ "description": bhakoot_desc,
689
+ },
690
+ "nadi": {
691
+ "score": nadi_score,
692
+ "max": 8,
693
+ "description": nadi_desc,
694
+ },
695
+ },
696
+ "boy_moon_rashi": boy_rashi,
697
+ "girl_moon_rashi": girl_rashi,
698
+ "boy_nakshatra": boy_nak,
699
+ "girl_nakshatra": girl_nak,
700
+ "mangal_dosha_check": {
701
+ "boy": boy_mangal,
702
+ "girl": girl_mangal,
703
+ "compatible": mangal_compatible,
704
+ },
705
+ }