From 3e606e5fe892b3fbf1bb5b7f838f854c6935e8e9 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Fri, 26 Jun 2026 17:29:47 +0800 Subject: [PATCH] feat(benchmark): add tajika annual oracle dashboard --- README.md | 19 ++ ...ika_sahams_annual_benchmark_dashboard.json | 25 ++ ...ajika_sahams_annual_benchmark_dashboard.md | 26 ++ .../oracle/tajika_annual_oracle_cases.json | 201 +++++++++++++++ scripts/tajika_annual_benchmark_dashboard.py | 123 ++++++++++ scripts/tajika_annual_oracle_queue.py | 231 ++++++++++++++++++ .../test_tajika_annual_benchmark_dashboard.py | 57 +++++ tests/test_tajika_annual_oracle_queue.py | 58 +++++ 8 files changed, 740 insertions(+) create mode 100644 docs/benchmark/tajika_sahams_annual_benchmark_dashboard.json create mode 100644 docs/benchmark/tajika_sahams_annual_benchmark_dashboard.md create mode 100644 references/oracle/tajika_annual_oracle_cases.json create mode 100644 scripts/tajika_annual_benchmark_dashboard.py create mode 100644 scripts/tajika_annual_oracle_queue.py create mode 100644 tests/test_tajika_annual_benchmark_dashboard.py create mode 100644 tests/test_tajika_annual_oracle_queue.py diff --git a/README.md b/README.md index 39f113d9..3c7103fd 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,25 @@ python3 scripts/public_benchmark_dashboard.py \ 当前看板固定输出 `can_claim_global_first: false`,直到外部 oracle 样本、差异审计和长期公开 benchmark 都达到生产调参标准。 +Tajika/Sahams 年运系统使用独立的外部 oracle 队列,专门追踪 Varshaphala、太阳回归、Muntha、Year Lord、Mudda Dasha、Sahams 与 Tajika Yogas 的外部验证状态: + +```bash +python3 scripts/tajika_annual_oracle_queue.py \ + --oracle-file references/oracle/tajika_annual_oracle_cases.json \ + --format markdown +``` + +公开年运看板可这样生成: + +```bash +python3 scripts/tajika_annual_benchmark_dashboard.py \ + --oracle-file references/oracle/tajika_annual_oracle_cases.json \ + --format markdown \ + --output docs/benchmark/tajika_sahams_annual_benchmark_dashboard.md +``` + +当前 Tajika/Sahams 看板固定输出 `can_claim_tajika_sahams_closure: false`:本地 skill 已有年运计算与解释骨架,但太阳回归精确时刻、Varsha Lagna、Muntha、Mudda Dasha、Punya/Rajya/Vivah Saham 和 Tajika Yogas 仍需 JHora/PyJHora/书例级外部证据后,才能宣称年运闭环。 + `full-reading` 也会输出 `ai_prompt_pack`:这是给网页/app、skill 或后端 AI 代理使用的结构化 Prompt/RAG 上下文包。它不会硬编码断语,而是携带 D1/D9/Dasha/Shadbala/Ashtakavarga 的证据快照、推荐检索文档和边界提示,要求大模型基于计算证据交叉验证,避免单一配置下结论。 ### Prerequisites diff --git a/docs/benchmark/tajika_sahams_annual_benchmark_dashboard.json b/docs/benchmark/tajika_sahams_annual_benchmark_dashboard.json new file mode 100644 index 00000000..da2b697e --- /dev/null +++ b/docs/benchmark/tajika_sahams_annual_benchmark_dashboard.json @@ -0,0 +1,25 @@ +{ + "scope": "tajika_sahams_annual_benchmark_dashboard", + "schema_version": 1, + "generated_at": "2026-06-26T09:27:54.639600+00:00", + "summary": { + "total_tasks": 5, + "ready_for_collection": 5, + "ready_for_calibration": 0, + "production_tuning_allowed": false, + "by_status": { + "template_only": 5 + } + }, + "annual_claim": { + "can_claim_tajika_sahams_closure": false, + "reason": "Do not claim annual-chart closure until every template row is promoted to external_verified with human-reviewable artifacts." + }, + "remaining_gap": "Solar return exact time, Varsha Lagna, Muntha, Year Lord, Mudda Dasha first lord, Sahams and Tajika Yogas still need external JHora/PyJHora/book-example evidence before the Jyotish skill can claim Tajika/Sahams annual closure.", + "next_actions": [ + "Fill one Steve Jobs annual evidence packet from JHora or PyJHora.", + "Add solar return datetime and Varsha Lagna tolerance checks after the first external row exists.", + "Add Saham-specific tolerance checks for Punya, Rajya and Vivah Saham.", + "Expand the annual benchmark with at least one printed Varshaphala example." + ] +} \ No newline at end of file diff --git a/docs/benchmark/tajika_sahams_annual_benchmark_dashboard.md b/docs/benchmark/tajika_sahams_annual_benchmark_dashboard.md new file mode 100644 index 00000000..d3bf6e53 --- /dev/null +++ b/docs/benchmark/tajika_sahams_annual_benchmark_dashboard.md @@ -0,0 +1,26 @@ +# Tajika/Sahams Annual Benchmark Dashboard + +Generated: `2026-06-26T09:27:55.462154+00:00` + +## Annual Oracle Readiness + +- total_tasks: `5` +- ready_for_collection: `5` +- ready_for_calibration: `0` +- production_tuning_allowed: `false` + +## Annual Closure Claim + +- can_claim_tajika_sahams_closure: `false` +- reason: Do not claim annual-chart closure until every template row is promoted to external_verified with human-reviewable artifacts. + +## Remaining Gap + +Solar return exact time, Varsha Lagna, Muntha, Year Lord, Mudda Dasha first lord, Sahams and Tajika Yogas still need external JHora/PyJHora/book-example evidence before the Jyotish skill can claim Tajika/Sahams annual closure. + +## Next Actions + +- Fill one Steve Jobs annual evidence packet from JHora or PyJHora. +- Add solar return datetime and Varsha Lagna tolerance checks after the first external row exists. +- Add Saham-specific tolerance checks for Punya, Rajya and Vivah Saham. +- Expand the annual benchmark with at least one printed Varshaphala example. diff --git a/references/oracle/tajika_annual_oracle_cases.json b/references/oracle/tajika_annual_oracle_cases.json new file mode 100644 index 00000000..01533193 --- /dev/null +++ b/references/oracle/tajika_annual_oracle_cases.json @@ -0,0 +1,201 @@ +{ + "schema_version": 1, + "scope": "external_tajika_sahams_annual_oracle_cases", + "notes": [ + "These rows collect external annual-chart evidence for the Jyotish skill, not web/app UI fixtures.", + "Do not tune Tajika, Varshaphala, Muntha, Mudda Dasha, Sahams or yoga rules from template-only rows.", + "JHora, PyJHora, printed Varshaphala examples or documented black-box outputs may be used as external behavior references. Do not port incompatible licensed code." + ], + "template_cases": [ + { + "id": "template_steve_jobs_varshaphala_1984_lahiri", + "status": "template_only", + "source": "JHora Varshaphala screenshot / PyJHora black-box annual output / printed example", + "privacy": "public_figure_template", + "birth": { + "year": 1955, + "month": 2, + "day": 24, + "hour": 19, + "minute": 15, + "second": 0, + "lat": 37.7749, + "lon": -122.4194, + "tz": -8 + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "true", + "annual_system": "varshaphala", + "target_year": 1984 + }, + "target": { + "solar_return_datetime": null, + "varsha_lagna_deg": null, + "muntha_sign": null, + "year_lord": null, + "mudda_dasha_first_lord": null, + "sahams": { + "punya_saham": null, + "rajya_saham": null, + "vivah_saham": null + }, + "tajika_yogas": null, + "source_artifact": null + }, + "verification_note": "Fill only from external annual-chart output. Local scripts/varshaphala.py output is not valid evidence." + }, + { + "id": "template_einstein_varshaphala_1905_lahiri", + "status": "template_only", + "source": "JHora Varshaphala screenshot / PyJHora black-box annual output / printed example", + "privacy": "public_figure_template", + "birth": { + "year": 1879, + "month": 3, + "day": 14, + "hour": 11, + "minute": 30, + "second": 0, + "lat": 48.3984, + "lon": 9.9916, + "tz": 0.883333 + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "mean", + "annual_system": "varshaphala", + "target_year": 1905 + }, + "target": { + "solar_return_datetime": null, + "varsha_lagna_deg": null, + "muntha_sign": null, + "year_lord": null, + "mudda_dasha_first_lord": null, + "sahams": { + "punya_saham": null, + "rajya_saham": null, + "vivah_saham": null + }, + "tajika_yogas": null, + "source_artifact": null + }, + "verification_note": "Historical timezone and source convention must be documented beside the external annual output." + }, + { + "id": "template_marilyn_monroe_varshaphala_1962_lahiri", + "status": "template_only", + "source": "JHora Varshaphala screenshot / PyJHora black-box annual output / printed example", + "privacy": "public_figure_template", + "birth": { + "year": 1926, + "month": 6, + "day": 1, + "hour": 9, + "minute": 30, + "second": 0, + "lat": 34.0522, + "lon": -118.2437, + "tz": -8 + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "true", + "annual_system": "varshaphala", + "target_year": 1962 + }, + "target": { + "solar_return_datetime": null, + "varsha_lagna_deg": null, + "muntha_sign": null, + "year_lord": null, + "mudda_dasha_first_lord": null, + "sahams": { + "punya_saham": null, + "rajya_saham": null, + "vivah_saham": null + }, + "tajika_yogas": null, + "source_artifact": null + }, + "verification_note": "Annual reading evidence must include exact solar return convention and visible source metadata." + }, + { + "id": "template_historical_dst_london_varshaphala_1943_lahiri", + "status": "template_only", + "source": "JHora Varshaphala screenshot / PyJHora black-box annual output / printed example", + "privacy": "synthetic_historical_dst_template", + "birth": { + "year": 1910, + "month": 4, + "day": 15, + "hour": 12, + "minute": 0, + "second": 0, + "lat": 51.5074, + "lon": -0.1278, + "tz": 0 + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "mean", + "annual_system": "varshaphala", + "target_year": 1943 + }, + "target": { + "solar_return_datetime": null, + "varsha_lagna_deg": null, + "muntha_sign": null, + "year_lord": null, + "mudda_dasha_first_lord": null, + "sahams": { + "punya_saham": null, + "rajya_saham": null, + "vivah_saham": null + }, + "tajika_yogas": null, + "source_artifact": null + }, + "verification_note": "This row exists to force historical DST documentation before annual-chart accuracy claims." + }, + { + "id": "template_synthetic_extreme_latitude_varshaphala_kp", + "status": "template_only", + "source": "JHora Varshaphala screenshot / PyJHora black-box annual output / printed example", + "privacy": "synthetic_extreme_latitude_template", + "birth": { + "year": 2000, + "month": 6, + "day": 21, + "hour": 0, + "minute": 0, + "second": 0, + "lat": 65.0, + "lon": 15.0, + "tz": 1 + }, + "settings": { + "ayanamsa": "kp", + "node_mode": "true", + "annual_system": "varshaphala", + "target_year": 2024 + }, + "target": { + "solar_return_datetime": null, + "varsha_lagna_deg": null, + "muntha_sign": null, + "year_lord": null, + "mudda_dasha_first_lord": null, + "sahams": { + "punya_saham": null, + "rajya_saham": null, + "vivah_saham": null + }, + "tajika_yogas": null, + "source_artifact": null + }, + "verification_note": "High-latitude annual chart row; external source must document house and ascendant assumptions." + } + ] +} diff --git a/scripts/tajika_annual_benchmark_dashboard.py b/scripts/tajika_annual_benchmark_dashboard.py new file mode 100644 index 00000000..6d453a78 --- /dev/null +++ b/scripts/tajika_annual_benchmark_dashboard.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Generate a Tajika/Sahams annual benchmark dashboard for the Jyotish skill.""" + +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +PYTHON = sys.executable + + +def _run_json(command: list[str]) -> dict[str, Any]: + completed = subprocess.run( + command, + cwd=ROOT, + text=True, + capture_output=True, + timeout=60, + check=False, + ) + if completed.returncode != 0: + raise RuntimeError(completed.stderr.strip() or completed.stdout.strip()) + return json.loads(completed.stdout) + + +def build_dashboard(oracle_file: str) -> dict[str, Any]: + queue = _run_json([PYTHON, "scripts/tajika_annual_oracle_queue.py", "--oracle-file", oracle_file, "--format", "json"]) + summary = queue["summary"] + can_claim_closure = bool(summary["production_tuning_allowed"] and summary["ready_for_calibration"] == summary["total_tasks"]) + remaining_gap = ( + "Solar return exact time, Varsha Lagna, Muntha, Year Lord, Mudda Dasha first lord, " + "Sahams and Tajika Yogas still need external JHora/PyJHora/book-example evidence before " + "the Jyotish skill can claim Tajika/Sahams annual closure." + ) + return { + "scope": "tajika_sahams_annual_benchmark_dashboard", + "schema_version": 1, + "generated_at": datetime.now(timezone.utc).isoformat(), + "summary": { + "total_tasks": summary["total_tasks"], + "ready_for_collection": summary["ready_for_collection"], + "ready_for_calibration": summary["ready_for_calibration"], + "production_tuning_allowed": summary["production_tuning_allowed"], + "by_status": summary["by_status"], + }, + "annual_claim": { + "can_claim_tajika_sahams_closure": can_claim_closure, + "reason": ( + "Do not claim annual-chart closure until every template row is promoted to " + "external_verified with human-reviewable artifacts." + ), + }, + "remaining_gap": remaining_gap, + "next_actions": [ + "Fill one Steve Jobs annual evidence packet from JHora or PyJHora.", + "Add solar return datetime and Varsha Lagna tolerance checks after the first external row exists.", + "Add Saham-specific tolerance checks for Punya, Rajya and Vivah Saham.", + "Expand the annual benchmark with at least one printed Varshaphala example.", + ], + } + + +def render_markdown(report: dict[str, Any]) -> str: + summary = report["summary"] + claim = report["annual_claim"] + lines = [ + "# Tajika/Sahams Annual Benchmark Dashboard", + "", + f"Generated: `{report['generated_at']}`", + "", + "## Annual Oracle Readiness", + "", + f"- total_tasks: `{summary['total_tasks']}`", + f"- ready_for_collection: `{summary['ready_for_collection']}`", + f"- ready_for_calibration: `{summary['ready_for_calibration']}`", + f"- production_tuning_allowed: `{str(summary['production_tuning_allowed']).lower()}`", + "", + "## Annual Closure Claim", + "", + f"- can_claim_tajika_sahams_closure: `{str(claim['can_claim_tajika_sahams_closure']).lower()}`", + f"- reason: {claim['reason']}", + "", + "## Remaining Gap", + "", + report["remaining_gap"], + "", + "## Next Actions", + "", + ] + lines.extend(f"- {item}" for item in report["next_actions"]) + lines.append("") + return "\n".join(lines) + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Generate Tajika/Sahams annual benchmark dashboard") + parser.add_argument("--oracle-file", default="references/oracle/tajika_annual_oracle_cases.json") + parser.add_argument("--format", choices=["json", "markdown"], default="json") + parser.add_argument("--output", help="Optional output path") + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + report = build_dashboard(args.oracle_file) + text = json.dumps(report, ensure_ascii=False, indent=2) if args.format == "json" else render_markdown(report) + if args.output: + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(text, encoding="utf-8") + print(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/tajika_annual_oracle_queue.py b/scripts/tajika_annual_oracle_queue.py new file mode 100644 index 00000000..d2493be7 --- /dev/null +++ b/scripts/tajika_annual_oracle_queue.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +"""Generate Tajika/Sahams annual external-oracle collection tasks. + +This is skill-level verification infrastructure. It does not compute annual +chart values and must not be used to tune production rules from template rows. +""" + +from __future__ import annotations + +import argparse +import json +import os +from typing import Any + + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +REQUIRED_EVIDENCE_METADATA_FIELDS = [ + "tool_name", + "tool_version_or_url", + "capture_date", + "source_artifact", + "ayanamsa", + "node_mode", + "timezone", + "annual_system", + "target_year", + "operator_note", +] + +SOURCE_GUIDANCE = { + "preferred_sources": [ + "JHora Varshaphala screenshot", + "PyJHora black-box annual output", + "Printed Tajika/Varshaphala example", + ], + "collection_steps": [ + "Set the exact birth data, ayanamsa, node mode, timezone and target year in the external annual-chart tool.", + "Record solar return datetime, Varsha Lagna, Muntha sign, Year Lord and first Mudda Dasha lord.", + "Record Punya Saham, Rajya Saham and Vivah Saham in absolute 0-360 degree format.", + "Record visible Tajika Yogas without translating them through this repository's interpretation layer.", + "Attach a redacted screenshot, stdout snippet or book-example citation under references/oracle/artifacts/.", + ], + "promotion_criteria": [ + "All target fields are filled from a documented external annual-chart source.", + "The source artifact is external evidence, not scripts/varshaphala.py or this repository's local output.", + "Solar return and timezone/DST conventions are documented before promotion to external_verified.", + "At least one human-reviewable artifact path is preserved in target.source_artifact.", + ], +} + + +def _resolve_path(path: str) -> str: + if os.path.isabs(path): + return path + return os.path.join(ROOT_DIR, path) + + +def _load_json(path: str) -> dict[str, Any]: + with open(_resolve_path(path), "r", encoding="utf-8") as fh: + return json.load(fh) + + +def _target_fields(value: Any, prefix: str = "target") -> list[str]: + if prefix == "target" and isinstance(value, dict): + fields: list[str] = [] + for key, child in value.items(): + fields.extend(_target_fields(child, f"{prefix}.{key}")) + return fields + if isinstance(value, dict): + fields = [] + for key, child in value.items(): + fields.extend(_target_fields(child, f"{prefix}.{key}")) + return fields + return [prefix] + + +def _target_value(target: dict[str, Any], field: str) -> Any: + value: Any = target + for part in field.split(".")[1:]: + if not isinstance(value, dict): + return None + value = value.get(part) + return value + + +def _missing_target_fields(value: Any, prefix: str = "target") -> list[str]: + missing: list[str] = [] + if isinstance(value, dict): + for key, child in value.items(): + missing.extend(_missing_target_fields(child, f"{prefix}.{key}")) + elif value is None or value == "" or value == [] or value == {}: + missing.append(prefix) + return missing + + +def _evidence_packet(case: dict[str, Any], target_fields: list[str]) -> dict[str, Any]: + case_id = case.get("id") or case.get("case_id") + target = case.get("target", {}) + settings = case.get("settings", {}) + metadata = { + "tool_name": "", + "tool_version_or_url": "", + "capture_date": "", + "source_artifact": "references/oracle/artifacts/", + "ayanamsa": settings.get("ayanamsa", ""), + "node_mode": settings.get("node_mode", ""), + "timezone": case.get("birth", {}).get("tz", ""), + "annual_system": settings.get("annual_system", "varshaphala"), + "target_year": settings.get("target_year", ""), + "operator_note": "", + } + return { + "capture_id": f"external_{case_id}", + "status": "draft", + "case_id": case_id, + "birth": case.get("birth", {}), + "settings": settings, + "required_metadata_fields": REQUIRED_EVIDENCE_METADATA_FIELDS, + "metadata": metadata, + "target_placeholders": {field: _target_value(target, field) for field in target_fields}, + "integrity_checks": { + "must_not_come_from_local_engine": True, + "requires_external_artifact": True, + "requires_status_external_verified_before_calibration": True, + "requires_solar_return_convention": True, + }, + "promotion_status_after_fill": "external_verified", + } + + +def _task_from_template(case: dict[str, Any]) -> dict[str, Any]: + case_id = case.get("id") or case.get("case_id") + target = case.get("target", {}) + target_fields = _target_fields(target) + missing_fields = _missing_target_fields(target) + status = case.get("status", "template_only") + ready_for_calibration = status == "external_verified" and not missing_fields + return { + "task_id": f"collect_{case_id}", + "case_id": case_id, + "status": status, + "source": case.get("source"), + "privacy": case.get("privacy"), + "birth": case.get("birth", {}), + "settings": case.get("settings", {}), + "target_fields": target_fields, + "missing_target_fields": missing_fields, + "preferred_sources": SOURCE_GUIDANCE["preferred_sources"], + "collection_steps": SOURCE_GUIDANCE["collection_steps"], + "promotion_criteria": SOURCE_GUIDANCE["promotion_criteria"], + "evidence_packet": _evidence_packet(case, target_fields), + "ready_for_collection": bool(missing_fields), + "ready_for_calibration": ready_for_calibration, + "blocked_reason": "" if ready_for_calibration else "external_annual_evidence_required", + "do_not_tune_production": not ready_for_calibration, + "verification_note": case.get("verification_note", ""), + } + + +def build_queue(oracle: dict[str, Any]) -> dict[str, Any]: + tasks = [_task_from_template(case) for case in oracle.get("template_cases", [])] + by_status: dict[str, int] = {} + for task in tasks: + status = task.get("status", "unknown") + by_status[status] = by_status.get(status, 0) + 1 + ready_for_calibration = sum(1 for task in tasks if task["ready_for_calibration"]) + return { + "scope": "tajika_sahams_annual_oracle_collection_queue", + "schema_version": 1, + "summary": { + "total_tasks": len(tasks), + "by_status": by_status, + "ready_for_collection": sum(1 for task in tasks if task["ready_for_collection"]), + "ready_for_calibration": ready_for_calibration, + "production_tuning_allowed": ready_for_calibration > 0 and ready_for_calibration == len(tasks), + "next_action": "Collect external Varshaphala target values, then promote rows to external_verified.", + }, + "tasks": tasks, + "boundary": ( + "Rows remain collection tasks until external solar return, Muntha, Year Lord, Mudda Dasha, " + "Sahams and Tajika Yogas targets are filled. Local annual-chart output and template-only rows " + "must not be used for production tuning." + ), + } + + +def render_markdown(queue: dict[str, Any]) -> str: + summary = queue["summary"] + lines = [ + "# Tajika/Sahams Annual External Oracle Collection Queue", + "", + f"total_tasks: `{summary['total_tasks']}`", + f"ready_for_collection: `{summary['ready_for_collection']}`", + f"ready_for_calibration: `{summary['ready_for_calibration']}`", + f"production_tuning_allowed: `{str(summary['production_tuning_allowed']).lower()}`", + "", + "## Required Evidence", + "", + "Solar return datetime, Varsha Lagna, Muntha, Year Lord, Mudda Dasha, Punya Saham, Rajya Saham, Vivah Saham and Tajika Yogas.", + "", + "| task_id | status | missing fields | preferred sources |", + "|---|---|---|---|", + ] + for task in queue["tasks"]: + lines.append( + f"| {task['task_id']} | `{task['status']}` | {', '.join(task['missing_target_fields'])} | {', '.join(task['preferred_sources'])} |" + ) + lines.extend(["", queue["boundary"], ""]) + return "\n".join(lines) + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Generate Tajika/Sahams annual oracle collection tasks") + parser.add_argument("--oracle-file", required=True) + parser.add_argument("--format", choices=["json", "markdown"], default="json") + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + queue = build_queue(_load_json(args.oracle_file)) + if args.format == "markdown": + print(render_markdown(queue)) + else: + print(json.dumps(queue, ensure_ascii=False, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_tajika_annual_benchmark_dashboard.py b/tests/test_tajika_annual_benchmark_dashboard.py new file mode 100644 index 00000000..429b87c2 --- /dev/null +++ b/tests/test_tajika_annual_benchmark_dashboard.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Tests for the Tajika/Sahams annual benchmark dashboard.""" + +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +ORACLE_FILE = "references/oracle/tajika_annual_oracle_cases.json" + + +def run_dashboard(*args: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [ + sys.executable, + "scripts/tajika_annual_benchmark_dashboard.py", + "--oracle-file", + ORACLE_FILE, + *args, + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=60, + check=False, + ) + + +def test_tajika_annual_dashboard_outputs_stable_json_summary() -> None: + completed = run_dashboard("--format", "json") + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + assert report["scope"] == "tajika_sahams_annual_benchmark_dashboard" + assert report["schema_version"] == 1 + assert report["summary"]["total_tasks"] == 5 + assert report["summary"]["ready_for_calibration"] == 0 + assert report["summary"]["production_tuning_allowed"] is False + assert report["annual_claim"]["can_claim_tajika_sahams_closure"] is False + assert "Solar return" in report["remaining_gap"] + assert "Sahams" in report["remaining_gap"] + + +def test_tajika_annual_dashboard_outputs_markdown_and_can_write_file(tmp_path: Path) -> None: + output = tmp_path / "tajika_dashboard.md" + completed = run_dashboard("--format", "markdown", "--output", str(output)) + + assert completed.returncode == 0, completed.stderr or completed.stdout + assert output.exists() + markdown = output.read_text(encoding="utf-8") + assert "# Tajika/Sahams Annual Benchmark Dashboard" in markdown + assert "can_claim_tajika_sahams_closure: `false`" in markdown + assert "production_tuning_allowed: `false`" in markdown diff --git a/tests/test_tajika_annual_oracle_queue.py b/tests/test_tajika_annual_oracle_queue.py new file mode 100644 index 00000000..f1828de4 --- /dev/null +++ b/tests/test_tajika_annual_oracle_queue.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Tests for the Tajika/Sahams annual external-oracle collection queue.""" + +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +ORACLE_FILE = "references/oracle/tajika_annual_oracle_cases.json" + + +def run_queue(*args: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, "scripts/tajika_annual_oracle_queue.py", "--oracle-file", ORACLE_FILE, *args], + cwd=ROOT, + text=True, + capture_output=True, + timeout=60, + check=False, + ) + + +def test_tajika_annual_queue_outputs_collection_tasks() -> None: + completed = run_queue("--format", "json") + + assert completed.returncode == 0, completed.stderr or completed.stdout + queue = json.loads(completed.stdout) + assert queue["scope"] == "tajika_sahams_annual_oracle_collection_queue" + assert queue["schema_version"] == 1 + assert queue["summary"]["total_tasks"] == 5 + assert queue["summary"]["ready_for_calibration"] == 0 + assert queue["summary"]["production_tuning_allowed"] is False + assert "solar return" in queue["boundary"].lower() + + first = queue["tasks"][0] + assert first["task_id"].startswith("collect_") + assert first["status"] == "template_only" + assert first["ready_for_collection"] is True + assert first["ready_for_calibration"] is False + assert "target.solar_return_datetime" in first["target_fields"] + assert "target.sahams.punya_saham" in first["missing_target_fields"] + assert "JHora Varshaphala screenshot" in first["preferred_sources"] + assert first["evidence_packet"]["integrity_checks"]["requires_external_artifact"] is True + + +def test_tajika_annual_queue_markdown_lists_sahams_and_yogas() -> None: + completed = run_queue("--format", "markdown") + + assert completed.returncode == 0, completed.stderr or completed.stdout + markdown = completed.stdout + assert "# Tajika/Sahams Annual External Oracle Collection Queue" in markdown + assert "Punya Saham" in markdown + assert "Tajika Yogas" in markdown + assert "production_tuning_allowed: `false`" in markdown