Files
Jyotisha/scripts/ashtottari_rule_profiles.py
T
Jesse_Chen f224146348
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled
feat(upstream): merge a6f47abd engine, MCP, and orchestrator
Three-way-merge calculation modules and pl9-export into the product fork while keeping commercial API routes, Raman ayanamsa, and the consultation contract as a keypath superset.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 20:28:57 +08:00

91 lines
3.8 KiB
Python

"""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}"