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,243 @@
1
+ """Kartari Yoga: planets hemmed between benefics (Subha) or malefics (Papa)."""
2
+
3
+ # ---------------------------------------------------------------------------
4
+ # Reference data
5
+ # ---------------------------------------------------------------------------
6
+
7
+ _NATURAL_BENEFICS = {"Moon", "Mercury", "Jupiter", "Venus"}
8
+ _NATURAL_MALEFICS = {"Sun", "Mars", "Saturn", "Rahu", "Ketu"}
9
+
10
+
11
+ def _planet_type(name: str) -> str:
12
+ """Return 'benefic', 'malefic', or 'neutral' for a planet name."""
13
+ if name in _NATURAL_BENEFICS:
14
+ return "benefic"
15
+ if name in _NATURAL_MALEFICS:
16
+ return "malefic"
17
+ return "neutral"
18
+
19
+
20
+ def _dominant_type(planet_names: list) -> str:
21
+ """Return the dominant type among a list of planet names."""
22
+ if not planet_names:
23
+ return "empty"
24
+ types = [_planet_type(n) for n in planet_names]
25
+ benefic_count = types.count("benefic")
26
+ malefic_count = types.count("malefic")
27
+ if benefic_count > 0 and malefic_count == 0:
28
+ return "benefic"
29
+ if malefic_count > 0 and benefic_count == 0:
30
+ return "malefic"
31
+ if benefic_count > 0 and malefic_count > 0:
32
+ return "mixed"
33
+ return "neutral"
34
+
35
+
36
+ def _adjacent_houses(house: int) -> tuple:
37
+ """Return the (preceding, following) house numbers (1–12, wrapping)."""
38
+ preceding = ((house - 2) % 12) + 1
39
+ following = (house % 12) + 1
40
+ return preceding, following
41
+
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Main function
45
+ # ---------------------------------------------------------------------------
46
+
47
+
48
+ def check_kartari_yoga(planetary_positions: list, houses: dict) -> list:
49
+ """Detect Kartari Yoga for every occupied house and every planet.
50
+
51
+ Parameters
52
+ ----------
53
+ planetary_positions : list
54
+ Each item is a planet dict with at least ``name`` and ``house``.
55
+ houses : dict
56
+ Maps house number (int) to a list of planet name strings
57
+ present in that house. E.g. ``{1: ["Sun"], 7: ["Mars", "Venus"]}``.
58
+ Houses with no planets should either be absent or map to ``[]``.
59
+
60
+ Returns
61
+ -------
62
+ list of dict
63
+ One entry per planet/point that is hemmed between two occupied houses.
64
+ Includes special Paapa Kartari on Lagna and Subha Kartari on Moon
65
+ annotations.
66
+ """
67
+ results = []
68
+ seen_houses = set() # avoid duplicating house checks
69
+
70
+ # ----------------------------------------------------------------
71
+ # Per-planet analysis
72
+ # ----------------------------------------------------------------
73
+ for planet in planetary_positions:
74
+ name = planet["name"]
75
+ house = planet["house"]
76
+
77
+ pre_house, fol_house = _adjacent_houses(house)
78
+ pre_planets = houses.get(pre_house, [])
79
+ fol_planets = houses.get(fol_house, [])
80
+
81
+ pre_type = _dominant_type(pre_planets)
82
+ fol_type = _dominant_type(fol_planets)
83
+
84
+ # A Kartari Yoga requires at least one planet on each side
85
+ if not pre_planets or not fol_planets:
86
+ continue
87
+
88
+ if pre_type in ("benefic", "malefic") and pre_type == fol_type:
89
+ yoga_type = "Subha Kartari" if pre_type == "benefic" else "Papa Kartari"
90
+ elif pre_type == "mixed" or fol_type == "mixed":
91
+ yoga_type = "Mixed Kartari"
92
+ elif pre_type in ("benefic", "malefic") and fol_type in ("benefic", "malefic"):
93
+ yoga_type = "Mixed Kartari"
94
+ else:
95
+ continue # neutral/empty — no Kartari
96
+
97
+ effect = (
98
+ "Highly beneficial — planet's significations are enhanced"
99
+ if yoga_type == "Subha Kartari"
100
+ else (
101
+ "Harmful — planet's significations are suppressed/afflicted"
102
+ if yoga_type == "Papa Kartari"
103
+ else "Mixed results — conflicting influences on either side"
104
+ )
105
+ )
106
+
107
+ results.append(
108
+ {
109
+ "planet_or_house": name,
110
+ "house_number": house,
111
+ "yoga_type": yoga_type,
112
+ "preceding_house": pre_house,
113
+ "following_house": fol_house,
114
+ "preceding_planets": list(pre_planets),
115
+ "following_planets": list(fol_planets),
116
+ "effect": effect,
117
+ "strength": "Strong",
118
+ }
119
+ )
120
+ seen_houses.add(house)
121
+
122
+ # ----------------------------------------------------------------
123
+ # Per-occupied-house analysis (catches houses with planets that
124
+ # might be hemmed even if individual planets were already checked,
125
+ # but primarily adds house-level entries not covered above)
126
+ # ----------------------------------------------------------------
127
+ all_occupied = set(h for h, plist in houses.items() if plist)
128
+
129
+ for house in sorted(all_occupied):
130
+ if house in seen_houses:
131
+ continue # already handled via a planet entry
132
+
133
+ pre_house, fol_house = _adjacent_houses(house)
134
+ pre_planets = houses.get(pre_house, [])
135
+ fol_planets = houses.get(fol_house, [])
136
+
137
+ if not pre_planets or not fol_planets:
138
+ continue
139
+
140
+ pre_type = _dominant_type(pre_planets)
141
+ fol_type = _dominant_type(fol_planets)
142
+
143
+ if pre_type in ("benefic", "malefic") and pre_type == fol_type:
144
+ yoga_type = "Subha Kartari" if pre_type == "benefic" else "Papa Kartari"
145
+ elif pre_type in ("benefic", "malefic") and fol_type in ("benefic", "malefic"):
146
+ yoga_type = "Mixed Kartari"
147
+ else:
148
+ continue
149
+
150
+ effect = (
151
+ "Highly beneficial — house significations are protected and enhanced"
152
+ if yoga_type == "Subha Kartari"
153
+ else (
154
+ "Harmful — house significations are hemmed and afflicted"
155
+ if yoga_type == "Papa Kartari"
156
+ else "Mixed results — conflicting influences on either side"
157
+ )
158
+ )
159
+
160
+ results.append(
161
+ {
162
+ "planet_or_house": f"House_{house}",
163
+ "house_number": house,
164
+ "yoga_type": yoga_type,
165
+ "preceding_house": pre_house,
166
+ "following_house": fol_house,
167
+ "preceding_planets": list(pre_planets),
168
+ "following_planets": list(fol_planets),
169
+ "effect": effect,
170
+ "strength": "Strong",
171
+ }
172
+ )
173
+
174
+ # ----------------------------------------------------------------
175
+ # Special checks
176
+ # ----------------------------------------------------------------
177
+ _check_paapa_kartari_lagna(houses, results)
178
+ _check_subha_kartari_moon(planetary_positions, houses, results)
179
+
180
+ return results
181
+
182
+
183
+ def _check_paapa_kartari_lagna(houses: dict, results: list) -> None:
184
+ """Annotate if both 2nd and 12th houses from Lagna are occupied by malefics."""
185
+ h2 = houses.get(2, [])
186
+ h12 = houses.get(12, [])
187
+ if not h2 or not h12:
188
+ return
189
+ if _dominant_type(h2) == "malefic" and _dominant_type(h12) == "malefic":
190
+ results.append(
191
+ {
192
+ "planet_or_house": "Lagna (Special — Paapa Kartari)",
193
+ "house_number": 1,
194
+ "yoga_type": "Papa Kartari",
195
+ "preceding_house": 12,
196
+ "following_house": 2,
197
+ "preceding_planets": list(h12),
198
+ "following_planets": list(h2),
199
+ "effect": (
200
+ "Very harmful — Lagna hemmed between malefics. "
201
+ "Physical constitution and self-expression are strongly afflicted."
202
+ ),
203
+ "strength": "Strong",
204
+ }
205
+ )
206
+
207
+
208
+ def _check_subha_kartari_moon(
209
+ planetary_positions: list, houses: dict, results: list
210
+ ) -> None:
211
+ """Annotate if Moon is hemmed by benefics on both sides (Durudhura Yoga)."""
212
+ moon = next((p for p in planetary_positions if p["name"] == "Moon"), None)
213
+ if moon is None:
214
+ return
215
+
216
+ moon_house = moon["house"]
217
+ pre_house, fol_house = _adjacent_houses(moon_house)
218
+ pre_planets = houses.get(pre_house, [])
219
+ fol_planets = houses.get(fol_house, [])
220
+
221
+ if not pre_planets or not fol_planets:
222
+ return
223
+
224
+ if (
225
+ _dominant_type(pre_planets) == "benefic"
226
+ and _dominant_type(fol_planets) == "benefic"
227
+ ):
228
+ results.append(
229
+ {
230
+ "planet_or_house": "Moon (Special — Durudhura Yoga)",
231
+ "house_number": moon_house,
232
+ "yoga_type": "Subha Kartari",
233
+ "preceding_house": pre_house,
234
+ "following_house": fol_house,
235
+ "preceding_planets": list(pre_planets),
236
+ "following_planets": list(fol_planets),
237
+ "effect": (
238
+ "Very auspicious — Moon flanked by benefics (Durudhura Yoga). "
239
+ "Prosperity, fame, and good fortune throughout life."
240
+ ),
241
+ "strength": "Strong",
242
+ }
243
+ )
@@ -0,0 +1,383 @@
1
+ """Kurmachakra: geographical direction system for auspicious travel and construction Muhurta.
2
+
3
+ The Kurmachakra (Tortoise Wheel) maps the 27 Nakshatras to the 8 cardinal directions
4
+ plus a centre/upward direction. It is used to determine:
5
+ 1. Auspicious travel directions based on birth nakshatra (avoid the bad direction)
6
+ 2. Current Moon nakshatra direction (avoid travelling in that direction on that day)
7
+ 3. Auspicious directions for construction, marriage, and major events
8
+ 4. Monthly/daily directional strength using the Moon's nakshatra
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+
15
+ # ── Core mapping ───────────────────────────────────────────────────────────────
16
+
17
+ # The 27 Nakshatras mapped to 8 directions + Centre (9 slots × 3 nakshatras each)
18
+ NAKSHATRA_DIRECTION: dict[str, str] = {
19
+ # East (Purva)
20
+ "Ashwini": "East",
21
+ "Bharani": "East",
22
+ "Krittika": "East",
23
+ # South-East (Agneya)
24
+ "Rohini": "South-East",
25
+ "Mrigashira": "South-East",
26
+ "Ardra": "South-East",
27
+ # South (Dakshina)
28
+ "Punarvasu": "South",
29
+ "Pushya": "South",
30
+ "Ashlesha": "South",
31
+ # South-West (Nairutya)
32
+ "Magha": "South-West",
33
+ "Purva Phalguni": "South-West",
34
+ "Uttara Phalguni": "South-West",
35
+ # West (Pashchima)
36
+ "Hasta": "West",
37
+ "Chitra": "West",
38
+ "Swati": "West",
39
+ # North-West (Vayavya)
40
+ "Vishakha": "North-West",
41
+ "Anuradha": "North-West",
42
+ "Jyeshtha": "North-West",
43
+ # North (Uttara)
44
+ "Mula": "North",
45
+ "Purva Ashadha": "North",
46
+ "Uttara Ashadha": "North",
47
+ # North-East (Ishanya)
48
+ "Shravana": "North-East",
49
+ "Dhanishta": "North-East",
50
+ "Shatabhisha": "North-East",
51
+ # Centre / Upward (Urdhva / Brahma)
52
+ "Purva Bhadrapada": "Centre",
53
+ "Uttara Bhadrapada": "Centre",
54
+ "Revati": "Centre",
55
+ }
56
+
57
+ DIRECTIONS_ORDER = [
58
+ "East",
59
+ "South-East",
60
+ "South",
61
+ "South-West",
62
+ "West",
63
+ "North-West",
64
+ "North",
65
+ "North-East",
66
+ "Centre",
67
+ ]
68
+
69
+ # Sanskrit names
70
+ DIRECTION_SANSKRIT: dict[str, str] = {
71
+ "East": "Purva",
72
+ "South-East": "Agneya",
73
+ "South": "Dakshina",
74
+ "South-West": "Nairutya",
75
+ "West": "Pashchima",
76
+ "North-West": "Vayavya",
77
+ "North": "Uttara",
78
+ "North-East": "Ishanya",
79
+ "Centre": "Urdhva (Brahma)",
80
+ }
81
+
82
+ # General auspiciousness for travel by direction (contextual — depends on event)
83
+ DIRECTION_GENERAL: dict[str, dict[str, str]] = {
84
+ "East": {
85
+ "nature": "Highly auspicious",
86
+ "good_for": "Education, jobs, seeking blessings from elders",
87
+ "avoid": "Nothing specific",
88
+ },
89
+ "South-East": {
90
+ "nature": "Mixed",
91
+ "good_for": "Trade, cooking, fire-related activities",
92
+ "avoid": "Long journeys",
93
+ },
94
+ "South": {
95
+ "nature": "Inauspicious",
96
+ "good_for": "War, competition, debt collection",
97
+ "avoid": "General travel, marriage, new ventures",
98
+ },
99
+ "South-West": {
100
+ "nature": "Inauspicious",
101
+ "good_for": "Endings, wrapping up matters",
102
+ "avoid": "New beginnings, travel, construction",
103
+ },
104
+ "West": {
105
+ "nature": "Neutral to auspicious",
106
+ "good_for": "Business, trade, water activities",
107
+ "avoid": "Aggressive activities",
108
+ },
109
+ "North-West": {
110
+ "nature": "Mixed",
111
+ "good_for": "Quick travel, messages, communication",
112
+ "avoid": "Long-term ventures",
113
+ },
114
+ "North": {
115
+ "nature": "Auspicious",
116
+ "good_for": "Wealth, new ventures, prosperity",
117
+ "avoid": "Nothing specific",
118
+ },
119
+ "North-East": {
120
+ "nature": "Highly auspicious",
121
+ "good_for": "Worship, spiritual activities, education, divine matters",
122
+ "avoid": "Nothing specific",
123
+ },
124
+ "Centre": {
125
+ "nature": "Upward / Special",
126
+ "good_for": "Spiritual elevation, liberation, meditation",
127
+ "avoid": "Mundane worldly activities",
128
+ },
129
+ }
130
+
131
+ # Nakshatras by direction for quick lookup
132
+ DIRECTION_NAKSHATRAS: dict[str, list[str]] = {}
133
+ for nak, direction in NAKSHATRA_DIRECTION.items():
134
+ DIRECTION_NAKSHATRAS.setdefault(direction, []).append(nak)
135
+
136
+
137
+ # ── Kurmachakra for Muhurta ───────────────────────────────────────────────────
138
+
139
+
140
+ # Which directions are auspicious to travel when the Moon is in a specific direction
141
+ # (Avoid travelling in the same direction as Moon's nakshatra direction on that day)
142
+ def _directions_to_avoid_for_moon(moon_direction: str) -> list[str]:
143
+ """Directions to avoid when Moon is in the given direction."""
144
+ # Standard rule: avoid Moon's direction and its immediate flanking directions
145
+ idx = DIRECTIONS_ORDER.index(moon_direction)
146
+ avoid = [moon_direction]
147
+ # Also avoid the two opposite quadrant (simplified traditional rule)
148
+ opp_idx = (idx + 4) % 8 if idx < 8 else idx
149
+ avoid.append(DIRECTIONS_ORDER[opp_idx])
150
+ return avoid
151
+
152
+
153
+ # ── Public API ─────────────────────────────────────────────────────────────────
154
+
155
+
156
+ def get_kurmachakra(
157
+ birth_nakshatra: str,
158
+ current_moon_nakshatra: str | None = None,
159
+ event_type: str = "travel",
160
+ ) -> dict[str, Any]:
161
+ """Get Kurmachakra directional analysis.
162
+
163
+ Parameters
164
+ ----------
165
+ birth_nakshatra : person's birth nakshatra (Janma Nakshatra)
166
+ current_moon_nakshatra: today's Moon nakshatra (for daily Muhurta)
167
+ event_type : "travel", "construction", "marriage", "general"
168
+ """
169
+ birth_direction = NAKSHATRA_DIRECTION.get(birth_nakshatra, "Unknown")
170
+
171
+ result: dict[str, Any] = {
172
+ "event_type": event_type,
173
+ "birth_nakshatra": birth_nakshatra,
174
+ "birth_direction": birth_direction,
175
+ "birth_direction_sanskrit": DIRECTION_SANSKRIT.get(birth_direction, ""),
176
+ "birth_direction_analysis": DIRECTION_GENERAL.get(birth_direction, {}),
177
+ "direction_map": {
178
+ direction: {
179
+ "sanskrit": DIRECTION_SANSKRIT[direction],
180
+ "nakshatras": DIRECTION_NAKSHATRAS[direction],
181
+ "nature": DIRECTION_GENERAL[direction]["nature"],
182
+ "good_for": DIRECTION_GENERAL[direction]["good_for"],
183
+ }
184
+ for direction in DIRECTIONS_ORDER
185
+ },
186
+ "auspicious_directions": [],
187
+ "inauspicious_directions": [],
188
+ "best_direction": "",
189
+ "direction_to_avoid": birth_direction,
190
+ }
191
+
192
+ # Auspicious vs inauspicious directions for the birth nakshatra holder
193
+ auspicious = []
194
+ inauspicious = []
195
+ for direction in DIRECTIONS_ORDER:
196
+ nature = DIRECTION_GENERAL[direction]["nature"].lower()
197
+ if direction == birth_direction:
198
+ continue # own direction — special rules apply
199
+ if "highly auspicious" in nature or "auspicious" in nature:
200
+ auspicious.append(direction)
201
+ elif "inauspicious" in nature:
202
+ inauspicious.append(direction)
203
+ else:
204
+ auspicious.append(direction) # neutral counts as ok
205
+
206
+ result["auspicious_directions"] = auspicious
207
+ result["inauspicious_directions"] = inauspicious
208
+ result["best_direction"] = (
209
+ "North-East"
210
+ if "North-East" in auspicious
211
+ else (auspicious[0] if auspicious else "North")
212
+ )
213
+
214
+ # If current Moon nakshatra is given, add daily guidance
215
+ if current_moon_nakshatra:
216
+ moon_direction = NAKSHATRA_DIRECTION.get(current_moon_nakshatra, "Unknown")
217
+ avoid_today = (
218
+ _directions_to_avoid_for_moon(moon_direction)
219
+ if moon_direction in DIRECTIONS_ORDER
220
+ else [moon_direction]
221
+ )
222
+ good_today = [d for d in DIRECTIONS_ORDER if d not in avoid_today]
223
+ result["daily_analysis"] = {
224
+ "current_moon_nakshatra": current_moon_nakshatra,
225
+ "moon_direction": moon_direction,
226
+ "moon_direction_sanskrit": DIRECTION_SANSKRIT.get(moon_direction, ""),
227
+ "avoid_today": avoid_today,
228
+ "good_directions_today": good_today,
229
+ "note": f"Avoid travelling towards {', '.join(avoid_today)} today as Moon is in that direction.",
230
+ }
231
+
232
+ # Event-specific recommendations
233
+ event_notes: dict[str, str] = {
234
+ "travel": (
235
+ f"Best direction for travel from birth nakshatra: {result['best_direction']}. "
236
+ f"Avoid: {birth_direction} (birth direction). "
237
+ "Never travel South on Tuesday, or South-West at night."
238
+ ),
239
+ "construction": (
240
+ "North or North-East is ideal for main entrance. "
241
+ "South-West for master bedroom/safe room. "
242
+ "Avoid South-West for main gate."
243
+ ),
244
+ "marriage": (
245
+ "North-East is ideal for sacred fire direction. "
246
+ "East is best for bride's entry. "
247
+ "Avoid South and South-West for ceremony timing."
248
+ ),
249
+ "general": (
250
+ "North-East for spiritual and auspicious activities. "
251
+ "East for new beginnings. "
252
+ "Avoid South and South-West when possible."
253
+ ),
254
+ }
255
+ result["recommendation"] = event_notes.get(event_type, event_notes["general"])
256
+
257
+ return result
258
+
259
+
260
+ def get_travel_direction_score(
261
+ birth_nakshatra: str,
262
+ intended_direction: str,
263
+ current_moon_nakshatra: str | None = None,
264
+ weekday: str | None = None,
265
+ ) -> dict[str, Any]:
266
+ """Score a specific intended travel direction for auspiciousness.
267
+
268
+ Parameters
269
+ ----------
270
+ birth_nakshatra : person's birth nakshatra
271
+ intended_direction : direction of travel ("North", "South", "East", etc.)
272
+ current_moon_nakshatra: current Moon's nakshatra
273
+ weekday : "Monday", "Tuesday", etc.
274
+ """
275
+ birth_direction = NAKSHATRA_DIRECTION.get(birth_nakshatra, "Unknown")
276
+ moon_direction = (
277
+ NAKSHATRA_DIRECTION.get(current_moon_nakshatra, "")
278
+ if current_moon_nakshatra
279
+ else ""
280
+ )
281
+
282
+ score = 100.0
283
+ warnings = []
284
+ positives = []
285
+
286
+ nature = DIRECTION_GENERAL.get(intended_direction, {}).get("nature", "")
287
+ if "highly auspicious" in nature.lower():
288
+ positives.append(f"{intended_direction} is highly auspicious in general")
289
+ score += 10
290
+ elif "inauspicious" in nature.lower():
291
+ warnings.append(f"{intended_direction} is generally inauspicious for travel")
292
+ score -= 30
293
+
294
+ if intended_direction == birth_direction:
295
+ warnings.append(
296
+ f"Travelling in birth nakshatra direction ({birth_direction}) is generally avoided"
297
+ )
298
+ score -= 20
299
+
300
+ if moon_direction and intended_direction == moon_direction:
301
+ warnings.append(
302
+ f"Avoid travelling in Moon's current direction ({moon_direction})"
303
+ )
304
+ score -= 25
305
+
306
+ # Weekday-direction restrictions
307
+ BAD_WEEKDAY_DIRECTIONS: dict[str, list[str]] = {
308
+ "Sunday": ["West"],
309
+ "Monday": ["North-West"],
310
+ "Tuesday": ["South"],
311
+ "Wednesday": ["North"],
312
+ "Thursday": ["South-East"],
313
+ "Friday": ["South-West"],
314
+ "Saturday": ["East"],
315
+ }
316
+ if weekday and intended_direction in BAD_WEEKDAY_DIRECTIONS.get(weekday, []):
317
+ warnings.append(f"Avoid {intended_direction} on {weekday} (traditional rule)")
318
+ score -= 20
319
+
320
+ if not warnings:
321
+ positives.append("No directional conflicts found")
322
+
323
+ score = max(0.0, min(100.0, score))
324
+ grade = (
325
+ "Excellent"
326
+ if score >= 80
327
+ else "Good"
328
+ if score >= 60
329
+ else "Caution"
330
+ if score >= 40
331
+ else "Avoid"
332
+ )
333
+
334
+ return {
335
+ "intended_direction": intended_direction,
336
+ "score": round(score, 1),
337
+ "grade": grade,
338
+ "birth_nakshatra": birth_nakshatra,
339
+ "birth_direction": birth_direction,
340
+ "moon_direction": moon_direction or "Not specified",
341
+ "warnings": warnings,
342
+ "positives": positives,
343
+ "good_for": DIRECTION_GENERAL.get(intended_direction, {}).get("good_for", ""),
344
+ "recommendation": f"Direction is {grade} for travel."
345
+ + (" " + "; ".join(warnings) if warnings else ""),
346
+ }
347
+
348
+
349
+ def get_full_kurmachakra_chart() -> dict[str, Any]:
350
+ """Return the complete Kurmachakra chart — all 9 directions with all 27 nakshatras."""
351
+ return {
352
+ "description": (
353
+ "Kurmachakra (Tortoise Wheel): maps the 27 Nakshatras to 8 cardinal directions "
354
+ "+ Centre (Brahma/Urdhva). Used in Muhurta for travel, construction, and auspicious activities."
355
+ ),
356
+ "directions": {
357
+ direction: {
358
+ "sanskrit": DIRECTION_SANSKRIT[direction],
359
+ "nakshatras": DIRECTION_NAKSHATRAS[direction],
360
+ "nature": DIRECTION_GENERAL[direction]["nature"],
361
+ "good_for": DIRECTION_GENERAL[direction]["good_for"],
362
+ "avoid": DIRECTION_GENERAL[direction]["avoid"],
363
+ }
364
+ for direction in DIRECTIONS_ORDER
365
+ },
366
+ "weekday_avoid_directions": {
367
+ "Sunday": "West",
368
+ "Monday": "North-West",
369
+ "Tuesday": "South",
370
+ "Wednesday": "North",
371
+ "Thursday": "South-East",
372
+ "Friday": "South-West",
373
+ "Saturday": "East",
374
+ },
375
+ "travel_rules": [
376
+ "Never travel in the direction of your birth nakshatra (Janma Nakshatra direction) for long journeys",
377
+ "Avoid the direction occupied by Moon's current nakshatra on that day",
378
+ "South and South-West are generally inauspicious for new ventures",
379
+ "North-East and North are generally auspicious",
380
+ "East is good for education and spiritual journeys",
381
+ "Check weekday-specific direction restrictions",
382
+ ],
383
+ }