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,277 @@
|
|
|
1
|
+
"""Gochara: Vedic transit analysis — how transiting planets affect natal houses."""
|
|
2
|
+
|
|
3
|
+
from kundali_lib.vedic.constants import RASHIS
|
|
4
|
+
|
|
5
|
+
# ---------------------------------------------------------------------------
|
|
6
|
+
# Gochara effect tables (house counted FROM natal Moon sign)
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
GOCHARA_EFFECTS = {
|
|
10
|
+
"Sun": {"good": [3, 6, 10, 11], "bad": [1, 2, 4, 5, 7, 8, 9, 12]},
|
|
11
|
+
"Moon": {"good": [1, 3, 6, 7, 10, 11], "bad": [2, 4, 5, 8, 9, 12]},
|
|
12
|
+
"Mars": {"good": [3, 6, 11], "bad": [1, 2, 4, 5, 7, 8, 9, 10, 12]},
|
|
13
|
+
"Mercury": {"good": [2, 4, 6, 8, 10, 11], "bad": [1, 3, 5, 7, 9, 12]},
|
|
14
|
+
"Jupiter": {"good": [2, 5, 7, 9, 11], "bad": [1, 3, 4, 6, 8, 10, 12]},
|
|
15
|
+
"Venus": {"good": [1, 2, 3, 4, 5, 8, 9, 11, 12], "bad": [6, 7, 10]},
|
|
16
|
+
"Saturn": {"good": [3, 6, 11], "bad": [1, 2, 4, 5, 7, 8, 9, 10, 12]},
|
|
17
|
+
"Rahu": {"good": [3, 6, 11], "bad": [1, 2, 4, 5, 7, 8, 9, 10, 12]},
|
|
18
|
+
"Ketu": {"good": [3, 6, 11], "bad": [1, 2, 4, 5, 7, 8, 9, 10, 12]},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Vedha obstruction points: {planet: {good_house: vedha_house}}
|
|
22
|
+
# If a planet is in good_house AND another planet is in vedha_house → good effect cancelled.
|
|
23
|
+
VEDHA_POINTS = {
|
|
24
|
+
"Sun": {3: 9, 6: 12, 10: 4, 11: 5},
|
|
25
|
+
"Moon": {1: 5, 3: 9, 6: 12, 7: 2, 10: 4, 11: 8},
|
|
26
|
+
"Mars": {3: 12, 6: 9, 11: 5},
|
|
27
|
+
"Mercury": {2: 5, 4: 3, 6: 9, 8: 1, 10: 8, 11: 12},
|
|
28
|
+
"Jupiter": {2: 12, 5: 4, 7: 3, 9: 10, 11: 8},
|
|
29
|
+
"Venus": {1: 8, 2: 7, 3: 1, 4: 10, 5: 9, 8: 5, 9: 1, 11: 8, 12: 6},
|
|
30
|
+
"Saturn": {3: 12, 6: 9, 11: 5},
|
|
31
|
+
"Rahu": {3: 12, 6: 9, 11: 5},
|
|
32
|
+
"Ketu": {3: 12, 6: 9, 11: 5},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Short descriptions for each effect category
|
|
36
|
+
_EFFECT_DESCRIPTIONS = {
|
|
37
|
+
"Sun": {
|
|
38
|
+
"good": "Sun in {h}th from Moon: favours authority, health, career visibility.",
|
|
39
|
+
"bad": "Sun in {h}th from Moon: ego conflicts, health strain, obstacles from officials.",
|
|
40
|
+
},
|
|
41
|
+
"Moon": {
|
|
42
|
+
"good": "Moon in {h}th from Moon: emotional harmony, public success, good relationships.",
|
|
43
|
+
"bad": "Moon in {h}th from Moon: emotional turbulence, domestic issues, mental stress.",
|
|
44
|
+
},
|
|
45
|
+
"Mars": {
|
|
46
|
+
"good": "Mars in {h}th from Moon: courage, competition success, energy channelled well.",
|
|
47
|
+
"bad": "Mars in {h}th from Moon: accidents, arguments, impulsiveness, sibling friction.",
|
|
48
|
+
},
|
|
49
|
+
"Mercury": {
|
|
50
|
+
"good": "Mercury in {h}th from Moon: business gains, sharp intellect, communication flows.",
|
|
51
|
+
"bad": "Mercury in {h}th from Moon: miscommunication, travel problems, nervous tension.",
|
|
52
|
+
},
|
|
53
|
+
"Jupiter": {
|
|
54
|
+
"good": "Jupiter in {h}th from Moon: wisdom, wealth, expansion, spiritual growth.",
|
|
55
|
+
"bad": "Jupiter in {h}th from Moon: over-expansion, false optimism, financial missteps.",
|
|
56
|
+
},
|
|
57
|
+
"Venus": {
|
|
58
|
+
"good": "Venus in {h}th from Moon: romance, artistic gains, comforts, social pleasure.",
|
|
59
|
+
"bad": "Venus in {h}th from Moon: relationship strain, extravagance, health of women.",
|
|
60
|
+
},
|
|
61
|
+
"Saturn": {
|
|
62
|
+
"good": "Saturn in {h}th from Moon: disciplined gains through hard work and perseverance.",
|
|
63
|
+
"bad": "Saturn in {h}th from Moon: delays, burdens, health issues, melancholy.",
|
|
64
|
+
},
|
|
65
|
+
"Rahu": {
|
|
66
|
+
"good": "Rahu in {h}th from Moon: unconventional gains, foreign connections, ambition.",
|
|
67
|
+
"bad": "Rahu in {h}th from Moon: confusion, deception, hidden enemies, anxiety.",
|
|
68
|
+
},
|
|
69
|
+
"Ketu": {
|
|
70
|
+
"good": "Ketu in {h}th from Moon: spiritual insight, liberation from past karma.",
|
|
71
|
+
"bad": "Ketu in {h}th from Moon: losses, separations, health issues, disconnection.",
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _house_from_moon(transit_rashi: str, moon_rashi: str) -> int:
|
|
77
|
+
"""Calculate transit house number (1-12) counted from natal Moon sign."""
|
|
78
|
+
moon_i = RASHIS.index(moon_rashi)
|
|
79
|
+
transit_i = RASHIS.index(transit_rashi)
|
|
80
|
+
return (transit_i - moon_i) % 12 + 1
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _sade_sati_info(saturn_house_from_moon: int) -> dict | None:
|
|
84
|
+
"""Return Sade Sati / Kantaka Shani details if applicable."""
|
|
85
|
+
if saturn_house_from_moon == 12:
|
|
86
|
+
return {
|
|
87
|
+
"type": "Sade Sati — Rising Phase",
|
|
88
|
+
"phase": "first",
|
|
89
|
+
"description": (
|
|
90
|
+
"Saturn is in the 12th sign from natal Moon, marking the start of the 7.5-year "
|
|
91
|
+
"Sade Sati. Losses, expenses, sleep issues, and preparation for major life changes. "
|
|
92
|
+
"Foreign travel or spiritual inclinations may increase."
|
|
93
|
+
),
|
|
94
|
+
}
|
|
95
|
+
elif saturn_house_from_moon == 1:
|
|
96
|
+
return {
|
|
97
|
+
"type": "Sade Sati — Peak Phase",
|
|
98
|
+
"phase": "second",
|
|
99
|
+
"description": (
|
|
100
|
+
"Saturn transits natal Moon sign — the most intense phase of Sade Sati. "
|
|
101
|
+
"Health, relationships, and career may all be tested simultaneously. "
|
|
102
|
+
"Strong discipline and introspection are essential. Avoid major irreversible decisions."
|
|
103
|
+
),
|
|
104
|
+
}
|
|
105
|
+
elif saturn_house_from_moon == 2:
|
|
106
|
+
return {
|
|
107
|
+
"type": "Sade Sati — Setting Phase",
|
|
108
|
+
"phase": "third",
|
|
109
|
+
"description": (
|
|
110
|
+
"Saturn moves past the Moon sign — final phase of Sade Sati. Financial pressures "
|
|
111
|
+
"and family tensions linger. Gradual relief begins but demands continued patience."
|
|
112
|
+
),
|
|
113
|
+
}
|
|
114
|
+
elif saturn_house_from_moon == 4:
|
|
115
|
+
return {
|
|
116
|
+
"type": "Kantaka Shani (4th house from Moon)",
|
|
117
|
+
"phase": "kantaka",
|
|
118
|
+
"description": (
|
|
119
|
+
"Saturn in 4th from natal Moon causes domestic unrest, property disputes, "
|
|
120
|
+
"mother's health concerns, and career instability. Known as Ardhashtama Shani."
|
|
121
|
+
),
|
|
122
|
+
}
|
|
123
|
+
elif saturn_house_from_moon == 8:
|
|
124
|
+
return {
|
|
125
|
+
"type": "Kantaka Shani (8th house from Moon)",
|
|
126
|
+
"phase": "kantaka",
|
|
127
|
+
"description": (
|
|
128
|
+
"Saturn in 8th from natal Moon — Ashtama Shani. A highly challenging transit "
|
|
129
|
+
"involving chronic health issues, sudden obstacles, transformation through crisis, "
|
|
130
|
+
"and karmic reckoning. Requires patience and surrender."
|
|
131
|
+
),
|
|
132
|
+
}
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def get_gochara(natal_chart: dict, transit_positions: list) -> dict:
|
|
137
|
+
"""Analyse Gochara (transit) effects of current planets on the natal chart.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
natal_chart: Output of ``build_chart()``. Must contain ``moon_sign``
|
|
141
|
+
and ``planetary_positions``.
|
|
142
|
+
transit_positions: List of current planet position dicts (same shape as
|
|
143
|
+
``planetary_positions`` entries in a built chart).
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
A dict with ``natal_moon_rashi``, ``transit_analysis`` (per planet),
|
|
147
|
+
``overall_period``, and optional ``special_conditions``.
|
|
148
|
+
"""
|
|
149
|
+
moon_rashi: str = natal_chart["moon_sign"]
|
|
150
|
+
|
|
151
|
+
# Build quick lookup: {planet_name: house_from_moon} for Vedha checks
|
|
152
|
+
transit_house_map: dict[str, int] = {}
|
|
153
|
+
transit_rashi_map: dict[str, str] = {}
|
|
154
|
+
for tp in transit_positions:
|
|
155
|
+
name = tp["name"]
|
|
156
|
+
rashi = tp["rashi"]
|
|
157
|
+
transit_rashi_map[name] = rashi
|
|
158
|
+
transit_house_map[name] = _house_from_moon(rashi, moon_rashi)
|
|
159
|
+
|
|
160
|
+
transit_analysis: dict[str, dict] = {}
|
|
161
|
+
benefic_count = 0
|
|
162
|
+
malefic_count = 0
|
|
163
|
+
special_conditions: list[dict] = []
|
|
164
|
+
|
|
165
|
+
for tp in transit_positions:
|
|
166
|
+
planet = tp["name"]
|
|
167
|
+
if planet not in GOCHARA_EFFECTS:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
house = transit_house_map[planet]
|
|
171
|
+
effects = GOCHARA_EFFECTS[planet]
|
|
172
|
+
|
|
173
|
+
if house in effects["good"]:
|
|
174
|
+
base_effect = "Beneficial"
|
|
175
|
+
elif house in effects["bad"]:
|
|
176
|
+
base_effect = "Harmful"
|
|
177
|
+
else:
|
|
178
|
+
base_effect = "Neutral"
|
|
179
|
+
|
|
180
|
+
# --- Vedha check ---
|
|
181
|
+
vedha_present = False
|
|
182
|
+
vedha_planet_name = None
|
|
183
|
+
if base_effect == "Beneficial" and planet in VEDHA_POINTS:
|
|
184
|
+
vedha_house = VEDHA_POINTS[planet].get(house)
|
|
185
|
+
if vedha_house is not None:
|
|
186
|
+
for other_planet, other_house in transit_house_map.items():
|
|
187
|
+
if other_planet != planet and other_house == vedha_house:
|
|
188
|
+
vedha_present = True
|
|
189
|
+
vedha_planet_name = other_planet
|
|
190
|
+
break
|
|
191
|
+
|
|
192
|
+
if vedha_present:
|
|
193
|
+
final_effect = "Cancelled"
|
|
194
|
+
else:
|
|
195
|
+
final_effect = base_effect
|
|
196
|
+
|
|
197
|
+
# Build description
|
|
198
|
+
desc_tmpl = _EFFECT_DESCRIPTIONS.get(planet, {})
|
|
199
|
+
key = "good" if base_effect == "Beneficial" else "bad"
|
|
200
|
+
description = desc_tmpl.get(key, f"{planet} in {house}th from Moon.").format(
|
|
201
|
+
h=house
|
|
202
|
+
)
|
|
203
|
+
if vedha_present:
|
|
204
|
+
description += (
|
|
205
|
+
f" However, {vedha_planet_name} in the Vedha house ({VEDHA_POINTS[planet][house]}th) "
|
|
206
|
+
"cancels this good effect."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
transit_analysis[planet] = {
|
|
210
|
+
"transit_rashi": transit_rashi_map[planet],
|
|
211
|
+
"house_from_moon": house,
|
|
212
|
+
"effect": base_effect,
|
|
213
|
+
"vedha_present": vedha_present,
|
|
214
|
+
"vedha_planet": vedha_planet_name,
|
|
215
|
+
"final_effect": final_effect,
|
|
216
|
+
"description": description,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if final_effect == "Beneficial":
|
|
220
|
+
benefic_count += 1
|
|
221
|
+
elif final_effect in ("Harmful", "Cancelled"):
|
|
222
|
+
malefic_count += 1
|
|
223
|
+
|
|
224
|
+
# Saturn special conditions
|
|
225
|
+
if planet == "Saturn":
|
|
226
|
+
sade_sati = _sade_sati_info(house)
|
|
227
|
+
if sade_sati:
|
|
228
|
+
special_conditions.append(sade_sati)
|
|
229
|
+
|
|
230
|
+
# Overall summary
|
|
231
|
+
total = benefic_count + malefic_count
|
|
232
|
+
if total == 0:
|
|
233
|
+
summary = "No significant Gochara data available."
|
|
234
|
+
recommendation = "Proceed with caution and mindfulness."
|
|
235
|
+
elif benefic_count > malefic_count:
|
|
236
|
+
summary = (
|
|
237
|
+
f"Predominantly favourable transit period with {benefic_count} benefic "
|
|
238
|
+
f"and {malefic_count} malefic planetary influences."
|
|
239
|
+
)
|
|
240
|
+
recommendation = (
|
|
241
|
+
"Good time to initiate new ventures, make important decisions, and strengthen "
|
|
242
|
+
"relationships. Leverage benefic planetary support."
|
|
243
|
+
)
|
|
244
|
+
elif malefic_count > benefic_count:
|
|
245
|
+
summary = (
|
|
246
|
+
f"Challenging transit period with {malefic_count} malefic "
|
|
247
|
+
f"and {benefic_count} benefic planetary influences."
|
|
248
|
+
)
|
|
249
|
+
recommendation = (
|
|
250
|
+
"Exercise caution, avoid major irreversible decisions, focus on health and "
|
|
251
|
+
"consolidation. Perform recommended remedies."
|
|
252
|
+
)
|
|
253
|
+
else:
|
|
254
|
+
summary = (
|
|
255
|
+
f"Mixed transit period — {benefic_count} benefic and {malefic_count} malefic "
|
|
256
|
+
"influences balance each other."
|
|
257
|
+
)
|
|
258
|
+
recommendation = (
|
|
259
|
+
"Proceed carefully. Balance opportunities with risks; seek guidance before "
|
|
260
|
+
"major commitments."
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
result: dict = {
|
|
264
|
+
"natal_moon_rashi": moon_rashi,
|
|
265
|
+
"transit_analysis": transit_analysis,
|
|
266
|
+
"overall_period": {
|
|
267
|
+
"benefic_count": benefic_count,
|
|
268
|
+
"malefic_count": malefic_count,
|
|
269
|
+
"summary": summary,
|
|
270
|
+
"recommendation": recommendation,
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if special_conditions:
|
|
275
|
+
result["special_conditions"] = special_conditions
|
|
276
|
+
|
|
277
|
+
return result
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Hora: Planetary hours — each hour of the day/night ruled by a planet."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timedelta, timezone
|
|
4
|
+
|
|
5
|
+
import swisseph as swe
|
|
6
|
+
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# Chaldean order and weekday mappings
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
CHALDEAN_ORDER = ["Saturn", "Jupiter", "Mars", "Sun", "Venus", "Mercury", "Moon"]
|
|
12
|
+
|
|
13
|
+
# Index into CHALDEAN_ORDER for the first (day-break) hour of each weekday.
|
|
14
|
+
# weekday() returns 0=Monday … 6=Sunday (Python convention).
|
|
15
|
+
WEEKDAY_FIRST_HOUR: dict[int, int] = {
|
|
16
|
+
6: 3, # Sunday → Sun (index 3)
|
|
17
|
+
0: 6, # Monday → Moon (index 6)
|
|
18
|
+
1: 2, # Tuesday → Mars (index 2)
|
|
19
|
+
2: 5, # Wednesday → Mercury (index 5)
|
|
20
|
+
3: 1, # Thursday → Jupiter (index 1)
|
|
21
|
+
4: 4, # Friday → Venus (index 4)
|
|
22
|
+
5: 0, # Saturday → Saturn (index 0)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
WEEKDAY_NAMES = [
|
|
26
|
+
"Monday",
|
|
27
|
+
"Tuesday",
|
|
28
|
+
"Wednesday",
|
|
29
|
+
"Thursday",
|
|
30
|
+
"Friday",
|
|
31
|
+
"Saturday",
|
|
32
|
+
"Sunday",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
HORA_SIGNIFICANCE: dict[str, str] = {
|
|
36
|
+
"Sun": "Leadership, government work, father, authority, health",
|
|
37
|
+
"Moon": "Travel, dealing with public, women, emotions, water matters",
|
|
38
|
+
"Mars": "Surgery, arguments, sports, enemies, property, courage",
|
|
39
|
+
"Mercury": "Communication, business, writing, learning, trade",
|
|
40
|
+
"Jupiter": "Religious work, education, wealth, children, law, spirituality",
|
|
41
|
+
"Venus": "Love, marriage, arts, luxury, entertainment, beauty",
|
|
42
|
+
"Saturn": "Hard labor, construction, agriculture, service, old people",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
# Sunrise / Sunset helpers
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _jd_from_utc(dt: datetime) -> float:
|
|
52
|
+
"""Convert a UTC-aware (or naive-UTC) datetime to Julian Day."""
|
|
53
|
+
if dt.tzinfo is None:
|
|
54
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
55
|
+
h = dt.hour + dt.minute / 60.0 + dt.second / 3600.0
|
|
56
|
+
return swe.julday(dt.year, dt.month, dt.day, h)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _get_sunrise_sunset(
|
|
60
|
+
dt_utc: datetime, lat: float, lon: float
|
|
61
|
+
) -> tuple[float, float]:
|
|
62
|
+
"""Return (sunrise_jd, sunset_jd) for the calendar day of dt_utc at (lat, lon).
|
|
63
|
+
|
|
64
|
+
Uses ``swe.rise_trans``. Falls back to 06:00 / 18:00 local solar noon
|
|
65
|
+
approximation if the call fails (e.g. extreme latitudes or ephemeris
|
|
66
|
+
data unavailable).
|
|
67
|
+
"""
|
|
68
|
+
jd_noon = _jd_from_utc(dt_utc.replace(hour=12, minute=0, second=0, microsecond=0))
|
|
69
|
+
geopos = (lon, lat, 0.0) # (longitude, latitude, altitude) – swe convention
|
|
70
|
+
ephe_flag = swe.FLG_SWIEPH
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
# Sunrise
|
|
74
|
+
ret_rise, tret_rise = swe.rise_trans(
|
|
75
|
+
jd_noon - 0.5, swe.SUN, "", ephe_flag, swe.CALC_RISE, geopos, 0.0, 0.0
|
|
76
|
+
)
|
|
77
|
+
sunrise_jd = tret_rise[0]
|
|
78
|
+
|
|
79
|
+
# Sunset – search forward from sunrise
|
|
80
|
+
ret_set, tret_set = swe.rise_trans(
|
|
81
|
+
sunrise_jd, swe.SUN, "", ephe_flag, swe.CALC_SET, geopos, 0.0, 0.0
|
|
82
|
+
)
|
|
83
|
+
sunset_jd = tret_set[0]
|
|
84
|
+
|
|
85
|
+
if sunrise_jd < sunset_jd:
|
|
86
|
+
return sunrise_jd, sunset_jd
|
|
87
|
+
except Exception:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
# Fallback: approximate as 06:00 and 18:00 UTC (naive, ignore timezone)
|
|
91
|
+
approx_rise = dt_utc.replace(hour=6, minute=0, second=0, microsecond=0)
|
|
92
|
+
approx_set = dt_utc.replace(hour=18, minute=0, second=0, microsecond=0)
|
|
93
|
+
return _jd_from_utc(approx_rise), _jd_from_utc(approx_set)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _jd_to_utc(jd: float) -> datetime:
|
|
97
|
+
"""Convert a Julian Day number back to a UTC datetime."""
|
|
98
|
+
y, m, d, h_frac = swe.revjul(jd)
|
|
99
|
+
hours = int(h_frac)
|
|
100
|
+
rem = (h_frac - hours) * 60.0
|
|
101
|
+
minutes = int(rem)
|
|
102
|
+
seconds = int((rem - minutes) * 60.0)
|
|
103
|
+
return datetime(y, m, d, hours, minutes, seconds, tzinfo=timezone.utc)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _next_sunrise(sunset_jd: float, lat: float, lon: float) -> float:
|
|
107
|
+
"""Return the JD of the next sunrise after sunset_jd."""
|
|
108
|
+
geopos = (lon, lat, 0.0)
|
|
109
|
+
try:
|
|
110
|
+
_, tret = swe.rise_trans(
|
|
111
|
+
sunset_jd, swe.SUN, "", swe.FLG_SWIEPH, swe.CALC_RISE, geopos, 0.0, 0.0
|
|
112
|
+
)
|
|
113
|
+
return tret[0]
|
|
114
|
+
except Exception:
|
|
115
|
+
return sunset_jd + 0.5 # +12 hours as fallback
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Hour-slot builders
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _build_hour_slots(
|
|
124
|
+
period_start_jd: float,
|
|
125
|
+
period_end_jd: float,
|
|
126
|
+
first_planet_index: int,
|
|
127
|
+
slot_offset: int,
|
|
128
|
+
) -> list[dict]:
|
|
129
|
+
"""Divide a period into 12 equal planetary hours and return slot metadata.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
period_start_jd: Start of the period (JD).
|
|
133
|
+
period_end_jd: End of the period (JD).
|
|
134
|
+
first_planet_index: Index into CHALDEAN_ORDER for the first hour.
|
|
135
|
+
slot_offset: Running offset so night hours continue from daytime's
|
|
136
|
+
last planet index.
|
|
137
|
+
Returns:
|
|
138
|
+
List of 12 dicts with hour number, start/end times, and ruling planet.
|
|
139
|
+
"""
|
|
140
|
+
duration_jd = (period_end_jd - period_start_jd) / 12.0
|
|
141
|
+
slots: list[dict] = []
|
|
142
|
+
for i in range(12):
|
|
143
|
+
planet_idx = (first_planet_index + slot_offset + i) % 7
|
|
144
|
+
start_jd = period_start_jd + i * duration_jd
|
|
145
|
+
end_jd = period_start_jd + (i + 1) * duration_jd
|
|
146
|
+
slots.append(
|
|
147
|
+
{
|
|
148
|
+
"hour": i + 1,
|
|
149
|
+
"start_time": _jd_to_utc(start_jd).strftime("%H:%M:%S UTC"),
|
|
150
|
+
"end_time": _jd_to_utc(end_jd).strftime("%H:%M:%S UTC"),
|
|
151
|
+
"planet": CHALDEAN_ORDER[planet_idx],
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
return slots
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _compute_hora(dt_utc: datetime, lat: float, lon: float) -> dict:
|
|
158
|
+
"""Core computation shared by the public API functions."""
|
|
159
|
+
sunrise_jd, sunset_jd = _get_sunrise_sunset(dt_utc, lat, lon)
|
|
160
|
+
next_sunrise_jd = _next_sunrise(sunset_jd, lat, lon)
|
|
161
|
+
|
|
162
|
+
current_jd = _jd_from_utc(dt_utc)
|
|
163
|
+
weekday = _jd_to_utc(sunrise_jd).weekday() # weekday of the sunrise for this day
|
|
164
|
+
weekday_name = WEEKDAY_NAMES[weekday]
|
|
165
|
+
day_ruler_idx = WEEKDAY_FIRST_HOUR[weekday]
|
|
166
|
+
day_ruler = CHALDEAN_ORDER[day_ruler_idx]
|
|
167
|
+
|
|
168
|
+
# Build day hours (sunrise → sunset), starting at day_ruler_idx
|
|
169
|
+
day_hours = _build_hour_slots(sunrise_jd, sunset_jd, day_ruler_idx, slot_offset=0)
|
|
170
|
+
# Night hours continue from where day hours left off (12 slots consumed = +12)
|
|
171
|
+
night_hours = _build_hour_slots(
|
|
172
|
+
sunset_jd, next_sunrise_jd, day_ruler_idx, slot_offset=12
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Determine which hour the current moment falls in
|
|
176
|
+
is_day = sunrise_jd <= current_jd < sunset_jd
|
|
177
|
+
hour_number = 1
|
|
178
|
+
planetary_hour_lord = day_ruler
|
|
179
|
+
|
|
180
|
+
if is_day:
|
|
181
|
+
day_slot_jd = (sunset_jd - sunrise_jd) / 12.0
|
|
182
|
+
slot_i = int((current_jd - sunrise_jd) / day_slot_jd)
|
|
183
|
+
slot_i = max(0, min(11, slot_i))
|
|
184
|
+
hour_number = slot_i + 1
|
|
185
|
+
planetary_hour_lord = day_hours[slot_i]["planet"]
|
|
186
|
+
else:
|
|
187
|
+
# Normalise current_jd into night window
|
|
188
|
+
if current_jd < sunrise_jd:
|
|
189
|
+
# Pre-sunrise: part of previous day's night — treat as night hour
|
|
190
|
+
effective_start = sunset_jd - (next_sunrise_jd - sunset_jd)
|
|
191
|
+
# This is an edge case; fall back to last night slot
|
|
192
|
+
night_slot_jd = (next_sunrise_jd - sunset_jd) / 12.0
|
|
193
|
+
elapsed = current_jd - sunset_jd
|
|
194
|
+
if elapsed < 0:
|
|
195
|
+
elapsed += next_sunrise_jd - sunset_jd
|
|
196
|
+
else:
|
|
197
|
+
night_slot_jd = (next_sunrise_jd - sunset_jd) / 12.0
|
|
198
|
+
elapsed = current_jd - sunset_jd
|
|
199
|
+
|
|
200
|
+
slot_i = int(elapsed / night_slot_jd)
|
|
201
|
+
slot_i = max(0, min(11, slot_i))
|
|
202
|
+
hour_number = slot_i + 1
|
|
203
|
+
planetary_hour_lord = night_hours[slot_i]["planet"]
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
"datetime_utc": dt_utc.strftime("%Y-%m-%d %H:%M:%S UTC"),
|
|
207
|
+
"weekday": weekday_name,
|
|
208
|
+
"sunrise_approx": _jd_to_utc(sunrise_jd).strftime("%H:%M:%S UTC"),
|
|
209
|
+
"sunset_approx": _jd_to_utc(sunset_jd).strftime("%H:%M:%S UTC"),
|
|
210
|
+
"is_day": is_day,
|
|
211
|
+
"hour_number": hour_number,
|
|
212
|
+
"planetary_hour_lord": planetary_hour_lord,
|
|
213
|
+
"day_ruler": day_ruler,
|
|
214
|
+
"all_day_hours": day_hours,
|
|
215
|
+
"all_night_hours": night_hours,
|
|
216
|
+
"significance": HORA_SIGNIFICANCE.get(planetary_hour_lord, ""),
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ---------------------------------------------------------------------------
|
|
221
|
+
# Public API
|
|
222
|
+
# ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def get_planetary_hour(dt_utc: datetime, latitude: float, longitude: float) -> dict:
|
|
226
|
+
"""Return the planetary hour ruling the given UTC moment at (latitude, longitude).
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
dt_utc: A datetime object representing the moment of interest (UTC).
|
|
230
|
+
May be timezone-naive (treated as UTC) or tz-aware.
|
|
231
|
+
latitude: Geographic latitude in decimal degrees (north positive).
|
|
232
|
+
longitude: Geographic longitude in decimal degrees (east positive).
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
A dict with weekday, sunrise/sunset times, day/night flag, hour number,
|
|
236
|
+
ruling planet, all 12 day hours, all 12 night hours, and significance text.
|
|
237
|
+
"""
|
|
238
|
+
swe.set_ephe_path(None)
|
|
239
|
+
return _compute_hora(dt_utc, latitude, longitude)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def get_hora_chart(
|
|
243
|
+
birth_datetime_utc: datetime,
|
|
244
|
+
birth_lat: float,
|
|
245
|
+
birth_lon: float,
|
|
246
|
+
) -> dict:
|
|
247
|
+
"""Return the planetary hour ruling the birth moment.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
birth_datetime_utc: Birth datetime (UTC).
|
|
251
|
+
birth_lat: Birth latitude.
|
|
252
|
+
birth_lon: Birth longitude.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Same structure as ``get_planetary_hour`` — the Hora at the time of birth.
|
|
256
|
+
"""
|
|
257
|
+
swe.set_ephe_path(None)
|
|
258
|
+
result = _compute_hora(birth_datetime_utc, birth_lat, birth_lon)
|
|
259
|
+
result["context"] = (
|
|
260
|
+
"Hora chart at birth: the planetary hour lord at birth colours the native's "
|
|
261
|
+
"temperament and early instincts, supplementing the Lagna lord."
|
|
262
|
+
)
|
|
263
|
+
return result
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""House cusps (sidereal, configurable house system)."""
|
|
2
|
+
|
|
3
|
+
import swisseph as swe
|
|
4
|
+
|
|
5
|
+
from kundali_lib.vedic.ayanamsa import DEFAULT_HOUSE_SYSTEM
|
|
6
|
+
from kundali_lib.vedic.zodiac import get_rashi
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def calc_houses_sidereal_with_system(
|
|
10
|
+
julian_day: float, lat: float, lon: float, house_system: str | None = None
|
|
11
|
+
) -> tuple[list[float], list[float]]:
|
|
12
|
+
"""Returns (cusps[12], ascmc[8]).
|
|
13
|
+
|
|
14
|
+
house_system: single char code like 'P' (Placidus), 'K' (Koch), 'E' (Equal), etc.
|
|
15
|
+
"""
|
|
16
|
+
hs = (house_system or DEFAULT_HOUSE_SYSTEM).upper()[:1]
|
|
17
|
+
cusps, ascmc = swe.houses_ex(
|
|
18
|
+
julian_day, lat, lon, hs.encode("ascii"), swe.FLG_SIDEREAL
|
|
19
|
+
)
|
|
20
|
+
return list(cusps), list(ascmc)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def houses_dict_and_degrees(cusps: list[float]) -> tuple[dict[str, str], list[float]]:
|
|
24
|
+
"""From cusps, return ({House_1: rashi, ...}, [deg1, ...])."""
|
|
25
|
+
houses = {}
|
|
26
|
+
degrees = []
|
|
27
|
+
for i, cusp in enumerate(cusps[:12]):
|
|
28
|
+
houses[f"House_{i + 1}"] = get_rashi(cusp)
|
|
29
|
+
degrees.append(round(cusp % 30, 4))
|
|
30
|
+
return houses, degrees
|