#!/usr/bin/env python3 from __future__ import annotations import hashlib import json import re from dataclasses import dataclass from pathlib import Path ROOT = Path(__file__).resolve().parents[1] OUTPUT = ROOT / "references/oracle/full_technique_invocation_matrix_2026_07_22.json" SCAN_ROOTS = { "skill": ["SKILL.md", "skills"], "api": ["mcp_server.py", "scripts/jyotish_api_server.py", "scripts"], "ui": ["jyotish-app"], "scripts": ["scripts"], "references": ["references"], "tests": ["tests"], } SKIP_PARTS = { ".git", "__pycache__", "node_modules", "dist", "build", ".pytest_cache", ".mypy_cache", } MAX_SCAN_BYTES = 1_000_000 @dataclass(frozen=True) class Technique: technique_id: str label: str domain: str aliases: tuple[str, ...] p_batch: str first_batch: bool = False TECHNIQUES: tuple[Technique, ...] = ( Technique("ul_arudha_upapada", "UL / Upapada Lagna", "arudha_rectification", ("UL", "Upapada", "upapada"), "P0", True), Technique("a7_arudha_relationship", "A7 Arudha", "arudha_rectification", ("A7", "arudha a7", "darapada"), "P0", True), Technique("a10_arudha_career", "A10 Arudha", "arudha_rectification", ("A10", "arudha a10", "rajyapada"), "P0", True), Technique("kp_exact_cusp", "KP exact cusp", "kp_precision_timing", ("KP cusp", "house cusp", "exact cusp", "cusp longitude"), "P0", True), Technique("kp_star_sub_sub", "KP star / sub / sub-sub", "kp_precision_timing", ("star lord", "sub lord", "sub-sub", "sub sub", "KP star"), "P0", True), Technique("kp_significator", "KP significator table", "kp_precision_timing", ("significator", "KP significator"), "P1", True), Technique("kp_ruling_planets", "KP ruling planets", "kp_precision_timing", ("ruling planets", "RP", "KP ruling"), "P1", True), Technique("shadbala", "Shadbala total", "strength", ("Shadbala", "Ṣaḍbala", "Sadbal"), "P0", True), Technique("shadbala_chesta", "Shadbala Chesta Bala", "strength", ("Chesta", "ceṣṭā", "mean-motion", "Seeghrochcha"), "P0", True), Technique("shadbala_sthana", "Shadbala Sthana Bala", "strength", ("Sthana", "Uccha", "Saptavargaja", "Ojayugma", "Kendradi", "Drekkana"), "P0", True), Technique("shadbala_dig", "Shadbala Dig Bala", "strength", ("Dig Bala", "Digbala"), "P1"), Technique("shadbala_drik", "Shadbala Drik Bala", "strength", ("Drik Bala", "aspect strength"), "P1"), Technique("shadbala_kala", "Shadbala Kala Bala", "strength", ("Kala Bala", "Natonnata", "Paksha", "Tribhaga", "Abda", "Masa", "Vara", "Hora"), "P1"), Technique("naisargika_bala", "Naisargika Bala", "strength", ("Naisargika", "natural strength"), "P1"), Technique("ashtakavarga_bav", "Ashtakavarga BAV", "ashtakavarga", ("BAV", "Bhinnashtakavarga", "Bhinnashtaka"), "P1"), Technique("ashtakavarga_sav", "Ashtakavarga SAV", "ashtakavarga", ("SAV", "Sarvashtakavarga", "Sarvashtaka"), "P1"), Technique("ashtakavarga_kakshya", "Ashtakavarga Kakshya", "ashtakavarga", ("Kakshya", "kakshya transit"), "P1"), Technique("muhurta", "Muhurta full scoring", "muhurta", ("Muhurta", "Electional", "择日"), "P0", True), Technique("tarabala", "Tarabala", "muhurta", ("Tarabala", "Tara Bala"), "P0", True), Technique("chandrabala", "Chandrabala", "muhurta", ("Chandrabala", "Chandra Bala"), "P0", True), Technique("rahu_kalam", "Rahu Kalam", "muhurta", ("Rahu Kalam", "Rahu Kala", "Rāhukāla"), "P0", True), Technique("yamaganda", "Yamaganda", "muhurta", ("Yamaganda", "Yama Gandam", "Yamakanda"), "P1"), Technique("gulika_kalam", "Gulika Kalam", "muhurta", ("Gulika Kalam", "Gulikai", "Mandi Kalam"), "P1"), Technique("abhijit_muhurta", "Abhijit Muhurta", "muhurta", ("Abhijit", "Abhijit Muhurta"), "P0", True), Technique("panchaka", "Panchaka", "muhurta", ("Panchaka",), "P1"), Technique("vyatipata_vaidhriti", "Vyatipata / Vaidhriti", "muhurta", ("Vyatipata", "Vaidhriti"), "P1"), Technique("sankranti_gate", "Sankranti gate", "muhurta", ("Sankranti", "Saṅkrānti"), "P1"), Technique("prashna", "Prashna chart", "horary", ("Prashna", "Horary", "question chart"), "P0", True), Technique("sphuta", "Sphuta set", "horary", ("Sphuta", "Trisphuta", "Catusphuta", "Panchasphuta"), "P0", True), Technique("gulika", "Gulika / Mandi", "horary", ("Gulika", "Mandi"), "P0", True), Technique("saham", "Saham lots", "horary_annual", ("Saham", "Sahams", "Arabic parts"), "P0", True), Technique("tajika", "Tajika / Varshaphala", "annual", ("Tajika", "Varshaphala", "annual chart"), "P0", True), Technique("muntha", "Muntha", "annual", ("Muntha",), "P1"), Technique("mudda_dasha", "Mudda Dasha", "annual", ("Mudda", "Mudda Dasha"), "P1"), Technique("vimshottari", "Vimshottari Dasha", "dasha", ("Vimshottari",), "P1"), Technique("narayana_dasha", "Narayana Dasha", "dasha", ("Narayana",), "P1"), Technique("chara_dasha", "Chara Dasha", "dasha", ("Chara Dasha",), "P2"), Technique("functional_benefic_malefic", "Functional Benefic / Malefic", "interpretation_gate", ("Functional Benefic", "Functional Malefic", "功能性吉凶"), "P0"), Technique("mevg", "MEVG / real-case calibration", "interpretation_gate", ("MEVG", "Real Case Calibration", "Global Web Evidence"), "P0"), Technique("birth_time_rectification", "Birth-time rectification", "rectification", ("birth-time rectification", "生时校正", "rectification"), "P0"), Technique("timing_holdout", "Timing negative holdout", "timing", ("holdout", "negative controls", "blind ranking", "负样本"), "P0"), Technique("d1_rasi", "D1 Rasi", "varga", ("D1", "Rasi", "Rāśi"), "P1"), Technique("d2_hora", "D2 Hora", "varga", ("D2", "Hora"), "P1"), Technique("d3_drekkana", "D3 Drekkana", "varga", ("D3", "Drekkana", "Decanate"), "P1"), Technique("d4_chaturthamsha", "D4 Chaturthamsha", "varga", ("D4", "Chaturthamsha"), "P1"), Technique("d7_saptamsha", "D7 Saptamsha", "varga", ("D7", "Saptamsha"), "P1"), Technique("d9_navamsa", "D9 Navamsa", "varga", ("D9", "Navamsa"), "P1"), Technique("d10_dasamsha", "D10 Dasamsha", "varga", ("D10", "Dasamsha"), "P1"), Technique("d12_dwadashamsha", "D12 Dwadashamsha", "varga", ("D12", "Dwadashamsha"), "P2"), Technique("d24_chaturvimshamsha", "D24 Chaturvimshamsha", "varga", ("D24", "Chaturvimshamsha", "Siddhamsa"), "P2"), Technique("d30_trimsamsha", "D30 Trimsamsha", "varga", ("D30", "Trimsamsha"), "P2"), Technique("d60_shashtiamsha", "D60 Shashtiamsha", "varga", ("D60", "Shashtiamsha"), "P1"), Technique("darakaraka", "Darakaraka", "relationship", ("Darakaraka", "DK"), "P1"), Technique("ashtakoota", "Ashtakoota matching", "relationship", ("Ashtakoota", "Koota", "Guna Milan"), "P1"), Technique("mangal_dosha", "Mangal Dosha", "relationship", ("Mangal Dosha", "Kuja Dosha"), "P1"), Technique("relationship_combinations", "Relationship rule-family combinations", "relationship", ("relationship combinations", "marriage combinations", "合盘"), "P1"), Technique("conception_adhana_niseka", "Adhana / Niseka conception chart", "conception", ("Adhana", "Niseka", "conception chart"), "P2"), Technique("argala", "Argala", "interpretation", ("Argala",), "P2"), Technique("yoga", "Yoga rules", "interpretation", ("Yoga", "Raja Yoga", "Dhana Yoga"), "P1"), ) def _iter_files(paths: list[str]) -> list[Path]: files: list[Path] = [] for rel in paths: base = ROOT / rel if not base.exists(): continue if base.is_file(): files.append(base) continue for path in base.rglob("*"): if path.is_file() and not (set(path.parts) & SKIP_PARTS): if path.suffix.lower() in {".py", ".js", ".ts", ".jsx", ".tsx", ".json", ".md", ".txt"}: files.append(path) return sorted(set(files)) def _read_layer(files: list[Path]) -> tuple[list[tuple[str, str]], list[str]]: indexed: list[tuple[str, str]] = [] skipped: list[str] = [] for path in files: try: if path.stat().st_size > MAX_SCAN_BYTES: skipped.append(str(path.relative_to(ROOT))) continue text = path.read_text(encoding="utf-8", errors="ignore").lower() except OSError: continue indexed.append((str(path.relative_to(ROOT)), text)) return indexed, skipped def _count_hits(indexed_files: list[tuple[str, str]], aliases: tuple[str, ...]) -> tuple[int, list[str]]: patterns = [] for alias in aliases: escaped = re.escape(alias.lower()) if re.fullmatch(r"[a-z0-9]+", alias.lower()) and len(alias) <= 4: patterns.append(re.compile(rf"(? str: if count <= 0: return "missing" if layer in {"references", "tests"}: return "present" if count <= 2: return "thin" return "present" def _claim_status(tech: Technique, layer_states: dict[str, str]) -> str: refs = layer_states["references"] != "missing" code = layer_states["scripts"] != "missing" or layer_states["api"] != "missing" ui = layer_states["ui"] != "missing" tests = layer_states["tests"] != "missing" if tech.technique_id in { "kp_exact_cusp", "kp_star_sub_sub", "timing_holdout", "saham", "tajika", "gulika", "sphuta", "prashna", }: return "blocked_or_observation_only" if tech.technique_id in {"shadbala_chesta", "shadbala_sthana", "muhurta"}: return "component_partial" if refs and code and tests and ui: return "invoked_with_boundary" if refs and (code or tests): return "evidence_present_not_fully_invoked" if refs: return "reference_only" return "gap_candidate" def _missing_integration(layer_states: dict[str, str]) -> list[str]: missing = [] for key in ("skill", "api", "ui", "scripts", "tests"): if layer_states[key] == "missing": missing.append(key) return missing def _priority_score(tech: Technique, layer_states: dict[str, str], claim_status: str) -> int: score = {"P0": 300, "P1": 200, "P2": 100}[tech.p_batch] if tech.first_batch: score += 80 if layer_states["references"] != "missing": score += 40 if layer_states["scripts"] != "missing" or layer_states["tests"] != "missing": score += 25 if layer_states["api"] == "missing": score += 12 if layer_states["ui"] == "missing": score += 10 if claim_status in {"blocked_or_observation_only", "component_partial", "evidence_present_not_fully_invoked"}: score += 20 return score def build_matrix() -> dict: files_by_layer = {layer: _iter_files(paths) for layer, paths in SCAN_ROOTS.items()} indexed_by_layer = {} skipped_by_layer = {} for layer, files in files_by_layer.items(): indexed_by_layer[layer], skipped_by_layer[layer] = _read_layer(files) rows = [] for tech in TECHNIQUES: layer_hits = {} layer_samples = {} layer_states = {} for layer, indexed_files in indexed_by_layer.items(): count, samples = _count_hits(indexed_files, tech.aliases) layer_hits[layer] = count layer_samples[layer] = samples layer_states[layer] = _state_from_hits(count, layer) claim_status = _claim_status(tech, layer_states) missing = _missing_integration(layer_states) has_material = layer_states["references"] != "missing" or layer_states["tests"] != "missing" or layer_states["scripts"] != "missing" uninvoked = has_material and ("api" in missing or "ui" in missing or "skill" in missing) rows.append( { "technique_id": tech.technique_id, "label": tech.label, "domain": tech.domain, "priority_batch": tech.p_batch, "first_batch_requested": tech.first_batch, "layer_states": layer_states, "hit_counts": layer_hits, "sample_files": layer_samples, "has_material": has_material, "has_material_but_not_fully_invoked": uninvoked, "missing_integration": missing, "claim_status": claim_status, "commercial_sync_policy": "sync_observation_or_boundary_contract_only" if claim_status in {"blocked_or_observation_only", "component_partial"} else "sync_when_invocation_and_claim_gate_are_explicit", "claim_boundary": _claim_boundary(tech, claim_status), "priority_score": _priority_score(tech, layer_states, claim_status), } ) top50 = sorted( [row for row in rows if row["has_material_but_not_fully_invoked"]], key=lambda row: (-row["priority_score"], row["technique_id"]), )[:50] first_batch_ids = { "ul_arudha_upapada", "a7_arudha_relationship", "a10_arudha_career", "kp_exact_cusp", "kp_star_sub_sub", "kp_significator", "kp_ruling_planets", "shadbala_chesta", "shadbala_sthana", "muhurta", "tarabala", "chandrabala", "rahu_kalam", "abhijit_muhurta", "prashna", "sphuta", "gulika", "saham", "tajika", } migration_batches = { "P0_first_batch_requested": [ row for row in rows if row["technique_id"] in first_batch_ids and row["priority_batch"] == "P0" ], "P1_first_batch_requested": [ row for row in rows if row["technique_id"] in first_batch_ids and row["priority_batch"] == "P1" ], "P2_deferred": [row for row in rows if row["priority_batch"] == "P2"], } return { "scope": "full_technique_invocation_matrix", "created_at": "2026-07-22", "claim_status": "planning_and_invocation_audit", "production_tuning_allowed": False, "truth_matrix_allowed": False, "boundary": "Automated static invocation scan across skill/API/UI/scripts/references/tests. Hit presence is not numeric oracle closure or predictive validation.", "scan_roots": SCAN_ROOTS, "scan_limits": { "max_file_bytes": MAX_SCAN_BYTES, "skipped_large_files_by_layer": skipped_by_layer, }, "summary": { "technique_count": len(rows), "first_batch_count": sum(1 for row in rows if row["first_batch_requested"]), "material_but_not_fully_invoked_count": sum(1 for row in rows if row["has_material_but_not_fully_invoked"]), "top50_count": len(top50), "p0_count": sum(1 for row in rows if row["priority_batch"] == "P0"), "p1_count": sum(1 for row in rows if row["priority_batch"] == "P1"), "p2_count": sum(1 for row in rows if row["priority_batch"] == "P2"), }, "first_batch_execution_queue": [ row for row in rows if row["first_batch_requested"] and row["priority_batch"] in {"P0", "P1"} ], "migration_batches": migration_batches, "top50_material_not_invoked": top50, "rows": rows, "content_hash": "", } def _claim_boundary(tech: Technique, claim_status: str) -> str: if tech.technique_id.startswith("kp_"): return "KP runtime/probes may be displayed as observation; exact cusp/star-sub-sub needs public numeric worked example before verified timing use." if tech.technique_id.startswith("shadbala"): return "Shadbala must show component-level unit/formula/method-variant status; no absolute Virupa truth upgrade until component arbitration closes." if tech.domain == "muhurta": return "Muhurta factors may be shown as observations/scoring inputs; no final election verdict until worked examples validate the scoring matrix." if tech.technique_id in {"prashna", "sphuta", "gulika", "saham", "tajika"}: return "Horary/annual numeric packets require public input contract plus expected numeric fields; otherwise observation/queue only." if tech.technique_id == "timing_holdout": return "Exact month/day and rectification claims require frozen positive/negative labels and blind ranking." if claim_status == "reference_only": return "Reference exists but runtime invocation is not proven." return "Use only with explicit source, settings, and claim boundary." def main() -> None: packet = build_matrix() without_hash = dict(packet) without_hash["content_hash"] = "" digest = hashlib.sha256(json.dumps(without_hash, ensure_ascii=False, sort_keys=True).encode("utf-8")).hexdigest() packet["content_hash"] = digest OUTPUT.write_text(json.dumps(packet, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") print(json.dumps({"path": str(OUTPUT.relative_to(ROOT)), "sha256": digest, "summary": packet["summary"]}, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()