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,349 @@
1
+ """Nabhasha Yogas: special yogas based on overall chart patterns and planetary distributions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from kundali_lib.vedic.constants import RASHIS
6
+
7
+ # ---------------------------------------------------------------------------
8
+ # Reference data
9
+ # ---------------------------------------------------------------------------
10
+
11
+ MOVABLE_SIGNS = {"Aries", "Cancer", "Libra", "Capricorn"}
12
+ FIXED_SIGNS = {"Taurus", "Leo", "Scorpio", "Aquarius"}
13
+ DUAL_SIGNS = {"Gemini", "Virgo", "Sagittarius", "Pisces"}
14
+
15
+ KENDRA_HOUSES = {1, 4, 7, 10}
16
+ APOKLIMA_HOUSES = {3, 6, 9, 12}
17
+
18
+ BENEFIC_PLANETS = {"Moon", "Mercury", "Jupiter", "Venus"}
19
+ MALEFIC_PLANETS = {"Sun", "Mars", "Saturn", "Rahu", "Ketu"}
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Ashrayas
23
+ # ---------------------------------------------------------------------------
24
+
25
+ ASHRAYAS: dict[str, dict] = {
26
+ "Rajju": {
27
+ "condition": "All planets in movable signs (Aries, Cancer, Libra, Capricorn)",
28
+ "effect": "Person travels frequently, unsettled",
29
+ },
30
+ "Musala": {
31
+ "condition": "All planets in fixed signs (Taurus, Leo, Scorpio, Aquarius)",
32
+ "effect": "Determined, stubborn, famous",
33
+ },
34
+ "Nala": {
35
+ "condition": "All planets in dual/common signs (Gemini, Virgo, Sagittarius, Pisces)",
36
+ "effect": "Skilled in arts, skilled in work with hands",
37
+ },
38
+ }
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Dala
42
+ # ---------------------------------------------------------------------------
43
+
44
+ DALA: dict[str, dict] = {
45
+ "Mala": {
46
+ "condition": "Benefics (Moon, Mercury, Jupiter, Venus) all in kendras (1,4,7,10)",
47
+ "effect": "Happiness, wealth, honour",
48
+ },
49
+ "Sarpa": {
50
+ "condition": "Only malefics (Sun, Mars, Saturn, Rahu, Ketu) in kendras",
51
+ "effect": "Wicked, poor, dependent",
52
+ },
53
+ }
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Akriti patterns (simplified — primary 10)
57
+ # ---------------------------------------------------------------------------
58
+
59
+ AKRITI_YOGAS: dict[int, dict] = {
60
+ 2: {
61
+ "name": "Gada",
62
+ "description": "Planets in two adjacent houses",
63
+ "effect": "Wealthy, skilled",
64
+ },
65
+ 3: {
66
+ "name": "Shakata",
67
+ "description": "Planets in houses 1 and 7",
68
+ "effect": "Variable fortune",
69
+ },
70
+ 4: {
71
+ "name": "Vihaga",
72
+ "description": "Planets in houses 4 and 10",
73
+ "effect": "Messenger, traveller",
74
+ },
75
+ 5: {
76
+ "name": "Shringataka",
77
+ "description": "Planets in trikonas (1,5,9)",
78
+ "effect": "Pleasure seeker",
79
+ },
80
+ 6: {
81
+ "name": "Hala",
82
+ "description": "Planets in consecutive houses",
83
+ "effect": "Farmer, works hard",
84
+ },
85
+ 7: {
86
+ "name": "Vajra",
87
+ "description": "Benefics in 1 and 7, malefics in 4 and 10",
88
+ "effect": "Rich, happy in youth and old age",
89
+ },
90
+ 8: {
91
+ "name": "Yava",
92
+ "description": "Benefics in 1 and 7, malefics in 4 and 10 (reverse of Vajra)",
93
+ "effect": "Rich in middle age",
94
+ },
95
+ 9: {
96
+ "name": "Kamala",
97
+ "description": "Planets in all 4 kendras",
98
+ "effect": "Virtuous, wealthy, famous",
99
+ },
100
+ 10: {
101
+ "name": "Vapi",
102
+ "description": "All planets in Apoklimas (3,6,9,12)",
103
+ "effect": "Wealthy but hidden",
104
+ },
105
+ 18: {
106
+ "name": "Ardha Chandra",
107
+ "description": "Planets in 7 consecutive houses",
108
+ "effect": "Commander",
109
+ },
110
+ }
111
+
112
+
113
+ # ---------------------------------------------------------------------------
114
+ # Internal helpers
115
+ # ---------------------------------------------------------------------------
116
+
117
+
118
+ def _sign_type(rashi: str) -> str:
119
+ if rashi in MOVABLE_SIGNS:
120
+ return "movable"
121
+ if rashi in FIXED_SIGNS:
122
+ return "fixed"
123
+ return "dual"
124
+
125
+
126
+ def _occupied_houses(planetary_positions: list[dict]) -> list[int]:
127
+ """Return sorted list of houses that have at least one planet."""
128
+ seen: set[int] = set()
129
+ for p in planetary_positions:
130
+ h = p.get("house", 0)
131
+ if 1 <= h <= 12:
132
+ seen.add(h)
133
+ return sorted(seen)
134
+
135
+
136
+ def _houses_are_consecutive(houses: list[int], span: int) -> bool:
137
+ """True if all houses lie within a consecutive arc of ``span`` houses."""
138
+ if not houses:
139
+ return False
140
+ for start in range(1, 13):
141
+ arc = set((start + i - 1) % 12 + 1 for i in range(span))
142
+ if set(houses).issubset(arc):
143
+ return True
144
+ return False
145
+
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # Yoga checkers
149
+ # ---------------------------------------------------------------------------
150
+
151
+
152
+ def _check_ashrayas(planetary_positions: list[dict]) -> str | None:
153
+ """Return the name of the Ashrayas yoga present, or None."""
154
+ rashis = [p.get("rashi", "") for p in planetary_positions]
155
+ types = [_sign_type(r) for r in rashis if r]
156
+ if not types:
157
+ return None
158
+ if all(t == "movable" for t in types):
159
+ return "Rajju"
160
+ if all(t == "fixed" for t in types):
161
+ return "Musala"
162
+ if all(t == "dual" for t in types):
163
+ return "Nala"
164
+ return None
165
+
166
+
167
+ def _check_dala(planetary_positions: list[dict]) -> str | None:
168
+ """Return 'Mala', 'Sarpa', or None."""
169
+ kendra_planets = [
170
+ p for p in planetary_positions if p.get("house", 0) in KENDRA_HOUSES
171
+ ]
172
+ if not kendra_planets:
173
+ return None
174
+
175
+ kendra_names = {p["name"] for p in kendra_planets}
176
+
177
+ # Mala: all 4 benefics in kendras
178
+ benefics_in_kendra = BENEFIC_PLANETS.intersection(kendra_names)
179
+ if benefics_in_kendra == BENEFIC_PLANETS:
180
+ return "Mala"
181
+
182
+ # Sarpa: only malefics in kendras (no benefic in kendra at all)
183
+ if not kendra_names.intersection(BENEFIC_PLANETS) and kendra_names:
184
+ return "Sarpa"
185
+
186
+ return None
187
+
188
+
189
+ def _check_akriti(occupied: list[int], planetary_positions: list[dict]) -> dict | None:
190
+ """Return the Akriti yoga dict or None based on occupied house patterns."""
191
+ occ_set = set(occupied)
192
+ n = len(occ_set)
193
+
194
+ # Kamala: planets in all 4 kendras
195
+ if KENDRA_HOUSES.issubset(occ_set):
196
+ return AKRITI_YOGAS[9]
197
+
198
+ # Vapi: all in Apoklimas
199
+ if occ_set and occ_set.issubset(APOKLIMA_HOUSES):
200
+ return AKRITI_YOGAS[10]
201
+
202
+ # Ardha Chandra: planets in 7 consecutive houses
203
+ if _houses_are_consecutive(occupied, 7) and 5 <= n <= 8:
204
+ return AKRITI_YOGAS[18]
205
+
206
+ # Shringataka: only trikonas occupied
207
+ if occ_set and occ_set.issubset({1, 5, 9}):
208
+ return AKRITI_YOGAS[5]
209
+
210
+ # Vihaga: only 4 and 10 occupied
211
+ if occ_set and occ_set.issubset({4, 10}):
212
+ return AKRITI_YOGAS[4]
213
+
214
+ # Shakata: only 1 and 7 occupied
215
+ if occ_set and occ_set.issubset({1, 7}):
216
+ return AKRITI_YOGAS[3]
217
+
218
+ # Gada: two adjacent houses occupied only
219
+ if n == 2 and len(occupied) == 2:
220
+ diff = abs(occupied[0] - occupied[1])
221
+ if diff == 1 or diff == 11:
222
+ return AKRITI_YOGAS[2]
223
+
224
+ # Hala: all in 6 consecutive houses
225
+ if _houses_are_consecutive(occupied, 6) and n <= 7:
226
+ return AKRITI_YOGAS[6]
227
+
228
+ # Vajra / Yava (both use houses 1, 4, 7, 10)
229
+ if occ_set and occ_set.issubset({1, 4, 7, 10}):
230
+ benefics = {
231
+ p["name"] for p in planetary_positions if p["name"] in BENEFIC_PLANETS
232
+ }
233
+ malefics = {
234
+ p["name"] for p in planetary_positions if p["name"] in MALEFIC_PLANETS
235
+ }
236
+
237
+ def _in_house(names: set, houses: set) -> bool:
238
+ return all(
239
+ any(
240
+ p["name"] in names and p.get("house", 0) in houses
241
+ for p in planetary_positions
242
+ )
243
+ for _ in [1]
244
+ )
245
+
246
+ benefic_houses = {
247
+ p.get("house", 0)
248
+ for p in planetary_positions
249
+ if p["name"] in BENEFIC_PLANETS
250
+ }
251
+ malefic_houses = {
252
+ p.get("house", 0)
253
+ for p in planetary_positions
254
+ if p["name"] in MALEFIC_PLANETS
255
+ }
256
+
257
+ if benefic_houses.issubset({1, 7}) and malefic_houses.issubset({4, 10}):
258
+ return AKRITI_YOGAS[7] # Vajra
259
+ if benefic_houses.issubset({4, 10}) and malefic_houses.issubset({1, 7}):
260
+ return AKRITI_YOGAS[8] # Yava
261
+
262
+ return None
263
+
264
+
265
+ # ---------------------------------------------------------------------------
266
+ # Public API
267
+ # ---------------------------------------------------------------------------
268
+
269
+
270
+ def get_nabhasha_yogas(planetary_positions: list[dict]) -> dict:
271
+ """Identify Nabhasha Yogas from a chart's planetary positions.
272
+
273
+ Args:
274
+ planetary_positions: List of planet dicts as produced by ``build_chart()``
275
+ (each dict must have ``name``, ``rashi``, and ``house``).
276
+
277
+ Returns:
278
+ dict with Ashrayas, Dala, Akriti yogas found, along with supporting
279
+ statistics and a plain-language summary.
280
+ """
281
+ occupied = _occupied_houses(planetary_positions)
282
+
283
+ # ── Sign-type counts ──────────────────────────────────────────────────────
284
+ type_counts: dict[str, int] = {"movable": 0, "fixed": 0, "dual": 0}
285
+ for p in planetary_positions:
286
+ r = p.get("rashi", "")
287
+ if r:
288
+ type_counts[_sign_type(r)] += 1
289
+
290
+ # ── Yoga checks ───────────────────────────────────────────────────────────
291
+ ashrayas_name = _check_ashrayas(planetary_positions)
292
+ dala_name = _check_dala(planetary_positions)
293
+ akriti_data = _check_akriti(occupied, planetary_positions)
294
+
295
+ yogas_present: list[dict] = []
296
+
297
+ if ashrayas_name:
298
+ info = ASHRAYAS[ashrayas_name]
299
+ yogas_present.append(
300
+ {
301
+ "name": ashrayas_name,
302
+ "category": "Ashrayas",
303
+ "description": info["condition"],
304
+ "effect": info["effect"],
305
+ }
306
+ )
307
+
308
+ if dala_name:
309
+ info = DALA[dala_name]
310
+ yogas_present.append(
311
+ {
312
+ "name": dala_name,
313
+ "category": "Dala",
314
+ "description": info["condition"],
315
+ "effect": info["effect"],
316
+ }
317
+ )
318
+
319
+ if akriti_data:
320
+ yogas_present.append(
321
+ {
322
+ "name": akriti_data["name"],
323
+ "category": "Akriti",
324
+ "description": akriti_data["description"],
325
+ "effect": akriti_data["effect"],
326
+ }
327
+ )
328
+
329
+ # ── Summary ───────────────────────────────────────────────────────────────
330
+ if yogas_present:
331
+ names = ", ".join(y["name"] for y in yogas_present)
332
+ summary = f"Nabhasha yogas present: {names}. Planets occupy houses: {occupied}."
333
+ else:
334
+ summary = (
335
+ f"No primary Nabhasha yoga detected. "
336
+ f"Planets occupy houses: {occupied}. "
337
+ f"Sign distribution — movable: {type_counts['movable']}, "
338
+ f"fixed: {type_counts['fixed']}, dual: {type_counts['dual']}."
339
+ )
340
+
341
+ return {
342
+ "ashrayas_yoga": ashrayas_name,
343
+ "dala_yoga": dala_name,
344
+ "akriti_yoga": akriti_data["name"] if akriti_data else None,
345
+ "occupied_houses": occupied,
346
+ "occupied_signs_type": type_counts,
347
+ "yogas_present": yogas_present,
348
+ "summary": summary,
349
+ }