dashaflow 1.0.0__py3-none-any.whl

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.
dashaflow/__init__.py ADDED
@@ -0,0 +1,241 @@
1
+ """
2
+ DashaFlow — Vedic Astrology Calculation Engine
3
+ ===============================================
4
+
5
+ Swiss Ephemeris with Sidereal Lahiri ayanamsha.
6
+ Rooted in Brihat Parashara Hora Shastra (BPHS) and B.V. Raman's Hindu Predictive Astrology.
7
+
8
+ Quick start::
9
+
10
+ import dashaflow
11
+
12
+ chart = dashaflow.cast_chart("1990-04-15", "14:30", 28.6139, 77.2090, "Asia/Kolkata")
13
+ print(chart["lagna"]["sign"]) # e.g. "Leo"
14
+ print(chart["planets"]["Moon"]) # full Moon data
15
+ print(chart["yogas"]) # detected yogas
16
+ """
17
+
18
+ from ._version import __version__
19
+ from ._validation import validate_birth_input
20
+ from .vedic_calculator import calculate_vedic_chart, calculate_transit
21
+ from .matchmaking import calculate_ashtakoot, calc_kuja_dosha, match_kuja_dosha
22
+ from .muhurtha import evaluate_muhurtha, ACTIVITY_RULES
23
+ from .career import analyze_career as _analyze_career_internal
24
+ from .constants import ZODIAC_SIGNS
25
+
26
+ __all__ = [
27
+ "__version__",
28
+ "cast_chart",
29
+ "cast_transit",
30
+ "calculate_compatibility",
31
+ "check_muhurtha",
32
+ "analyze_career",
33
+ ]
34
+
35
+
36
+ def cast_chart(
37
+ dob: str,
38
+ time: str,
39
+ lat: float,
40
+ lon: float,
41
+ timezone: str,
42
+ query_date: str = None,
43
+ ephe_path: str = '',
44
+ ) -> dict:
45
+ """
46
+ Cast a complete Vedic natal chart (Sidereal Lahiri ayanamsha).
47
+
48
+ Returns a dict with: metadata, panchang, lagna, planets (with dignity,
49
+ combustion, aspects, 14 varga signs), dashas (5 levels), yogas (24 types),
50
+ ashtakavarga, jaimini_karakas, shadbala, bhava_chalit, avasthas,
51
+ kaal_sarpa, graha_yuddha, gandanta, arudha_padas, upapada, karakamsha.
52
+
53
+ Parameters
54
+ ----------
55
+ dob : str
56
+ Date of birth as "YYYY-MM-DD"
57
+ time : str
58
+ Time of birth as "HH:MM" (24-hour)
59
+ lat : float
60
+ Birth latitude (-90 to 90)
61
+ lon : float
62
+ Birth longitude (-180 to 180)
63
+ timezone : str
64
+ IANA timezone (e.g. "Asia/Kolkata")
65
+ query_date : str, optional
66
+ Date for Dasha lookup as "YYYY-MM-DD". Defaults to today.
67
+ ephe_path : str, optional
68
+ Path to Swiss Ephemeris data files. Defaults to '' (bundled).
69
+
70
+ Returns
71
+ -------
72
+ dict
73
+ Complete chart data.
74
+
75
+ Raises
76
+ ------
77
+ ValueError
78
+ If any input parameter is invalid.
79
+ """
80
+ validate_birth_input(dob, time, lat, lon, timezone)
81
+ return calculate_vedic_chart(
82
+ dob_str=dob,
83
+ time_str=time,
84
+ lat=lat,
85
+ lon=lon,
86
+ timezone_str=timezone,
87
+ query_date_str=query_date,
88
+ ephe_path=ephe_path,
89
+ )
90
+
91
+
92
+ def cast_transit(
93
+ transit_date: str,
94
+ natal_chart: dict,
95
+ timezone: str = "Asia/Kolkata",
96
+ ) -> dict:
97
+ """
98
+ Calculate planetary transits overlaid on a natal chart.
99
+
100
+ Parameters
101
+ ----------
102
+ transit_date : str
103
+ Date to compute transits as "YYYY-MM-DD"
104
+ natal_chart : dict
105
+ Full dict output from cast_chart()
106
+ timezone : str, optional
107
+ IANA timezone. Defaults to "Asia/Kolkata".
108
+
109
+ Returns
110
+ -------
111
+ dict
112
+ Transit planets with house positions, SAV points, Sade Sati, Rahu-Ketu axis.
113
+ """
114
+ return calculate_transit(
115
+ transit_date_str=transit_date,
116
+ natal_chart=natal_chart,
117
+ timezone_str=timezone,
118
+ )
119
+
120
+
121
+ def calculate_compatibility(
122
+ dob1: str, time1: str, lat1: float, lon1: float, tz1: str,
123
+ dob2: str, time2: str, lat2: float, lon2: float, tz2: str,
124
+ ) -> dict:
125
+ """
126
+ Calculate 36-point Ashtakoot compatibility + Kuja Dosha.
127
+
128
+ Person 1 = Male, Person 2 = Female (by tradition for accurate scoring).
129
+
130
+ Parameters
131
+ ----------
132
+ dob1, time1, lat1, lon1, tz1 : Birth details for Person 1 (Male)
133
+ dob2, time2, lat2, lon2, tz2 : Birth details for Person 2 (Female)
134
+
135
+ Returns
136
+ -------
137
+ dict
138
+ 8 Ashtakoot kutas (36 pts), extended kutas (Mahendra, Stree Deergha,
139
+ Vedha, Rajju, etc.), Kuja Dosha analysis with compatibility verdict.
140
+ """
141
+ validate_birth_input(dob1, time1, lat1, lon1, tz1)
142
+ validate_birth_input(dob2, time2, lat2, lon2, tz2)
143
+
144
+ chart1 = calculate_vedic_chart(dob1, time1, lat1, lon1, tz1)
145
+ chart2 = calculate_vedic_chart(dob2, time2, lat2, lon2, tz2)
146
+
147
+ m_moon = chart1["planets"]["Moon"]
148
+ f_moon = chart2["planets"]["Moon"]
149
+
150
+ m_lon = ZODIAC_SIGNS.index(m_moon["sign"]) * 30 + m_moon["degree"]
151
+ f_lon = ZODIAC_SIGNS.index(f_moon["sign"]) * 30 + f_moon["degree"]
152
+
153
+ score = calculate_ashtakoot(m_lon, f_lon, male_chart=chart1, female_chart=chart2)
154
+
155
+ male_dosha = calc_kuja_dosha(chart1)
156
+ female_dosha = calc_kuja_dosha(chart2)
157
+ kuja_match = match_kuja_dosha(male_dosha["total_score"], female_dosha["total_score"])
158
+ score["kuja_dosha"] = {
159
+ "male": male_dosha,
160
+ "female": female_dosha,
161
+ "compatibility": kuja_match,
162
+ }
163
+
164
+ return score
165
+
166
+
167
+ def check_muhurtha(
168
+ activity: str,
169
+ date: str,
170
+ time: str,
171
+ lat: float,
172
+ lon: float,
173
+ timezone: str,
174
+ ) -> dict:
175
+ """
176
+ Check if a date/time is auspicious for a specific activity.
177
+
178
+ Parameters
179
+ ----------
180
+ activity : str
181
+ One of: 'marriage', 'travel', 'business', 'education', 'house_entry', 'medical'
182
+ date : str
183
+ Date as "YYYY-MM-DD"
184
+ time : str
185
+ Time as "HH:MM" (24-hour)
186
+ lat : float
187
+ Location latitude
188
+ lon : float
189
+ Location longitude
190
+ timezone : str
191
+ IANA timezone string
192
+
193
+ Returns
194
+ -------
195
+ dict
196
+ Verdict (auspicious/mixed/inauspicious), score, factors, panchang_suddhi.
197
+ """
198
+ validate_birth_input(date, time, lat, lon, timezone)
199
+ if activity not in ACTIVITY_RULES:
200
+ raise ValueError(f"Unknown activity '{activity}'. Supported: {list(ACTIVITY_RULES.keys())}")
201
+
202
+ chart = calculate_vedic_chart(date, time, lat, lon, timezone)
203
+ panchang = chart.get("panchang", {})
204
+ planets = chart.get("planets", {})
205
+ lagna_sign = chart.get("lagna", {}).get("sign")
206
+ return evaluate_muhurtha(activity, panchang, planets, lagna_sign)
207
+
208
+
209
+ def analyze_career(
210
+ dob: str,
211
+ time: str,
212
+ lat: float,
213
+ lon: float,
214
+ timezone: str,
215
+ ) -> dict:
216
+ """
217
+ Analyze career potential using the 10th house, D10 Dashamsha, and planetary significations.
218
+
219
+ Parameters
220
+ ----------
221
+ dob : str
222
+ Date of birth as "YYYY-MM-DD"
223
+ time : str
224
+ Time of birth as "HH:MM" (24-hour)
225
+ lat : float
226
+ Birth latitude
227
+ lon : float
228
+ Birth longitude
229
+ timezone : str
230
+ IANA timezone string
231
+
232
+ Returns
233
+ -------
234
+ dict
235
+ 10th house analysis, D10 indicators, career themes, strength factors.
236
+ """
237
+ validate_birth_input(dob, time, lat, lon, timezone)
238
+ chart = calculate_vedic_chart(dob, time, lat, lon, timezone)
239
+ planets = chart.get("planets", {})
240
+ lagna_sign = chart.get("lagna", {}).get("sign")
241
+ return _analyze_career_internal(planets, lagna_sign)
@@ -0,0 +1,21 @@
1
+ """Input validation for DashaFlow public API."""
2
+
3
+ import re
4
+ import pytz
5
+
6
+ _DATE_RE = re.compile(r"^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$")
7
+ _TIME_RE = re.compile(r"^(?:[01]\d|2[0-3]):[0-5]\d$")
8
+
9
+
10
+ def validate_birth_input(dob: str, time: str, lat: float, lon: float, timezone: str):
11
+ """Validate common birth chart inputs. Raises ValueError on bad data."""
12
+ if not isinstance(dob, str) or not _DATE_RE.match(dob):
13
+ raise ValueError(f"Invalid date format '{dob}'. Expected YYYY-MM-DD.")
14
+ if not isinstance(time, str) or not _TIME_RE.match(time):
15
+ raise ValueError(f"Invalid time format '{time}'. Expected HH:MM (24h).")
16
+ if not (-90 <= lat <= 90):
17
+ raise ValueError(f"Latitude {lat} out of range [-90, 90].")
18
+ if not (-180 <= lon <= 180):
19
+ raise ValueError(f"Longitude {lon} out of range [-180, 180].")
20
+ if timezone not in pytz.all_timezones:
21
+ raise ValueError(f"Unknown timezone '{timezone}'. Use IANA format (e.g. 'Asia/Kolkata').")
dashaflow/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,128 @@
1
+ from .constants import ZODIAC_SIGNS
2
+
3
+ # 1-indexed houses from the placement of the planet
4
+ ASHTAKAVARGA_TABLES = {
5
+ "Sun": {
6
+ "Sun": [1, 2, 4, 7, 8, 9, 10, 11],
7
+ "Moon": [3, 6, 10, 11],
8
+ "Mars": [1, 2, 4, 7, 8, 9, 10, 11],
9
+ "Mercury": [3, 5, 6, 9, 10, 11, 12],
10
+ "Jupiter": [5, 6, 9, 11],
11
+ "Venus": [6, 7, 12],
12
+ "Saturn": [1, 2, 4, 7, 8, 9, 10, 11],
13
+ "Ascendant": [3, 4, 6, 10, 11, 12]
14
+ },
15
+ "Moon": {
16
+ "Sun": [3, 6, 7, 8, 10, 11],
17
+ "Moon": [1, 3, 6, 7, 10, 11],
18
+ "Mars": [2, 3, 5, 6, 9, 10, 11],
19
+ "Mercury": [1, 3, 4, 5, 7, 8, 10, 11],
20
+ "Jupiter": [1, 4, 7, 8, 10, 11, 12],
21
+ "Venus": [3, 4, 5, 7, 9, 10, 11],
22
+ "Saturn": [3, 5, 6, 11],
23
+ "Ascendant": [3, 6, 10, 11]
24
+ },
25
+ "Mars": {
26
+ "Sun": [3, 5, 6, 10, 11],
27
+ "Moon": [3, 6, 11],
28
+ "Mars": [1, 2, 4, 7, 8, 10, 11],
29
+ "Mercury": [3, 5, 6, 11],
30
+ "Jupiter": [6, 10, 11, 12],
31
+ "Venus": [6, 8, 11, 12],
32
+ "Saturn": [1, 4, 7, 8, 9, 10, 11],
33
+ "Ascendant": [1, 3, 6, 10, 11]
34
+ },
35
+ "Mercury": {
36
+ "Sun": [5, 6, 9, 11, 12],
37
+ "Moon": [2, 4, 6, 8, 10, 11],
38
+ "Mars": [1, 2, 4, 7, 8, 9, 10, 11],
39
+ "Mercury": [1, 3, 5, 6, 9, 10, 11, 12],
40
+ "Jupiter": [6, 8, 11, 12],
41
+ "Venus": [1, 2, 3, 4, 5, 8, 9, 11],
42
+ "Saturn": [1, 2, 4, 7, 8, 9, 10, 11],
43
+ "Ascendant": [1, 2, 4, 6, 8, 10, 11]
44
+ },
45
+ "Jupiter": {
46
+ "Sun": [1, 2, 3, 4, 7, 8, 9, 10, 11],
47
+ "Moon": [2, 5, 7, 9, 11],
48
+ "Mars": [1, 2, 4, 7, 8, 10, 11],
49
+ "Mercury": [1, 2, 4, 5, 6, 9, 10, 11],
50
+ "Jupiter": [1, 2, 3, 4, 7, 8, 10, 11],
51
+ "Venus": [2, 5, 6, 9, 10, 11],
52
+ "Saturn": [3, 5, 6, 12],
53
+ "Ascendant": [1, 2, 4, 5, 6, 7, 9, 10, 11]
54
+ },
55
+ "Venus": {
56
+ "Sun": [8, 11, 12],
57
+ "Moon": [1, 2, 3, 4, 5, 8, 9, 11, 12],
58
+ "Mars": [3, 5, 6, 9, 11, 12],
59
+ "Mercury": [3, 5, 6, 9, 11],
60
+ "Jupiter": [5, 8, 9, 10, 11],
61
+ "Venus": [1, 2, 3, 4, 5, 8, 9, 10, 11],
62
+ "Saturn": [3, 4, 5, 8, 9, 10, 11],
63
+ "Ascendant": [1, 2, 3, 4, 5, 8, 9, 11]
64
+ },
65
+ "Saturn": {
66
+ "Sun": [1, 2, 4, 7, 8, 10, 11],
67
+ "Moon": [3, 6, 11],
68
+ "Mars": [3, 5, 6, 10, 11, 12],
69
+ "Mercury": [6, 8, 9, 10, 11, 12],
70
+ "Jupiter": [5, 6, 11, 12],
71
+ "Venus": [6, 11, 12],
72
+ "Saturn": [3, 5, 6, 11],
73
+ "Ascendant": [1, 3, 4, 6, 10, 11]
74
+ }
75
+ }
76
+
77
+ def calculate_ashtakavarga(planets_in_signs: dict, ascendant_sign_idx: int):
78
+ """
79
+ Calculates Sarvashtakavarga (SAV) and Bhinnashtakavarga (BAV).
80
+ planets_in_signs dict maps "Sun", "Moon", etc. to their 0-11 sign index.
81
+
82
+ Returns a dict with 'sarvashtakavarga' (list of 12 ints mapping to ZODIAC_SIGNS)
83
+ and 'bhinnashtakavarga' mapping each planet to their 12-sign array.
84
+ """
85
+ # Initialize all BAV arrays with 0
86
+ bav = {p: [0]*12 for p in ASHTAKAVARGA_TABLES.keys()}
87
+ sav = [0]*12
88
+
89
+ # Extend planets dict with Ascendant for calculation
90
+ positions = planets_in_signs.copy()
91
+ positions["Ascendant"] = ascendant_sign_idx
92
+
93
+ for target_planet, contributions in ASHTAKAVARGA_TABLES.items():
94
+ for source_point, houses_list in contributions.items():
95
+ source_idx = positions[source_point]
96
+ for h in houses_list:
97
+ # h is 1-indexed house from the source planet.
98
+ # So if source is at idx 0 (Aries) and h=1, target sign is 0 (Aries)
99
+ target_sign_idx = (source_idx + (h - 1)) % 12
100
+ bav[target_planet][target_sign_idx] += 1
101
+ sav[target_sign_idx] += 1
102
+
103
+ # Return as a dict mapped to Zodiac Sign names for easier LLM reading
104
+ sav_dict = {ZODIAC_SIGNS[i]: sav[i] for i in range(12)}
105
+
106
+ bav_dict = {}
107
+ for p, arr in bav.items():
108
+ bav_dict[p] = {ZODIAC_SIGNS[i]: arr[i] for i in range(12)}
109
+
110
+ # Prashtarashtakavarga (expanded scatter chart)
111
+ # For each target planet, shows which source contributed bindus to which sign
112
+ prashtara = {}
113
+ for target_planet, contributions in ASHTAKAVARGA_TABLES.items():
114
+ prashtara[target_planet] = {}
115
+ for source_point, houses_list in contributions.items():
116
+ source_idx = positions[source_point]
117
+ row = [0] * 12
118
+ for h in houses_list:
119
+ target_sign_idx = (source_idx + (h - 1)) % 12
120
+ row[target_sign_idx] = 1
121
+ prashtara[target_planet][source_point] = {ZODIAC_SIGNS[i]: row[i] for i in range(12)}
122
+
123
+ return {
124
+ "sarvashtakavarga": sav_dict,
125
+ "bhinnashtakavarga": bav_dict,
126
+ "prashtarashtakavarga": prashtara,
127
+ "total_bindus": sum(sav) # Should be 337
128
+ }
dashaflow/career.py ADDED
@@ -0,0 +1,171 @@
1
+ """
2
+ Career Analysis Framework — D10 Dashamsha interpretation
3
+ Uses D10 chart, 10th house analysis, and planetary significations.
4
+ """
5
+
6
+ from .constants import ZODIAC_SIGNS, SIGN_LORDS, EXALTATION, OWN_SIGNS
7
+
8
+ # Planet -> career significations (Jyotish standard)
9
+ CAREER_SIGNIFICATIONS = {
10
+ "Sun": ["government", "politics", "administration", "medicine", "leadership", "authority"],
11
+ "Moon": ["nursing", "hospitality", "public_relations", "agriculture", "shipping", "dairy"],
12
+ "Mars": ["military", "engineering", "surgery", "sports", "police", "real_estate", "fire_services"],
13
+ "Mercury": ["writing", "accounting", "commerce", "communication", "IT", "teaching", "astrology"],
14
+ "Jupiter": ["law", "education", "finance", "banking", "religion", "philosophy", "consulting"],
15
+ "Venus": ["arts", "entertainment", "fashion", "luxury_goods", "hotel", "cosmetics", "music"],
16
+ "Saturn": ["mining", "agriculture", "labor", "construction", "oil", "manufacturing", "judiciary"],
17
+ "Rahu": ["technology", "foreign_trade", "aviation", "research", "pharmaceuticals", "diplomacy"],
18
+ "Ketu": ["spirituality", "occult", "research", "computing", "alternative_medicine", "languages"],
19
+ }
20
+
21
+ # Sign -> professional domains
22
+ SIGN_CAREERS = {
23
+ "Aries": ["military", "sports", "engineering", "entrepreneurship"],
24
+ "Taurus": ["banking", "agriculture", "luxury_goods", "arts", "food_industry"],
25
+ "Gemini": ["communication", "writing", "media", "IT", "trading"],
26
+ "Cancer": ["nursing", "hospitality", "real_estate", "food_industry"],
27
+ "Leo": ["government", "entertainment", "leadership", "politics"],
28
+ "Virgo": ["healthcare", "accounting", "analysis", "service", "editing"],
29
+ "Libra": ["law", "diplomacy", "arts", "fashion", "counseling"],
30
+ "Scorpio": ["research", "surgery", "occult", "insurance", "investigation"],
31
+ "Sagittarius": ["education", "law", "religion", "publishing", "travel"],
32
+ "Capricorn": ["administration", "mining", "construction", "management"],
33
+ "Aquarius": ["technology", "social_work", "aviation", "research", "NGO"],
34
+ "Pisces": ["spirituality", "arts", "healthcare", "shipping", "charity"],
35
+ }
36
+
37
+ KENDRA_HOUSES = {1, 4, 7, 10}
38
+ TRIKONA_HOUSES = {1, 5, 9}
39
+ DUSTHANA_HOUSES = {6, 8, 12}
40
+
41
+
42
+ def _is_strong(planet_name, sign):
43
+ """Check if planet is exalted or in own sign."""
44
+ if planet_name in EXALTATION and EXALTATION[planet_name][0] == sign:
45
+ return True
46
+ if planet_name in OWN_SIGNS and sign in OWN_SIGNS[planet_name]:
47
+ return True
48
+ return False
49
+
50
+
51
+ def analyze_career(planets, lagna_sign):
52
+ """
53
+ Comprehensive career analysis using 10th house, D10, and planetary influences.
54
+
55
+ Parameters
56
+ ----------
57
+ planets : dict — Planet data from calculate_vedic_chart (needs house, sign, d10_sign, dignity)
58
+ lagna_sign : str — Ascendant sign
59
+
60
+ Returns
61
+ -------
62
+ dict with career indicators, D10 analysis, and recommendations
63
+ """
64
+ lagna_idx = ZODIAC_SIGNS.index(lagna_sign)
65
+ tenth_sign = ZODIAC_SIGNS[(lagna_idx + 9) % 12]
66
+ tenth_lord = SIGN_LORDS[tenth_sign]
67
+
68
+ # D10 analysis
69
+ d10_indicators = {}
70
+ for p_name, pd in planets.items():
71
+ d10_sign = pd.get("d10_sign")
72
+ if d10_sign:
73
+ d10_lord = SIGN_LORDS.get(d10_sign)
74
+ d10_indicators[p_name] = {
75
+ "d10_sign": d10_sign,
76
+ "d10_lord": d10_lord,
77
+ "d10_strong": _is_strong(p_name, d10_sign),
78
+ }
79
+
80
+ # Planets in 10th house
81
+ tenth_house_planets = []
82
+ for p_name, pd in planets.items():
83
+ if pd.get("house") == 10:
84
+ tenth_house_planets.append(p_name)
85
+
86
+ # 10th lord analysis
87
+ tenth_lord_data = planets.get(tenth_lord, {})
88
+ tenth_lord_house = tenth_lord_data.get("house")
89
+ tenth_lord_sign = tenth_lord_data.get("sign", "")
90
+ tenth_lord_d10 = tenth_lord_data.get("d10_sign", "")
91
+ tenth_lord_dignity = tenth_lord_data.get("dignity", "neutral")
92
+
93
+ # Career themes from 10th house planets
94
+ career_themes = set()
95
+ primary_planets = []
96
+
97
+ # From 10th house occupants
98
+ for p_name in tenth_house_planets:
99
+ for theme in CAREER_SIGNIFICATIONS.get(p_name, []):
100
+ career_themes.add(theme)
101
+ primary_planets.append(p_name)
102
+
103
+ # From 10th lord's significations
104
+ for theme in CAREER_SIGNIFICATIONS.get(tenth_lord, []):
105
+ career_themes.add(theme)
106
+ if tenth_lord not in primary_planets:
107
+ primary_planets.append(tenth_lord)
108
+
109
+ # From 10th sign's domains
110
+ for theme in SIGN_CAREERS.get(tenth_sign, []):
111
+ career_themes.add(theme)
112
+
113
+ # From D10 10th lord placement
114
+ if tenth_lord in d10_indicators:
115
+ d10_info = d10_indicators[tenth_lord]
116
+ for theme in SIGN_CAREERS.get(d10_info["d10_sign"], []):
117
+ career_themes.add(theme)
118
+
119
+ # Strength assessment
120
+ strength_factors = []
121
+ if tenth_lord_dignity in ("exalted", "own_sign", "mooltrikona"):
122
+ strength_factors.append(f"10th lord {tenth_lord} in {tenth_lord_dignity} — strong career foundation")
123
+ if tenth_lord_house in KENDRA_HOUSES:
124
+ strength_factors.append(f"10th lord {tenth_lord} in kendra (house {tenth_lord_house}) — career prominence")
125
+ if tenth_lord_house in TRIKONA_HOUSES:
126
+ strength_factors.append(f"10th lord {tenth_lord} in trikona (house {tenth_lord_house}) — fortune in career")
127
+ if tenth_lord_house in DUSTHANA_HOUSES:
128
+ strength_factors.append(f"10th lord {tenth_lord} in dusthana (house {tenth_lord_house}) — career challenges")
129
+
130
+ for p_name in tenth_house_planets:
131
+ pd = planets.get(p_name, {})
132
+ if pd.get("dignity") in ("exalted", "own_sign"):
133
+ strength_factors.append(f"{p_name} strong in 10th house — powerful career planet")
134
+ if pd.get("is_retrograde"):
135
+ strength_factors.append(f"{p_name} retrograde in 10th — unconventional career path")
136
+
137
+ # D10 strength check
138
+ d10_strong_planets = [p for p, info in d10_indicators.items() if info.get("d10_strong")]
139
+ if d10_strong_planets:
140
+ strength_factors.append(f"Strong in D10: {', '.join(d10_strong_planets)} — career success in their domains")
141
+
142
+ # 6th lord analysis (competition/service)
143
+ sixth_sign = ZODIAC_SIGNS[(lagna_idx + 5) % 12]
144
+ sixth_lord = SIGN_LORDS[sixth_sign]
145
+ sixth_lord_data = planets.get(sixth_lord, {})
146
+ if sixth_lord_data.get("house") == 10:
147
+ strength_factors.append(f"6th lord {sixth_lord} in 10th — career in service, healthcare, or competition")
148
+
149
+ # 7th lord analysis (business/partnerships)
150
+ seventh_sign = ZODIAC_SIGNS[(lagna_idx + 6) % 12]
151
+ seventh_lord = SIGN_LORDS[seventh_sign]
152
+ seventh_lord_data = planets.get(seventh_lord, {})
153
+ if seventh_lord_data.get("house") == 10 or tenth_lord_house == 7:
154
+ strength_factors.append("10th-7th lord connection — career through partnerships or business")
155
+
156
+ return {
157
+ "tenth_house": {
158
+ "sign": tenth_sign,
159
+ "lord": tenth_lord,
160
+ "lord_house": tenth_lord_house,
161
+ "lord_sign": tenth_lord_sign,
162
+ "lord_d10": tenth_lord_d10,
163
+ "lord_dignity": tenth_lord_dignity,
164
+ "occupants": tenth_house_planets,
165
+ },
166
+ "d10_indicators": d10_indicators,
167
+ "career_themes": sorted(career_themes),
168
+ "primary_planets": primary_planets,
169
+ "strength_factors": strength_factors,
170
+ "d10_strong_planets": d10_strong_planets,
171
+ }