Port from yinduzhanxing 5fb8379c (MIT): Naisargika, Kaala, Karaka, Tara, Lagnamsaka, Yogardha, Mandooka, Paryaaya and Padanadhamsa dashas; Sarvatobhadra, Kota and Kalanala chakras; Nakshatra Latta; Pachakadi; Raman Amsayu and Pindayu. Producers and their upstream tests are copied; scripts/upstream_technique_layers.py feeds them our natal and transit positions and attaches chart.consultation_native_layers.upstream_techniques. - dasha_registry: kaala and padanadhamsa keys (35 -> 37); the nine ported dashas are served by compute_dasha or fail closed with upstream's reason. - technique_registry.json: 16 partial entries; README badges follow. - Evidence lookup section `upstream_techniques` in the consultation tool. - Boundaries: Kalanala carries no PyJHora (AGPL) grid coordinates; Padanadhamsa/Lagnamsaka run on our Narayana engine (no Rath Table 12, PVR co-lord variants or second cycle). PyJHora observers and the Amsayu worked-example scaffold live in scripts/research (root .py budget). - PyJHora black-box cross-checks: Naisargika (lords/years) and Kaala (seed segment and fraction within 5e-4) on a public chart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoyn7Hj3LCW3BUs5bZ8
83 lines
5.1 KiB
Python
83 lines
5.1 KiB
Python
"""Surya/Chandra Kalanala star-slot placement.
|
|
|
|
Ported from upstream yinduzhanxing (5fb8379c) scripts/kalanala_chakra.py with
|
|
one license-boundary change: upstream embeds the PyJHora (AGPL) UI grid
|
|
coordinates (src/jhora/ui/chakra.py ChandraKalanala / SuryaKalanala
|
|
`rasi_labels`). This repo keeps PyJHora black-box only (SKILL.md: GPL / AGPL
|
|
are benchmarked, never copied), so the coordinate tables are not carried here.
|
|
|
|
The native part — the absolute 28-star target (Abhijit slot after star 21) and
|
|
the slot relative to the base star — is plain arithmetic and is computed here.
|
|
Grid coordinates are attached only when a caller passes `positions` read from
|
|
an installed PyJHora by `read_pyjhora_kalanala_positions` (a subprocess AST
|
|
read, like scripts/research/sarvatobhadra_external.py); production never does that.
|
|
"""
|
|
from copy import deepcopy
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
|
|
try:
|
|
from scripts.ayanamsa_utils import normalize_ayanamsa_name
|
|
except ModuleNotFoundError: # pragma: no cover - scripts/ on sys.path
|
|
from ayanamsa_utils import normalize_ayanamsa_name
|
|
|
|
SOURCE = 'https://github.com/naturalstupid/PyJHora/blob/main/src/jhora/ui/chakra.py'
|
|
PLANETS = ('Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu')
|
|
_CLASS_NAMES = {'chandra': 'ChandraKalanala', 'surya': 'SuryaKalanala'}
|
|
|
|
|
|
def _validate(stars, base_star, ayanamsa):
|
|
if not isinstance(stars, dict) or set(stars) != set(PLANETS): raise ValueError('all nine planetary star indices required')
|
|
if type(base_star) is not int or not 1 <= base_star <= 28: raise ValueError('base_star must be 1..28')
|
|
if not isinstance(ayanamsa,str) or not ayanamsa.strip(): raise ValueError('explicit ayanamsa required')
|
|
for k,v in stars.items():
|
|
if type(v) is not int or not 1 <= v <= 27: raise ValueError(f'{k} star index must be 1..27')
|
|
return normalize_ayanamsa_name(ayanamsa)
|
|
|
|
|
|
def read_pyjhora_kalanala_positions(kind: str, timeout: int = 30) -> dict:
|
|
"""Black-box read of the installed PyJHora slot->(x, y) literal; never imports Qt."""
|
|
if kind not in _CLASS_NAMES: raise ValueError('kind must be chandra or surya')
|
|
code = '''import ast, json, importlib.util, sys
|
|
from pathlib import Path
|
|
p = Path(importlib.util.find_spec('jhora').origin).parent / 'ui/chakra.py'
|
|
tree = ast.parse(p.read_bytes())
|
|
cls = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == sys.argv[1])
|
|
init = next(n for n in cls.body if isinstance(n, ast.FunctionDef) and n.name == '__init__')
|
|
hits = [n.value for n in init.body if isinstance(n, ast.Assign) and isinstance(n.value, ast.Dict)
|
|
and any(isinstance(t, ast.Attribute) and t.attr == 'rasi_labels' for t in n.targets)]
|
|
if len(hits) != 1: raise ValueError('ambiguous layout definition')
|
|
print(json.dumps({str(k): list(v) for k, v in ast.literal_eval(hits[0]).items()}))
|
|
'''
|
|
run = subprocess.run([sys.executable, '-c', code, _CLASS_NAMES[kind]], text=True,
|
|
capture_output=True, timeout=timeout, check=True)
|
|
return {int(k): tuple(v) for k, v in json.loads(run.stdout).items()}
|
|
|
|
|
|
def build_kalanala_report(*, stars: dict, base_star: int, ayanamsa: str, kind: str='chandra',
|
|
positions: dict | None = None) -> dict:
|
|
mode = _validate(stars,base_star,ayanamsa)
|
|
if kind not in ('chandra','surya'): raise ValueError('kind must be chandra or surya')
|
|
mapped = None
|
|
if positions is not None:
|
|
if set(positions) != set(range(1, 29)): raise ValueError('positions must cover slots 1..28')
|
|
mapped = {((base_star-1)+(slot-1))%28+1: tuple(xy) for slot,xy in positions.items()}
|
|
rows=[]
|
|
for planet, star in stars.items():
|
|
# Upstream first rotates the star labels, then looks up the planet's
|
|
# absolute star (inserting the Abhijit slot after original star 21).
|
|
# Rotating the planet as well would cancel the base-star setting.
|
|
target = star + 1 if star > 21 else star
|
|
slot = (target - base_star) % 28 + 1
|
|
rows.append({'planet':planet,'source_star_index':star,'target_star_index':target,
|
|
'layout_slot':slot,'grid':mapped[target] if mapped else None})
|
|
evidence={'schema':'jyotish.kalanala_chakra.v1','status':'calculated' if mapped else 'partial',
|
|
'variant_profile_id':f'{kind}_kalanala_pyjhora_layout_v1','tradition_profile':'kalanala_external_pyjhora',
|
|
'kind':kind,'base_star':base_star,'ayanamsa':mode,'rows':rows,'source_url':SOURCE,
|
|
'grid_status':'external_pyjhora_layout' if mapped else 'not_carried_agpl_boundary',
|
|
'claim_boundary':'Layout and star placement only. No Vedha, transit activation, event timing or certainty conclusion.'}
|
|
lines=[f'# {kind.title()} Kalanala Chakra','',evidence['claim_boundary'],'','| Planet | Source star | Target star | Slot | Grid |','|---|---:|---:|---:|---|']
|
|
lines += [f'| {r["planet"]} | {r["source_star_index"]} | {r["target_star_index"]} | {r["layout_slot"]} | {r["grid"] or "—"} |' for r in rows]
|
|
return {'status':evidence['status'],'customer_export_admitted':False,'evidence_appendix':[evidence],'exports':{'ai_evidence_bundle':deepcopy(evidence),'markdown':'\n'.join(lines)}}
|