Files
Jyotisha/references/open_source_sources/dashaflow/muhurtha.py
T
732642856 f83db2fac1 Enhance Jyotish validation and Jaimini modules
- add external validation reports and open-source comparison references

- integrate Jaimini arudha/graha pada, enhanced argala, and additional synastry kutas

- update skill docs and capability matrices

- add smoke tests for open-source integrations
2026-06-10 20:50:52 +08:00

237 lines
8.1 KiB
Python

"""
Muhurtha — Electional Astrology (BPHS)
Evaluates auspiciousness of a given date/time for specific activities.
Uses Panchang elements, planetary positions, and classical rules.
"""
from .constants import ZODIAC_SIGNS, SIGN_LORDS
# ==========================================
# UNIVERSAL AVOIDANCE RULES
# ==========================================
# Inauspicious Panchang Yoga indices (0-indexed)
BAD_YOGAS = {0, 5, 8, 9, 12, 14, 16, 18, 26}
# Universally avoided lunar days (tithis)
BAD_TITHIS = {4, 6, 8, 12, 14, 30}
# Universally avoided nakshatras
BAD_NAKSHATRAS = {"Bharani", "Krittika"}
# ==========================================
# ACTIVITY-SPECIFIC RULES
# ==========================================
MARRIAGE_RULES = {
"good_nakshatras": {"Rohini", "Mrigashira", "Magha", "Uttara Phalguni", "Hasta",
"Swati", "Anuradha", "Moola", "Uttara Ashadha", "Uttara Bhadrapada", "Revati"},
"good_tithis": {2, 3, 5, 7, 10, 11, 13},
"good_lagnas": {"Taurus", "Gemini", "Cancer", "Virgo", "Libra", "Sagittarius"},
}
TRAVEL_RULES = {
"good_nakshatras": {"Ashwini", "Mrigashira", "Punarvasu", "Pushya", "Hasta",
"Anuradha", "Shravana", "Dhanishta", "Revati"},
"good_tithis": {2, 3, 5, 7, 10, 11, 13},
"good_lagnas": {"Aries", "Taurus", "Cancer", "Leo", "Libra", "Sagittarius"},
}
BUSINESS_RULES = {
"good_nakshatras": {"Ashwini", "Rohini", "Punarvasu", "Pushya", "Uttara Phalguni",
"Hasta", "Chitra", "Swati", "Anuradha", "Shravana", "Dhanishta", "Revati"},
"good_tithis": {2, 3, 5, 7, 10, 11, 13},
"good_weekdays": {"Monday", "Wednesday", "Thursday", "Friday"},
"moon_signs": {"Taurus", "Cancer", "Virgo", "Libra", "Sagittarius", "Pisces"},
}
EDUCATION_RULES = {
"good_nakshatras": {"Ashwini", "Punarvasu", "Pushya", "Hasta", "Chitra",
"Swati", "Shravana", "Dhanishta", "Shatabhisha", "Revati"},
"good_tithis": {2, 3, 5, 7, 10, 11, 13},
"good_lagnas": {"Gemini", "Virgo", "Sagittarius", "Pisces"},
}
HOUSE_ENTRY_RULES = {
"good_nakshatras": {"Rohini", "Uttara Phalguni", "Uttara Ashadha", "Uttara Bhadrapada",
"Shravana", "Dhanishta", "Revati", "Ashwini", "Mrigashira"},
"good_tithis": {2, 3, 5, 7, 10, 11, 13},
"good_weekdays": {"Monday", "Wednesday", "Thursday", "Friday"},
}
MEDICAL_RULES = {
"good_nakshatras": {"Ashwini", "Rohini", "Mrigashira", "Pushya", "Hasta",
"Chitra", "Swati", "Anuradha", "Shravana", "Revati"},
"good_tithis": {2, 3, 5, 7, 10, 11, 13},
"good_weekdays": {"Saturday", "Monday"},
}
ACTIVITY_RULES = {
"marriage": MARRIAGE_RULES,
"travel": TRAVEL_RULES,
"business": BUSINESS_RULES,
"education": EDUCATION_RULES,
"house_entry": HOUSE_ENTRY_RULES,
"medical": MEDICAL_RULES,
}
def _check_panchanga_suddhi(panchang):
"""Check universal Panchang purity (5-fold). Returns list of issues."""
issues = []
tithi_num = panchang.get("tithi", {}).get("number", 0)
if tithi_num in BAD_TITHIS:
issues.append(f"Inauspicious tithi: {panchang['tithi'].get('name', tithi_num)}")
nak_name = panchang.get("nakshatra", {}).get("name", "")
if nak_name in BAD_NAKSHATRAS:
issues.append(f"Inauspicious nakshatra: {nak_name}")
yoga_idx = panchang.get("yoga", {}).get("index", -1)
if yoga_idx in BAD_YOGAS:
issues.append(f"Inauspicious yoga: {panchang['yoga'].get('name', yoga_idx)}")
return issues
def _check_marriage_doshas(planets):
"""Check marriage-specific rejection doshas (per BPHS)."""
doshas = []
# Sagraha Dosha: Moon conjunct any planet
moon = planets.get("Moon", {})
moon_sign = moon.get("sign", "")
for p_name, pd in planets.items():
if p_name != "Moon" and pd.get("sign") == moon_sign:
doshas.append(f"Sagraha Dosha: Moon conjunct {p_name} in {moon_sign}")
break
# Moon in 6th, 8th, or 12th
moon_house = moon.get("house", 0)
if moon_house in (6, 8, 12):
doshas.append(f"Shashtashta Dosha: Moon in house {moon_house}")
# Venus in 6th
venus = planets.get("Venus", {})
if venus.get("house") == 6:
doshas.append("Bhrigupta Shatka: Venus in 6th house")
# Mars in 8th
mars = planets.get("Mars", {})
if mars.get("house") == 8:
doshas.append("Kujaasthama: Mars in 8th house")
return doshas
def evaluate_muhurtha(activity, panchang, planets=None, lagna_sign=None):
"""
Evaluate auspiciousness of a moment for a given activity.
Parameters
----------
activity : str
One of: 'marriage', 'travel', 'business', 'education', 'house_entry', 'medical'
panchang : dict
Panchang data from calculate_panchang()
planets : dict, optional
Planet positions (for marriage dosha checks)
lagna_sign : str, optional
Rising sign at the moment
Returns
-------
dict with verdict, reasons, and score
"""
rules = ACTIVITY_RULES.get(activity)
if not rules:
return {"verdict": "error", "reason": f"Unknown activity: {activity}",
"supported_activities": list(ACTIVITY_RULES.keys())}
positive = []
negative = []
# 1. Universal Panchanga Suddhi
panchang_issues = _check_panchanga_suddhi(panchang)
negative.extend(panchang_issues)
# 2. Activity-specific nakshatra
nak_name = panchang.get("nakshatra", {}).get("name", "")
good_naks = rules.get("good_nakshatras", set())
if nak_name in good_naks:
positive.append(f"Auspicious nakshatra for {activity}: {nak_name}")
elif nak_name and nak_name not in BAD_NAKSHATRAS:
negative.append(f"Nakshatra {nak_name} is not ideal for {activity}")
# 3. Activity-specific tithi
tithi_num = panchang.get("tithi", {}).get("number", 0)
good_tithis = rules.get("good_tithis", set())
if tithi_num in good_tithis:
positive.append(f"Auspicious tithi: {panchang['tithi'].get('name', tithi_num)}")
# 4. Weekday check
good_weekdays = rules.get("good_weekdays")
if good_weekdays:
vara = panchang.get("vara", {}).get("name", "")
if vara in good_weekdays:
positive.append(f"Auspicious weekday: {vara}")
else:
negative.append(f"Weekday {vara} is not ideal for {activity}")
# 5. Lagna check
good_lagnas = rules.get("good_lagnas")
if good_lagnas and lagna_sign:
if lagna_sign in good_lagnas:
positive.append(f"Auspicious Lagna: {lagna_sign}")
else:
negative.append(f"Lagna {lagna_sign} is not ideal for {activity}")
# 6. Moon sign check (for business)
moon_signs = rules.get("moon_signs")
if moon_signs and planets:
moon_sign = planets.get("Moon", {}).get("sign", "")
if moon_sign in moon_signs:
positive.append(f"Moon in auspicious sign for {activity}: {moon_sign}")
else:
negative.append(f"Moon in {moon_sign} is not ideal for {activity}")
# 7. Marriage-specific dosha checks
if activity == "marriage" and planets:
doshas = _check_marriage_doshas(planets)
for d in doshas:
negative.append(f"DOSHA: {d}")
# 8th house check (should be empty for marriage, medical, house_entry)
if activity in ("marriage", "medical", "house_entry") and planets:
for p_name, pd in planets.items():
if p_name not in ("Rahu", "Ketu") and pd.get("house") == 8:
negative.append(f"Planet in 8th house: {p_name}")
break
# Scoring
score = len(positive) * 10 - len(negative) * 15
has_hard_reject = any("DOSHA:" in n for n in negative)
if has_hard_reject:
verdict = "inauspicious"
elif len(negative) == 0 and len(positive) >= 2:
verdict = "auspicious"
elif len(positive) > len(negative):
verdict = "mixed_favorable"
elif len(negative) > len(positive):
verdict = "inauspicious"
else:
verdict = "mixed"
return {
"activity": activity,
"verdict": verdict,
"score": max(0, score),
"positive_factors": positive,
"negative_factors": negative,
"total_positive": len(positive),
"total_negative": len(negative),
}