Files
Jyotisha/scripts/tajika_kernel.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

108 lines
4.2 KiB
Python

"""Strict seven-planet Tajika aspect kernel.
This module deliberately exposes only the auditable interaction layer. Named
Tajika chains remain blocked until their classical definitions have golden
cases; it never treats nodes as Tajika planets.
"""
from __future__ import annotations
from typing import Any
SEVEN_PLANETS = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn")
DEEPTAMSA = {"Sun": 15.0, "Moon": 12.0, "Mars": 8.0, "Mercury": 7.0, "Jupiter": 9.0, "Venus": 7.0, "Saturn": 9.0}
ASPECT_ANGLES = (0.0, 60.0, 90.0, 120.0, 180.0)
def _signed_angle(value: float) -> float:
return (value + 180.0) % 360.0 - 180.0
def _nearest_aspect(delta: float) -> tuple[float, float]:
candidates = []
for aspect in ASPECT_ANGLES:
for target in ({0.0} if aspect in (0.0, 180.0) else {aspect, -aspect}):
candidates.append((target, _signed_angle(delta - target)))
return min(candidates, key=lambda item: abs(item[1]))
def evaluate_tajika_pair(
left: str,
right: str,
planets: dict[str, dict[str, Any]],
) -> dict[str, Any] | None:
"""Return the shared motion/orb contract for one seven-planet Tajika pair."""
if left not in planets or right not in planets:
return None
left_payload = planets[left]
right_payload = planets[right]
if not isinstance(left_payload, dict) or not isinstance(right_payload, dict):
return None
if "longitude" not in left_payload or "longitude" not in right_payload:
return None
if "speed" not in left_payload or "speed" not in right_payload:
return None
left_lon, right_lon = float(left_payload["longitude"]) % 360, float(right_payload["longitude"]) % 360
aspect, residual = _nearest_aspect(right_lon - left_lon)
orb = (DEEPTAMSA[left] + DEEPTAMSA[right]) / 2.0
if abs(residual) > orb:
return None
relative_speed = float(right_payload["speed"]) - float(left_payload["speed"])
future_residual = _signed_angle(residual + relative_speed)
applying = abs(future_residual) < abs(residual)
return {
"planets": [left, right],
"aspect": abs(aspect),
"residual": round(residual, 6),
"average_deeptamsa": orb,
"motion": "applying" if applying else "separating",
"within_deeptamsa": True,
}
def calculate_tajika_interactions(planets: dict[str, dict[str, Any]]) -> dict[str, Any]:
missing = [planet for planet in SEVEN_PLANETS if planet not in planets or "longitude" not in planets[planet] or "speed" not in planets[planet]]
if missing:
return {
"scope": "tajika_seven_planet_kernel",
"status": "blocked",
"reason": "longitude_and_speed_required_for_all_seven_planets",
"missing": missing,
"nodes_excluded": True,
}
interactions = []
for index, left in enumerate(SEVEN_PLANETS):
for right in SEVEN_PLANETS[index + 1:]:
pair = evaluate_tajika_pair(left, right, planets)
if pair is not None:
interactions.append(pair)
return {
"scope": "tajika_seven_planet_kernel",
"status": "partial",
"nodes_excluded": True,
"interactions": interactions,
"candidate_yogas": [
{
"name": "Ithasala_candidate" if row["motion"] == "applying" else "Easarapha_candidate",
"planets": row["planets"],
"aspect": row["aspect"],
"residual": row["residual"],
"average_deeptamsa": row["average_deeptamsa"],
"motion": row["motion"],
"rule_source": "references/tajika-yoga-complete-guide.md#2.1-2.2",
"status": "partial",
}
for row in interactions
],
"supported_named_yogas": ["Ithasala", "Easarapha"],
"blocked_named_yogas": ["Nakta", "Yamaya", "Manahoo", "Kamboola"],
"authority_lane": "scripts/tajika_named_yoga_authority.py",
"boundary": (
"Seven-planet aspect, Deeptamsa, and applying/separating evidence are "
"admissible for governed Ithasala/Easarapha authority. Broader named-yoga "
"chains and event verdicts remain blocked pending classic golden cases."
),
}