"""Shared Ashtottari applicability rule-family helpers. This module does not declare a single verified truth. It only centralizes the repo's current competing rule families so runtime surfaces can expose them consistently and audits can compare them without hidden drift. """ from __future__ import annotations from typing import Any RULE_FAMILY_MOON_NAKSHATRA_PLUS_PAKSHA = "moon_nakshatra_plus_paksha" RULE_FAMILY_RAHU_NOT_IN_KENDRA = "rahu_not_in_kendra" RULE_FAMILY_RAHU_FROM_LAGNA_LORD = "rahu_from_lagna_lord_kendra_trikona_plus_paksha_daynight" SHUKLA_PAKSHA_NAKSHATRAS = {0, 4, 6, 13, 20, 22} KRISHNA_PAKSHA_NAKSHATRAS = {3, 5, 7, 8, 9, 26} ALLOWED_RAHU_HOUSES_FROM_LAGNA_LORD = {4, 5, 7, 9, 10} KENDRA_HOUSES = {1, 4, 7, 10} def evaluate_moon_nakshatra_plus_paksha_rule( moon_nakshatra_index: int | None, is_shukla_paksha: bool | None, ) -> tuple[bool, str]: if moon_nakshatra_index is None or is_shukla_paksha is None: return False, "Moon-nakshatra/Paksha rule needs moon_nakshatra_index and is_shukla_paksha." allowed = SHUKLA_PAKSHA_NAKSHATRAS if is_shukla_paksha else KRISHNA_PAKSHA_NAKSHATRAS if moon_nakshatra_index in allowed: return True, "Moon-nakshatra/Paksha rule matched the current whitelist." return False, "Moon-nakshatra/Paksha rule did not match the current whitelist." def evaluate_rahu_not_in_kendra_rule(rahu_house: int | None) -> tuple[bool, str]: if rahu_house is None: return False, "Rahu-not-in-Kendra rule needs rahu_house." if rahu_house not in KENDRA_HOUSES: return True, f"Rahu-not-in-Kendra rule matched: Rahu house {rahu_house} is outside 1/4/7/10." return False, f"Rahu-not-in-Kendra rule blocked: Rahu house {rahu_house} is in 1/4/7/10." def evaluate_rahu_from_lagna_lord_rule(birth_info: dict[str, Any]) -> tuple[bool, str]: birth_is_daytime = birth_info.get("birth_is_daytime") is_shukla = birth_info.get("is_shukla_paksha") rahu_sign_index = birth_info.get("rahu_sign_index") lagna_lord_sign_index = birth_info.get("lagna_lord_sign_index") missing = [ name for name, value in ( ("birth_is_daytime", birth_is_daytime), ("is_shukla_paksha", is_shukla), ("rahu_sign_index", rahu_sign_index), ("lagna_lord_sign_index", lagna_lord_sign_index), ) if value is None ] if missing: return False, f"Candidate Ashtottari rule needs {', '.join(missing)}." if birth_is_daytime and not is_shukla: pass elif (not birth_is_daytime) and is_shukla: pass else: return False, ( "Candidate Ashtottari gate expects day birth in Krishna Paksha or " "night birth in Shukla Paksha." ) house_from_lagna_lord = ((int(rahu_sign_index) - int(lagna_lord_sign_index)) % 12) + 1 if house_from_lagna_lord not in ALLOWED_RAHU_HOUSES_FROM_LAGNA_LORD: return False, ( "Rahu is not in Kendra/Trikona from the Lagna lord under the frozen candidate rule." ) return True, ( f"Candidate rule matched: {('day' if birth_is_daytime else 'night')} birth, " f"{'Krishna' if not is_shukla else 'Shukla'} Paksha, Rahu {house_from_lagna_lord}th from Lagna lord." ) def evaluate_rule_family(rule_family: str, payload: dict[str, Any]) -> tuple[bool, str]: if rule_family == RULE_FAMILY_MOON_NAKSHATRA_PLUS_PAKSHA: return evaluate_moon_nakshatra_plus_paksha_rule( payload.get("moon_nakshatra_index"), payload.get("is_shukla_paksha"), ) if rule_family == RULE_FAMILY_RAHU_NOT_IN_KENDRA: return evaluate_rahu_not_in_kendra_rule(payload.get("rahu_house")) if rule_family == RULE_FAMILY_RAHU_FROM_LAGNA_LORD: return evaluate_rahu_from_lagna_lord_rule(payload) return False, f"Unknown Ashtottari rule family: {rule_family}"