Issue #94 T1. Place goel_jaimini_strength, trikona_raman, sthira_brahma_goel_short_method, and trikona_dasha from upstream 5fb8379c under scripts/. The only local edit is the SOURCE book citation. Raise ROOT_PY_BUDGET from 448 to 452. Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
167 lines
10 KiB
Python
167 lines
10 KiB
Python
"""Separate Trikona profiles: Raghava Bhatta MD and Raman confirmed-parent AD."""
|
|
import argparse
|
|
from copy import deepcopy
|
|
from datetime import datetime, timedelta, timezone
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
if __package__ in (None, ''):
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
from scripts.narayana_dasha import SIGNS, SIGN_LORDS, _NARAYANA_DIGNITY
|
|
|
|
PROFILE = 'trikona_raghava_bhatta_explicit_seed_md_v1'
|
|
SOURCE = 'https://saptarishisastrology.com/trikona-dasa-a-critical-review-by-iranganti-rangacharya/'
|
|
RAMAN_AD_PROFILE = 'trikona_raman_94_confirmed_full_md_ad_v1'
|
|
RAMAN_SOURCE = 'https://www.scribd.com/doc/127930441/Studies-in-Jaimini-Astrology-by-b-v-Raman'
|
|
|
|
|
|
def build_trikona_raman_antardasa_report(*, md_sign_idx, full_md_years,
|
|
full_md_start, parent_source, parent_profile, year_days, profile):
|
|
"""Raman section94 AD only; a confirmed, untruncated parent is input."""
|
|
if profile != RAMAN_AD_PROFILE or parent_profile != 'raman_studies_jaimini_92_93':
|
|
raise ValueError('explicit Raman AD and Raman sections92/93 parent profiles required')
|
|
if type(md_sign_idx) is not int or not 0 <= md_sign_idx < 12:
|
|
raise ValueError('md_sign_idx must be integer 0..11')
|
|
if not isinstance(parent_source, str) or not parent_source.strip():
|
|
raise ValueError('parent_source must identify the confirmed full MD, not birth balance')
|
|
for name, value, low, high in [('full_md_years', full_md_years, 0, 12),
|
|
('year_days', year_days, 300, 400)]:
|
|
if (isinstance(value, bool) or not isinstance(value, (int, float)) or
|
|
not math.isfinite(value) or not low <= value <= high or value == 0):
|
|
raise ValueError(f'{name} outside supported finite range')
|
|
if not isinstance(full_md_start, str):
|
|
raise ValueError('full_md_start must be aware ISO datetime')
|
|
anchor = datetime.fromisoformat(full_md_start.replace('Z', '+00:00'))
|
|
if anchor.tzinfo is None or anchor.utcoffset() is None:
|
|
raise ValueError('full_md_start must include UTC offset')
|
|
anchor = anchor.astimezone(timezone.utc)
|
|
# Raman section155 specifies these groups; neither parity nor ninth-from-parent.
|
|
direction = 1 if md_sign_idx in (0, 1, 2, 6, 7, 8) else -1
|
|
offsets = [group + trine for group in range(4) for trine in (0, 4, 8)]
|
|
try:
|
|
edges = [(anchor + timedelta(days=full_md_years * year_days * i / 12)).isoformat()
|
|
for i in range(13)]
|
|
except OverflowError as exc:
|
|
raise ValueError('calendar boundary outside supported datetime range') from exc
|
|
periods = [dict(sign_idx=(md_sign_idx + direction * offset) % 12,
|
|
sign=SIGNS[(md_sign_idx + direction * offset) % 12],
|
|
years=full_md_years / 12, start_utc=edges[i], end_utc=edges[i + 1],
|
|
start_age=full_md_years * i / 12, end_age=full_md_years * (i + 1) / 12)
|
|
for i, offset in enumerate(offsets)]
|
|
result = dict(status='parameter_sensitive', variant_profile_id=profile,
|
|
tradition_profile='bv_raman_studies_in_jaimini_astrology', source=RAMAN_SOURCE,
|
|
source_locator='section94 pp61-62; Savya groups section155 p127',
|
|
parent=dict(sign_idx=md_sign_idx, sign=SIGNS[md_sign_idx], years=full_md_years,
|
|
start_utc=edges[0], end_utc=edges[-1], source=parent_source,
|
|
profile=parent_profile, status='supplied_confirmed_full_parent'),
|
|
year_days=year_days, direction=direction, periods=periods,
|
|
child_periods=dict(level='AD', status='executed', count=12),
|
|
pratyantardasa=dict(status='blocked', reason='section94_does_not_establish_PD'),
|
|
limits=['Parent seed, duration and full start supplied, not calculated or independently verified',
|
|
'No birth-balance clipping; do not supply a residual MD as a full parent',
|
|
'Fixed year_days is explicit caller convention, not a source date oracle',
|
|
'Raman AD only; never attach to Raghava Bhatta or PyJHora parents automatically',
|
|
'No PD, event inference or default customer export'])
|
|
lines = ['# Trikona — Raman confirmed full MD / AD', '', f'Profile: {profile}',
|
|
f'Source: {RAMAN_SOURCE} — {result["source_locator"]}', '',
|
|
'| AD | Years | Start UTC | End UTC |', '| --- | --- | --- | --- |']
|
|
lines += [f"| {p['sign']} | {p['years']} | {p['start_utc']} | {p['end_utc']} |" for p in periods]
|
|
lines += ['', *result['limits'], '', '```json', json.dumps(result, ensure_ascii=False, indent=2), '```']
|
|
return dict(status=result['status'], customer_export_admitted=False,
|
|
evidence_appendix=[result], exports=dict(markdown='\n'.join(lines),
|
|
ai_evidence_bundle=deepcopy(result)))
|
|
|
|
|
|
def build_trikona_native_report(*, lagna_sign_idx, seed_sign_idx, seed_source,
|
|
planet_longitudes, epoch, year_days, profile):
|
|
if profile != PROFILE:
|
|
raise ValueError('explicit Raghava Bhatta profile required')
|
|
if any(type(v) is not int or not 0 <= v < 12 for v in (lagna_sign_idx, seed_sign_idx)):
|
|
raise ValueError('sign indices must be integers 0..11')
|
|
if (seed_sign_idx - lagna_sign_idx) % 12 not in (0, 4, 8):
|
|
raise ValueError('seed must be Lagna, fifth or ninth')
|
|
if not isinstance(seed_source, str) or not seed_source.strip():
|
|
raise ValueError('seed_source must explain the supplied strength decision')
|
|
required = set(SIGN_LORDS.values())
|
|
if not isinstance(planet_longitudes, dict) or set(planet_longitudes) != required:
|
|
raise ValueError('exactly seven classical planetary longitudes required; no node co-lords')
|
|
if any(isinstance(v, bool) or not isinstance(v, (int, float)) or
|
|
not math.isfinite(v) or not 0 <= v < 360 for v in planet_longitudes.values()):
|
|
raise ValueError('planetary longitudes must be finite in [0,360)')
|
|
if isinstance(year_days, bool) or not isinstance(year_days, (int, float)) or not math.isfinite(year_days) or not 300 <= year_days <= 400:
|
|
raise ValueError('explicit year_days 300..400 required')
|
|
if not isinstance(epoch, str):
|
|
raise ValueError('aware ISO epoch required')
|
|
anchor = datetime.fromisoformat(epoch.replace('Z', '+00:00'))
|
|
if anchor.tzinfo is None or anchor.utcoffset() is None:
|
|
raise ValueError('epoch must include UTC offset')
|
|
anchor = anchor.astimezone(timezone.utc)
|
|
direction = 1 if seed_sign_idx % 2 == 0 else -1
|
|
if seed_sign_idx % 3 == 0:
|
|
offsets = list(range(12))
|
|
elif seed_sign_idx % 3 == 1:
|
|
offsets = [5 * i for i in range(12)]
|
|
else:
|
|
offsets = [group + trine for group in range(4) for trine in (0, 4, 8)]
|
|
periods = []
|
|
age = 0
|
|
for offset in offsets:
|
|
sign = (seed_sign_idx + direction * offset) % 12
|
|
lord = SIGN_LORDS[SIGNS[sign]]
|
|
lord_sign = int(planet_longitudes[lord] // 30)
|
|
count_direction = 1 if sign % 2 == 0 else -1
|
|
count = ((lord_sign - sign) * count_direction) % 12 + 1
|
|
dignity = _NARAYANA_DIGNITY[lord]
|
|
adjustment = int(lord_sign in dignity['exalted']) - int(lord_sign in dignity['debilitated'])
|
|
years = 12 if lord_sign == sign else min(12, count + adjustment)
|
|
end = age + years
|
|
try:
|
|
start_utc = (anchor + timedelta(days=age * year_days)).isoformat()
|
|
end_utc = (anchor + timedelta(days=end * year_days)).isoformat()
|
|
except OverflowError as exc:
|
|
raise ValueError('calendar boundary outside supported datetime range') from exc
|
|
periods.append(dict(sign_idx=sign, sign=SIGNS[sign], lord=lord,
|
|
lord_sign_idx=lord_sign, inclusive_count=count, dignity_adjustment=adjustment,
|
|
years=years, start_age=age, end_age=end, start_utc=start_utc, end_utc=end_utc))
|
|
age = end
|
|
result = dict(status='parameter_sensitive', variant_profile_id=PROFILE,
|
|
tradition_profile='raghava_bhatta_as_presented_by_rangacharya', source=SOURCE,
|
|
lagna_sign_idx=lagna_sign_idx, seed_sign_idx=seed_sign_idx,
|
|
seed_selection=dict(status='supplied_not_calculated', source=seed_source),
|
|
epoch_utc=anchor.isoformat(), year_days=year_days, periods=periods,
|
|
child_periods=dict(status='blocked', reason='method_specific_AD_PD_rules_not_established'),
|
|
limits=['Supplied strongest trine; strength selection is not calculated',
|
|
'Full first MD begins at explicit epoch; no fractional birth balance inferred',
|
|
'Fixed-day calendar is caller convention, not verified source dates',
|
|
'First cycle only; no automatic customer export or event certainty'])
|
|
lines = ['# Trikona — Raghava Bhatta / Rangacharya method 2', '',
|
|
'Status: parameter_sensitive', f'Source: {SOURCE}', f'Profile: {PROFILE}', '',
|
|
'| Sign | Years | Start UTC | End UTC |', '| --- | --- | --- | --- |']
|
|
lines += [f"| {p['sign']} | {p['years']} | {p['start_utc']} | {p['end_utc']} |" for p in periods]
|
|
lines += ['', *result['limits'], 'AD/PD: blocked — method-specific rules not established',
|
|
'', '```json', json.dumps(result, ensure_ascii=False, indent=2), '```']
|
|
return dict(status=result['status'], customer_export_admitted=False,
|
|
evidence_appendix=[result], exports=dict(markdown='\n'.join(lines), ai_evidence_bundle=deepcopy(result)))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--request', type=Path, required=True)
|
|
parser.add_argument('--mode', choices=('method2-md', 'raman-confirmed-md-ad', 'raman-native'), default='method2-md')
|
|
parser.add_argument('--format', choices=('json', 'markdown'), default='json')
|
|
args = parser.parse_args()
|
|
builder = (build_trikona_raman_antardasa_report if args.mode == 'raman-confirmed-md-ad'
|
|
else build_trikona_native_report)
|
|
if args.mode == 'raman-native':
|
|
from scripts.trikona_raman import build_trikona_raman_report
|
|
builder = build_trikona_raman_report
|
|
report = builder(**json.loads(args.request.read_text()))
|
|
print(report['exports']['markdown'] if args.format == 'markdown' else json.dumps(report, ensure_ascii=False, indent=2))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|