diff --git a/references/oracle/d1_d60_generic_gap_queue_2026_07_19.json b/references/oracle/d1_d60_generic_gap_queue_2026_07_19.json new file mode 100644 index 00000000..8d7a411a --- /dev/null +++ b/references/oracle/d1_d60_generic_gap_queue_2026_07_19.json @@ -0,0 +1,28 @@ +{ + "scope": "d1_d60_generic_gap_queue", + "created_at": "2026-07-19", + "status": "queue_ready", + "claim_status": "partial", + "production_tuning_allowed": false, + "truth_matrix_allowed": false, + "source_registry": "references/oracle/d1_d60_varga_mapping_registry_2026_07_19.json", + "generic_only_divisions": [ + "D13", "D14", "D15", "D17", "D18", "D19", "D21", "D22", "D23", "D25", + "D26", "D28", "D29", "D31", "D32", "D33", "D34", "D35", "D36", "D37", + "D38", "D39", "D41", "D42", "D43", "D44", "D46", "D47", "D48", "D49", + "D50", "D51", "D52", "D53", "D54", "D55", "D56", "D57", "D58", "D59" + ], + "required_closure_fields": [ + "traditional_name", + "textual_source", + "special_formula_or_generic_formula", + "traditional_use", + "local_formula_path", + "api_entry_decision", + "ui_entry_decision", + "external_worked_example", + "claim_boundary" + ], + "next_action": "For each generic-only Dn, either attach a source-backed formal mapping and test, or keep hidden as generic calculation only.", + "boundary": "Queue prevents advertising generic fallback as formal D1-D60 support." +} diff --git a/references/oracle/evidence_packet_index_2026_07_19.json b/references/oracle/evidence_packet_index_2026_07_19.json index 722425b0..40d72ef9 100644 --- a/references/oracle/evidence_packet_index_2026_07_19.json +++ b/references/oracle/evidence_packet_index_2026_07_19.json @@ -5,8 +5,8 @@ "production_tuning_allowed": false, "boundary": "Index of current governance packets only. Raw oracle artifacts remain in references/oracle/artifacts and are not all duplicated here.", "summary": { - "packet_count": 34, - "blocked_or_partial_count": 28, + "packet_count": 45, + "blocked_or_partial_count": 40, "human_review_required_count": 1 }, "packets": [ @@ -281,6 +281,94 @@ "claim_status": "observation_only", "consumer_policy": "research_observation_only", "claim_boundary": "Permissive OSS candidates and worked-example queues are registered for probes only; no truth-matrix or production-tuning upgrade." + }, + { + "packet_id": "local_oss_observation_inventory", + "path": "scripts/local_oss_observation_inventory.py", + "domain": "oss_reuse_and_worked_examples", + "claim_status": "observation_only", + "consumer_policy": "research_observation_only", + "claim_boundary": "Local OSS fragments are inventoried with hash/license/API hints only; no runtime dependency or truth upgrade." + }, + { + "packet_id": "panchanga_api_observation_manifest", + "path": "references/oracle/panchanga_api_observation_manifest_2026_07_19.json", + "domain": "muhurta_panchanga", + "claim_status": "observation_only", + "consumer_policy": "research_observation_only", + "claim_boundary": "panchanga_api local snapshot is docs-only; no numeric parity claim." + }, + { + "packet_id": "jyotishganit_field_closure_queue", + "path": "references/oracle/jyotishganit_field_closure_queue_2026_07_19.json", + "domain": "three_engine_field_closure", + "claim_status": "observation_only", + "consumer_policy": "research_observation_only", + "claim_boundary": "Field-level closure queue only; does not resolve mismatch rows." + }, + { + "packet_id": "vedicastro_kp_surface_locator", + "path": "scripts/vedicastro_kp_surface_locator.py", + "domain": "kp_precision_timing", + "claim_status": "observation_only", + "consumer_policy": "research_observation_only", + "claim_boundary": "Locates KP source surface and missing/candidate table paths; no cusp truth upgrade." + }, + { + "packet_id": "product_skill_source_boundary", + "path": "references/oracle/product_skill_source_boundary_2026_07_19.json", + "domain": "skill_product_reference", + "claim_status": "reference_only", + "consumer_policy": "skill_prompt_reference_only", + "claim_boundary": "rishi-ai-mcp and vedic-astro-skills stay out of numeric truth layer." + }, + { + "packet_id": "jyotishganit_field_probe", + "path": "references/oracle/jyotishganit_field_probe_steve_jobs_2026_07_19.json", + "domain": "three_engine_field_closure", + "claim_status": "observation_only", + "consumer_policy": "research_observation_only", + "claim_boundary": "jyotishganit D2/D4/D9/D10 Panchanga AV raw/hash attached; Shadbala missing remains partial." + }, + { + "packet_id": "vedicastro_kp_api_probe", + "path": "references/oracle/vedicastro_kp_api_probe_2026_07_19.json", + "domain": "kp_precision_timing", + "claim_status": "observation_only", + "consumer_policy": "research_observation_only", + "claim_boundary": "VedicAstro KP API/source hash captured; exact cusp truth still requires public numeric worked examples." + }, + { + "packet_id": "public_worked_example_queue", + "path": "references/oracle/public_worked_example_queue_2026_07_19.json", + "domain": "worked_example_collection", + "claim_status": "open_queue", + "consumer_policy": "research_queue_only", + "claim_boundary": "Public worked-example queue only; URLs/search terms are not oracle-ready until numeric fields and hashes are captured." + }, + { + "packet_id": "d1_d60_varga_mapping_registry", + "path": "references/oracle/d1_d60_varga_mapping_registry_2026_07_19.json", + "domain": "varga_mapping", + "claim_status": "partial", + "consumer_policy": "research_to_commercial_contract_only", + "claim_boundary": "D1-D60 rows are enumerated; generic fallback rows are not verified formal varga support." + }, + { + "packet_id": "jyotishganit_vs_local_field_comparison", + "path": "references/oracle/jyotishganit_vs_local_field_comparison_steve_jobs_2026_07_19.json", + "domain": "three_engine_field_closure", + "claim_status": "observation_only", + "consumer_policy": "research_observation_only", + "claim_boundary": "Local vs jyotishganit D2/D4/D9/D10 sign comparison with hash; mismatch attribution still pending." + }, + { + "packet_id": "d1_d60_generic_gap_queue", + "path": "references/oracle/d1_d60_generic_gap_queue_2026_07_19.json", + "domain": "varga_mapping", + "claim_status": "partial", + "consumer_policy": "research_to_commercial_contract_only", + "claim_boundary": "40 generic-only Dn rows require source/formula/oracle closure or remain hidden generic fallback." } ] } diff --git a/scripts/jyotishganit_vs_local_field_comparison.py b/scripts/jyotishganit_vs_local_field_comparison.py new file mode 100644 index 00000000..9367cfae --- /dev/null +++ b/scripts/jyotishganit_vs_local_field_comparison.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""Compare local varga signs with jyotishganit observation raw. + +Only compares fields with shared schema: D2/D4/D9/D10 sign labels plus +coverage for Panchanga/BAV/SAV/Shadbala. Observation-only. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import subprocess +import sys +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +TARGETS = {"D2": "D2_Hora", "D4": "D4_Chaturthamsa", "D9": "D9_Navamsa", "D10": "D10_Dasamsa"} + + +def stable(data: Any) -> str: + return json.dumps(data, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str) + + +def local_varga(args: argparse.Namespace) -> dict[str, Any]: + cmd = [ + sys.executable, + "scripts/jyotish_engine.py", + "varga-full", + "--year", + str(args.year), + "--month", + str(args.month), + "--day", + str(args.day), + "--hour", + str(args.hour), + "--minute", + str(args.minute), + "--lat", + str(args.latitude), + "--lon", + str(args.longitude), + "--tz", + str(args.timezone), + "--divisions", + "D2,D4,D9,D10", + ] + return json.loads(subprocess.check_output(cmd, cwd=ROOT, text=True)) + + +def jyotishganit_raw(args: argparse.Namespace) -> dict[str, Any]: + sys.path.insert(0, str(ROOT / "references/open_source_sources/jyotishganit")) + from jyotishganit.main import calculate_birth_chart # type: ignore + from datetime import datetime + + chart = calculate_birth_chart( + datetime(args.year, args.month, args.day, args.hour, args.minute), + args.latitude, + args.longitude, + args.timezone, + args.location, + args.name, + ) + return chart.to_dict() + + +def jyotishganit_signs(raw: dict[str, Any], code: str) -> dict[str, str]: + section = raw.get("divisionalCharts", {}).get(code.lower(), {}) + rows: dict[str, str] = {} + asc = section.get("ascendant", {}).get("sign") + if asc: + rows["Ascendant"] = asc + for house in section.get("houses", []): + for occ in house.get("occupants", []): + planet = occ.get("celestialBody") + sign = occ.get("sign") + if planet and sign: + rows[planet] = sign + return rows + + +def local_signs(raw: dict[str, Any], code: str) -> dict[str, str]: + section = raw.get(TARGETS[code], {}) + return { + body: value.get("sign") + for body, value in section.items() + if isinstance(value, dict) and body != "_meta" and value.get("sign") + } + + +def build(args: argparse.Namespace) -> dict[str, Any]: + local = local_varga(args) + jyo = jyotishganit_raw(args) + rows = [] + for code in TARGETS: + lrows = local_signs(local, code) + jrows = jyotishganit_signs(jyo, code) + bodies = sorted(set(lrows) | set(jrows)) + for body in bodies: + rows.append( + { + "section": code, + "body": body, + "local_sign": lrows.get(body), + "jyotishganit_sign": jrows.get(body), + "status": "match" if lrows.get(body) == jrows.get(body) else "mismatch", + } + ) + summary = { + "row_count": len(rows), + "match_count": sum(1 for r in rows if r["status"] == "match"), + "mismatch_count": sum(1 for r in rows if r["status"] == "mismatch"), + } + return { + "scope": "jyotishganit_vs_local_field_comparison", + "created_at": "2026-07-19", + "status": "mismatch" if summary["mismatch_count"] else "match", + "claim_status": "observation_only", + "production_tuning_allowed": False, + "truth_matrix_allowed": False, + "request": vars(args), + "summary": summary, + "coverage": { + "panchanga_jyotishganit": jyo.get("panchanga") is not None, + "BAV_SAV_jyotishganit": isinstance(jyo.get("ashtakavarga"), dict) + and "sav" in jyo.get("ashtakavarga", {}), + "Shadbala_jyotishganit": jyo.get("shadbala") is not None or jyo.get("strengths") is not None, + }, + "comparison_hash": hashlib.sha256(stable(rows).encode("utf-8")).hexdigest(), + "rows": rows, + "boundary": "Sign-level comparison only. Mismatch rows require field-level formula/ayanamsa/node/rounding attribution; do not majority-vote truth.", + } + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--year", type=int, default=1955) + ap.add_argument("--month", type=int, default=2) + ap.add_argument("--day", type=int, default=24) + ap.add_argument("--hour", type=int, default=19) + ap.add_argument("--minute", type=int, default=15) + ap.add_argument("--latitude", type=float, default=37.3382) + ap.add_argument("--longitude", type=float, default=-122.0383) + ap.add_argument("--timezone", type=float, default=-8.0) + ap.add_argument("--location", default="San Francisco, CA") + ap.add_argument("--name", default="Steve Jobs public") + ap.add_argument("--output") + args = ap.parse_args() + payload = build(args) + text = json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + if args.output: + Path(args.output).write_text(text + "\n", encoding="utf-8") + print(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_jyotishganit_vs_local_field_comparison.py b/tests/test_jyotishganit_vs_local_field_comparison.py new file mode 100644 index 00000000..81d2c437 --- /dev/null +++ b/tests/test_jyotishganit_vs_local_field_comparison.py @@ -0,0 +1,40 @@ +import json +import subprocess +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +INDEX = ROOT / "references/oracle/evidence_packet_index_2026_07_19.json" + + +def test_jyotishganit_vs_local_comparison_outputs_sign_rows_and_hash(): + out = subprocess.check_output(["python3", "scripts/jyotishganit_vs_local_field_comparison.py"], cwd=ROOT, text=True) + data = json.loads(out) + assert data["scope"] == "jyotishganit_vs_local_field_comparison" + assert data["claim_status"] == "observation_only" + assert data["production_tuning_allowed"] is False + assert data["truth_matrix_allowed"] is False + assert data["summary"]["row_count"] >= 32 + assert data["comparison_hash"] + assert {row["section"] for row in data["rows"]} == {"D2", "D4", "D9", "D10"} + assert data["coverage"]["panchanga_jyotishganit"] is True + assert data["coverage"]["BAV_SAV_jyotishganit"] is True + assert data["coverage"]["Shadbala_jyotishganit"] is False + + +def test_d1_d60_generic_gap_queue_lists_all_generic_only_rows(): + data = json.loads((ROOT / "references/oracle/d1_d60_generic_gap_queue_2026_07_19.json").read_text(encoding="utf-8")) + assert data["scope"] == "d1_d60_generic_gap_queue" + assert data["claim_status"] == "partial" + assert data["production_tuning_allowed"] is False + assert len(data["generic_only_divisions"]) == 40 + assert "D13" in data["generic_only_divisions"] + assert "D59" in data["generic_only_divisions"] + assert "external_worked_example" in data["required_closure_fields"] + + +def test_evidence_index_registers_comparison_and_generic_gap_queue(): + index = json.loads(INDEX.read_text(encoding="utf-8")) + packets = {row["packet_id"]: row for row in index["packets"]} + assert packets["jyotishganit_vs_local_field_comparison"]["claim_status"] == "observation_only" + assert packets["d1_d60_generic_gap_queue"]["claim_status"] == "partial"