diff --git a/references/event_judgment_marriage.md b/references/event_judgment_marriage.md index 25b763ae..81f563e0 100644 --- a/references/event_judgment_marriage.md +++ b/references/event_judgment_marriage.md @@ -88,6 +88,30 @@ 若 `Vimshottari` 与 `Narayana` 明显相反,标记 `mixed` 或 `blocked`。 +### 4.2.1 Non-Standard Proxy Activation + +不要把婚期只限缩到 `Venus / Jupiter / Saturn`。 + +公开案例显示,`Mercury / Moon / Rahu / Ketu` 等非典型大运、小运也可能给出婚姻事件,但必须满足: + +```text +active_lord not in {Venus, Jupiter, Saturn} +AND active_lord linked to any of {2H, 7H, 8H, 11H, 7L, Venus, Jupiter, DK, UL, A7, D9_7H, D9_7L} +AND supported by at least one of {PD/PrAD, Chara/Narayana, Transit/Double Transit, D9, UL/A7} +``` + +解释边界: + +- `Rahu/Ketu` 型:更常见非常规、突然、异地/异文化、秘密性、压力或强吸引。 +- `Mercury` 型:更常见网络、学习、工作协作、朋友介绍、沟通、文书渠道。 +- `Moon` 型:更常见家庭、安全感、同居、情绪依赖、照顾与生活稳定。 + +机器可审计资料见: + +```bash +python3 scripts/non_standard_marriage_trigger_audit.py --pretty +``` + ### 4.3 Manifestation 这一层专门防止“有窗口但没落地”。 diff --git a/references/non_standard_marriage_trigger_cases.json b/references/non_standard_marriage_trigger_cases.json new file mode 100644 index 00000000..969b8fa6 --- /dev/null +++ b/references/non_standard_marriage_trigger_cases.json @@ -0,0 +1,103 @@ +{ + "schema_version": 1, + "scope": "non_standard_marriage_trigger_case_rules", + "boundary": "Public web case notes for marriage timing adjudication. Use as MEVG calibration, not as proof of deterministic prediction.", + "standard_marriage_lords": ["Venus", "Jupiter", "Saturn"], + "marriage_network_factors": [ + "2H", + "7H", + "8H", + "11H", + "7L", + "Venus", + "Jupiter", + "DK", + "UL", + "A7", + "D9_7H", + "D9_7L" + ], + "rules": [ + { + "id": "non_standard_proxy_trigger", + "description": "A marriage event can occur outside Venus/Jupiter/Saturn-led periods when the active dasha lord is tied to the marriage network and is confirmed by subperiod, Jaimini/Narayana or transit support.", + "required": ["active_lord_not_standard", "marriage_network_link"], + "confirmers": ["PD/PrAD", "Chara/Narayana", "Transit/Double Transit", "D9", "UL/A7"] + }, + { + "id": "rahu_ketu_nontraditional_trigger", + "description": "Rahu/Ketu can trigger marriage when connected to 7H/7L/Venus/Jupiter/DK/UL/D9, often with nontraditional, sudden, cross-cultural, secretive or pressured relationship texture.", + "required": ["Rahu_or_Ketu_active", "marriage_network_link"], + "confirmers": ["D9", "UL", "PD/PrAD", "Transit/Double Transit"] + }, + { + "id": "mercury_channel_trigger", + "description": "Mercury can act as a marriage proxy when it owns, occupies, aspects or disposits marriage factors; channel often appears through communication, study, work, friends, documents or online contact.", + "required": ["Mercury_active", "marriage_network_link"], + "confirmers": ["7L", "D9", "UL/A7", "PD/PrAD"] + }, + { + "id": "moon_family_emotional_trigger", + "description": "Moon can act as a marriage proxy when it links to 7L/7H/D9/UL; manifestation often comes through family, cohabitation, emotional dependence or domestic security.", + "required": ["Moon_active", "marriage_network_link"], + "confirmers": ["D9", "UL", "Transit/Double Transit"] + } + ], + "cases": [ + { + "id": "lal_bahadur_shastri_1928_marriage", + "source": "Journal of Astrology", + "url": "https://www.journalofastrology.com/article.php?article_id=357", + "event": "marriage", + "event_date": "1928-05-16", + "reported_dasha": ["Rahu", "Moon", "Mercury"], + "non_standard_lords": ["Rahu", "Moon", "Mercury"], + "marriage_network_links": ["7L", "DK", "D9", "Jaimini"], + "lesson": "Rahu/Moon/Mercury can still time marriage when Mercury functions as 7L and Jaimini/D9 support the marriage axis." + }, + { + "id": "joa_woman_lawyer_1986_marriage", + "source": "Journal of Astrology", + "url": "https://www.journalofastrology.com/article.php?article_id=149", + "event": "marriage", + "event_date": "1986-12", + "reported_dasha": ["Ketu", "Saturn", "Mercury"], + "non_standard_lords": ["Ketu", "Mercury"], + "marriage_network_links": ["Venus", "7L", "D9", "Jaimini"], + "lesson": "Ketu period is not automatically anti-marriage; if Ketu is Venus-colored and Mercury is 7L, it can deliver marriage." + }, + { + "id": "astrological_musings_early_marriage_2001", + "source": "Astrological Musings", + "url": "https://astrologicalmusings.com/astrological-yogas-for-an-early-marriage-ii/", + "event": "marriage", + "event_date": "2001", + "reported_dasha": ["Rahu", "Saturn"], + "non_standard_lords": ["Rahu"], + "marriage_network_links": ["D9_7L", "Venus", "7H_from_Venus"], + "lesson": "Rahu can deliver marriage when D9 and Venus-derived relationship houses are directly involved." + }, + { + "id": "lova_forum_marriage_1992", + "source": "LightOnVedicAstrology forum", + "url": "https://phpbb.lightonvedicastrology.com/viewtopic.php?t=10569", + "event": "marriage", + "event_date": "1992-12-01", + "reported_dasha": ["Moon", "Mercury"], + "non_standard_lords": ["Moon", "Mercury"], + "marriage_network_links": ["7H", "Jupiter", "D9"], + "lesson": "Moon/Mercury periods can coincide with marriage when the natal relationship promise is strong and Mercury/Moon connect to the marriage field." + }, + { + "id": "shubham_alock_marriage_timing_case", + "source": "Shubham Alock", + "url": "https://shubhamalock.wordpress.com/2016/05/18/timing-of-marriage-using-vimshottari-and-narayan-dasha-other-aspects-of-marital-life/", + "event": "marriage", + "event_date": "case_article", + "reported_dasha": ["Mercury", "Jupiter", "Mars", "Mercury", "Rahu"], + "non_standard_lords": ["Mercury", "Rahu"], + "marriage_network_links": ["7L", "D9", "A7", "Narayana"], + "lesson": "Mercury/Rahu can be decisive when Vimshottari subperiods and Narayana Dasha both route through marriage significators." + } + ] +} diff --git a/references/public_oracle_cases.json b/references/public_oracle_cases.json new file mode 100644 index 00000000..ce75425d --- /dev/null +++ b/references/public_oracle_cases.json @@ -0,0 +1,108 @@ +{ + "schema_version": 1, + "scope": "public_birth_case_replay_seed", + "boundary": "Public birth data seeds for engine replay. These are not private user data and are not sufficient by themselves to prove predictive accuracy.", + "cases": [ + { + "id": "einstein_1879_aa", + "name": "Albert Einstein", + "source_quality": "public_astro_databank_aa", + "sources": [ + "https://www.astro.com/astro-databank/Einstein,_Albert", + "https://vedastro.org/AstroDataBank/Person/Albert_Einstein" + ], + "birth": { + "year": 1879, + "month": 3, + "day": 14, + "hour": 11, + "minute": 30, + "second": 0, + "lat": 48.4, + "lon": 9.983333, + "tz": 0.883333 + }, + "expected_oracles": { + "pyjhora": { + "tajika_varshaphala_1905": { + "status": "partial_artifact_available", + "artifact": "references/oracle/artifacts/pyjhora_einstein_varshaphala_1905_lahiri_partial_20260629.txt", + "packet": "references/oracle/artifacts/pending_packets/external_template_einstein_varshaphala_1905_lahiri.json", + "expected_fields_status": "pending_external_capture" + } + }, + "jhora": {"status": "pending_external_capture"}, + "vedastro": {"status": "pending_raw_response"} + }, + "notes": "Ulm local mean time converted as +00:53. Use only as a public replay seed unless an external oracle output is attached." + }, + { + "id": "steve_jobs_1955_aa", + "name": "Steve Jobs", + "source_quality": "public_astro_databank_aa", + "sources": [ + "https://www.astro.com/astro-databank/Jobs,_Steve", + "https://vedastro.org/AstroDataBank/Person/Steve_Jobs" + ], + "birth": { + "year": 1955, + "month": 2, + "day": 24, + "hour": 19, + "minute": 15, + "second": 0, + "lat": 37.7749, + "lon": -122.4194, + "tz": -8 + }, + "expected_oracles": { + "pyjhora": { + "vimshottari_start_date": { + "status": "external_verified", + "artifact": "references/oracle/artifacts/pyjhora_steve_jobs_dasha_stdout_20260627.txt", + "packet": "references/oracle/artifacts/pending_packets/external_template_steve_jobs_dasha_lahiri_pyjhora_20260627.json", + "expected": {"vimshottari_start_date": "1939-03-22"} + }, + "shadbala_components": { + "status": "external_verified", + "artifact": "references/oracle/artifacts/pyjhora_steve_jobs_shadbala_lahiri_stdout_20260627.txt", + "packet": "references/oracle/artifacts/pending_packets/external_template_steve_jobs_shadbala_lahiri_pyjhora_20260627.json", + "expected_fields_status": "packet_contains_target_shadbala_components" + }, + "tajika_varshaphala_1984": { + "status": "external_verified", + "artifact": "references/oracle/artifacts/pyjhora_steve_jobs_varshaphala_1984_lahiri_stdout_20260627.txt", + "packet": "references/oracle/artifacts/pending_packets/external_template_steve_jobs_varshaphala_1984_lahiri_pyjhora_20260627.json" + } + }, + "jhora": {"status": "pending_external_capture"}, + "vedastro": {"status": "pending_raw_response"} + } + }, + { + "id": "barack_obama_1961_aa", + "name": "Barack Obama", + "source_quality": "public_birth_certificate", + "sources": [ + "https://www.astro.com/astro-databank/Obama,_Barack", + "https://vedastro.org/AstroDataBank/Person/Barack_Obama" + ], + "birth": { + "year": 1961, + "month": 8, + "day": 4, + "hour": 19, + "minute": 24, + "second": 0, + "lat": 21.3069, + "lon": -157.8583, + "tz": -10 + }, + "expected_oracles": { + "pyjhora": {"status": "pending_external_capture"}, + "jhora": {"status": "pending_external_capture"}, + "vedastro": {"status": "pending_raw_response"} + } + } + ] +} diff --git a/references/traditional_maturity_gap_matrix.json b/references/traditional_maturity_gap_matrix.json new file mode 100644 index 00000000..ed741775 --- /dev/null +++ b/references/traditional_maturity_gap_matrix.json @@ -0,0 +1,119 @@ +{ + "schema_version": 1, + "scope": "traditional_jyotish_maturity_gap_matrix", + "boundary": "covered means a stable local entrypoint or usable layer exists; it does not mean expert adjudication, external oracle parity, public-case replay, or frontend/API exposure is complete.", + "execution_phases": [ + { + "id": "phase_1_truth_boundary", + "name": "Truth Boundary", + "work": ["skill gap truth audit", "maturity dashboard", "covered-vs-complete guard"] + }, + { + "id": "phase_2_oracle_expansion", + "name": "Dasha / Shadbala / Tajika oracle expansion", + "work": ["external samples", "benchmark reports", "accepted/mismatch/blocked labels"] + }, + { + "id": "phase_3_event_adjudication", + "name": "Event adjudication skeletons", + "work": ["career event judgment", "KP event judgment", "Prashna branch workflows", "annual event judgment"] + }, + { + "id": "phase_4_expert_modifiers", + "name": "Expert interpretation modifiers", + "work": ["dispositor chain", "inter-chart linkage", "avastha/pushkara/vargottama modifiers"] + } + ], + "priorities": [ + { + "id": "p0_dasha_boundary_closure", + "priority": "P0", + "rank": 1, + "area": "Dasha boundary closure", + "available_layers": ["Vimshottari MD/AD", "PD/PrAD proportional expansion", "Narayana", "Chara", "Yogini", "Kalachakra"], + "missing_closure": ["global start-balance parity", "AD/PD/Prana boundary expansion", "multi-dasha family parity", "JHora/PyJHora/book-example expansion"], + "impact": "date-level event timing confidence" + }, + { + "id": "p0_strength_absolute_parity", + "priority": "P0", + "rank": 2, + "area": "Strength systems", + "available_layers": ["Shadbala", "Bhava Bala", "Vimsopaka Bala"], + "missing_closure": ["absolute component parity", "Raman/JHora/PyJHora sample expansion", "Ishta/Kashta integration", "report/frontend display"], + "impact": "planet and house strength statements" + }, + { + "id": "p0_public_benchmark_history", + "priority": "P0", + "rank": 3, + "area": "Long-running public benchmark", + "available_layers": ["public_oracle_replay", "oracle artifacts", "marriage benchmark summary"], + "missing_closure": ["versioned replay history", "domain-level pass/fail statistics", "expected oracle fields for all major techniques"], + "impact": "credible global-rank claims" + }, + { + "id": "p0_tajika_sahams_annual_templates", + "priority": "P0", + "rank": 4, + "area": "Tajika / Varshaphala / Sahams annual event templates", + "available_layers": ["Muntha", "Year Lord", "Mudda Dasha", "Sahams", "Tajika Yogas"], + "missing_closure": ["annual marriage/career/wealth/health templates", "Saham + Muntha + D9/UL/A10 integration", "public annual event replay"], + "impact": "annual event adjudication confidence" + }, + { + "id": "p1_kp_event_adjudication", + "priority": "P1", + "rank": 5, + "area": "KP event adjudication", + "available_layers": ["sub-lord", "sub-sub-lord", "ABCD significator"], + "missing_closure": ["Placidus cusp longitude parity", "1-249 sub-lord end-to-end table audit", "ruling planets", "horary/event judgment workflows", "real event replay"], + "impact": "professional KP-style timing" + }, + { + "id": "p1_prashna_branch_workflows", + "priority": "P1", + "rank": 6, + "area": "Prashna branch workflows", + "available_layers": ["Prashna chart entrypoint", "AL/Sphuta/Sahams references"], + "missing_closure": ["marriage/career/lost-object/transaction/health/travel branch templates", "question-time location policy", "ruling planet integration", "case replay"], + "impact": "question-specific adjudication" + }, + { + "id": "p1_career_a10_karakamsha_amk_chain", + "priority": "P1", + "rank": 7, + "area": "Career A10 / Karakamsha / AmK adjudication", + "available_layers": ["D10", "A10 references", "Chara Karaka", "Argala references", "dispositor chain"], + "missing_closure": ["career event skeleton", "A10/AmK/Karakamsha weighting", "10L from Lagna/Moon/D10 integration", "public career event replay"], + "impact": "traditional career-event judgment" + }, + { + "id": "p1_flying_star_chain", + "priority": "P1", + "rank": 8, + "area": "Flying-star / motion / chain techniques", + "available_layers": ["flying_star_audit", "BCP reference", "Tajika Muntha", "Sudarshana"], + "missing_closure": ["UL-specific chain adapter", "Nadi-style machine adjudicator", "domain score integration", "public-case replay lift statistics"], + "impact": "secondary convergence layer" + }, + { + "id": "p2_avastha_high_varga_integration", + "priority": "P2", + "rank": 9, + "area": "Avastha and high-varga integration", + "available_layers": ["Baladi", "Lajjitadi", "Deep Varga Avastha", "Sayanadi references"], + "missing_closure": ["D9/D10/D30/D60 integration rules", "interpretation vs event-weight policy", "case validation"], + "impact": "interpretive nuance and risk grading" + }, + { + "id": "p2_article_template_industrialization", + "priority": "P2", + "rank": 10, + "area": "Article-level rule industrialization", + "available_layers": ["interpretation_template_registry", "validation templates", "technique registry"], + "missing_closure": ["single-factor-warning rules", "domain-specific confidence ceilings", "case replay links"], + "impact": "prevents course/article-style overclaiming" + } + ] +} diff --git a/scripts/flying_star_audit.py b/scripts/flying_star_audit.py new file mode 100644 index 00000000..1c0fa15f --- /dev/null +++ b/scripts/flying_star_audit.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Inter-chart linkage, dispositor chain, and motion-point audit.""" + +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +SIGNS = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"] +SIGN_LORDS = { + "Aries": "Mars", "Taurus": "Venus", "Gemini": "Mercury", "Cancer": "Moon", + "Leo": "Sun", "Virgo": "Mercury", "Libra": "Venus", "Scorpio": "Mars", + "Sagittarius": "Jupiter", "Capricorn": "Saturn", "Aquarius": "Saturn", "Pisces": "Jupiter", +} + + +def _run_json(command: list[str], timeout: int = 90) -> dict[str, Any]: + completed = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, timeout=timeout, check=False) + if completed.returncode != 0: + return {"status": "error", "stderr": (completed.stderr or completed.stdout).strip()[:500]} + try: + return json.loads(completed.stdout) + except json.JSONDecodeError: + return {"status": "invalid_json", "stdout_excerpt": completed.stdout[:500]} + + +def _birth_args(args: argparse.Namespace) -> list[str]: + out: list[str] = [] + for key in ("year", "month", "day", "hour", "minute", "second", "lat", "lon", "tz"): + out.extend([f"--{key}", str(getattr(args, key))]) + return out + + +def _house(sign: str, asc_sign: str) -> int: + return (SIGNS.index(sign) - SIGNS.index(asc_sign)) % 12 + 1 + + +def _inter_chart_linkage(chart: dict[str, Any], varga: dict[str, Any], planets: list[str]) -> dict[str, Any]: + d1_asc = chart["ascendant"]["sign"] + charts = {"D1": {"ascendant": d1_asc, **chart["planets"]}} + charts.update(varga.get("divisional_charts", {})) + result: dict[str, Any] = {} + for planet in planets: + rows = {} + for chart_name, payload in charts.items(): + asc = payload.get("ascendant") + if isinstance(asc, dict): + asc = asc.get("sign") + item = payload.get(planet) if isinstance(payload, dict) else None + if not item or not asc: + continue + sign = item["sign"] + rows[chart_name] = { + "sign": sign, + "house_in_chart": _house(sign, asc), + "lord_of_sign": SIGN_LORDS.get(sign), + } + result[planet] = rows + return result + + +def _dispositor_chain(chart: dict[str, Any], planet: str, max_depth: int) -> list[dict[str, Any]]: + planets = chart["planets"] + chain = [] + seen = set() + current = planet + for _ in range(max_depth): + item = planets.get(current) + if not item: + break + sign = item["sign"] + lord = SIGN_LORDS[sign] + chain.append({"planet": current, "sign": sign, "dispositor": lord, "dispositor_sign": planets.get(lord, {}).get("sign")}) + key = (current, lord) + if key in seen or lord == current: + break + seen.add(key) + current = lord + return chain + + +def build_report(args: argparse.Namespace) -> dict[str, Any]: + birth = _birth_args(args) + chart = _run_json([sys.executable, "scripts/jyotish_engine.py", "chart", *birth]) + varga = _run_json([sys.executable, "scripts/jyotish_engine.py", "varga", *birth, "--all"]) + sudarshana = _run_json([sys.executable, "scripts/jyotish_engine.py", "sudarshana", *birth, "--house", str(args.event_house)]) + tajika = _run_json([sys.executable, "scripts/jyotish_engine.py", "tajika", *birth, "--age", str(args.age), "--mode", "muntha"]) if args.age is not None else {"status": "blocked", "reason": "age_required"} + planets = [p.strip() for p in args.planets.split(",") if p.strip()] + return { + "scope": "flying_star_audit", + "event_house": args.event_house, + "planets": planets, + "inter_chart_linkage": _inter_chart_linkage(chart, varga, planets), + "dispositor_chains": {planet: _dispositor_chain(chart, planet, args.max_depth) for planet in planets}, + "motion_points": { + "bcp": {"status": "available_reference", "module": "scripts/bhrigu_pada_dasha.py", "note": "BCP/Bhrigu Pada exists; this audit records availability pending clean CLI integration."}, + "tajika_muntha": tajika, + "sudarshana": sudarshana, + }, + "boundaries": { + "nadi_chain": "reference_only_not_full_machine_adjudicator", + "ul_specific_chain": "pending_ul_output_adapter", + }, + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--year", type=int, required=True) + parser.add_argument("--month", type=int, required=True) + parser.add_argument("--day", type=int, required=True) + parser.add_argument("--hour", type=int, required=True) + parser.add_argument("--minute", type=int, required=True) + parser.add_argument("--second", type=int, default=0) + parser.add_argument("--lat", type=float, required=True) + parser.add_argument("--lon", type=float, required=True) + parser.add_argument("--tz", type=float, default=0) + parser.add_argument("--age", type=int) + parser.add_argument("--event-house", type=int, default=7) + parser.add_argument("--planets", default="Venus,Saturn,Mars,Jupiter") + parser.add_argument("--max-depth", type=int, default=12) + args = parser.parse_args() + print(json.dumps(build_report(args), ensure_ascii=False, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/non_standard_marriage_trigger_audit.py b/scripts/non_standard_marriage_trigger_audit.py new file mode 100644 index 00000000..fd45d629 --- /dev/null +++ b/scripts/non_standard_marriage_trigger_audit.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Audit public non-standard marriage timing trigger cases.""" + +from __future__ import annotations + +import argparse +import json +from collections import Counter +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_CASES = ROOT / "references" / "non_standard_marriage_trigger_cases.json" + + +def _resolve(path: str | None) -> Path: + if not path: + return DEFAULT_CASES + candidate = Path(path) + return candidate if candidate.is_absolute() else ROOT / candidate + + +def audit_cases(path: str | None = None) -> dict[str, Any]: + data = json.loads(_resolve(path).read_text(encoding="utf-8")) + standard = set(data["standard_marriage_lords"]) + rows: list[dict[str, Any]] = [] + lord_counts: Counter[str] = Counter() + link_counts: Counter[str] = Counter() + + for case in data["cases"]: + active = set(case.get("reported_dasha") or []) + non_standard = sorted(active - standard) + links = sorted(set(case.get("marriage_network_links") or [])) + lord_counts.update(non_standard) + link_counts.update(links) + rows.append( + { + "id": case["id"], + "event_date": case["event_date"], + "reported_dasha": case.get("reported_dasha", []), + "classification": "non_standard_proxy" if non_standard and links else "standard_or_blocked", + "non_standard_lords": non_standard, + "marriage_network_links": links, + "source": case["source"], + "url": case["url"], + } + ) + + return { + "scope": "non_standard_marriage_trigger_audit", + "schema_version": 1, + "case_count": len(data["cases"]), + "standard_marriage_lords": sorted(standard), + "summary": { + "non_standard_proxy_cases": sum(row["classification"] == "non_standard_proxy" for row in rows), + "top_non_standard_lords": lord_counts.most_common(), + "top_marriage_network_links": link_counts.most_common(), + }, + "rules": data["rules"], + "rows": rows, + "boundary": ( + "Use this only to prevent over-narrow Venus/Jupiter/Saturn timing claims. " + "A non-standard lord still needs chart-specific D1/D9/UL/DK/A7, dasha subperiod and transit confirmation." + ), + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--cases", default=None) + parser.add_argument("--pretty", action="store_true") + args = parser.parse_args() + print(json.dumps(audit_cases(args.cases), ensure_ascii=False, indent=2 if args.pretty else None)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/public_oracle_replay.py b/scripts/public_oracle_replay.py new file mode 100644 index 00000000..4bd5f88e --- /dev/null +++ b/scripts/public_oracle_replay.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Replay local Jyotish engines against public birth-data seed cases.""" + +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_CASES = ROOT / "references" / "public_oracle_cases.json" + + +def _birth_args(birth: dict[str, Any]) -> list[str]: + args: list[str] = [] + for key in ("year", "month", "day", "hour", "minute", "second", "lat", "lon", "tz"): + args.extend([f"--{key}", str(birth[key])]) + return args + + +def _commands(case: dict[str, Any]) -> list[dict[str, Any]]: + birth = _birth_args(case["birth"]) + return [ + {"id": "chart_d1", "command": ["chart", *birth]}, + {"id": "varga_d9", "command": ["varga", *birth, "--d9"]}, + {"id": "varga_full_core", "command": ["varga-full", *birth, "--divisions", "D2,D4,D7,D9,D10,D12,D16,D20,D24,D30,D60"]}, + {"id": "vimshottari_md_ad", "command": ["dasha", *birth, "--years", "45"]}, + {"id": "vimshottari_pd", "script": "scripts/vimshottari_subperiod_timeline.py", "args": [*birth, "--years", "45"]}, + {"id": "yoga", "command": ["yoga", *birth]}, + {"id": "shadbala", "command": ["shadbala", *birth]}, + {"id": "ashtakavarga", "command": ["ashtakavarga", *birth]}, + {"id": "kp", "command": ["kp", *birth]}, + {"id": "jaimini", "command": ["jaimini", *birth]}, + {"id": "narayana_dasha", "command": ["narayana-dasha", *birth]}, + {"id": "tajika", "command": ["tajika", *birth, "--age", "30"]}, + {"id": "solar_return", "command": ["solar-return", *birth, "--target-year", str(case["birth"]["year"] + 30)]}, + {"id": "muhurta", "command": ["muhurta", "--date", "2027-03-01", "--scan-days", "1"]}, + {"id": "transit", "command": ["transit", "--year", "2027", "--month", "3", "--day", "1", "--planet", "Jupiter,Saturn", "--tz", str(case["birth"]["tz"])]}, + {"id": "daily_transit_scan", "script": "scripts/daily_transit_window_scan.py", "args": ["--start", "2027-03-01", "--end", "2027-03-01", "--planets", "Jupiter,Saturn", "--tz", str(case["birth"]["tz"])]}, + {"id": "double_transit_pac", "command": ["double-transit-pac", *birth, "--date", "2027-03-01", "--house", "7"]}, + {"id": "vivah_saham", "command": ["vivah-saham", *birth, "--transit-date", "2027-03-01"]}, + {"id": "flying_star_audit", "script": "scripts/flying_star_audit.py", "args": [*birth, "--age", "30", "--event-house", "7"]}, + {"id": "bhava_chalit", "command": ["bhava-chalit", *birth]}, + {"id": "sudarshana", "command": ["sudarshana", *birth]}, + {"id": "aspects", "command": ["aspects", *birth]}, + {"id": "full_reading", "command": ["full-reading", *birth, "--today", "2027-03-01", "--transit-date", "2027-03-01"], "timeout": 90, "heavy": True}, + ] + + +def _run(item: dict[str, Any], timeout: int) -> dict[str, Any]: + if "script" in item: + command = [sys.executable, item["script"], *item["args"]] + else: + command = [sys.executable, "scripts/jyotish_engine.py", *item["command"]] + completed = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, timeout=item.get("timeout", timeout), check=False) + status = "tested" if completed.returncode == 0 else "failed" + payload_type = "unknown" + if completed.returncode == 0: + try: + json.loads(completed.stdout) + payload_type = "json" + except json.JSONDecodeError: + payload_type = "text" + return { + "engine": item["id"], + "status": status, + "returncode": completed.returncode, + "payload_type": payload_type, + "stderr_excerpt": (completed.stderr or "").strip()[:300], + } + + +def _oracle_rows(case: dict[str, Any]) -> list[dict[str, Any]]: + rows: list[dict[str, Any]] = [] + oracles = case.get("expected_oracles", {}) + for provider, payload in oracles.items(): + if not isinstance(payload, dict): + continue + if "status" in payload: + rows.append({"case_id": case["id"], "provider": provider, "target": "provider", "status": payload["status"]}) + continue + for target, details in payload.items(): + if isinstance(details, dict): + rows.append({ + "case_id": case["id"], + "provider": provider, + "target": target, + "status": details.get("status", "pending_external_capture"), + "artifact": details.get("artifact"), + "packet": details.get("packet"), + "expected": details.get("expected"), + }) + return rows + + +def build_report(args: argparse.Namespace) -> dict[str, Any]: + data = json.loads(Path(args.cases).read_text(encoding="utf-8")) + cases = data["cases"][:1] if args.quick else data["cases"] + rows = [] + skipped = [] + oracle_rows = [] + for case in cases: + oracle_rows.extend(_oracle_rows(case)) + for item in _commands(case): + if item.get("heavy") and not args.include_heavy: + skipped.append({"case_id": case["id"], "engine": item["id"], "status": "untested", "reason": "heavy; rerun with --include-heavy"}) + continue + rows.append({"case_id": case["id"], **_run(item, args.timeout)}) + blocked = [ + {"engine": "VedAstro official full snapshot", "status": "blocked", "reason": "premium_key/budget/raw_response not closed"}, + {"engine": "PyJHora/JHora parity", "status": "blocked", "reason": "jhora dependency missing"}, + {"engine": "JHora desktop oracle", "status": "blocked", "reason": "manual desktop oracle required"}, + {"engine": "all_35_dasha_full_matrix", "status": "untested", "reason": "not mapped into public replay yet"}, + {"engine": "all_D1_to_D144_full_matrix", "status": "untested", "reason": "core subset replayed; exhaustive divisional sweep not yet run"}, + {"engine": "predictive_accuracy_claims", "status": "no_public_oracle", "reason": "public birth data alone cannot validate prediction accuracy"}, + ] + summary: dict[str, int] = {} + for row in [*rows, *skipped, *blocked]: + summary[row["status"]] = summary.get(row["status"], 0) + 1 + oracle_summary: dict[str, int] = {} + for row in oracle_rows: + oracle_summary[row["status"]] = oracle_summary.get(row["status"], 0) + 1 + return { + "scope": "public_oracle_replay", + "boundary": data["boundary"], + "case_count": len(cases), + "summary": summary, + "oracle_summary": oracle_summary, + "expected_oracles": oracle_rows, + "rows": rows, + "blocked_or_untested": [*skipped, *blocked], + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--cases", default=str(DEFAULT_CASES)) + parser.add_argument("--quick", action="store_true") + parser.add_argument("--include-heavy", action="store_true") + parser.add_argument("--timeout", type=int, default=45) + args = parser.parse_args() + report = build_report(args) + print(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)) + return 1 if report["summary"].get("failed") else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/traditional_maturity_gap_audit.py b/scripts/traditional_maturity_gap_audit.py new file mode 100644 index 00000000..39029f10 --- /dev/null +++ b/scripts/traditional_maturity_gap_audit.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Report traditional Jyotish maturity gaps beyond technique-name coverage.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_MATRIX = ROOT / "references" / "traditional_maturity_gap_matrix.json" + + +def build_report(matrix_path: str) -> dict[str, Any]: + matrix = json.loads(Path(matrix_path).read_text(encoding="utf-8")) + priorities = sorted(matrix["priorities"], key=lambda item: item.get("rank", 999)) + counts: dict[str, int] = {} + for item in priorities: + counts[item["priority"]] = counts.get(item["priority"], 0) + 1 + return { + "scope": matrix["scope"], + "boundary": matrix["boundary"], + "execution_phases": matrix.get("execution_phases", []), + "summary": counts, + "ordered_priorities": [{"rank": item.get("rank"), "id": item["id"], "priority": item["priority"]} for item in priorities], + "p0": [item for item in priorities if item["priority"] == "P0"], + "p1": [item for item in priorities if item["priority"] == "P1"], + "p2": [item for item in priorities if item["priority"] == "P2"], + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--matrix", default=str(DEFAULT_MATRIX)) + args = parser.parse_args() + print(json.dumps(build_report(args.matrix), ensure_ascii=False, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_flying_star_audit.py b/tests/test_flying_star_audit.py new file mode 100644 index 00000000..1a1a497c --- /dev/null +++ b/tests/test_flying_star_audit.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_flying_star_audit_outputs_linkage_chains_and_motion_points() -> None: + completed = subprocess.run( + [ + sys.executable, + "scripts/flying_star_audit.py", + "--year", + "2000", + "--month", + "1", + "--day", + "1", + "--hour", + "12", + "--minute", + "0", + "--lat", + "0", + "--lon", + "0", + "--tz", + "0", + "--age", + "30", + "--planets", + "Venus,Saturn", + "--event-house", + "7", + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=120, + check=False, + ) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + assert report["scope"] == "flying_star_audit" + assert "D1" in report["inter_chart_linkage"]["Venus"] + assert report["dispositor_chains"]["Venus"] + assert report["motion_points"]["bcp"]["status"] == "available_reference" + assert "sudarshana" in report["motion_points"] diff --git a/tests/test_non_standard_marriage_trigger_audit.py b/tests/test_non_standard_marriage_trigger_audit.py new file mode 100644 index 00000000..a30cc056 --- /dev/null +++ b/tests/test_non_standard_marriage_trigger_audit.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from scripts.non_standard_marriage_trigger_audit import audit_cases + + +def test_non_standard_marriage_trigger_audit_classifies_public_cases() -> None: + report = audit_cases() + + assert report["scope"] == "non_standard_marriage_trigger_audit" + assert report["case_count"] >= 5 + assert report["summary"]["non_standard_proxy_cases"] == report["case_count"] + + top_lords = {lord for lord, _count in report["summary"]["top_non_standard_lords"]} + assert {"Rahu", "Mercury", "Moon", "Ketu"} <= top_lords + + links = {link for link, _count in report["summary"]["top_marriage_network_links"]} + assert {"7L", "D9", "Jaimini"} <= links + + assert all(row["classification"] == "non_standard_proxy" for row in report["rows"]) diff --git a/tests/test_public_oracle_replay.py b/tests/test_public_oracle_replay.py new file mode 100644 index 00000000..a9beeb2e --- /dev/null +++ b/tests/test_public_oracle_replay.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_public_oracle_replay_quick_matrix_runs_on_public_case() -> None: + completed = subprocess.run( + [sys.executable, "scripts/public_oracle_replay.py", "--quick", "--timeout", "60"], + cwd=ROOT, + text=True, + capture_output=True, + timeout=180, + check=False, + ) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + assert report["case_count"] == 1 + assert report["summary"]["tested"] >= 10 + assert report["oracle_summary"] + assert any(row["engine"] == "chart_d1" and row["status"] == "tested" for row in report["rows"]) + assert any(row["provider"] == "pyjhora" for row in report["expected_oracles"]) + assert any(row["engine"] == "VedAstro official full snapshot" and row["status"] == "blocked" for row in report["blocked_or_untested"]) diff --git a/tests/test_traditional_maturity_gap_audit.py b/tests/test_traditional_maturity_gap_audit.py new file mode 100644 index 00000000..102282f7 --- /dev/null +++ b/tests/test_traditional_maturity_gap_audit.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_traditional_maturity_gap_audit_keeps_covered_not_complete_boundary() -> None: + completed = subprocess.run( + [sys.executable, "scripts/traditional_maturity_gap_audit.py"], + cwd=ROOT, + text=True, + capture_output=True, + timeout=30, + check=False, + ) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + assert "covered means" in report["boundary"] + assert [phase["id"] for phase in report["execution_phases"]] == [ + "phase_1_truth_boundary", + "phase_2_oracle_expansion", + "phase_3_event_adjudication", + "phase_4_expert_modifiers", + ] + assert report["ordered_priorities"][0]["id"] == "p0_dasha_boundary_closure" + assert report["ordered_priorities"][-1]["id"] == "p2_article_template_industrialization" + assert report["summary"]["P0"] >= 3 + assert any(item["id"] == "p0_dasha_boundary_closure" for item in report["p0"]) + assert any(item["id"] == "p0_tajika_sahams_annual_templates" for item in report["p0"]) + assert any(item["id"] == "p1_kp_event_adjudication" for item in report["p1"]) + assert any(item["id"] == "p1_career_a10_karakamsha_amk_chain" for item in report["p1"]) + assert any(item["id"] == "p1_flying_star_chain" for item in report["p1"])