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,482 @@
|
|
|
1
|
+
"""Tajaka Yogas: special yogas in annual charts (Varshaphal) based on Persian/Tajika system."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from kundali_lib.vedic.constants import RASHI_LORDS, RASHIS
|
|
6
|
+
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# Reference data
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
RASHI_LORDS_MAP: dict[str, str] = {r: RASHI_LORDS[i] for i, r in enumerate(RASHIS)}
|
|
12
|
+
|
|
13
|
+
# Approximate daily mean motion (degrees/day) used to determine faster vs slower planet.
|
|
14
|
+
MEAN_DAILY_MOTION: dict[str, float] = {
|
|
15
|
+
"Moon": 13.2,
|
|
16
|
+
"Mercury": 1.38,
|
|
17
|
+
"Venus": 1.2,
|
|
18
|
+
"Sun": 0.985,
|
|
19
|
+
"Mars": 0.524,
|
|
20
|
+
"Jupiter": 0.083,
|
|
21
|
+
"Saturn": 0.034,
|
|
22
|
+
"Rahu": -0.053,
|
|
23
|
+
"Ketu": -0.053,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
DUSTHANA_HOUSES = {6, 8, 12}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Internal helpers
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_planet(positions: list[dict], name: str) -> dict | None:
|
|
35
|
+
for p in positions:
|
|
36
|
+
if p["name"] == name:
|
|
37
|
+
return p
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _angular_diff(a: float, b: float) -> float:
|
|
42
|
+
"""Signed difference (a − b) normalised to [−180, 180]."""
|
|
43
|
+
diff = (a - b) % 360.0
|
|
44
|
+
if diff > 180.0:
|
|
45
|
+
diff -= 360.0
|
|
46
|
+
return diff
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _planet_speed(planet: dict) -> float:
|
|
50
|
+
"""Return absolute daily motion; prefer actual speed field if present."""
|
|
51
|
+
return abs(
|
|
52
|
+
planet.get("speed_longitude", MEAN_DAILY_MOTION.get(planet["name"], 0.5))
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _house_of_sign(sign: str, asc_sign: str) -> int:
|
|
57
|
+
"""Return the house number that a given sign falls in, relative to the ascendant sign."""
|
|
58
|
+
asc_idx = RASHIS.index(asc_sign)
|
|
59
|
+
sign_idx = RASHIS.index(sign)
|
|
60
|
+
return (sign_idx - asc_idx) % 12 + 1
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _sign_of_house(house: int, asc_sign: str) -> str:
|
|
64
|
+
asc_idx = RASHIS.index(asc_sign)
|
|
65
|
+
return RASHIS[(asc_idx + house - 1) % 12]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _planet_strength(planet: dict, varshaphal_chart: dict) -> str:
|
|
69
|
+
"""Rough strength assessment for a planet in the Varshaphal chart."""
|
|
70
|
+
name = planet["name"]
|
|
71
|
+
rashi = planet.get("rashi", "")
|
|
72
|
+
house = planet.get("house", 0)
|
|
73
|
+
|
|
74
|
+
from kundali_lib.vedic.yogas import ( # local import to avoid cycles
|
|
75
|
+
DEBILITATION,
|
|
76
|
+
EXALTATION,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if rashi == EXALTATION.get(name):
|
|
80
|
+
return "Exalted (Strong)"
|
|
81
|
+
if rashi == DEBILITATION.get(name):
|
|
82
|
+
return "Debilitated (Weak)"
|
|
83
|
+
if RASHI_LORDS_MAP.get(rashi) == name:
|
|
84
|
+
return "Own sign (Strong)"
|
|
85
|
+
if house in {1, 4, 7, 10}:
|
|
86
|
+
return "Angular (Moderate-Strong)"
|
|
87
|
+
if house in {6, 8, 12}:
|
|
88
|
+
return "Dusthana (Weak)"
|
|
89
|
+
return "Moderate"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# Yoga checkers
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _check_ithasala(varshaphal_positions: list[dict], asc_sign: str) -> list[dict]:
|
|
98
|
+
"""Ithasala (Approach) yoga: faster planet approaching slower one within 13°."""
|
|
99
|
+
results = []
|
|
100
|
+
planets = [p for p in varshaphal_positions if p["name"] not in ("Rahu", "Ketu")]
|
|
101
|
+
|
|
102
|
+
checked: set[frozenset] = set()
|
|
103
|
+
for i, p1 in enumerate(planets):
|
|
104
|
+
for p2 in planets[i + 1 :]:
|
|
105
|
+
pair = frozenset([p1["name"], p2["name"]])
|
|
106
|
+
if pair in checked:
|
|
107
|
+
continue
|
|
108
|
+
checked.add(pair)
|
|
109
|
+
|
|
110
|
+
speed1 = _planet_speed(p1)
|
|
111
|
+
speed2 = _planet_speed(p2)
|
|
112
|
+
|
|
113
|
+
faster, slower = (p1, p2) if speed1 >= speed2 else (p2, p1)
|
|
114
|
+
lon_f = faster["longitude"]
|
|
115
|
+
lon_s = slower["longitude"]
|
|
116
|
+
diff = _angular_diff(lon_s, lon_f) # positive → slower is ahead of faster
|
|
117
|
+
|
|
118
|
+
if 0 < diff <= 13:
|
|
119
|
+
yoga_type = "Poorna Ithasala" if diff <= 1 else "Ithasala"
|
|
120
|
+
results.append(
|
|
121
|
+
{
|
|
122
|
+
"name": yoga_type,
|
|
123
|
+
"present": True,
|
|
124
|
+
"planets": [faster["name"], slower["name"]],
|
|
125
|
+
"orb": round(diff, 2),
|
|
126
|
+
"description": (
|
|
127
|
+
f"{faster['name']} is approaching {slower['name']} "
|
|
128
|
+
f"(separation {round(diff, 2)}°)."
|
|
129
|
+
),
|
|
130
|
+
"effect": "The matter will come to pass — good results",
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
return results
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _check_ishrafa(varshaphal_positions: list[dict]) -> list[dict]:
|
|
137
|
+
"""Ishrafa (Separation) yoga: faster planet has just passed the slower one within 13°."""
|
|
138
|
+
results = []
|
|
139
|
+
planets = [p for p in varshaphal_positions if p["name"] not in ("Rahu", "Ketu")]
|
|
140
|
+
|
|
141
|
+
checked: set[frozenset] = set()
|
|
142
|
+
for i, p1 in enumerate(planets):
|
|
143
|
+
for p2 in planets[i + 1 :]:
|
|
144
|
+
pair = frozenset([p1["name"], p2["name"]])
|
|
145
|
+
if pair in checked:
|
|
146
|
+
continue
|
|
147
|
+
checked.add(pair)
|
|
148
|
+
|
|
149
|
+
speed1 = _planet_speed(p1)
|
|
150
|
+
speed2 = _planet_speed(p2)
|
|
151
|
+
faster, slower = (p1, p2) if speed1 >= speed2 else (p2, p1)
|
|
152
|
+
lon_f = faster["longitude"]
|
|
153
|
+
lon_s = slower["longitude"]
|
|
154
|
+
diff = _angular_diff(
|
|
155
|
+
lon_f, lon_s
|
|
156
|
+
) # positive → faster has moved past slower
|
|
157
|
+
|
|
158
|
+
if 0 < diff <= 13:
|
|
159
|
+
results.append(
|
|
160
|
+
{
|
|
161
|
+
"name": "Ishrafa",
|
|
162
|
+
"present": True,
|
|
163
|
+
"planets": [faster["name"], slower["name"]],
|
|
164
|
+
"orb": round(diff, 2),
|
|
165
|
+
"description": (
|
|
166
|
+
f"{faster['name']} has already passed {slower['name']} "
|
|
167
|
+
f"(separation {round(diff, 2)}°)."
|
|
168
|
+
),
|
|
169
|
+
"effect": "Matter will not succeed, past opportunity missed",
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
return results
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _check_mutthasila(varshaphal_positions: list[dict], asc_sign: str) -> list[dict]:
|
|
176
|
+
"""Mutthasila yoga: a third planet creates connection between Lagna lord and another."""
|
|
177
|
+
results = []
|
|
178
|
+
lagna_lord_name = RASHI_LORDS_MAP.get(asc_sign)
|
|
179
|
+
if not lagna_lord_name:
|
|
180
|
+
return results
|
|
181
|
+
|
|
182
|
+
lagna_lord = _get_planet(varshaphal_positions, lagna_lord_name)
|
|
183
|
+
if not lagna_lord:
|
|
184
|
+
return results
|
|
185
|
+
|
|
186
|
+
for connector in varshaphal_positions:
|
|
187
|
+
if connector["name"] in (lagna_lord_name, "Rahu", "Ketu"):
|
|
188
|
+
continue
|
|
189
|
+
for target in varshaphal_positions:
|
|
190
|
+
if target["name"] in (lagna_lord_name, connector["name"], "Rahu", "Ketu"):
|
|
191
|
+
continue
|
|
192
|
+
diff_lc = abs(
|
|
193
|
+
_angular_diff(connector["longitude"], lagna_lord["longitude"])
|
|
194
|
+
)
|
|
195
|
+
diff_ct = abs(_angular_diff(target["longitude"], connector["longitude"]))
|
|
196
|
+
if diff_lc <= 13 and diff_ct <= 13:
|
|
197
|
+
results.append(
|
|
198
|
+
{
|
|
199
|
+
"name": "Mutthasila",
|
|
200
|
+
"present": True,
|
|
201
|
+
"planets": [lagna_lord_name, connector["name"], target["name"]],
|
|
202
|
+
"orb": round(max(diff_lc, diff_ct), 2),
|
|
203
|
+
"description": (
|
|
204
|
+
f"{connector['name']} connects {lagna_lord_name} "
|
|
205
|
+
f"and {target['name']} indirectly."
|
|
206
|
+
),
|
|
207
|
+
"effect": "Results come through an intermediary",
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
return results
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _check_nakta(varshaphal_positions: list[dict]) -> list[dict]:
|
|
214
|
+
"""Nakta yoga: Moon collects light between two planets."""
|
|
215
|
+
results = []
|
|
216
|
+
moon = _get_planet(varshaphal_positions, "Moon")
|
|
217
|
+
if not moon:
|
|
218
|
+
return results
|
|
219
|
+
|
|
220
|
+
moon_lon = moon["longitude"]
|
|
221
|
+
others = [
|
|
222
|
+
p for p in varshaphal_positions if p["name"] not in ("Moon", "Rahu", "Ketu")
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
for i, p1 in enumerate(others):
|
|
226
|
+
for p2 in others[i + 1 :]:
|
|
227
|
+
lon1, lon2 = p1["longitude"], p2["longitude"]
|
|
228
|
+
lo, hi = (lon1, lon2) if lon1 <= lon2 else (lon2, lon1)
|
|
229
|
+
if lo <= moon_lon <= hi or (hi - lo > 180 and not (lo <= moon_lon <= hi)):
|
|
230
|
+
diff = abs(hi - lo)
|
|
231
|
+
if diff <= 30: # Moon collects within a reasonable arc
|
|
232
|
+
results.append(
|
|
233
|
+
{
|
|
234
|
+
"name": "Nakta",
|
|
235
|
+
"present": True,
|
|
236
|
+
"planets": [p1["name"], "Moon", p2["name"]],
|
|
237
|
+
"description": (
|
|
238
|
+
f"Moon is between {p1['name']} and {p2['name']}, "
|
|
239
|
+
f"collecting light."
|
|
240
|
+
),
|
|
241
|
+
"effect": "Results come slowly through intermediary help",
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
return results
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _check_yamaya(varshaphal_positions: list[dict], birth_chart: dict) -> list[dict]:
|
|
248
|
+
"""Yamaya yoga: planet within 1° of its natal position in Varshaphal."""
|
|
249
|
+
results = []
|
|
250
|
+
natal_positions = birth_chart.get("planetary_positions", [])
|
|
251
|
+
for vp in varshaphal_positions:
|
|
252
|
+
for np_ in natal_positions:
|
|
253
|
+
if vp["name"] == np_["name"]:
|
|
254
|
+
diff = abs(_angular_diff(vp["longitude"], np_["longitude"]))
|
|
255
|
+
if diff <= 1.0:
|
|
256
|
+
results.append(
|
|
257
|
+
{
|
|
258
|
+
"name": "Yamaya",
|
|
259
|
+
"present": True,
|
|
260
|
+
"planets": [vp["name"]],
|
|
261
|
+
"orb": round(diff, 2),
|
|
262
|
+
"description": (
|
|
263
|
+
f"{vp['name']} is within 1° of its natal position "
|
|
264
|
+
f"in the Varshaphal chart."
|
|
265
|
+
),
|
|
266
|
+
"effect": f"Strong year for matters ruled by {vp['name']}",
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
return results
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _check_duphali_kuttha(
|
|
273
|
+
varshaphal_positions: list[dict], asc_sign: str
|
|
274
|
+
) -> list[dict]:
|
|
275
|
+
"""Duphali Kuttha: Varsha Lagna lord in dusthana and weak."""
|
|
276
|
+
results = []
|
|
277
|
+
lagna_lord_name = RASHI_LORDS_MAP.get(asc_sign)
|
|
278
|
+
if not lagna_lord_name:
|
|
279
|
+
return results
|
|
280
|
+
ll = _get_planet(varshaphal_positions, lagna_lord_name)
|
|
281
|
+
if not ll:
|
|
282
|
+
return results
|
|
283
|
+
house = ll.get("house", 0)
|
|
284
|
+
strength = _planet_strength(ll, {})
|
|
285
|
+
if house in DUSTHANA_HOUSES and "Weak" in strength:
|
|
286
|
+
results.append(
|
|
287
|
+
{
|
|
288
|
+
"name": "Duphali Kuttha",
|
|
289
|
+
"present": True,
|
|
290
|
+
"planets": [lagna_lord_name],
|
|
291
|
+
"description": (
|
|
292
|
+
f"Varsha Lagna lord {lagna_lord_name} is in house {house} "
|
|
293
|
+
f"(dusthana) and {strength}."
|
|
294
|
+
),
|
|
295
|
+
"effect": "Difficult year",
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
return results
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _check_tambira(muntha_house: int) -> list[dict]:
|
|
302
|
+
"""Tambira yoga: Muntha in dusthana (6, 8, 12)."""
|
|
303
|
+
if muntha_house in DUSTHANA_HOUSES:
|
|
304
|
+
return [
|
|
305
|
+
{
|
|
306
|
+
"name": "Tambira",
|
|
307
|
+
"present": True,
|
|
308
|
+
"planets": [],
|
|
309
|
+
"description": f"Muntha is in house {muntha_house} (dusthana).",
|
|
310
|
+
"effect": "Health challenges and obstacles during the year",
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
return []
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# ---------------------------------------------------------------------------
|
|
317
|
+
# Year-quality assessment
|
|
318
|
+
# ---------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _assess_year_quality(
|
|
322
|
+
yogas: list[dict], muntha_house: int, lagna_lord_strong: bool
|
|
323
|
+
) -> str:
|
|
324
|
+
names = [y["name"] for y in yogas]
|
|
325
|
+
score = 0
|
|
326
|
+
if "Poorna Ithasala" in names:
|
|
327
|
+
score += 3
|
|
328
|
+
if "Ithasala" in names:
|
|
329
|
+
score += 2
|
|
330
|
+
if "Yamaya" in names:
|
|
331
|
+
score += 2
|
|
332
|
+
if "Mutthasila" in names:
|
|
333
|
+
score += 1
|
|
334
|
+
if "Nakta" in names:
|
|
335
|
+
score += 1
|
|
336
|
+
if "Ishrafa" in names:
|
|
337
|
+
score -= 2
|
|
338
|
+
if "Duphali Kuttha" in names:
|
|
339
|
+
score -= 3
|
|
340
|
+
if "Tambira" in names:
|
|
341
|
+
score -= 2
|
|
342
|
+
if muntha_house in {1, 4, 7, 10}:
|
|
343
|
+
score += 1
|
|
344
|
+
if muntha_house in DUSTHANA_HOUSES:
|
|
345
|
+
score -= 1
|
|
346
|
+
if lagna_lord_strong:
|
|
347
|
+
score += 1
|
|
348
|
+
|
|
349
|
+
if score >= 4:
|
|
350
|
+
return "Excellent"
|
|
351
|
+
if score >= 2:
|
|
352
|
+
return "Good"
|
|
353
|
+
if score >= -1:
|
|
354
|
+
return "Average"
|
|
355
|
+
return "Difficult"
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# ---------------------------------------------------------------------------
|
|
359
|
+
# Public API
|
|
360
|
+
# ---------------------------------------------------------------------------
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def get_tajaka_analysis(
|
|
364
|
+
birth_chart: dict,
|
|
365
|
+
varshaphal_chart: dict,
|
|
366
|
+
year_of_life: int,
|
|
367
|
+
) -> dict:
|
|
368
|
+
"""Analyse Tajaka yogas for a Varshaphal (Solar Return) chart.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
birth_chart: Output of ``build_chart()`` for the native's birth.
|
|
372
|
+
varshaphal_chart: Output of ``get_varshaphal()``; the ``varshaphal_chart``
|
|
373
|
+
sub-dict (containing ``planetary_positions``, ``houses``,
|
|
374
|
+
``ascendant``, …).
|
|
375
|
+
year_of_life: Solar return number (e.g. 26 for the 26th year).
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
dict with Muntha details, Munthesh, all detected Tajaka yogas, year
|
|
379
|
+
quality assessment, and a brief summary.
|
|
380
|
+
"""
|
|
381
|
+
# ── Accept either the top-level get_varshaphal() output or the inner chart ─
|
|
382
|
+
if "varshaphal_chart" in varshaphal_chart:
|
|
383
|
+
vp = varshaphal_chart["varshaphal_chart"]
|
|
384
|
+
else:
|
|
385
|
+
vp = varshaphal_chart
|
|
386
|
+
|
|
387
|
+
vp_positions: list[dict] = vp.get("planetary_positions", [])
|
|
388
|
+
asc_info: dict = vp.get("ascendant", {})
|
|
389
|
+
asc_sign: str = asc_info.get("rashi", RASHIS[0])
|
|
390
|
+
|
|
391
|
+
# ── Muntha ────────────────────────────────────────────────────────────────
|
|
392
|
+
birth_asc_sign: str = birth_chart.get("ascendant", {}).get("rashi", RASHIS[0])
|
|
393
|
+
birth_asc_idx = RASHIS.index(birth_asc_sign)
|
|
394
|
+
muntha_idx = (birth_asc_idx + year_of_life) % 12
|
|
395
|
+
muntha_sign = RASHIS[muntha_idx]
|
|
396
|
+
muntha_house = _house_of_sign(muntha_sign, asc_sign)
|
|
397
|
+
muntha_lord_name = RASHI_LORDS_MAP[muntha_sign]
|
|
398
|
+
|
|
399
|
+
muntha_significance_map = {
|
|
400
|
+
1: "Self, health, vitality — very prominent year",
|
|
401
|
+
2: "Finances, family, speech",
|
|
402
|
+
3: "Courage, siblings, short travel",
|
|
403
|
+
4: "Home, mother, property",
|
|
404
|
+
5: "Children, creativity, intellect",
|
|
405
|
+
6: "Enemies, disease, service",
|
|
406
|
+
7: "Partnerships, marriage, business",
|
|
407
|
+
8: "Obstacles, transformation, longevity",
|
|
408
|
+
9: "Luck, dharma, father, long travel",
|
|
409
|
+
10: "Career, reputation, authority — prominent year",
|
|
410
|
+
11: "Gains, social network, fulfilment",
|
|
411
|
+
12: "Losses, expenditure, spirituality",
|
|
412
|
+
}
|
|
413
|
+
muntha_significance = muntha_significance_map.get(muntha_house, "")
|
|
414
|
+
|
|
415
|
+
muntha_lord_planet = _get_planet(vp_positions, muntha_lord_name)
|
|
416
|
+
muntha_lord_house = muntha_lord_planet.get("house", 0) if muntha_lord_planet else 0
|
|
417
|
+
muntha_lord_strength = (
|
|
418
|
+
_planet_strength(muntha_lord_planet, vp) if muntha_lord_planet else "Unknown"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# ── Lagna lord for Duphali Kuttha check ──────────────────────────────────
|
|
422
|
+
lagna_lord_name = RASHI_LORDS_MAP.get(asc_sign, "")
|
|
423
|
+
lagna_lord_planet = _get_planet(vp_positions, lagna_lord_name)
|
|
424
|
+
lagna_lord_strong = (
|
|
425
|
+
"Strong" in _planet_strength(lagna_lord_planet, vp)
|
|
426
|
+
if lagna_lord_planet
|
|
427
|
+
else False
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# ── Yoga detection ────────────────────────────────────────────────────────
|
|
431
|
+
yogas: list[dict] = []
|
|
432
|
+
yogas.extend(_check_ithasala(vp_positions, asc_sign))
|
|
433
|
+
yogas.extend(_check_ishrafa(vp_positions))
|
|
434
|
+
yogas.extend(_check_mutthasila(vp_positions, asc_sign))
|
|
435
|
+
yogas.extend(_check_nakta(vp_positions))
|
|
436
|
+
yogas.extend(_check_yamaya(vp_positions, birth_chart))
|
|
437
|
+
yogas.extend(_check_duphali_kuttha(vp_positions, asc_sign))
|
|
438
|
+
yogas.extend(_check_tambira(muntha_house))
|
|
439
|
+
|
|
440
|
+
# Normalise yoga list to match the requested output schema
|
|
441
|
+
normalised_yogas = []
|
|
442
|
+
for y in yogas:
|
|
443
|
+
normalised_yogas.append(
|
|
444
|
+
{
|
|
445
|
+
"name": y["name"],
|
|
446
|
+
"present": y["present"],
|
|
447
|
+
"description": y["description"],
|
|
448
|
+
"effect": y["effect"],
|
|
449
|
+
}
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# ── Year quality ──────────────────────────────────────────────────────────
|
|
453
|
+
year_quality = _assess_year_quality(yogas, muntha_house, lagna_lord_strong)
|
|
454
|
+
|
|
455
|
+
# ── Summary ───────────────────────────────────────────────────────────────
|
|
456
|
+
yoga_names = [y["name"] for y in yogas]
|
|
457
|
+
summary_parts = [
|
|
458
|
+
f"Year {year_of_life} Varshaphal analysis.",
|
|
459
|
+
f"Muntha is in {muntha_sign} (house {muntha_house}): {muntha_significance}.",
|
|
460
|
+
f"Munthesh ({muntha_lord_name}) is in house {muntha_lord_house} — {muntha_lord_strength}.",
|
|
461
|
+
]
|
|
462
|
+
if yoga_names:
|
|
463
|
+
summary_parts.append(f"Active Tajaka yogas: {', '.join(yoga_names)}.")
|
|
464
|
+
summary_parts.append(f"Overall year quality: {year_quality}.")
|
|
465
|
+
summary = " ".join(summary_parts)
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
"muntha": {
|
|
469
|
+
"sign": muntha_sign,
|
|
470
|
+
"house_in_varshaphal": muntha_house,
|
|
471
|
+
"lord": muntha_lord_name,
|
|
472
|
+
"significance": muntha_significance,
|
|
473
|
+
},
|
|
474
|
+
"munthesh": {
|
|
475
|
+
"planet": muntha_lord_name,
|
|
476
|
+
"varshaphal_house": muntha_lord_house,
|
|
477
|
+
"strength": muntha_lord_strength,
|
|
478
|
+
},
|
|
479
|
+
"yogas": normalised_yogas,
|
|
480
|
+
"year_quality": year_quality,
|
|
481
|
+
"summary": summary,
|
|
482
|
+
}
|