Files
Jyotisha/scripts/rangacharya.py
2026-07-17 01:03:50 +08:00

144 lines
4.5 KiB
Python

"""Experimental Rangacharya/Jaimini variant.
All outputs are blocked from adjudication until formula-level validation passes.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Dict, Mapping
SIGNS = [
"Aries",
"Taurus",
"Gemini",
"Cancer",
"Leo",
"Virgo",
"Libra",
"Scorpio",
"Sagittarius",
"Capricorn",
"Aquarius",
"Pisces",
]
SOURCE_CARDS_PATH = Path(__file__).resolve().parent.parent / "references" / "rangacharya_source_cards.json"
class RangacharyaValidationError(RuntimeError):
pass
def _source_cards() -> Dict[str, Dict[str, Any]]:
try:
data = json.loads(SOURCE_CARDS_PATH.read_text(encoding="utf-8"))
except (FileNotFoundError, json.JSONDecodeError):
return {}
return {str(card.get("id")): dict(card) for card in data.get("cards", []) if card.get("id")}
def _card_meta(card_id: str) -> Dict[str, Any]:
card = _source_cards().get(card_id, {})
status = str(card.get("status") or "blocked")
meta = {
"source_card_id": card_id,
"source_card_status": status,
"validation_status": status,
"adjudication_enabled": False,
}
if status != "source_verified":
meta["blocked_reason"] = card.get("blocked_reason") or "source card is not verified for adjudication"
return meta
def _sign_name(index: int) -> str:
return SIGNS[index % 12]
def _placeholder_pada(label: str, asc_sign_idx: int, source_house: int) -> Dict[str, Any]:
sign_idx = (asc_sign_idx + source_house - 1) % 12
return {
"label": label,
"sign": _sign_name(sign_idx),
"sign_index": sign_idx,
"source_house": source_house,
"note": "Rangacharya formula pending source-card implementation",
**_card_meta("rangacharya_core_arudha"),
}
def calc_rangacharya_variant(asc_sign_idx: int, planet_longitudes: Mapping[str, float]) -> Dict[str, Any]:
asc_sign_idx %= 12
arudha_padas = {
"AL": _placeholder_pada("AL", asc_sign_idx, 1),
"A7": _placeholder_pada("A7", asc_sign_idx, 7),
"A10": _placeholder_pada("A10", asc_sign_idx, 10),
"UL": _placeholder_pada("UL", asc_sign_idx, 12),
}
return {
"variant": "rangacharya",
"status": "experimental_not_for_adjudication",
"adjudication_enabled": False,
"source_status": "transcribed",
"active_lagna": {
"sign": _sign_name(asc_sign_idx),
**_card_meta("active_effective_lagna"),
},
"effective_lagna": {
"sign": _sign_name(asc_sign_idx),
**_card_meta("active_effective_lagna"),
},
"arudha_padas": arudha_padas,
"input_planets_present": sorted(planet_longitudes),
}
def _flatten(prefix: str, value: Any) -> Dict[str, Any]:
if not isinstance(value, dict):
return {prefix: value}
rows: Dict[str, Any] = {}
for key, child in value.items():
child_key = f"{prefix}.{key}" if prefix else str(key)
rows.update(_flatten(child_key, child))
return rows
def diff_current_vs_rangacharya(current: Mapping[str, Any], variant: Mapping[str, Any]) -> Dict[str, Any]:
current_flat = _flatten("", dict(current))
variant_flat = _flatten("", dict(variant.get("arudha_padas", variant)))
differences = []
for key in sorted(set(current_flat) | set(variant_flat)):
current_value = current_flat.get(key)
variant_value = variant_flat.get(key)
if current_value != variant_value:
differences.append({"key": key, "current": current_value, "rangacharya": variant_value})
return {
"current_algorithm": "current_jaimini",
"variant_algorithm": "rangacharya",
"adjudication_enabled": False,
"differences": differences,
}
def validation_summary(result: Mapping[str, Any]) -> Dict[str, Any]:
statuses = []
for key, value in _flatten("", dict(result)).items():
if key.endswith("validation_status"):
statuses.append(str(value))
blocking = sorted({status for status in statuses if status != "adjudication_enabled"})
return {
"adjudication_enabled": bool(result.get("adjudication_enabled")) and not blocking,
"blocking_statuses": blocking,
}
def assert_adjudication_allowed(result: Mapping[str, Any]) -> None:
summary = validation_summary(result)
if not summary["adjudication_enabled"]:
raise RangacharyaValidationError(
"Rangacharya variant is not adjudication-enabled; validation gates are incomplete"
)