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,338 @@
1
+ """Derived Vedic chart type helpers.
2
+
3
+ The base chart calculation returns accurate sidereal planetary longitudes. These
4
+ helpers derive common chart views from those longitudes without changing the
5
+ underlying ephemeris calculation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from kundali_lib.vedic.constants import RASHIS
13
+
14
+ # Full traditional varga set with names.
15
+ DIVISIONAL_CHARTS = {
16
+ 1: ("d1", "Rashi / Lagna"),
17
+ 2: ("d2", "Hora"),
18
+ 3: ("d3", "Drekkana"),
19
+ 4: ("d4", "Chaturthamsa"),
20
+ 7: ("d7", "Saptamsa"),
21
+ 9: ("d9", "Navamsa"),
22
+ 10: ("d10", "Dashamsa"),
23
+ 12: ("d12", "Dwadashamsa"),
24
+ 16: ("d16", "Shodashamsa"),
25
+ 20: ("d20", "Vimshamsa"),
26
+ 24: ("d24", "Chaturvimshamsa"),
27
+ 27: ("d27", "Saptavimshamsa"),
28
+ 30: ("d30", "Trimsamsa"),
29
+ 40: ("d40", "Khavedamsa"),
30
+ 45: ("d45", "Akshavedamsa"),
31
+ 60: ("d60", "Shashtiamsa"),
32
+ }
33
+
34
+ SUPPORTED_CHART_TYPES: dict[str, dict[str, str]] = {
35
+ "lagna": {
36
+ "name": "Lagna / Rashi / D1",
37
+ "description": "Default birth chart using ascendant as the 1st house.",
38
+ },
39
+ "rashi": {
40
+ "name": "Rashi / D1",
41
+ "description": "Alias for the default Lagna/Rashi chart.",
42
+ },
43
+ "d1": {
44
+ "name": "D1 / Rashi",
45
+ "description": "Alias for the default Lagna/Rashi chart.",
46
+ },
47
+ "chandra": {
48
+ "name": "Chandra Kundali",
49
+ "description": "Moon sign treated as the 1st house; planet houses recalculated from Moon sign.",
50
+ },
51
+ "moon": {"name": "Chandra Kundali", "description": "Alias for Chandra Kundali."},
52
+ "surya": {
53
+ "name": "Surya Kundali",
54
+ "description": "Sun sign treated as the 1st house; planet houses recalculated from Sun sign.",
55
+ },
56
+ "sun": {"name": "Surya Kundali", "description": "Alias for Surya Kundali."},
57
+ "all": {
58
+ "name": "All quick derived charts",
59
+ "description": "Returns Lagna + Chandra + Surya + D9 + D10.",
60
+ },
61
+ "all_divisionals": {
62
+ "name": "All traditional divisional charts",
63
+ "description": "Returns Lagna chart plus all traditional vargas (D1, D2, D3, D4, D7, D9, D10, D12, D16, D20, D24, D27, D30, D40, D45, D60).",
64
+ },
65
+ }
66
+
67
+ for div, (key, name) in DIVISIONAL_CHARTS.items():
68
+ if key not in SUPPORTED_CHART_TYPES:
69
+ SUPPORTED_CHART_TYPES[key] = {
70
+ "name": f"{name} / D{div}",
71
+ "description": f"Divisional chart D{div} ({name}) derived from sidereal longitudes.",
72
+ }
73
+
74
+ ALIASES = {
75
+ "rashi": "lagna",
76
+ "d1": "lagna",
77
+ "moon": "chandra",
78
+ "sun": "surya",
79
+ "navamsa": "d9",
80
+ "dashamsa": "d10",
81
+ "drekkana": "d3",
82
+ "chaturthamsa": "d4",
83
+ "saptamsa": "d7",
84
+ "dwadashamsa": "d12",
85
+ "shodashamsa": "d16",
86
+ "vimshamsa": "d20",
87
+ "chaturvimshamsa": "d24",
88
+ "saptavimshamsa": "d27",
89
+ "trimsamsa": "d30",
90
+ "khavedamsa": "d40",
91
+ "akshavedamsa": "d45",
92
+ "shashtiamsa": "d60",
93
+ }
94
+
95
+ DEFAULT_CHART_TYPE = "lagna"
96
+ ALL_QUICK_TYPES = ["chandra", "surya", "d9", "d10"]
97
+ ALL_DIVISIONAL_KEYS = [key for div, (key, _) in DIVISIONAL_CHARTS.items()]
98
+
99
+
100
+ def normalize_chart_type(chart_type: str | None) -> str:
101
+ key = (chart_type or DEFAULT_CHART_TYPE).strip().lower().replace("-", "_")
102
+ key = ALIASES.get(key, key)
103
+ if key not in SUPPORTED_CHART_TYPES:
104
+ raise ValueError(
105
+ f"Unsupported chart_type '{chart_type}'. Supported values: "
106
+ + ", ".join(sorted(SUPPORTED_CHART_TYPES))
107
+ )
108
+ return key
109
+
110
+
111
+ def available_chart_types() -> dict[str, Any]:
112
+ canonical = {
113
+ k: v
114
+ for k, v in SUPPORTED_CHART_TYPES.items()
115
+ if "_" not in k or k in ["lagna", "chandra", "surya", "all", "all_divisionals"]
116
+ }
117
+ divisional_list = [
118
+ {"division": div, "key": key, "name": name}
119
+ for div, (key, name) in DIVISIONAL_CHARTS.items()
120
+ ]
121
+ return {
122
+ "default": DEFAULT_CHART_TYPE,
123
+ "canonical_types": canonical,
124
+ "aliases": ALIASES,
125
+ "divisional_charts": divisional_list,
126
+ "note": "D1/Lagna is the original full chart. Chandra and Surya are house-reference views. Divisional charts (D2–D60) are derived from sidereal longitudes.",
127
+ }
128
+
129
+
130
+ def _sign_index_from_rashi(rashi: str) -> int:
131
+ return RASHIS.index(rashi)
132
+
133
+
134
+ def _house_from_reference(planet_rashi: str, reference_rashi: str) -> int:
135
+ return (
136
+ _sign_index_from_rashi(planet_rashi) - _sign_index_from_rashi(reference_rashi)
137
+ ) % 12 + 1
138
+
139
+
140
+ def _copy_planet_with_house(
141
+ planet: dict[str, Any], reference_rashi: str
142
+ ) -> dict[str, Any]:
143
+ copied = dict(planet)
144
+ copied["house"] = _house_from_reference(copied["rashi"], reference_rashi)
145
+ return copied
146
+
147
+
148
+ def chandra_chart(base_chart: dict[str, Any]) -> dict[str, Any]:
149
+ reference_rashi = base_chart["moon_sign"]
150
+ return {
151
+ "chart_type": "chandra",
152
+ "name": SUPPORTED_CHART_TYPES["chandra"]["name"],
153
+ "reference": {"planet": "Moon", "rashi": reference_rashi},
154
+ "houses": {
155
+ f"House_{i + 1}": RASHIS[(_sign_index_from_rashi(reference_rashi) + i) % 12]
156
+ for i in range(12)
157
+ },
158
+ "planetary_positions": [
159
+ _copy_planet_with_house(p, reference_rashi)
160
+ for p in base_chart["planetary_positions"]
161
+ ],
162
+ }
163
+
164
+
165
+ def surya_chart(base_chart: dict[str, Any]) -> dict[str, Any]:
166
+ reference_rashi = base_chart["sun_sign"]
167
+ return {
168
+ "chart_type": "surya",
169
+ "name": SUPPORTED_CHART_TYPES["surya"]["name"],
170
+ "reference": {"planet": "Sun", "rashi": reference_rashi},
171
+ "houses": {
172
+ f"House_{i + 1}": RASHIS[(_sign_index_from_rashi(reference_rashi) + i) % 12]
173
+ for i in range(12)
174
+ },
175
+ "planetary_positions": [
176
+ _copy_planet_with_house(p, reference_rashi)
177
+ for p in base_chart["planetary_positions"]
178
+ ],
179
+ }
180
+
181
+
182
+ def _varga_sign_index(longitude: float, division: int) -> int:
183
+ sign_index = int(longitude / 30) % 12
184
+ degree_in_sign = longitude % 30
185
+ part_index = min(int(degree_in_sign / (30 / division)), division - 1)
186
+
187
+ if division == 1:
188
+ return sign_index
189
+
190
+ if division == 2:
191
+ # Hora: odd signs Sun, even signs Moon (simplified common rule)
192
+ return (
193
+ sign_index % 2
194
+ ) * 6 # 0->0 (Leo range), 1->6 (Aquarius range) simplified mapping
195
+
196
+ if division == 3:
197
+ # Drekkana: 1st/2nd/3rd decan → same sign / 5th / 9th
198
+ offset = part_index * 4
199
+ return (sign_index + offset) % 12
200
+
201
+ if division == 4:
202
+ # Chaturthamsa: movable 1-4-7-10, fixed 4-7-10-1, dual 7-10-1-4
203
+ if sign_index % 3 == 0: # movable
204
+ start = sign_index
205
+ elif sign_index % 3 == 1: # fixed
206
+ start = (sign_index + 3) % 12
207
+ else: # dual
208
+ start = (sign_index + 6) % 12
209
+ return (start + part_index) % 12
210
+
211
+ if division == 7:
212
+ # Saptamsa: odd signs count forward, even backward
213
+ if sign_index % 2 == 0:
214
+ return (sign_index + part_index) % 12
215
+ else:
216
+ return (sign_index - part_index) % 12
217
+
218
+ if division == 9:
219
+ # Navamsa: movable starts same sign, fixed starts 9th, dual starts 5th
220
+ if sign_index % 3 == 0: # movable
221
+ start = sign_index
222
+ elif sign_index % 3 == 1: # fixed
223
+ start = (sign_index + 8) % 12
224
+ else: # dual
225
+ start = (sign_index + 4) % 12
226
+ return (start + part_index) % 12
227
+
228
+ if division == 10:
229
+ # Dashamsa: odd signs same, even signs 9th
230
+ start = sign_index if sign_index % 2 == 0 else (sign_index + 8) % 12
231
+ return (start + part_index) % 12
232
+
233
+ if division == 12:
234
+ # Dwadashamsa: each degree moves to next sign (simplified as part_index advance)
235
+ return (sign_index + part_index) % 12
236
+
237
+ if division == 16:
238
+ # Shodashamsa: common rule uses 16 parts; simplified as 4-part cycle repeated
239
+ return (sign_index + part_index) % 12
240
+
241
+ if division == 20:
242
+ # Vimshamsa: simplified cycle
243
+ return (sign_index * 2 + part_index) % 12
244
+
245
+ if division == 24:
246
+ # Chaturvimshamsa
247
+ return (sign_index * 2 + part_index) % 12
248
+
249
+ if division == 27:
250
+ # Saptavimshamsa
251
+ return (sign_index + part_index) % 12
252
+
253
+ if division == 30:
254
+ # Trimsamsa: special 5/7 part rules; simplified here
255
+ return (sign_index + part_index) % 12
256
+
257
+ if division in (40, 45):
258
+ # Khavedamsa / Akshavedamsa: complex gender-based rules; simplified as cyclic
259
+ return (sign_index + part_index) % 12
260
+
261
+ if division == 60:
262
+ # Shashtiamsa: pure 60-part cycle independent of sign
263
+ return part_index % 12
264
+
265
+ raise ValueError(f"Unsupported varga division D{division}")
266
+
267
+
268
+ def divisional_chart(
269
+ base_chart: dict[str, Any], division: int, chart_type: str, name: str
270
+ ) -> dict[str, Any]:
271
+ positions = []
272
+ for planet in base_chart["planetary_positions"]:
273
+ sign_index = _varga_sign_index(float(planet["longitude"]), division)
274
+ copied = dict(planet)
275
+ copied["rashi"] = RASHIS[sign_index]
276
+ copied["division"] = division
277
+ copied["source_longitude"] = planet["longitude"]
278
+ copied["house"] = 0
279
+ positions.append(copied)
280
+
281
+ asc_sign_index = _varga_sign_index(
282
+ float(base_chart["ascendant"]["longitude"]), division
283
+ )
284
+ houses = {f"House_{i + 1}": RASHIS[(asc_sign_index + i) % 12] for i in range(12)}
285
+ for planet in positions:
286
+ planet["house"] = _house_from_reference(planet["rashi"], RASHIS[asc_sign_index])
287
+
288
+ return {
289
+ "chart_type": chart_type,
290
+ "name": name,
291
+ "division": division,
292
+ "ascendant": {
293
+ "rashi": RASHIS[asc_sign_index],
294
+ "source_longitude": base_chart["ascendant"]["longitude"],
295
+ },
296
+ "houses": houses,
297
+ "planetary_positions": positions,
298
+ }
299
+
300
+
301
+ def derive_chart(base_chart: dict[str, Any], chart_type: str | None) -> dict[str, Any]:
302
+ normalized = normalize_chart_type(chart_type)
303
+
304
+ if normalized == "lagna":
305
+ result = dict(base_chart)
306
+ result["chart_type"] = "lagna"
307
+ return result
308
+
309
+ if normalized == "chandra":
310
+ return chandra_chart(base_chart)
311
+
312
+ if normalized == "surya":
313
+ return surya_chart(base_chart)
314
+
315
+ if normalized == "all":
316
+ result = dict(base_chart)
317
+ result["chart_type"] = "lagna"
318
+ result["derived_charts"] = {
319
+ ctype: derive_chart(base_chart, ctype) for ctype in ALL_QUICK_TYPES
320
+ }
321
+ return result
322
+
323
+ if normalized == "all_divisionals":
324
+ result = dict(base_chart)
325
+ result["chart_type"] = "lagna"
326
+ result["divisional_charts"] = {}
327
+ for div, (key, name) in DIVISIONAL_CHARTS.items():
328
+ result["divisional_charts"][key] = divisional_chart(
329
+ base_chart, div, key, name
330
+ )
331
+ return result
332
+
333
+ # Check if it's a divisional chart key
334
+ for div, (key, name) in DIVISIONAL_CHARTS.items():
335
+ if normalized == key:
336
+ return divisional_chart(base_chart, div, key, name)
337
+
338
+ raise ValueError(f"Unsupported chart_type '{chart_type}'")