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
164 lines
9.2 KiB
Python
164 lines
9.2 KiB
Python
"""Source-named Kota/Durga structure, guardians and actual-motion routes.
|
|
|
|
Modern symmetrical plan; no imported external algorithm or predictive score.
|
|
"""
|
|
import argparse
|
|
from copy import deepcopy
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
if __package__ in (None, ''):
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
from scripts.ayanamsa_utils import normalize_ayanamsa_name
|
|
|
|
SOURCE = 'https://vijayalur.com/2011/06/02/kota-chakra/'
|
|
TABLE_SOURCE = 'https://www.indiadivine.org/content/topic/1492433-kota-cakra/'
|
|
ALTERNATIVE_SOURCE = 'https://www.theartofvedicastrology.com/?page_id=389'
|
|
PADA_PROFILE = 'kota_2006_pada_guard_modern_v1'
|
|
STAR_PROFILE = 'kota_vs_nakshatra_guard_modern_v1'
|
|
PLANETS = ('Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu')
|
|
RASHI_LORDS = ('Mars','Venus','Mercury','Moon','Sun','Mercury','Venus','Mars','Jupiter','Saturn','Saturn','Jupiter')
|
|
STAR_LORDS = ('Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury')
|
|
# Numeric lookup facts transcribed from the signed Swee/Pradeep Gaurav thread.
|
|
# 27 ordinary stars, four padas each; source's 'Venusz' normalized to Venus.
|
|
PALA_TABLE = (
|
|
('Venus','Venus','Venus','Moon'), ('Moon',)*4, ('Sun',)*4,
|
|
('Sun','Moon','Moon','Moon'), ('Moon','Moon','Mars','Mars'),
|
|
('Mars','Mars','Mars','Venus'), ('Mars','Mars','Rahu','Rahu'),
|
|
('Rahu','Rahu','Rahu','Mercury'), ('Mercury',)*4, ('Saturn',)*4,
|
|
('Saturn','Mercury','Mercury','Mercury'), ('Mercury','Mercury','Saturn','Saturn'),
|
|
('Saturn','Rahu','Mercury','Mercury'), ('Saturn','Saturn','Moon','Moon'),
|
|
('Moon','Moon','Moon','Jupiter'), ('Jupiter',)*4, ('Jupiter',)*4,
|
|
('Jupiter','Moon','Moon','Moon'), ('Moon','Moon','Saturn','Saturn'),
|
|
('Saturn','Jupiter','Saturn','Mercury'), ('Saturn','Saturn','Venus','Venus'),
|
|
('Mars',)*4, ('Mars',)*4, ('Mars','Rahu','Rahu','Rahu'),
|
|
('Rahu','Rahu','Jupiter','Jupiter'), ('Jupiter','Jupiter','Venus','Venus'),
|
|
('Jupiter','Jupiter','Venus','Venus'),
|
|
)
|
|
LAYERS = ('bahya','prakara','madhya','stambha','madhya','prakara','bahya')
|
|
GATES = ('NE','SE','SW','NW')
|
|
EXITS = ('E','S','W','N')
|
|
|
|
|
|
def _integer(value, maximum, label):
|
|
if type(value) is not int or not 1 <= value <= maximum:
|
|
raise ValueError(f'{label} must be integer1..{maximum}')
|
|
return value
|
|
|
|
|
|
def _ring(value, basis):
|
|
_integer(value,27 if basis=='nakshatra_27' else 28,'star')
|
|
return value+(value>21) if basis=='nakshatra_27' else value
|
|
|
|
|
|
def build_kota_report(*, stars, motions, birth_star, birth_pada, moon_sign,
|
|
star_basis, ayanamsa, variant_profile_id):
|
|
"""stars are transits; birth_star/pada and moon_sign describe natal Moon.
|
|
|
|
All nine motions must be direct/retrograde/stationary. Node motion is input,
|
|
so true-node direct observations are not silently forced retrograde.
|
|
"""
|
|
if variant_profile_id not in (PADA_PROFILE,STAR_PROFILE):
|
|
raise ValueError('explicit supported variant_profile_id required')
|
|
if star_basis not in ('nakshatra_27','abhijit_ring_28'):
|
|
raise ValueError('explicit star_basis required for both birth and transit stars')
|
|
if not isinstance(stars,dict) or set(stars)!=set(PLANETS):
|
|
raise ValueError('all nine transit stars required')
|
|
if not isinstance(motions,dict) or set(motions)!=set(PLANETS):
|
|
raise ValueError('all nine actual motion states required')
|
|
if any(m not in ('direct','retrograde','stationary') for m in motions.values()):
|
|
raise ValueError('motion must be direct, retrograde or stationary')
|
|
if motions['Sun']!='direct' or motions['Moon']!='direct':
|
|
raise ValueError('Sun and Moon must be direct')
|
|
if not isinstance(ayanamsa,str) or not ayanamsa.strip():
|
|
raise ValueError('explicit ayanamsa required')
|
|
mode=normalize_ayanamsa_name(ayanamsa)
|
|
_integer(moon_sign,12,'moon_sign')
|
|
base=_ring(birth_star,star_basis)
|
|
natal27=None if base==22 else base-(base>22)
|
|
if natal27 is None:
|
|
if birth_pada is not None:
|
|
raise ValueError('Abhijit uses no ordinary27-star pada; pass birth_pada=None')
|
|
if moon_sign!=10:
|
|
raise ValueError('Abhijit must have Capricorn Moon')
|
|
else:
|
|
_integer(birth_pada,4,'birth_pada')
|
|
expected=((natal27-1)*4+birth_pada-1)//9+1
|
|
if moon_sign!=expected:
|
|
raise ValueError('moon_sign conflicts with supplied natal star/pada')
|
|
transit={p:_ring(stars[p],star_basis) for p in PLANETS}
|
|
rows=[]
|
|
for i in range(28):
|
|
group,pos=divmod(i,7)
|
|
ring=(base+i-1)%28+1
|
|
road='entry' if pos<3 else 'pillar_junction' if pos==3 else 'exit'
|
|
rows.append(dict(relative_star=i+1,ring_star_index=ring,
|
|
nakshatra_index=None if ring==22 else ring-(ring>22),is_abhijit=ring==22,
|
|
layer=LAYERS[pos],road=road,
|
|
direction=GATES[group] if pos<3 else EXITS[group] if pos>3 else None,
|
|
entry_gate=GATES[group],exit_side=EXITS[group],
|
|
planets=[p for p,star in transit.items() if star==ring]))
|
|
placements={}
|
|
for planet in PLANETS:
|
|
row=deepcopy(next(r for r in rows if planet in r['planets']))
|
|
motion=motions[planet]
|
|
if motion=='stationary': travel='stationary'
|
|
elif row['road']=='pillar_junction': travel='at_pillar'
|
|
elif motion=='retrograde': travel='exit' if row['road']=='entry' else 'entry'
|
|
else: travel=row['road']
|
|
row.update(motion=motion,travel=travel)
|
|
placements[planet]=row
|
|
swami=RASHI_LORDS[moon_sign-1]
|
|
if variant_profile_id==PADA_PROFILE:
|
|
pala='Venus' if natal27 is None else PALA_TABLE[natal27-1][birth_pada-1]
|
|
pala_result=dict(status='calculated',planet=pala,source_url=TABLE_SOURCE)
|
|
elif natal27 is None:
|
|
pala=None
|
|
pala_result=dict(status='blocked',planet=None,reason='No ordinary Vimshottari lord for Abhijit; select named pada profile or supply ordinary natal-star allocation separately')
|
|
else:
|
|
pala=STAR_LORDS[(natal27-1)%9]
|
|
pala_result=dict(status='calculated',planet=pala,source_url=ALTERNATIVE_SOURCE)
|
|
guardians=dict(swami=dict(planet=swami,placement=deepcopy(placements[swami])),
|
|
pala={**pala_result,'placement':deepcopy(placements[pala]) if pala else None})
|
|
# Evaluate only named placement predicates, not outcomes. The two sources
|
|
# prefer different zones; retain both rather than silently unify them.
|
|
checks=[dict(rule_id='vijayalur2011_swami_madhya',matched=placements[swami]['layer']=='madhya',source_url=SOURCE),
|
|
dict(rule_id='gaurav2006_swami_stambha',matched=placements[swami]['layer']=='stambha',source_url=TABLE_SOURCE)]
|
|
if pala:
|
|
checks += [dict(rule_id='vijayalur2011_pala_bahya',matched=placements[pala]['layer']=='bahya',source_url=SOURCE),
|
|
dict(rule_id='gaurav2006_pala_prakara',matched=placements[pala]['layer']=='prakara',source_url=TABLE_SOURCE)]
|
|
data=dict(schema='jyotish.kota_native.v1',status='calculated' if pala else 'partial',
|
|
variant_profile_id=variant_profile_id,tradition_profile='modern_kota_parallel_guard_profiles',
|
|
source_url=SOURCE,ayanamsa=mode,star_basis=star_basis,base_ring_star=base,
|
|
natal_moon_sign=moon_sign,birth_pada=birth_pada,rows=rows,placements=placements,
|
|
guardians=guardians,placement_rule_checks=checks,
|
|
claim_boundary='Source-named structure and rule predicates, not event, health or lifespan conclusions.',
|
|
limitations=['Transit positions and actual motions are supplied, not recomputed ephemerides',
|
|
'27-star basis inserts an empty Abhijit slot;28-ring allocation must be supplied explicitly',
|
|
'Pillar is a shared route junction; stationary is not entry or exit',
|
|
'Guardian preferred-zone rules differ; matched predicates are not predictions',
|
|
'No natural/functional benefic classification, Durga Bhanga verdict, Vedha or Dasha corroboration inferred',
|
|
'Year, balance and child periods not applicable; no automatic customer routing'])
|
|
lines=['# Kota / Durga Chakra','',f'Profile: {variant_profile_id}',
|
|
f'Kota Swami: {swami}; Kota Pala: {pala or "blocked (Abhijit has no ordinary star lord)"}.',data['claim_boundary'],'',
|
|
'| Planet | Star28 | Layer | Road | Motion | Travel |','|---|---:|---|---|---|---|']
|
|
lines += [f"| {p} | {r['ring_star_index']} | {r['layer']} | {r['road']} | {r['motion']} | {r['travel']} |" for p,r in placements.items()]
|
|
lines += ['', 'Named placement checks (not outcomes):']+[f"- {c['rule_id']}: {c['matched']}" for c in checks]
|
|
lines += ['']+[f'- {x}' for x in data['limitations']]
|
|
return dict(status=data['status'],customer_export_admitted=False,evidence_appendix=[data],
|
|
exports=dict(ai_evidence_bundle=deepcopy(data),markdown='\n'.join(lines)))
|
|
|
|
|
|
def main():
|
|
parser=argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--request-json',required=True)
|
|
parser.add_argument('--format',choices=('json','markdown'),default='json')
|
|
args=parser.parse_args()
|
|
result=build_kota_report(**json.loads(Path(args.request_json).read_text()))
|
|
print(result['exports']['markdown'] if args.format=='markdown' else json.dumps(result,ensure_ascii=False,indent=2))
|
|
|
|
|
|
if __name__=='__main__':
|
|
main()
|