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.
- package/README.md +67 -0
- package/azure-function/function_app.py +93 -0
- package/azure-function/host.json +15 -0
- package/azure-function/kundali_bridge.py +952 -0
- package/azure-function/python/kundali_lib/__init__.py +1 -0
- package/azure-function/python/kundali_lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/__pycache__/ephemeris.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/__pycache__/geocoder.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/__pycache__/vedicastro_bridge.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/ephemeris.py +30 -0
- package/azure-function/python/kundali_lib/geocoder.py +82 -0
- package/azure-function/python/kundali_lib/vedic/__init__.py +1 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/arishta.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/ashtakavarga.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/avasthas.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/ayanamsa.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/bhava_chalit.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/char_dasha.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/chart.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/chart_types.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/compatibility.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/constants.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/dasha_extended.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/dasha_systems.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/doshas.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/gandanta.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/gochara.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/hora.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/houses.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/jaimini.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/kalachakra.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/kartari.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/kurmachakra.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/lunar_return.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/muhurta.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/nabhasha.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/nakshatra_details.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/panchanga.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/planets.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/shadbala.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/special_conditions.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/sudarshana.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/tajaka.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/upagrahas.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/varshaphal.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/yogas.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/__pycache__/zodiac.cpython-313.pyc +0 -0
- package/azure-function/python/kundali_lib/vedic/arishta.py +465 -0
- package/azure-function/python/kundali_lib/vedic/ashtakavarga.py +213 -0
- package/azure-function/python/kundali_lib/vedic/avasthas.py +292 -0
- package/azure-function/python/kundali_lib/vedic/ayanamsa.py +106 -0
- package/azure-function/python/kundali_lib/vedic/bhava_chalit.py +137 -0
- package/azure-function/python/kundali_lib/vedic/char_dasha.py +308 -0
- package/azure-function/python/kundali_lib/vedic/chart.py +126 -0
- package/azure-function/python/kundali_lib/vedic/chart_types.py +338 -0
- package/azure-function/python/kundali_lib/vedic/compatibility.py +705 -0
- package/azure-function/python/kundali_lib/vedic/constants.py +108 -0
- package/azure-function/python/kundali_lib/vedic/dasha_extended.py +262 -0
- package/azure-function/python/kundali_lib/vedic/dasha_systems.py +439 -0
- package/azure-function/python/kundali_lib/vedic/doshas.py +453 -0
- package/azure-function/python/kundali_lib/vedic/gandanta.py +213 -0
- package/azure-function/python/kundali_lib/vedic/gochara.py +277 -0
- package/azure-function/python/kundali_lib/vedic/hora.py +263 -0
- package/azure-function/python/kundali_lib/vedic/houses.py +30 -0
- package/azure-function/python/kundali_lib/vedic/jaimini.py +361 -0
- package/azure-function/python/kundali_lib/vedic/kalachakra.py +226 -0
- package/azure-function/python/kundali_lib/vedic/kartari.py +243 -0
- package/azure-function/python/kundali_lib/vedic/kurmachakra.py +383 -0
- package/azure-function/python/kundali_lib/vedic/lunar_return.py +402 -0
- package/azure-function/python/kundali_lib/vedic/muhurta.py +414 -0
- package/azure-function/python/kundali_lib/vedic/nabhasha.py +349 -0
- package/azure-function/python/kundali_lib/vedic/nakshatra_details.py +945 -0
- package/azure-function/python/kundali_lib/vedic/panchanga.py +297 -0
- package/azure-function/python/kundali_lib/vedic/planets.py +55 -0
- package/azure-function/python/kundali_lib/vedic/shadbala.py +500 -0
- package/azure-function/python/kundali_lib/vedic/special_conditions.py +319 -0
- package/azure-function/python/kundali_lib/vedic/sudarshana.py +232 -0
- package/azure-function/python/kundali_lib/vedic/tajaka.py +482 -0
- package/azure-function/python/kundali_lib/vedic/upagrahas.py +229 -0
- package/azure-function/python/kundali_lib/vedic/varshaphal.py +185 -0
- package/azure-function/python/kundali_lib/vedic/yogas.py +935 -0
- package/azure-function/python/kundali_lib/vedic/zodiac.py +42 -0
- package/azure-function/python/kundali_lib/vedicastro_bridge.py +198 -0
- package/azure-function/requirements.txt +9 -0
- package/index.js +747 -0
- package/kundali-chart-mcp.js +159 -0
- package/kundali_bridge.py +952 -0
- package/package.json +41 -0
- package/python/kundali_lib/__init__.py +1 -0
- package/python/kundali_lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/kundali_lib/__pycache__/ephemeris.cpython-313.pyc +0 -0
- package/python/kundali_lib/__pycache__/geocoder.cpython-313.pyc +0 -0
- package/python/kundali_lib/__pycache__/vedicastro_bridge.cpython-313.pyc +0 -0
- package/python/kundali_lib/ephemeris.py +30 -0
- package/python/kundali_lib/geocoder.py +82 -0
- package/python/kundali_lib/vedic/__init__.py +1 -0
- package/python/kundali_lib/vedic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/arishta.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/ashtakavarga.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/avasthas.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/ayanamsa.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/bhava_chalit.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/char_dasha.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/chart.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/chart_types.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/compatibility.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/constants.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/dasha_extended.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/dasha_systems.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/doshas.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/gandanta.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/gochara.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/hora.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/houses.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/jaimini.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/kalachakra.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/kartari.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/kurmachakra.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/lunar_return.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/muhurta.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/nabhasha.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/nakshatra_details.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/panchanga.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/planets.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/shadbala.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/special_conditions.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/sudarshana.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/tajaka.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/upagrahas.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/varshaphal.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/yogas.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/__pycache__/zodiac.cpython-313.pyc +0 -0
- package/python/kundali_lib/vedic/arishta.py +465 -0
- package/python/kundali_lib/vedic/ashtakavarga.py +213 -0
- package/python/kundali_lib/vedic/avasthas.py +292 -0
- package/python/kundali_lib/vedic/ayanamsa.py +106 -0
- package/python/kundali_lib/vedic/bhava_chalit.py +137 -0
- package/python/kundali_lib/vedic/char_dasha.py +308 -0
- package/python/kundali_lib/vedic/chart.py +126 -0
- package/python/kundali_lib/vedic/chart_types.py +338 -0
- package/python/kundali_lib/vedic/compatibility.py +705 -0
- package/python/kundali_lib/vedic/constants.py +108 -0
- package/python/kundali_lib/vedic/dasha_extended.py +262 -0
- package/python/kundali_lib/vedic/dasha_systems.py +439 -0
- package/python/kundali_lib/vedic/doshas.py +453 -0
- package/python/kundali_lib/vedic/gandanta.py +213 -0
- package/python/kundali_lib/vedic/gochara.py +277 -0
- package/python/kundali_lib/vedic/hora.py +263 -0
- package/python/kundali_lib/vedic/houses.py +30 -0
- package/python/kundali_lib/vedic/jaimini.py +361 -0
- package/python/kundali_lib/vedic/kalachakra.py +226 -0
- package/python/kundali_lib/vedic/kartari.py +243 -0
- package/python/kundali_lib/vedic/kurmachakra.py +383 -0
- package/python/kundali_lib/vedic/lunar_return.py +402 -0
- package/python/kundali_lib/vedic/muhurta.py +414 -0
- package/python/kundali_lib/vedic/nabhasha.py +349 -0
- package/python/kundali_lib/vedic/nakshatra_details.py +945 -0
- package/python/kundali_lib/vedic/panchanga.py +297 -0
- package/python/kundali_lib/vedic/planets.py +55 -0
- package/python/kundali_lib/vedic/shadbala.py +500 -0
- package/python/kundali_lib/vedic/special_conditions.py +319 -0
- package/python/kundali_lib/vedic/sudarshana.py +232 -0
- package/python/kundali_lib/vedic/tajaka.py +482 -0
- package/python/kundali_lib/vedic/upagrahas.py +229 -0
- package/python/kundali_lib/vedic/varshaphal.py +185 -0
- package/python/kundali_lib/vedic/yogas.py +935 -0
- package/python/kundali_lib/vedic/zodiac.py +42 -0
- package/python/kundali_lib/vedicastro_bridge.py +198 -0
- package/remote-server.js +590 -0
- package/requirements.txt +8 -0
- package/setup.sh +218 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Upagrahas: Shadow planets and sub-planets in Vedic astrology."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
import swisseph as swe
|
|
7
|
+
|
|
8
|
+
_SWE_AVAILABLE = True
|
|
9
|
+
except ImportError: # pragma: no cover
|
|
10
|
+
_SWE_AVAILABLE = False
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Reference data
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
RASHIS = [
|
|
17
|
+
"Aries",
|
|
18
|
+
"Taurus",
|
|
19
|
+
"Gemini",
|
|
20
|
+
"Cancer",
|
|
21
|
+
"Leo",
|
|
22
|
+
"Virgo",
|
|
23
|
+
"Libra",
|
|
24
|
+
"Scorpio",
|
|
25
|
+
"Sagittarius",
|
|
26
|
+
"Capricorn",
|
|
27
|
+
"Aquarius",
|
|
28
|
+
"Pisces",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
NAKSHATRAS = [
|
|
32
|
+
"Ashwini",
|
|
33
|
+
"Bharani",
|
|
34
|
+
"Krittika",
|
|
35
|
+
"Rohini",
|
|
36
|
+
"Mrigashira",
|
|
37
|
+
"Ardra",
|
|
38
|
+
"Punarvasu",
|
|
39
|
+
"Pushya",
|
|
40
|
+
"Ashlesha",
|
|
41
|
+
"Magha",
|
|
42
|
+
"Purva Phalguni",
|
|
43
|
+
"Uttara Phalguni",
|
|
44
|
+
"Hasta",
|
|
45
|
+
"Chitra",
|
|
46
|
+
"Swati",
|
|
47
|
+
"Vishakha",
|
|
48
|
+
"Anuradha",
|
|
49
|
+
"Jyeshtha",
|
|
50
|
+
"Mula",
|
|
51
|
+
"Purva Ashadha",
|
|
52
|
+
"Uttara Ashadha",
|
|
53
|
+
"Shravana",
|
|
54
|
+
"Dhanishta",
|
|
55
|
+
"Shatabhisha",
|
|
56
|
+
"Purva Bhadrapada",
|
|
57
|
+
"Uttara Bhadrapada",
|
|
58
|
+
"Revati",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
# How many portions into the day each weekday's Gulika begins.
|
|
62
|
+
# Weekday index: 0=Sunday, 1=Monday, ..., 6=Saturday
|
|
63
|
+
# Each day is split into 8 equal portions; Gulika occupies one of them.
|
|
64
|
+
_GULIKA_WEEKDAY_PORTION = {
|
|
65
|
+
0: 6, # Sunday — 7th portion (index 6)
|
|
66
|
+
1: 0, # Monday — 1st portion (index 0)
|
|
67
|
+
2: 1, # Tuesday — 2nd portion (index 1)
|
|
68
|
+
3: 2, # Wednesday— 3rd portion (index 2)
|
|
69
|
+
4: 3, # Thursday — 4th portion (index 3)
|
|
70
|
+
5: 4, # Friday — 5th portion (index 4)
|
|
71
|
+
6: 5, # Saturday — 6th portion (index 5)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Mandi (start of portion, vs Gulika which is midpoint)
|
|
75
|
+
_MANDI_WEEKDAY_PORTION = _GULIKA_WEEKDAY_PORTION # same portions, different point
|
|
76
|
+
|
|
77
|
+
_UPAGRAHA_SIGNIFICANCE = {
|
|
78
|
+
"Gulika": "Most malefic upagraha, son of Saturn. Very important in KP and Nadi astrology.",
|
|
79
|
+
"Mandi": "Shadow of Gulika; marks the start of Saturn's portion. Strongly malefic.",
|
|
80
|
+
"Dhuma": "Smoky planet; Sun + 133°20'. Associated with obstacles and disruptions.",
|
|
81
|
+
"Vyatipata": "Calamity indicator; 360° − Dhuma. Highly inauspicious transit point.",
|
|
82
|
+
"Parivesha": "Encirclement; Vyatipata + 180°. Related to entrapment or enclosure.",
|
|
83
|
+
"Indra_Chapa": "Indra's bow; 360° − Parivesha (= Kodanda). Indicates ambition and pride.",
|
|
84
|
+
"Upaketu": "Comet-like sub-planet; Sun + 30°. Associated with sudden events.",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Helpers
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _rashi_from_lon(lon: float) -> str:
|
|
93
|
+
"""Return the rashi name for an absolute sidereal longitude."""
|
|
94
|
+
return RASHIS[int(lon / 30) % 12]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _nakshatra_from_lon(lon: float) -> str:
|
|
98
|
+
"""Return the nakshatra name for an absolute sidereal longitude."""
|
|
99
|
+
return NAKSHATRAS[int(lon / (360 / 27)) % 27]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _house_from_lon(lon: float, asc_lon: float) -> int:
|
|
103
|
+
"""Return the house number (1–12) for a longitude given the ascendant longitude."""
|
|
104
|
+
diff = (lon - asc_lon) % 360
|
|
105
|
+
return int(diff / 30) + 1
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _normalise(lon: float) -> float:
|
|
109
|
+
"""Bring a longitude into the [0, 360) range."""
|
|
110
|
+
return lon % 360
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _build_upagraha_entry(name: str, lon: float, asc_lon: float) -> dict:
|
|
114
|
+
lon = _normalise(lon)
|
|
115
|
+
return {
|
|
116
|
+
"longitude": round(lon, 4),
|
|
117
|
+
"rashi": _rashi_from_lon(lon),
|
|
118
|
+
"degree_in_sign": round(lon % 30, 4),
|
|
119
|
+
"nakshatra": _nakshatra_from_lon(lon),
|
|
120
|
+
"house": _house_from_lon(lon, asc_lon),
|
|
121
|
+
"significance": _UPAGRAHA_SIGNIFICANCE.get(name, ""),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _get_sun_longitude(julian_day: float, ayanamsa_offset: float) -> float:
|
|
126
|
+
"""Compute the Sun's sidereal longitude using swisseph (if available)."""
|
|
127
|
+
if _SWE_AVAILABLE:
|
|
128
|
+
flags = swe.FLG_SWIEPH | swe.FLG_SPEED | swe.FLG_SIDEREAL
|
|
129
|
+
result, _ = swe.calc_ut(julian_day, swe.SUN, flags)
|
|
130
|
+
return result[0] % 360
|
|
131
|
+
# Fallback: very rough tropical approximation minus ayanamsa
|
|
132
|
+
# (useful only if swisseph is genuinely absent)
|
|
133
|
+
T = (julian_day - 2451545.0) / 36525.0
|
|
134
|
+
lon_tropical = (280.46646 + 36000.76983 * T) % 360
|
|
135
|
+
return (lon_tropical - ayanamsa_offset) % 360
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _get_ascendant_longitude(
|
|
139
|
+
julian_day: float, birth_lat: float, birth_lon: float, ayanamsa_offset: float
|
|
140
|
+
) -> float:
|
|
141
|
+
"""Compute the sidereal ascendant longitude."""
|
|
142
|
+
if _SWE_AVAILABLE:
|
|
143
|
+
houses, ascmc = swe.houses(julian_day, birth_lat, birth_lon, b"P")
|
|
144
|
+
asc_tropical = ascmc[0]
|
|
145
|
+
return (asc_tropical - ayanamsa_offset) % 360
|
|
146
|
+
# Rough fallback: estimate LST-based ascendant
|
|
147
|
+
T = (julian_day - 2451545.0) / 36525.0
|
|
148
|
+
gst = (280.46061837 + 360.98564736629 * (julian_day - 2451545.0)) % 360
|
|
149
|
+
lst = (gst + birth_lon) % 360
|
|
150
|
+
return (lst - ayanamsa_offset) % 360
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# Main function
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def get_upagrahas(
|
|
159
|
+
julian_day: float, birth_lat: float, birth_lon: float, ayanamsa_offset: float
|
|
160
|
+
) -> dict:
|
|
161
|
+
"""Calculate all major upagrahas (shadow sub-planets).
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
julian_day : float
|
|
166
|
+
Julian Day Number for the birth moment (UT).
|
|
167
|
+
birth_lat : float
|
|
168
|
+
Geographic latitude of birth location (degrees, N positive).
|
|
169
|
+
birth_lon : float
|
|
170
|
+
Geographic longitude of birth location (degrees, E positive).
|
|
171
|
+
ayanamsa_offset : float
|
|
172
|
+
Ayanamsa in degrees at the time of birth
|
|
173
|
+
(e.g. from ``swe.get_ayanamsa_ut(jd)``).
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
dict
|
|
178
|
+
Keys: Gulika, Mandi, Dhuma, Vyatipata, Parivesha, Indra_Chapa, Upaketu.
|
|
179
|
+
Each value is a dict with longitude, rashi, degree_in_sign,
|
|
180
|
+
nakshatra, house, significance.
|
|
181
|
+
"""
|
|
182
|
+
sun_lon = _get_sun_longitude(julian_day, ayanamsa_offset)
|
|
183
|
+
asc_lon = _get_ascendant_longitude(
|
|
184
|
+
julian_day, birth_lat, birth_lon, ayanamsa_offset
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# ------------------------------------------------------------------
|
|
188
|
+
# Weekday from Julian Day
|
|
189
|
+
# jd 0.0 = Monday noon UT; int(jd + 1.5) % 7 gives 0=Sunday … 6=Sat
|
|
190
|
+
# ------------------------------------------------------------------
|
|
191
|
+
weekday = int(julian_day + 1.5) % 7 # 0=Sunday, 6=Saturday
|
|
192
|
+
|
|
193
|
+
# ------------------------------------------------------------------
|
|
194
|
+
# Gulika & Mandi
|
|
195
|
+
# Day duration assumed = 12 hours (simplified; sunrise 6 AM, sunset 6 PM).
|
|
196
|
+
# Split into 8 equal portions of 90 minutes each.
|
|
197
|
+
# Lagna (ascendant sign) changes roughly every 2 hours (120 min).
|
|
198
|
+
# Portion sign offset: each 90-min portion ≈ 90/120 × 30° = 22.5° from asc.
|
|
199
|
+
# Gulika longitude = ascendant + portion_index × (360/8)
|
|
200
|
+
# ------------------------------------------------------------------
|
|
201
|
+
day_duration_deg = 180.0 # 12 hours × 15°/hour (Sun travels 180° in a day)
|
|
202
|
+
portion_size_deg = day_duration_deg / 8 # 22.5° per portion
|
|
203
|
+
|
|
204
|
+
gulika_portion = _GULIKA_WEEKDAY_PORTION[weekday]
|
|
205
|
+
gulika_lon = _normalise(
|
|
206
|
+
asc_lon + gulika_portion * portion_size_deg + portion_size_deg / 2
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
mandi_portion = _MANDI_WEEKDAY_PORTION[weekday]
|
|
210
|
+
mandi_lon = _normalise(asc_lon + mandi_portion * portion_size_deg)
|
|
211
|
+
|
|
212
|
+
# ------------------------------------------------------------------
|
|
213
|
+
# Dhuma and derived upagrahas (purely Sun-based calculations)
|
|
214
|
+
# ------------------------------------------------------------------
|
|
215
|
+
dhuma_lon = _normalise(sun_lon + 133.333) # Sun + 133°20'
|
|
216
|
+
vyatipata_lon = _normalise(360.0 - dhuma_lon) # 360° − Dhuma
|
|
217
|
+
parivesha_lon = _normalise(vyatipata_lon + 180.0) # Vyatipata + 180°
|
|
218
|
+
indrachapa_lon = _normalise(360.0 - parivesha_lon) # 360° − Parivesha
|
|
219
|
+
upaketu_lon = _normalise(sun_lon + 30.0) # Sun + 30°
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
"Gulika": _build_upagraha_entry("Gulika", gulika_lon, asc_lon),
|
|
223
|
+
"Mandi": _build_upagraha_entry("Mandi", mandi_lon, asc_lon),
|
|
224
|
+
"Dhuma": _build_upagraha_entry("Dhuma", dhuma_lon, asc_lon),
|
|
225
|
+
"Vyatipata": _build_upagraha_entry("Vyatipata", vyatipata_lon, asc_lon),
|
|
226
|
+
"Parivesha": _build_upagraha_entry("Parivesha", parivesha_lon, asc_lon),
|
|
227
|
+
"Indra_Chapa": _build_upagraha_entry("Indra_Chapa", indrachapa_lon, asc_lon),
|
|
228
|
+
"Upaketu": _build_upagraha_entry("Upaketu", upaketu_lon, asc_lon),
|
|
229
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Varshaphal: Vedic Solar Return chart for any given year of life.
|
|
2
|
+
|
|
3
|
+
The Varshaphal chart is cast for the exact moment the Sun returns to its
|
|
4
|
+
natal sidereal longitude each year (solar return). The chart is erected
|
|
5
|
+
for the querent's current place of residence rather than their birth place.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import calendar
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
import pytz
|
|
12
|
+
import swisseph as swe
|
|
13
|
+
|
|
14
|
+
from kundali_lib.vedic.chart import _jd_ut, build_chart
|
|
15
|
+
|
|
16
|
+
_SIDEREAL_FLAGS = swe.FLG_SWIEPH | swe.FLG_SPEED | swe.FLG_SIDEREAL
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ── Internal helpers ─────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _sun_lon(jd: float) -> float:
|
|
23
|
+
"""Sidereal (Lahiri) Sun longitude for a given Julian Day."""
|
|
24
|
+
swe.set_sid_mode(swe.SIDM_LAHIRI)
|
|
25
|
+
xx, _ = swe.calc_ut(jd, swe.SUN, _SIDEREAL_FLAGS)
|
|
26
|
+
return xx[0]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _angular_diff(a: float, b: float) -> float:
|
|
30
|
+
"""Signed difference (a − b) normalised to [−180, 180]."""
|
|
31
|
+
diff = (a - b) % 360.0
|
|
32
|
+
if diff > 180.0:
|
|
33
|
+
diff -= 360.0
|
|
34
|
+
return diff
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _find_solar_return_jd(
|
|
38
|
+
natal_sun_lon: float,
|
|
39
|
+
target_year: int,
|
|
40
|
+
birth_month: int,
|
|
41
|
+
birth_day: int,
|
|
42
|
+
) -> float:
|
|
43
|
+
"""Return the Julian Day when the sidereal Sun equals natal_sun_lon.
|
|
44
|
+
|
|
45
|
+
Strategy:
|
|
46
|
+
1. Start scanning ~2 months before the expected solar-return date in
|
|
47
|
+
``target_year`` (to be safely before the crossing).
|
|
48
|
+
2. Walk day by day until a sign change in (sun_lon − natal_lon) is found.
|
|
49
|
+
3. Refine with 60 iterations of binary search (sub-second accuracy).
|
|
50
|
+
"""
|
|
51
|
+
start_month = birth_month - 2
|
|
52
|
+
start_year = target_year
|
|
53
|
+
if start_month < 1:
|
|
54
|
+
start_month += 12
|
|
55
|
+
start_year -= 1
|
|
56
|
+
|
|
57
|
+
# Clamp birth_day to a valid day in start_month
|
|
58
|
+
max_day = calendar.monthrange(start_year, start_month)[1]
|
|
59
|
+
start_day = min(birth_day, max_day)
|
|
60
|
+
start_jd = swe.julday(start_year, start_month, start_day, 0.0)
|
|
61
|
+
|
|
62
|
+
# ── Coarse day-level scan ────────────────────────────────────────────────
|
|
63
|
+
prev_diff = _angular_diff(_sun_lon(start_jd), natal_sun_lon)
|
|
64
|
+
lo_jd: float | None = None
|
|
65
|
+
hi_jd: float | None = None
|
|
66
|
+
|
|
67
|
+
for day_offset in range(1, 400):
|
|
68
|
+
jd = start_jd + day_offset
|
|
69
|
+
curr_diff = _angular_diff(_sun_lon(jd), natal_sun_lon)
|
|
70
|
+
|
|
71
|
+
if abs(curr_diff) < 0.001:
|
|
72
|
+
return jd # Landed almost exactly on the crossing
|
|
73
|
+
|
|
74
|
+
# Sun moves forward — we want the zero-crossing from negative → positive
|
|
75
|
+
if prev_diff < 0.0 and curr_diff >= 0.0:
|
|
76
|
+
lo_jd = jd - 1.0
|
|
77
|
+
hi_jd = jd
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
prev_diff = curr_diff
|
|
81
|
+
|
|
82
|
+
if lo_jd is None or hi_jd is None:
|
|
83
|
+
# Fallback (should not occur with a 400-day window)
|
|
84
|
+
return start_jd
|
|
85
|
+
|
|
86
|
+
# ── Fine binary search (~60 iterations → microsecond precision) ──────────
|
|
87
|
+
for _ in range(60):
|
|
88
|
+
mid = (lo_jd + hi_jd) / 2.0
|
|
89
|
+
diff = _angular_diff(_sun_lon(mid), natal_sun_lon)
|
|
90
|
+
if abs(diff) < 0.0001:
|
|
91
|
+
return mid
|
|
92
|
+
if diff < 0.0:
|
|
93
|
+
lo_jd = mid
|
|
94
|
+
else:
|
|
95
|
+
hi_jd = mid
|
|
96
|
+
|
|
97
|
+
return (lo_jd + hi_jd) / 2.0
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _jd_to_utc_datetime(jd: float) -> datetime:
|
|
101
|
+
"""Convert a Julian Day number to a UTC-aware datetime."""
|
|
102
|
+
year, month, day, hour_frac = swe.revjul(jd, 1) # 1 = Gregorian calendar
|
|
103
|
+
hour = int(hour_frac)
|
|
104
|
+
minute_frac = (hour_frac - hour) * 60.0
|
|
105
|
+
minute = int(minute_frac)
|
|
106
|
+
second_frac = (minute_frac - minute) * 60.0
|
|
107
|
+
second = min(int(round(second_frac)), 59) # guard against rounding to 60
|
|
108
|
+
return datetime(
|
|
109
|
+
int(year), int(month), int(day), hour, minute, second, tzinfo=pytz.UTC
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ── Public API ───────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_varshaphal(
|
|
117
|
+
birth_dt: datetime,
|
|
118
|
+
birth_lat: float,
|
|
119
|
+
birth_lon: float,
|
|
120
|
+
birth_timezone: str,
|
|
121
|
+
year_of_life: int,
|
|
122
|
+
query_lat: float,
|
|
123
|
+
query_lon: float,
|
|
124
|
+
query_timezone: str,
|
|
125
|
+
) -> dict:
|
|
126
|
+
"""Calculate a Varshaphal (Solar Return) chart for a given year of life.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
birth_dt: Naive local birth datetime.
|
|
130
|
+
birth_lat: Latitude of birth place.
|
|
131
|
+
birth_lon: Longitude of birth place.
|
|
132
|
+
birth_timezone: IANA timezone string for the birth place (e.g. "Asia/Kolkata").
|
|
133
|
+
year_of_life: Which solar return to compute (e.g. 26 for the 26th year).
|
|
134
|
+
Target calendar year = birth_year + year_of_life.
|
|
135
|
+
query_lat: Latitude of current residence.
|
|
136
|
+
query_lon: Longitude of current residence.
|
|
137
|
+
query_timezone: IANA timezone string for current residence.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
dict with solar-return timing and the full Varshaphal chart.
|
|
141
|
+
"""
|
|
142
|
+
swe.set_ephe_path(None)
|
|
143
|
+
swe.set_sid_mode(swe.SIDM_LAHIRI)
|
|
144
|
+
|
|
145
|
+
# ── Step 1: natal Sun sidereal longitude ─────────────────────────────────
|
|
146
|
+
birth_jd = _jd_ut(birth_dt, birth_timezone)
|
|
147
|
+
xx, _ = swe.calc_ut(birth_jd, swe.SUN, _SIDEREAL_FLAGS)
|
|
148
|
+
natal_sun_lon: float = xx[0]
|
|
149
|
+
|
|
150
|
+
# ── Step 2: find JD of solar return in target year ───────────────────────
|
|
151
|
+
target_year = birth_dt.year + year_of_life
|
|
152
|
+
sr_jd = _find_solar_return_jd(
|
|
153
|
+
natal_sun_lon, target_year, birth_dt.month, birth_dt.day
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# ── Step 3: convert to datetimes ─────────────────────────────────────────
|
|
157
|
+
sr_utc = _jd_to_utc_datetime(sr_jd)
|
|
158
|
+
query_tz = pytz.timezone(query_timezone)
|
|
159
|
+
sr_local_aware = sr_utc.astimezone(query_tz)
|
|
160
|
+
sr_local_naive = sr_local_aware.replace(tzinfo=None)
|
|
161
|
+
|
|
162
|
+
# ── Step 4: build chart at query location ────────────────────────────────
|
|
163
|
+
chart = build_chart(
|
|
164
|
+
name="Varshaphal",
|
|
165
|
+
birth_dt=sr_local_naive,
|
|
166
|
+
lat=query_lat,
|
|
167
|
+
lon=query_lon,
|
|
168
|
+
timezone=query_timezone,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
"year_of_life": year_of_life,
|
|
173
|
+
"solar_return_datetime_utc": sr_utc.isoformat(),
|
|
174
|
+
"solar_return_datetime_local": sr_local_aware.isoformat(),
|
|
175
|
+
"natal_sun_longitude": round(natal_sun_lon, 4),
|
|
176
|
+
"varshaphal_chart": {
|
|
177
|
+
"planetary_positions": chart["planetary_positions"],
|
|
178
|
+
"houses": chart["houses"],
|
|
179
|
+
"ascendant": chart["ascendant"],
|
|
180
|
+
"moon_sign": chart["moon_sign"],
|
|
181
|
+
"sun_sign": chart["sun_sign"],
|
|
182
|
+
"ayanamsa": chart["ayanamsa"],
|
|
183
|
+
"ayanamsa_name": chart["ayanamsa_name"],
|
|
184
|
+
},
|
|
185
|
+
}
|