feat: add rectification input evidence contract

This commit is contained in:
732642856
2026-07-21 17:49:43 +08:00
parent 8df4a74fec
commit 31dc3a9b97
11 changed files with 261 additions and 16 deletions
@@ -11,6 +11,8 @@ from datetime import datetime, timedelta
from pathlib import Path
from typing import Any
from scripts.rectification_input_contract import candidate_input_fingerprint, stability_probe_contract
ROOT = Path(__file__).resolve().parents[1]
ENGINE = ROOT / "scripts" / "jyotish_engine.py"
@@ -63,6 +65,7 @@ def scan_candidate_times(payload: dict[str, Any], *, uncertainty_minutes: int =
"d1_ascendant": asc.get("sign"),
"d1_degree_in_sign": asc.get("degree_in_sign"),
"divisional_ascendants": divisional,
"input_fingerprint": candidate_input_fingerprint(point),
})
signatures = [tuple([row["d1_ascendant"], *row["divisional_ascendants"].values()]) for row in rows]
unavailable_vargas = [varga.upper() for varga in _VARGAS if all(row["divisional_ascendants"][varga.upper()] is None for row in rows)]
@@ -88,6 +91,15 @@ def scan_candidate_times(payload: dict[str, Any], *, uncertainty_minutes: int =
"uncertainty_minutes": uncertainty_minutes,
"step_minutes": step_minutes,
"rows": rows,
"input_contract": {
"version": "rectification-input-v1",
"center_input_fingerprint": candidate_input_fingerprint(payload),
"settings": {
"ayanamsa": str(payload.get("ayanamsa") or "lahiri").lower(),
"node_mode": str(payload.get("node_mode") or "true").lower(),
},
},
"stability_contract": stability_probe_contract(payload),
"transitions": transitions,
"supported_vargas": [varga.upper() for varga in supported_vargas],
"unavailable_vargas": unavailable_vargas,
@@ -143,20 +143,24 @@ def build(args: argparse.Namespace) -> dict[str, Any]:
}
def main() -> int:
def build_parser() -> argparse.ArgumentParser:
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("--latitude", type=float, default=37.7749)
ap.add_argument("--longitude", type=float, default=-122.4194)
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()
return ap
def main() -> int:
args = build_parser().parse_args()
payload = build(args)
text = json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True)
if args.output:
+96
View File
@@ -0,0 +1,96 @@
"""Stable, privacy-safe input identities for birth-time rectification evidence."""
from __future__ import annotations
import hashlib
import json
from datetime import datetime, timedelta
from typing import Any
_REQUIRED = ("year", "month", "day", "hour", "minute", "lat", "lon", "tz")
_STABILITY_OFFSETS = (-5, -2, -1, 1, 2, 5)
def _canonical_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=True, sort_keys=True, separators=(",", ":"))
def canonical_birth_input(case: dict[str, Any]) -> dict[str, Any]:
"""Return the calculation-relevant input with explicit calculation settings."""
missing = [field for field in _REQUIRED if case.get(field) is None]
if missing:
raise ValueError(f"missing rectification input fields: {', '.join(missing)}")
year, month, day = int(case["year"]), int(case["month"]), int(case["day"])
hour, minute, second = int(case["hour"]), int(case["minute"]), int(case.get("second", 0))
datetime(year, month, day, hour, minute, second)
return {
"version": "rectification-input-v1",
"birth": {
"year": year,
"month": month,
"day": day,
"hour": hour,
"minute": minute,
"second": second,
"latitude": float(case["lat"]),
"longitude": float(case["lon"]),
"timezone": float(case["tz"]),
},
"ayanamsa": str(case.get("ayanamsa") or "lahiri").lower(),
# Preserve the deployed request-level default; callers must make any
# node-mode change explicit so it receives a different fingerprint.
"node_mode": str(case.get("node_mode") or "true").lower(),
}
def candidate_input_fingerprint(case: dict[str, Any]) -> str:
return hashlib.sha256(_canonical_json(canonical_birth_input(case)).encode("utf-8")).hexdigest()
def stability_probe_contract(case: dict[str, Any]) -> dict[str, Any]:
"""List required local perturbations without claiming they have passed."""
baseline = canonical_birth_input(case)
birth = baseline["birth"]
center = datetime(birth["year"], birth["month"], birth["day"], birth["hour"], birth["minute"], birth["second"])
probes = []
for offset in _STABILITY_OFFSETS:
moment = center + timedelta(minutes=offset)
probe = {
"year": moment.year,
"month": moment.month,
"day": moment.day,
"hour": moment.hour,
"minute": moment.minute,
"second": moment.second,
"lat": birth["latitude"],
"lon": birth["longitude"],
"tz": birth["timezone"],
"ayanamsa": baseline["ayanamsa"],
"node_mode": baseline["node_mode"],
}
probes.append({"offset_minutes": offset, "input_fingerprint": candidate_input_fingerprint(probe)})
return {
"scope": "candidate_minute_stability_contract",
"status": "pending_score_comparison",
"baseline_input_fingerprint": candidate_input_fingerprint(case),
"probes": probes,
"minute_confirmation_allowed": False,
"blocker": "public_blind_minute_holdout_not_closed",
"boundary": "Probe identities are reproducible inputs, not evidence that a minute has passed stability or outcome validation.",
}
def _semantic_normalize(value: Any, *, parent_key: str | None = None) -> Any:
if isinstance(value, dict):
return {key: _semantic_normalize(item, parent_key=key) for key, item in sorted(value.items())}
if isinstance(value, list):
normalized = [_semantic_normalize(item, parent_key=parent_key) for item in value]
if parent_key in {"gives", "receives"}:
return sorted(normalized, key=_canonical_json)
return normalized
return value
def semantic_evidence_hash(value: Any) -> str:
"""Hash known order-insensitive evidence fields while retaining raw hashes elsewhere."""
return hashlib.sha256(_canonical_json(_semantic_normalize(value)).encode("utf-8")).hexdigest()
+4 -4
View File
@@ -1,15 +1,14 @@
"""Build a privacy-safe, request-level three-engine rectification parity packet."""
from __future__ import annotations
import hashlib
import importlib
import json
import sys
from datetime import datetime
from pathlib import Path
from typing import Any
from domain_calculation_service import compute_chart
from scripts.rectification_input_contract import candidate_input_fingerprint, stability_probe_contract
ROOT = Path(__file__).resolve().parents[1]
JYOTISHGANIT_ROOT = ROOT / "references" / "open_source_sources" / "jyotishganit"
@@ -19,8 +18,7 @@ SIGNS = ("Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpi
def case_hash(case: dict[str, Any]) -> str:
"""Stable identity for evidence correlation; never exposes birth data."""
payload = json.dumps(case, sort_keys=True, ensure_ascii=True, separators=(",", ":"))
return hashlib.sha256(payload.encode()).hexdigest()
return candidate_input_fingerprint(case)
def _local_d1(case: dict[str, Any]) -> dict[str, str]:
@@ -121,6 +119,8 @@ def build_packet(
return {
"scope": "request_level_three_engine_d1_parity",
"case_hash": case_hash(case),
"input_contract_hash": candidate_input_fingerprint(case),
"stability_contract": stability_probe_contract(case),
"engine_status": engine_status,
"match_count": sum(row["status"] == "match" for row in rows),
"mismatch_count": sum(row["status"] == "mismatch" for row in rows),
+4 -3
View File
@@ -17,6 +17,7 @@ if str(ROOT) not in sys.path:
from benchmarks.jyotish.scripts.run_pyjhora_compare import build_pyjhora_sample
from benchmarks.jyotish.scripts.run_skill_baseline import run_sample
from scripts.three_engine_parity_runner import _capture_jyotishganit_raw
from scripts.rectification_input_contract import semantic_evidence_hash
ORACLE = ROOT / "references" / "oracle"
ARTIFACTS = ORACLE / "artifacts"
@@ -146,9 +147,9 @@ def build() -> dict[str, Any]:
manifest = {
"case_id": "steve_jobs_public_1955_lahiri", "birth_data_policy": "public_case_only", "blocked_reason": "none",
"engines": {
"VedAstro": {"status": "official_verified", "official_raw_response_path": "artifacts/" + ved_path.name, "artifact_hash": _sha(ved_path), "settings": ved["settings"]},
"PyJHora_JHora": {"status": "imported", "raw_output_path": "artifacts/" + py_path.name, "artifact_hash": _sha(py_path), "settings": pyjhora["settings"]},
"jyotishganit": {"status": "imported", "raw_output_path": "artifacts/" + jy_path.name, "artifact_hash": _sha(jy_path), "settings": {"ayanamsa": jyotish["ayanamsa"]}},
"VedAstro": {"status": "official_verified", "official_raw_response_path": "artifacts/" + ved_path.name, "artifact_hash": _sha(ved_path), "semantic_hash": semantic_evidence_hash(ved), "settings": ved["settings"]},
"PyJHora_JHora": {"status": "imported", "raw_output_path": "artifacts/" + py_path.name, "artifact_hash": _sha(py_path), "semantic_hash": semantic_evidence_hash(pyjhora), "settings": pyjhora["settings"]},
"jyotishganit": {"status": "imported", "raw_output_path": "artifacts/" + jy_path.name, "artifact_hash": _sha(jy_path), "semantic_hash": semantic_evidence_hash(jyotish), "settings": {"ayanamsa": jyotish["ayanamsa"]}},
},
"comparison_rows": rows,
"method_arbitration": {