Files
Jyotisha/scripts/full_technique_invocation_matrix.py
2026-07-23 12:03:58 +08:00

353 lines
17 KiB
Python

#!/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"(?<![a-z0-9]){escaped}(?![a-z0-9])"))
else:
patterns.append(re.compile(escaped))
hit_files: list[str] = []
total = 0
for rel, text in indexed_files:
count = sum(len(pattern.findall(text)) for pattern in patterns)
if count:
total += count
hit_files.append(rel)
return total, hit_files[:12]
def _state_from_hits(count: int, layer: str) -> 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()