kundali-chart-mcp 0.2.8 → 0.3.0

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 (101) hide show
  1. package/azure-function/__pycache__/kundali_bridge.cpython-313.pyc +0 -0
  2. package/azure-function/function_app.py +17 -0
  3. package/azure-function/kundali_bridge.py +152 -0
  4. package/azure-function/python/kundali_lib/__pycache__/__init__.cpython-313.pyc +0 -0
  5. package/azure-function/python/kundali_lib/__pycache__/ephemeris.cpython-313.pyc +0 -0
  6. package/azure-function/python/kundali_lib/__pycache__/geocoder.cpython-313.pyc +0 -0
  7. package/azure-function/python/kundali_lib/__pycache__/vedicastro_bridge.cpython-313.pyc +0 -0
  8. package/azure-function/python/kundali_lib/vedic/__pycache__/__init__.cpython-313.pyc +0 -0
  9. package/azure-function/python/kundali_lib/vedic/__pycache__/arishta.cpython-313.pyc +0 -0
  10. package/azure-function/python/kundali_lib/vedic/__pycache__/arudha.cpython-313.pyc +0 -0
  11. package/azure-function/python/kundali_lib/vedic/__pycache__/ashtakavarga.cpython-313.pyc +0 -0
  12. package/azure-function/python/kundali_lib/vedic/__pycache__/avasthas.cpython-313.pyc +0 -0
  13. package/azure-function/python/kundali_lib/vedic/__pycache__/ayanamsa.cpython-313.pyc +0 -0
  14. package/azure-function/python/kundali_lib/vedic/__pycache__/bhava_chalit.cpython-313.pyc +0 -0
  15. package/azure-function/python/kundali_lib/vedic/__pycache__/char_dasha.cpython-313.pyc +0 -0
  16. package/azure-function/python/kundali_lib/vedic/__pycache__/chart.cpython-313.pyc +0 -0
  17. package/azure-function/python/kundali_lib/vedic/__pycache__/chart_types.cpython-313.pyc +0 -0
  18. package/azure-function/python/kundali_lib/vedic/__pycache__/compatibility.cpython-313.pyc +0 -0
  19. package/azure-function/python/kundali_lib/vedic/__pycache__/constants.cpython-313.pyc +0 -0
  20. package/azure-function/python/kundali_lib/vedic/__pycache__/dasha_extended.cpython-313.pyc +0 -0
  21. package/azure-function/python/kundali_lib/vedic/__pycache__/dasha_systems.cpython-313.pyc +0 -0
  22. package/azure-function/python/kundali_lib/vedic/__pycache__/doshas.cpython-313.pyc +0 -0
  23. package/azure-function/python/kundali_lib/vedic/__pycache__/gandanta.cpython-313.pyc +0 -0
  24. package/azure-function/python/kundali_lib/vedic/__pycache__/gochara.cpython-313.pyc +0 -0
  25. package/azure-function/python/kundali_lib/vedic/__pycache__/hora.cpython-313.pyc +0 -0
  26. package/azure-function/python/kundali_lib/vedic/__pycache__/houses.cpython-313.pyc +0 -0
  27. package/azure-function/python/kundali_lib/vedic/__pycache__/inauspicious_times.cpython-313.pyc +0 -0
  28. package/azure-function/python/kundali_lib/vedic/__pycache__/jaimini.cpython-313.pyc +0 -0
  29. package/azure-function/python/kundali_lib/vedic/__pycache__/kalachakra.cpython-313.pyc +0 -0
  30. package/azure-function/python/kundali_lib/vedic/__pycache__/kartari.cpython-313.pyc +0 -0
  31. package/azure-function/python/kundali_lib/vedic/__pycache__/kurmachakra.cpython-313.pyc +0 -0
  32. package/azure-function/python/kundali_lib/vedic/__pycache__/lunar_return.cpython-313.pyc +0 -0
  33. package/azure-function/python/kundali_lib/vedic/__pycache__/medical_astrology.cpython-313.pyc +0 -0
  34. package/azure-function/python/kundali_lib/vedic/__pycache__/muhurta.cpython-313.pyc +0 -0
  35. package/azure-function/python/kundali_lib/vedic/__pycache__/nabhasha.cpython-313.pyc +0 -0
  36. package/azure-function/python/kundali_lib/vedic/__pycache__/nakshatra_details.cpython-313.pyc +0 -0
  37. package/azure-function/python/kundali_lib/vedic/__pycache__/panchanga.cpython-313.pyc +0 -0
  38. package/azure-function/python/kundali_lib/vedic/__pycache__/planets.cpython-313.pyc +0 -0
  39. package/azure-function/python/kundali_lib/vedic/__pycache__/remedial.cpython-313.pyc +0 -0
  40. package/azure-function/python/kundali_lib/vedic/__pycache__/shadbala.cpython-313.pyc +0 -0
  41. package/azure-function/python/kundali_lib/vedic/__pycache__/special_conditions.cpython-313.pyc +0 -0
  42. package/azure-function/python/kundali_lib/vedic/__pycache__/sudarshana.cpython-313.pyc +0 -0
  43. package/azure-function/python/kundali_lib/vedic/__pycache__/tajaka.cpython-313.pyc +0 -0
  44. package/azure-function/python/kundali_lib/vedic/__pycache__/upagrahas.cpython-313.pyc +0 -0
  45. package/azure-function/python/kundali_lib/vedic/__pycache__/varshaphal.cpython-313.pyc +0 -0
  46. package/azure-function/python/kundali_lib/vedic/__pycache__/yogas.cpython-313.pyc +0 -0
  47. package/azure-function/python/kundali_lib/vedic/__pycache__/zodiac.cpython-313.pyc +0 -0
  48. package/azure-function/python/kundali_lib/vedic/arudha.py +189 -0
  49. package/azure-function/python/kundali_lib/vedic/inauspicious_times.py +303 -0
  50. package/azure-function/python/kundali_lib/vedic/medical_astrology.py +424 -0
  51. package/azure-function/python/kundali_lib/vedic/remedial.py +849 -0
  52. package/kundali-chart-mcp.js +67 -505
  53. package/package.json +2 -2
  54. package/python/kundali_lib/__pycache__/__init__.cpython-313.pyc +0 -0
  55. package/python/kundali_lib/__pycache__/ephemeris.cpython-313.pyc +0 -0
  56. package/python/kundali_lib/__pycache__/geocoder.cpython-313.pyc +0 -0
  57. package/python/kundali_lib/__pycache__/vedicastro_bridge.cpython-313.pyc +0 -0
  58. package/python/kundali_lib/vedic/__pycache__/__init__.cpython-313.pyc +0 -0
  59. package/python/kundali_lib/vedic/__pycache__/arishta.cpython-313.pyc +0 -0
  60. package/python/kundali_lib/vedic/__pycache__/arudha.cpython-313.pyc +0 -0
  61. package/python/kundali_lib/vedic/__pycache__/ashtakavarga.cpython-313.pyc +0 -0
  62. package/python/kundali_lib/vedic/__pycache__/avasthas.cpython-313.pyc +0 -0
  63. package/python/kundali_lib/vedic/__pycache__/ayanamsa.cpython-313.pyc +0 -0
  64. package/python/kundali_lib/vedic/__pycache__/bhava_chalit.cpython-313.pyc +0 -0
  65. package/python/kundali_lib/vedic/__pycache__/char_dasha.cpython-313.pyc +0 -0
  66. package/python/kundali_lib/vedic/__pycache__/chart.cpython-313.pyc +0 -0
  67. package/python/kundali_lib/vedic/__pycache__/chart_types.cpython-313.pyc +0 -0
  68. package/python/kundali_lib/vedic/__pycache__/compatibility.cpython-313.pyc +0 -0
  69. package/python/kundali_lib/vedic/__pycache__/constants.cpython-313.pyc +0 -0
  70. package/python/kundali_lib/vedic/__pycache__/dasha_extended.cpython-313.pyc +0 -0
  71. package/python/kundali_lib/vedic/__pycache__/dasha_systems.cpython-313.pyc +0 -0
  72. package/python/kundali_lib/vedic/__pycache__/doshas.cpython-313.pyc +0 -0
  73. package/python/kundali_lib/vedic/__pycache__/gandanta.cpython-313.pyc +0 -0
  74. package/python/kundali_lib/vedic/__pycache__/gochara.cpython-313.pyc +0 -0
  75. package/python/kundali_lib/vedic/__pycache__/hora.cpython-313.pyc +0 -0
  76. package/python/kundali_lib/vedic/__pycache__/houses.cpython-313.pyc +0 -0
  77. package/python/kundali_lib/vedic/__pycache__/inauspicious_times.cpython-313.pyc +0 -0
  78. package/python/kundali_lib/vedic/__pycache__/jaimini.cpython-313.pyc +0 -0
  79. package/python/kundali_lib/vedic/__pycache__/kalachakra.cpython-313.pyc +0 -0
  80. package/python/kundali_lib/vedic/__pycache__/kartari.cpython-313.pyc +0 -0
  81. package/python/kundali_lib/vedic/__pycache__/kurmachakra.cpython-313.pyc +0 -0
  82. package/python/kundali_lib/vedic/__pycache__/lunar_return.cpython-313.pyc +0 -0
  83. package/python/kundali_lib/vedic/__pycache__/medical_astrology.cpython-313.pyc +0 -0
  84. package/python/kundali_lib/vedic/__pycache__/muhurta.cpython-313.pyc +0 -0
  85. package/python/kundali_lib/vedic/__pycache__/nabhasha.cpython-313.pyc +0 -0
  86. package/python/kundali_lib/vedic/__pycache__/nakshatra_details.cpython-313.pyc +0 -0
  87. package/python/kundali_lib/vedic/__pycache__/panchanga.cpython-313.pyc +0 -0
  88. package/python/kundali_lib/vedic/__pycache__/planets.cpython-313.pyc +0 -0
  89. package/python/kundali_lib/vedic/__pycache__/remedial.cpython-313.pyc +0 -0
  90. package/python/kundali_lib/vedic/__pycache__/shadbala.cpython-313.pyc +0 -0
  91. package/python/kundali_lib/vedic/__pycache__/special_conditions.cpython-313.pyc +0 -0
  92. package/python/kundali_lib/vedic/__pycache__/sudarshana.cpython-313.pyc +0 -0
  93. package/python/kundali_lib/vedic/__pycache__/tajaka.cpython-313.pyc +0 -0
  94. package/python/kundali_lib/vedic/__pycache__/upagrahas.cpython-313.pyc +0 -0
  95. package/python/kundali_lib/vedic/__pycache__/varshaphal.cpython-313.pyc +0 -0
  96. package/python/kundali_lib/vedic/__pycache__/yogas.cpython-313.pyc +0 -0
  97. package/python/kundali_lib/vedic/__pycache__/zodiac.cpython-313.pyc +0 -0
  98. package/python/kundali_lib/vedic/arudha.py +189 -0
  99. package/python/kundali_lib/vedic/inauspicious_times.py +303 -0
  100. package/python/kundali_lib/vedic/medical_astrology.py +424 -0
  101. package/python/kundali_lib/vedic/remedial.py +849 -0
@@ -49,6 +49,23 @@ except Exception as e:
49
49
  # Don't exit - we can still serve the health endpoint
50
50
 
51
51
 
52
+ @app.route(route="tools")
53
+ def tools_handler(req: func.HttpRequest) -> func.HttpResponse:
54
+ """Return list of all available tools with descriptions."""
55
+ tool_list = []
56
+ for name, func_obj in TOOLS.items():
57
+ tool_list.append(
58
+ {
59
+ "name": name,
60
+ "description": func_obj.__doc__ or "",
61
+ }
62
+ )
63
+ return func.HttpResponse(
64
+ json.dumps({"tools": tool_list}, ensure_ascii=False),
65
+ mimetype="application/json",
66
+ )
67
+
68
+
52
69
  @app.route(route="kundali/{toolName}")
53
70
  def kundali_handler(req: func.HttpRequest) -> func.HttpResponse:
54
71
  tool_name = req.route_params.get("toolName")
@@ -26,6 +26,10 @@ os.environ.setdefault("REQUESTS_CA_BUNDLE", certifi.where())
26
26
  from kundali_lib.ephemeris import EphemerisService # noqa: E402
27
27
  from kundali_lib.geocoder import GeocoderService # noqa: E402
28
28
  from kundali_lib.vedic.arishta import get_arishta_yogas # noqa: E402
29
+ from kundali_lib.vedic.arudha import ( # noqa: E402
30
+ calculate_all_arudhas,
31
+ calculate_upapada,
32
+ )
29
33
  from kundali_lib.vedic.ashtakavarga import get_ashtakavarga # noqa: E402
30
34
  from kundali_lib.vedic.avasthas import get_avasthas # noqa: E402
31
35
  from kundali_lib.vedic.ayanamsa import ( # noqa: E402
@@ -64,6 +68,10 @@ from kundali_lib.vedic.doshas import ( # noqa: E402
64
68
  from kundali_lib.vedic.gandanta import check_gandanta, get_gandanta_info # noqa: E402
65
69
  from kundali_lib.vedic.gochara import get_gochara # noqa: E402
66
70
  from kundali_lib.vedic.hora import get_hora_chart, get_planetary_hour # noqa: E402
71
+ from kundali_lib.vedic.inauspicious_times import ( # noqa: E402
72
+ calculate_inauspicious_times,
73
+ is_time_auspicious,
74
+ )
67
75
  from kundali_lib.vedic.jaimini import ( # noqa: E402
68
76
  get_jaimini_aspects,
69
77
  get_jaimini_karakas,
@@ -80,6 +88,7 @@ from kundali_lib.vedic.lunar_return import ( # noqa: E402
80
88
  get_lunar_return,
81
89
  get_prasna_chart,
82
90
  )
91
+ from kundali_lib.vedic.medical_astrology import get_medical_astrology # noqa: E402
83
92
  from kundali_lib.vedic.muhurta import ( # noqa: E402
84
93
  get_best_muhurta_in_range,
85
94
  get_muhurta_score,
@@ -91,6 +100,7 @@ from kundali_lib.vedic.nakshatra_details import ( # noqa: E402
91
100
  get_planet_nakshatra_analysis,
92
101
  )
93
102
  from kundali_lib.vedic.panchanga import get_panchanga # noqa: E402
103
+ from kundali_lib.vedic.remedial import get_comprehensive_remedial_measures # noqa: E402
94
104
  from kundali_lib.vedic.shadbala import get_shadbala # noqa: E402
95
105
  from kundali_lib.vedic.special_conditions import ( # noqa: E402
96
106
  check_pushkara_navamsha,
@@ -876,6 +886,141 @@ def get_full_kurmachakra_chart_tool(payload: dict[str, Any]) -> dict[str, Any]:
876
886
  return get_full_kurmachakra_chart()
877
887
 
878
888
 
889
+ def get_arudha_lagna_tool(payload: dict[str, Any]) -> dict[str, Any]:
890
+ """Calculate Arudha Lagna (AL) and all 12 Arudha Padas — how the world perceives you.
891
+
892
+ Arudha Lagna reveals your public image, reputation, and social standing.
893
+ Each Arudha Pada shows the manifested reality of that house.
894
+ Upapada Lagna indicates marriage nature and spouse characteristics.
895
+ Used to understand fame, status, and how others see you vs who you really are."""
896
+ base = _require_base_chart(payload)
897
+ lagna_rashi = base.get("ascendant", {}).get("rashi", "")
898
+ positions = base.get("planetary_positions", [])
899
+ return calculate_all_arudhas(lagna_rashi, positions)
900
+
901
+
902
+ def get_upapada_tool(payload: dict[str, Any]) -> dict[str, Any]:
903
+ """Calculate Upapada Lagna — the key indicator for marriage and spouse analysis.
904
+
905
+ Upapada Lagna is derived from Arudha Lagna and specifically indicates:
906
+ - Nature of marriage (love/arranged, harmonious/difficult)
907
+ - Spouse characteristics and personality
908
+ - Marital happiness and longevity of marriage
909
+ Essential for relationship and marriage timing analysis."""
910
+ base = _require_base_chart(payload)
911
+ lagna_rashi = base.get("ascendant", {}).get("rashi", "")
912
+ positions = base.get("planetary_positions", [])
913
+ return calculate_upapada(lagna_rashi, positions)
914
+
915
+
916
+ def get_remedial_measures_tool(payload: dict[str, Any]) -> dict[str, Any]:
917
+ """Get personalized Vedic remedies — gemstones, mantras, charity, fasting, and lifestyle changes.
918
+
919
+ Analyzes each planet's condition (debilitated, combust, retrograde, weak in Shadbala, etc.)
920
+ and provides specific, actionable remedies:
921
+ - Gemstone recommendations (which stone, which finger, which metal, when to wear)
922
+ - Mantra recommendations (specific Beej mantras for each planet)
923
+ - Charity recommendations (what to donate, to whom, on which day)
924
+ - Fasting recommendations (which day, what type of fast)
925
+ - Lifestyle changes (colors, directions, activities to embrace or avoid)
926
+ Remedies are prioritized by urgency (high/medium/low)."""
927
+ base = _require_base_chart(payload)
928
+ positions = base.get("planetary_positions", [])
929
+ # Try to get Shadbala for more accurate assessment
930
+ shadbala_data = None
931
+ try:
932
+ shadbala_data = get_shadbala(positions)
933
+ except Exception:
934
+ pass
935
+ return get_comprehensive_remedial_measures(positions, shadbala_data)
936
+
937
+
938
+ def get_inauspicious_times_tool(payload: dict[str, Any]) -> dict[str, Any]:
939
+ """Get inauspicious time periods for a given date — Rahu Kala, Yamaganda, Gulika, Choghadiya.
940
+
941
+ Calculates all major inauspicious (and auspicious) time periods:
942
+ - Rahu Kala: The most inauspicious period — avoid starting new ventures
943
+ - Yamaganda: Yama's inauspicious period — avoid travel
944
+ - Gulika Kala: Inauspicious for important decisions
945
+ - Choghadiya: 8 time divisions showing auspicious/inauspicious nature
946
+ - Abhijit Muhurta: Most auspicious midday window
947
+ - Brahma Muhurta: Pre-dawn spiritual time
948
+ Essential for muhurta (electional astrology) and daily planning."""
949
+ from datetime import datetime as dt
950
+
951
+ date = dt(
952
+ payload.get("year", 2000),
953
+ payload.get("month", 1),
954
+ payload.get("day", 1),
955
+ )
956
+ sunrise = None
957
+ sunset = None
958
+ if "sunrise_hour" in payload:
959
+ sunrise = date.replace(
960
+ hour=payload.get("sunrise_hour", 6),
961
+ minute=payload.get("sunrise_minute", 0),
962
+ )
963
+ if "sunset_hour" in payload:
964
+ sunset = date.replace(
965
+ hour=payload.get("sunset_hour", 18),
966
+ minute=payload.get("sunset_minute", 0),
967
+ )
968
+ return calculate_inauspicious_times(date, sunrise, sunset)
969
+
970
+
971
+ def check_time_auspiciousness_tool(payload: dict[str, Any]) -> dict[str, Any]:
972
+ """Check if a specific date and time is auspicious or inauspicious.
973
+
974
+ Evaluates any given moment against all Vedic time-keeping systems:
975
+ - Is it in Rahu Kala? (avoid important work)
976
+ - Is it in Yamaganda? (avoid travel)
977
+ - Is it in Gulika Kala? (avoid decisions)
978
+ - Is it Abhijit Muhurta? (excellent for new beginnings)
979
+ - Is it Brahma Muhurta? (best for spiritual practices)
980
+ - What is the current Choghadiya? (auspicious/inauspicious)
981
+ Returns a clear rating: Highly Auspicious / Auspicious / Neutral / Inauspicious
982
+ with a recommendation on what to do or avoid."""
983
+ from datetime import datetime as dt
984
+
985
+ check_time = dt(
986
+ payload.get("year", 2000),
987
+ payload.get("month", 1),
988
+ payload.get("day", 1),
989
+ payload.get("hour", 12),
990
+ payload.get("minute", 0),
991
+ )
992
+ sunrise = None
993
+ sunset = None
994
+ if "sunrise_hour" in payload:
995
+ sunrise = check_time.replace(
996
+ hour=payload.get("sunrise_hour", 6),
997
+ minute=payload.get("sunrise_minute", 0),
998
+ )
999
+ if "sunset_hour" in payload:
1000
+ sunset = check_time.replace(
1001
+ hour=payload.get("sunset_hour", 18),
1002
+ minute=payload.get("sunset_minute", 0),
1003
+ )
1004
+ return is_time_auspicious(check_time, sunrise, sunset)
1005
+
1006
+
1007
+ def get_medical_astrology_tool(payload: dict[str, Any]) -> dict[str, Any]:
1008
+ """Get health predictions from Vedic Medical Astrology (Ayurvedic Astrology).
1009
+
1010
+ Maps your birth chart to health vulnerabilities:
1011
+ - Vulnerable body parts based on house placements
1012
+ - Planet-conditioned health risks (debilitated/retrograde planets)
1013
+ - Ayurvedic Dosha balance (Vata/Pitta/Kapha constitution)
1014
+ - Health recommendations based on your chart
1015
+ - Overall vitality, mental health, and longevity indicators
1016
+ Based on traditional Vedic medical astrology connecting houses, signs,
1017
+ and planets to body systems and Ayurvedic principles."""
1018
+ base = _require_base_chart(payload)
1019
+ lagna_rashi = base.get("ascendant", {}).get("rashi", "")
1020
+ positions = base.get("planetary_positions", [])
1021
+ return get_medical_astrology(positions, lagna_rashi)
1022
+
1023
+
879
1024
  TOOLS = {
880
1025
  # Core chart tools
881
1026
  "generate_kundali": generate_kundali,
@@ -930,6 +1075,13 @@ TOOLS = {
930
1075
  "get_kurmachakra": get_kurmachakra_tool,
931
1076
  "get_travel_direction_score": get_travel_direction_tool,
932
1077
  "get_full_kurmachakra_chart": get_full_kurmachakra_chart_tool,
1078
+ # Batch 3 — Arudha, Remedial, Inauspicious Times, Medical Astrology
1079
+ "get_arudha_lagna": get_arudha_lagna_tool,
1080
+ "get_upapada": get_upapada_tool,
1081
+ "get_remedial_measures": get_remedial_measures_tool,
1082
+ "get_inauspicious_times": get_inauspicious_times_tool,
1083
+ "check_time_auspiciousness": check_time_auspiciousness_tool,
1084
+ "get_medical_astrology": get_medical_astrology_tool,
933
1085
  }
934
1086
 
935
1087
 
@@ -0,0 +1,189 @@
1
+ """Arudha Lagna and Arudha Padas calculation.
2
+
3
+ Arudha Lagna (AL) is the "image" or "maya" house — how the world perceives you.
4
+ Arudha Pada for each house shows the manifested reality of that house.
5
+
6
+ Calculation:
7
+ - Count from Lagna lord to Lagna sign (excluding Lagna itself)
8
+ - Arudha Lagna = that many signs from Lagna lord's position
9
+ - If result is same as Lagna (1st house), use 10th from there (special rule)
10
+ - Similar for each house: count from house lord to house sign
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any
16
+
17
+ from kundali_lib.vedic.constants import PLANET_IDS, RASHI_LORDS, RASHIS
18
+
19
+ # Build HOUSE_LORDS dict from RASHI_LORDS list
20
+ HOUSE_LORDS = {rashi: lord for rashi, lord in zip(RASHIS, RASHI_LORDS)}
21
+
22
+
23
+ def _sign_index(rashi: str) -> int:
24
+ return RASHIS.index(rashi)
25
+
26
+
27
+ def _count_signs(from_rashi: str, to_rashi: str, exclude_self: bool = True) -> int:
28
+ """Count signs from from_rashi to to_rashi (forward)."""
29
+ from_i = _sign_index(from_rashi)
30
+ to_i = _sign_index(to_rashi)
31
+ count = (to_i - from_i) % 12
32
+ if exclude_self and count == 0:
33
+ count = 12
34
+ return count
35
+
36
+
37
+ def _advance_signs(rashi: str, n: int) -> str:
38
+ return RASHIS[(_sign_index(rashi) + n) % 12]
39
+
40
+
41
+ def calculate_arudha_lagna(
42
+ lagna_rashi: str,
43
+ lagna_lord_sign: str,
44
+ ) -> dict[str, Any]:
45
+ """Calculate Arudha Lagna (AL).
46
+
47
+ Count from Lagna lord's position to Lagna sign.
48
+ Then advance that many signs from Lagna lord.
49
+ If result equals Lagna, use 10th from Lagna instead.
50
+ """
51
+ count = _count_signs(lagna_lord_sign, lagna_rashi, exclude_self=True)
52
+ arudha = _advance_signs(lagna_lord_sign, count)
53
+
54
+ # Special rule: if Arudha Lagna falls in 1st house, use 10th from there
55
+ if arudha == lagna_rashi:
56
+ arudha = _advance_signs(lagna_rashi, 9)
57
+
58
+ return {
59
+ "arudha_lagna_sign": arudha,
60
+ "count": count,
61
+ "lagna_rashi": lagna_rashi,
62
+ "lagna_lord_sign": lagna_lord_sign,
63
+ "special_rule_applied": arudha != _advance_signs(lagna_lord_sign, count),
64
+ }
65
+
66
+
67
+ def calculate_arudha_pada(
68
+ house_sign: str,
69
+ house_lord_sign: str,
70
+ ) -> dict[str, Any]:
71
+ """Calculate Arudha Pada for a specific house.
72
+
73
+ Count from house lord's position to house sign.
74
+ Then advance that many signs from house lord.
75
+ If result equals the house itself, use 10th from there.
76
+ """
77
+ count = _count_signs(house_lord_sign, house_sign, exclude_self=True)
78
+ arudha = _advance_signs(house_lord_sign, count)
79
+
80
+ # Special rule
81
+ if arudha == house_sign:
82
+ arudha = _advance_signs(house_sign, 9)
83
+
84
+ return {
85
+ "house_sign": house_sign,
86
+ "house_lord_sign": house_lord_sign,
87
+ "arudha_pada_sign": arudha,
88
+ "count": count,
89
+ "special_rule_applied": arudha != _advance_signs(house_lord_sign, count),
90
+ }
91
+
92
+
93
+ def calculate_all_arudhas(
94
+ lagna_rashi: str,
95
+ planetary_positions: list[dict[str, Any]],
96
+ ) -> dict[str, Any]:
97
+ """Calculate Arudha Lagna and all 12 Arudha Padas.
98
+
99
+ Args:
100
+ lagna_rashi: The ascendant sign
101
+ planetary_positions: List of planet dicts with 'name' and 'rashi' keys
102
+
103
+ Returns:
104
+ Dictionary with AL and all Arudha Padas
105
+ """
106
+ # Build planet sign lookup
107
+ planet_signs = {}
108
+ for p in planetary_positions:
109
+ planet_signs[p["name"]] = p["rashi"]
110
+
111
+ # Get Lagna lord sign
112
+ lagna_lord = HOUSE_LORDS.get(lagna_rashi, "Mars")
113
+ lagna_lord_sign = planet_signs.get(lagna_lord, lagna_rashi)
114
+
115
+ # Calculate Arudha Lagna
116
+ al = calculate_arudha_lagna(lagna_rashi, lagna_lord_sign)
117
+
118
+ # Calculate Arudha Padas for each house
119
+ arudha_padas = {}
120
+ for i, rashi in enumerate(RASHIS):
121
+ house_num = i + 1
122
+ house_lord = HOUSE_LORDS.get(rashi, "Mars")
123
+ house_lord_sign = planet_signs.get(house_lord, rashi)
124
+
125
+ pada = calculate_arudha_pada(rashi, house_lord_sign)
126
+ arudha_padas[f"house_{house_num}"] = {
127
+ "house_sign": rashi,
128
+ "house_lord": house_lord,
129
+ "arudha_pada_sign": pada["arudha_pada_sign"],
130
+ "meaning": _arudha_pada_meaning(house_num),
131
+ }
132
+
133
+ return {
134
+ "arudha_lagna": al,
135
+ "arudha_padas": arudha_padas,
136
+ "interpretation": {
137
+ "al_significance": "Arudha Lagna shows how the world perceives you — your public image and reputation",
138
+ "pada_significance": "Each Arudha Pada shows the manifested reality of that house",
139
+ },
140
+ }
141
+
142
+
143
+ def calculate_upapada(
144
+ lagna_rashi: str,
145
+ planetary_positions: list[dict[str, Any]],
146
+ ) -> dict[str, Any]:
147
+ """Calculate Upapada Lagna — the 12th house from Arudha Lagna.
148
+
149
+ Upapada is used for marriage analysis and spouse identification.
150
+ It shows the nature of marriage and the spouse.
151
+ """
152
+ # Build planet sign lookup
153
+ planet_signs = {}
154
+ for p in planetary_positions:
155
+ planet_signs[p["name"]] = p["rashi"]
156
+
157
+ lagna_lord = HOUSE_LORDS.get(lagna_rashi, "Mars")
158
+ lagna_lord_sign = planet_signs.get(lagna_lord, lagna_rashi)
159
+
160
+ al = calculate_arudha_lagna(lagna_rashi, lagna_lord_sign)
161
+ al_sign = al["arudha_lagna_sign"]
162
+
163
+ # Upapada = 12th from AL
164
+ upapada_sign = _advance_signs(al_sign, 11)
165
+
166
+ return {
167
+ "upapada_sign": upapada_sign,
168
+ "arudha_lagna_sign": al_sign,
169
+ "significance": "Upapada Lagna indicates marriage nature, spouse characteristics, and marital happiness",
170
+ "lord": HOUSE_LORDS.get(upapada_sign, "Unknown"),
171
+ }
172
+
173
+
174
+ def _arudha_pada_meaning(house_num: int) -> str:
175
+ meanings = {
176
+ 1: "Self-image and personality projection",
177
+ 2: "Wealth manifestation and family image",
178
+ 3: "Communication style and courage display",
179
+ 4: "Home life and emotional expression",
180
+ 5: "Creativity and children's image",
181
+ 6: "Health service and conflict handling",
182
+ 7: "Partnerships and relationship image",
183
+ 8: "Transformation and hidden matters",
184
+ 9: "Fortune and spiritual image",
185
+ 10: "Career and public standing",
186
+ 11: "Gains and social network image",
187
+ 12: "Losses and spiritual liberation",
188
+ }
189
+ return meanings.get(house_num, "")