test: add jyotishganit local field comparison

This commit is contained in:
732642856
2026-07-19 20:11:46 +08:00
parent 819c6f6e43
commit 837ce316b1
4 changed files with 318 additions and 2 deletions
@@ -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."
}
@@ -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."
}
]
}
@@ -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())
@@ -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"