From bb904156e6e678446f28ca4cf7359d387b2154b6 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Mon, 29 Jun 2026 10:45:11 +0800 Subject: [PATCH] Add compact oracle and local env closure packs --- scripts/local_env.py | 49 +++++++ scripts/oracle_batch_closure_pack.py | 111 +++++++++++++++ scripts/vedastro_ingestion_closure_pack.py | 128 ++++++++++++++++++ tests/test_local_env.py | 68 ++++++++++ tests/test_oracle_batch_closure_pack.py | 18 +++ tests/test_vedastro_ingestion_closure_pack.py | 18 +++ tests/test_vimsopaka_semantic_summary.py | 19 +++ 7 files changed, 411 insertions(+) create mode 100644 scripts/local_env.py create mode 100644 scripts/oracle_batch_closure_pack.py create mode 100644 scripts/vedastro_ingestion_closure_pack.py create mode 100644 tests/test_local_env.py create mode 100644 tests/test_oracle_batch_closure_pack.py create mode 100644 tests/test_vedastro_ingestion_closure_pack.py create mode 100644 tests/test_vimsopaka_semantic_summary.py diff --git a/scripts/local_env.py b/scripts/local_env.py new file mode 100644 index 00000000..3a3c0dbc --- /dev/null +++ b/scripts/local_env.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Minimal local env loader for repo-scoped developer configuration.""" + +from __future__ import annotations + +import os +from pathlib import Path + + +DEFAULT_ENV_FILES = (".env.local", ".jyotish.local.env") +_LOADED_ROOTS: set[Path] = set() + + +def _parse_env_line(line: str) -> tuple[str, str] | None: + text = line.strip() + if not text or text.startswith("#") or "=" not in text: + return None + key, value = text.split("=", 1) + key = key.strip() + value = value.strip() + if not key: + return None + if value and value[0] == value[-1] and value[0] in {"'", '"'}: + value = value[1:-1] + return key, value + + +def load_local_env(root: str | Path | None = None) -> list[Path]: + if os.environ.get("JYOTISH_SKIP_LOCAL_ENV", "").strip().lower() in {"1", "true", "yes"}: + return [] + + repo_root = (Path(root) if root is not None else Path(__file__).resolve().parents[1]).resolve() + if repo_root in _LOADED_ROOTS: + return [] + _LOADED_ROOTS.add(repo_root) + + loaded: list[Path] = [] + for name in DEFAULT_ENV_FILES: + path = repo_root / name + if not path.exists(): + continue + for line in path.read_text(encoding="utf-8").splitlines(): + parsed = _parse_env_line(line) + if not parsed: + continue + key, value = parsed + os.environ.setdefault(key, value) + loaded.append(path) + return loaded diff --git a/scripts/oracle_batch_closure_pack.py b/scripts/oracle_batch_closure_pack.py new file mode 100644 index 00000000..b6c432b9 --- /dev/null +++ b/scripts/oracle_batch_closure_pack.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Build a compact batch closure report by reusing existing oracle entrypoints.""" + +from __future__ import annotations + +import argparse +import json +from typing import Any + +from scripts.dasha_oracle_closure_status import build_status as build_dasha_status +from scripts.shadbala_oracle_comparison import compare_case as compare_shadbala_case + + +SHADBALA_CASE_IDS = [ + "template_user_REDACTED_YEAR_moon_longitude_lahiri", + "template_steve_jobs_dasha_lahiri", +] + + +def build_report(oracle_file: str) -> dict[str, Any]: + dasha = build_dasha_status(oracle_file) + shadbala_rows = [] + shadbala_within_tolerance = 0 + + for case_id in SHADBALA_CASE_IDS: + report = compare_shadbala_case(oracle_file=oracle_file, case_id=case_id) + within = report["summary"]["planet_count"] == report["summary"]["planets_within_total_tolerance"] + if within: + shadbala_within_tolerance += 1 + shadbala_rows.append({ + "kind": "shadbala", + "case_id": report["case_id"], + "status": report["status"], + "ayanamsa": report["settings"].get("ayanamsa"), + "planets_within_total_tolerance": report["summary"]["planets_within_total_tolerance"], + "planet_count": report["summary"]["planet_count"], + "max_abs_total_delta_rupa": report["summary"]["max_abs_total_delta_rupa"], + "global_scaling_recommendation": report["global_scaling_check"].get("recommendation"), + "within_case_tolerance": within, + }) + + rows = [{ + "kind": "dasha", + "dasha_task_count": dasha["summary"]["dasha_task_count"], + "external_verified_dasha_tasks": dasha["summary"]["external_verified_dasha_tasks"], + "can_claim_dasha_oracle_closure": dasha["summary"]["can_claim_dasha_oracle_closure"], + "production_tuning_allowed": dasha["summary"]["production_tuning_allowed"], + }, *shadbala_rows] + + return { + "scope": "oracle_batch_closure_pack", + "schema_version": 1, + "summary": { + "dasha_can_claim_closure": dasha["summary"]["can_claim_dasha_oracle_closure"], + "shadbala_case_count": len(shadbala_rows), + "shadbala_within_tolerance_case_count": shadbala_within_tolerance, + "global_oracle_closure_blocked": True, + }, + "rows": rows, + "boundary": ( + "This pack reuses existing Dasha and Shadbala oracle entrypoints. " + "Dasha-only closure can be complete while global oracle closure remains blocked " + "until Shadbala and other non-Dasha fronts are closed." + ), + "next_actions": [ + "Keep reusing dasha_oracle_closure_status.py for Dasha truth instead of duplicating logic.", + "Expand Shadbala comparison case count before changing any production-tuning claim.", + "Do not apply a global scaling factor when component-level deltas disagree by planet.", + ], + } + + +def main() -> None: + parser = argparse.ArgumentParser(description="Build a compact batched oracle closure report") + parser.add_argument("--oracle-file", default="references/oracle/dasha_shadbala_oracle_cases.json") + parser.add_argument("--format", choices=("json", "markdown"), default="json") + args = parser.parse_args() + + report = build_report(args.oracle_file) + if args.format == "markdown": + lines = [ + "# Oracle Batch Closure Pack", + "", + f"- dasha_can_claim_closure: `{str(report['summary']['dasha_can_claim_closure']).lower()}`", + f"- shadbala_case_count: `{report['summary']['shadbala_case_count']}`", + f"- shadbala_within_tolerance_case_count: `{report['summary']['shadbala_within_tolerance_case_count']}`", + f"- global_oracle_closure_blocked: `{str(report['summary']['global_oracle_closure_blocked']).lower()}`", + "", + "| Kind | Case | Status | Notes |", + "| --- | --- | --- | --- |", + ] + for row in report["rows"]: + if row["kind"] == "dasha": + lines.append( + f"| dasha | target_set | {'closed' if row['can_claim_dasha_oracle_closure'] else 'open'} | " + f"{row['external_verified_dasha_tasks']}/{row['dasha_task_count']} external verified |" + ) + else: + lines.append( + f"| shadbala | {row['case_id']} | {row['status']} | " + f"{row['planets_within_total_tolerance']}/{row['planet_count']} planets within tolerance; " + f"max delta {row['max_abs_total_delta_rupa']} |" + ) + print("\n".join(lines)) + return + + print(json.dumps(report, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/scripts/vedastro_ingestion_closure_pack.py b/scripts/vedastro_ingestion_closure_pack.py new file mode 100644 index 00000000..0442773c --- /dev/null +++ b/scripts/vedastro_ingestion_closure_pack.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""Build a compact closure report for VedAstro strict ingestion.""" + +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +PYTHON = sys.executable + + +def _run_json(args: list[str], env: dict[str, str] | None = None) -> dict[str, Any]: + completed = subprocess.run( + [PYTHON, "scripts/vedastro_service_adapter.py", *args], + cwd=ROOT, + text=True, + capture_output=True, + timeout=120, + check=False, + env=env, + ) + if completed.returncode != 0: + raise RuntimeError(completed.stderr.strip() or completed.stdout.strip()) + return json.loads(completed.stdout) + + +def build_report() -> dict[str, Any]: + schema = _run_json(["--print-schema"]) + + unconfigured_env = os.environ.copy() + unconfigured_env.pop("VEDASTRO_API_ENDPOINT", None) + unconfigured_env.pop("VEDASTRO_ENABLE_NETWORK", None) + unconfigured_env["JYOTISH_SKIP_LOCAL_ENV"] = "1" + unconfigured = _run_json( + ["--range-scan", "--domain", "marriage", "--case", "beijing_first_use_demo", "--start-date", "2026-01-01", "--end-date", "2031-01-01"], + env=unconfigured_env, + ) + + preview_env = os.environ.copy() + preview_env["VEDASTRO_API_ENDPOINT"] = "https://example.invalid/vedastro" + preview_env.pop("VEDASTRO_ENABLE_NETWORK", None) + preview_env["JYOTISH_SKIP_LOCAL_ENV"] = "1" + preview = _run_json( + ["--range-scan", "--domain", "wealth", "--case", "beijing_first_use_demo", "--start-date", "2026-01-01", "--end-date", "2031-01-01"], + env=preview_env, + ) + + life_event_graph_test = subprocess.run( + [ + PYTHON, + "-m", + "pytest", + "tests/test_life_event_graph_v1.py", + "-q", + "-k", + "vedastro or strict_workflow_accepts_adapter_range_scan_result_without_manual_repackaging", + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=120, + check=False, + ) + life_event_graph_accepts_external_window = life_event_graph_test.returncode == 0 + + return { + "scope": "vedastro_ingestion_closure_pack", + "schema_version": 1, + "summary": { + "range_scan_domains": sorted(schema["range_scan_event_allowlist"].keys()), + "schema_declares_allowlist": "range_scan_event_allowlist" in schema, + "unconfigured_status": unconfigured["status"], + "network_preview_status": preview["status"], + "life_event_graph_accepts_external_window": life_event_graph_accepts_external_window, + "global_live_closure_blocked": True, + }, + "rows": [ + { + "kind": "schema", + "allowlist_domains": sorted(schema["range_scan_event_allowlist"].keys()), + "coverage": schema["vedastro_calculation_coverage"], + }, + { + "kind": "blocked_boundary", + "status": unconfigured["status"], + "domain": unconfigured["request_preview"]["domain"], + "event_method": unconfigured["request_preview"]["vedastro_event_method"], + }, + { + "kind": "network_preview", + "status": preview["status"], + "domain": preview["request_preview"]["domain"], + "event_method": preview["request_preview"]["vedastro_event_method"], + }, + { + "kind": "life_event_graph", + "status": "pass" if life_event_graph_accepts_external_window else "fail", + "test_selector": ( + "tests/test_life_event_graph_v1.py " + "-k 'vedastro or strict_workflow_accepts_adapter_range_scan_result_without_manual_repackaging'" + ), + }, + ], + "boundary": ( + "This pack reuses the existing VedAstro adapter schema, blocked boundary, network-preview boundary, " + "and Life Event Graph ingestion tests. It does not claim live official endpoint closure." + ), + "next_actions": [ + "Keep using the adapter allowlist contract instead of inventing a second filtering layer.", + "Keep the live path blocked until a real endpoint-backed smoke is configured.", + "Only promote allowlisted external_window signals into strict workflow secondary evidence.", + ], + } + + +def main() -> None: + report = build_report() + print(json.dumps(report, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/tests/test_local_env.py b/tests/test_local_env.py new file mode 100644 index 00000000..624e51f8 --- /dev/null +++ b/tests/test_local_env.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +import os +from pathlib import Path + + +from scripts import local_env + + +def test_load_local_env_reads_repo_env_file_without_overriding_explicit_env(tmp_path: Path, monkeypatch) -> None: + env_file = tmp_path / ".env.local" + env_file.write_text( + "VEDASTRO_API_ENDPOINT=https://api.vedastro.org/api\n" + "VEDASTRO_ENABLE_NETWORK=1\n" + "VEDASTRO_API_KEY=from_file\n", + encoding="utf-8", + ) + monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False) + monkeypatch.setenv("VEDASTRO_API_KEY", "from_process") + + try: + loaded = local_env.load_local_env(root=tmp_path) + + assert loaded == [env_file] + assert os.environ["VEDASTRO_API_ENDPOINT"] == "https://api.vedastro.org/api" + assert os.environ["VEDASTRO_ENABLE_NETWORK"] == "1" + assert os.environ["VEDASTRO_API_KEY"] == "from_process" + finally: + os.environ.pop("VEDASTRO_API_ENDPOINT", None) + os.environ.pop("VEDASTRO_ENABLE_NETWORK", None) + + +def test_load_local_env_ignores_missing_file(tmp_path: Path, monkeypatch) -> None: + monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False) + os.environ.pop("VEDASTRO_ENABLE_NETWORK", None) + + loaded = local_env.load_local_env(root=tmp_path) + + assert loaded == [] + assert "VEDASTRO_API_ENDPOINT" not in os.environ + + +def test_load_local_env_does_not_resurrect_deleted_values_after_bootstrap(tmp_path: Path, monkeypatch) -> None: + env_file = tmp_path / ".env.local" + env_file.write_text("VEDASTRO_ENABLE_NETWORK=1\n", encoding="utf-8") + monkeypatch.delenv("VEDASTRO_ENABLE_NETWORK", raising=False) + + loaded = local_env.load_local_env(root=tmp_path) + assert loaded == [env_file] + assert os.environ["VEDASTRO_ENABLE_NETWORK"] == "1" + + monkeypatch.delenv("VEDASTRO_ENABLE_NETWORK", raising=False) + loaded_again = local_env.load_local_env(root=tmp_path) + + assert loaded_again == [] + assert "VEDASTRO_ENABLE_NETWORK" not in os.environ + + +def test_load_local_env_respects_explicit_skip_flag(tmp_path: Path, monkeypatch) -> None: + env_file = tmp_path / ".env.local" + env_file.write_text("VEDASTRO_API_ENDPOINT=https://api.vedastro.org/api\n", encoding="utf-8") + monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False) + monkeypatch.setenv("JYOTISH_SKIP_LOCAL_ENV", "1") + + loaded = local_env.load_local_env(root=tmp_path) + + assert loaded == [] + assert "VEDASTRO_API_ENDPOINT" not in os.environ diff --git a/tests/test_oracle_batch_closure_pack.py b/tests/test_oracle_batch_closure_pack.py new file mode 100644 index 00000000..2160cfa9 --- /dev/null +++ b/tests/test_oracle_batch_closure_pack.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +"""Regression tests for the batched oracle closure pack.""" + +from __future__ import annotations + +from scripts.oracle_batch_closure_pack import build_report + + +def test_oracle_batch_closure_pack_combines_dasha_and_shadbala_truth() -> None: + report = build_report("references/oracle/dasha_shadbala_oracle_cases.json") + + assert report["scope"] == "oracle_batch_closure_pack" + assert report["summary"]["dasha_can_claim_closure"] is True + assert report["summary"]["shadbala_case_count"] >= 2 + assert report["summary"]["shadbala_within_tolerance_case_count"] <= report["summary"]["shadbala_case_count"] + assert any(item["kind"] == "dasha" for item in report["rows"]) + assert any(item["kind"] == "shadbala" for item in report["rows"]) + assert report["summary"]["global_oracle_closure_blocked"] is True diff --git a/tests/test_vedastro_ingestion_closure_pack.py b/tests/test_vedastro_ingestion_closure_pack.py new file mode 100644 index 00000000..a4673277 --- /dev/null +++ b/tests/test_vedastro_ingestion_closure_pack.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +"""Regression tests for the minimal VedAstro ingestion closure pack.""" + +from __future__ import annotations + +from scripts.vedastro_ingestion_closure_pack import build_report + + +def test_vedastro_ingestion_closure_pack_reuses_blocked_and_allowlisted_paths() -> None: + report = build_report() + + assert report["scope"] == "vedastro_ingestion_closure_pack" + assert report["summary"]["range_scan_domains"] == ["career", "marriage", "wealth"] + assert report["summary"]["schema_declares_allowlist"] is True + assert report["summary"]["unconfigured_status"] == "service_endpoint_not_configured" + assert report["summary"]["network_preview_status"] == "network_execution_disabled" + assert report["summary"]["life_event_graph_accepts_external_window"] is True + assert report["summary"]["global_live_closure_blocked"] is True diff --git a/tests/test_vimsopaka_semantic_summary.py b/tests/test_vimsopaka_semantic_summary.py new file mode 100644 index 00000000..71b0fa13 --- /dev/null +++ b/tests/test_vimsopaka_semantic_summary.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +"""Regression tests for user-visible Vimsopaka semantic summaries.""" + +from __future__ import annotations + +from scripts.jyotish_engine import _build_vimsopaka_semantic_summary + + +def test_build_vimsopaka_semantic_summary_surfaces_high_value_dignity_terms() -> None: + summary = _build_vimsopaka_semantic_summary({ + "Sun": {"dignity": "GREAT_FRIEND"}, + "Moon": {"dignity": "NEECHA_BHANGA"}, + "Mars": {"dignity": "GREAT_ENEMY"}, + }) + + assert "Great Friend" in summary["highlights"][0] or "极友" in summary["highlights"][0] + assert any("Neecha Bhanga" in item or "落陷取消" in item for item in summary["highlights"]) + assert any("Great Enemy" in item or "极敌" in item for item in summary["warnings"]) + assert summary["status"] == "used"