expose formula-only prashna sphuta evidence

This commit is contained in:
732642856
2026-07-14 18:36:50 +08:00
parent e5f7e0ddc0
commit 63f56f9457
6 changed files with 95 additions and 11 deletions
+1
View File
@@ -90,6 +90,7 @@ For large architecture or release work, also read:
| ERR-057 | The release quality profile checked untracked files but did not execute the privacy AST scan or the real Chromium report-isolation probe. | mitigated 2026-07-14 | `release_hygiene_check()` now requires `public_release_privacy_scan.py --json` and `report_renderer_isolation_poc.py --strict`; parity manifest validation also runs as a contract check. |
| ERR-058 | Formula-based Sahams used the day/night operand rules but omitted the documented zodiacal-order `+30°` exception. | mitigated 2026-07-14 | `_calc_formula_saham()` applies the `references/saham_rules.json` forward-arc condition and one-sign correction; keep external numeric oracle parity as a separate `partial` requirement. |
| ERR-059 | Gulika was either an approximate fallback or falsely implied as a chart module output. | mitigated 2026-07-14 | `scripts/gulika.py` computes Prasna Marga Ghatika segment Ascendant with Swiss sunrise/sunset and Lahiri sidereal houses. It is exposed only as `prashna_context.supporting_indicators.gulika`, remains `partial`, and cannot unlock Sphuta or verdict layers until external numeric parity exists. |
| ERR-060 | Legacy Sphuta functions combined approximate Gulika with interpretive signals, while the exact formula could not be inspected in the production question context. | mitigated 2026-07-14 | `prashna_sphuta.py` exposes formula-only Trisphuta/Catusphuta/Pancasphuta from the partial Gulika evidence. It is supporting-only; Kunda, life-sensitive Sphutas and Prashna verdicts remain blocked pending external numeric parity. |
## Fragment Sweep Command Set
+5 -7
View File
@@ -452,7 +452,7 @@
"timing",
"life"
],
"status": "blocked",
"status": "partial",
"knowledge_refs": [
"references/prashna-complete-guide.md",
"references/single-event-inquiry-protocol.md"
@@ -463,18 +463,16 @@
"scripts/jyotish_engine.py --mode life"
],
"output_paths": [
"scripts/prashna.py:calc_sphutas",
"scripts/prashna.py:calc_life_sphutas",
"kp_horary.sphutas"
"prashna_context.supporting_indicators.sphuta"
],
"audit_label": "Sphuta/Trisphuta",
"missing_impact": "Prashna energy convergence and life-sensitivity markers are absent, leaving horary and life-risk reading thinner than classical toolchains.",
"missing_impact": "Formula-only Trisphuta/Catusphuta/Pancasphuta evidence is available from exact Gulika. Life-sensitive Sphutas, external numeric parity and all verdict use remain unavailable.",
"entry_type": "supporting_indicator",
"evidence_role": "secondary",
"user_visibility": "expert_audit",
"verification_level": {
"calculation": "verified",
"rule": "verified",
"calculation": "partial",
"rule": "partial",
"prediction": "support_only"
},
"conclusion_policy": "Supporting evidence only; it can raise/lower confidence but cannot by itself decide an event or timing claim."
+20 -3
View File
@@ -13,6 +13,10 @@ try:
from scripts.gulika import calculate_gulika
except ModuleNotFoundError: # pragma: no cover - CLI execution path
from gulika import calculate_gulika
try:
from scripts.prashna_sphuta import calculate_sphuta_evidence
except ModuleNotFoundError: # pragma: no cover - CLI execution path
from prashna_sphuta import calculate_sphuta_evidence
class PrashnaContextError(ValueError):
@@ -69,6 +73,19 @@ def build_prashna_context(payload: dict[str, Any]) -> dict[str, Any]:
"status": "blocked",
"reason": f"gulika_supporting_indicator_failed:{type(exc).__name__}",
}
if gulika.get("status") == "partial":
longitudes = {
name: item.get("degree_raw", item.get("lon"))
for name, item in chart["planets"].items()
if isinstance(item, dict)
}
sphuta = calculate_sphuta_evidence(
ascendant_longitude=chart["ascendant"].get("degree_raw", chart["ascendant"].get("lon")),
planet_longitudes=longitudes,
gulika_longitude=gulika["longitude"],
)
else:
sphuta = {"status": "blocked", "reason": "gulika_supporting_indicator_unavailable"}
return {
"scope": "prashna_context",
"status": "computed",
@@ -82,7 +99,7 @@ def build_prashna_context(payload: dict[str, Any]) -> dict[str, Any]:
"planets": chart["planets"],
"calculation_contract": chart["calculation_contract"],
"result_hash": chart["result_hash"],
"supporting_indicators": {"gulika": gulika},
"blocked_layers": ["Trisphuta", "Kunda", "Prashna verdict"],
"boundary": "No client-supplied planets or ascendant are accepted. Gulika is supporting-only pending external numeric parity; Sphuta and verdict layers remain blocked.",
"supporting_indicators": {"gulika": gulika, "sphuta": sphuta},
"blocked_layers": ["Kunda", "Prashna verdict"],
"boundary": "No client-supplied planets or ascendant are accepted. Gulika and formula-only Sphuta are supporting-only pending external numeric parity; verdict layers remain blocked.",
}
+44
View File
@@ -0,0 +1,44 @@
"""Formula-only Prasna Marga Sphuta evidence, without verdict interpretation."""
from __future__ import annotations
from typing import Any
def _norm(value: float) -> float:
return float(value) % 360.0
def calculate_sphuta_evidence(
*,
ascendant_longitude: float,
planet_longitudes: dict[str, Any],
gulika_longitude: float,
) -> dict[str, Any]:
required = ("Sun", "Moon", "Rahu")
missing = [name for name in required if name not in planet_longitudes]
if missing:
return {"status": "blocked", "reason": "missing_sphuta_planets", "missing": missing}
asc = _norm(ascendant_longitude)
moon = _norm(planet_longitudes["Moon"])
sun = _norm(planet_longitudes["Sun"])
rahu = _norm(planet_longitudes["Rahu"])
gulika = _norm(gulika_longitude)
trisphuta = _norm(asc + moon + gulika)
catusphuta = _norm(trisphuta + sun)
pancasphuta = _norm(catusphuta + rahu)
return {
"scope": "prasna_marga_sphuta_evidence",
"status": "partial",
"points": {
"trisphuta": trisphuta,
"catusphuta": catusphuta,
"pancasphuta": pancasphuta,
},
"formula_trace": {
"trisphuta": "Lagna + Moon + Gulika",
"catusphuta": "Trisphuta + Sun",
"pancasphuta": "Catusphuta + Rahu",
},
"rule_source": "references/prashna-complete-guide.md#3.2-3.3",
"boundary": "Formula-only supporting evidence. No health, event, or Prashna verdict is permitted without external numeric parity and adjudication rules.",
}
+2 -1
View File
@@ -22,7 +22,8 @@ def test_cli_prashna_uses_question_moment_swiss_context_only():
assert payload["status"] == "computed"
assert payload["chart_source"] == "swiss_ephemeris_backend"
assert payload["supporting_indicators"]["gulika"]["status"] == "partial"
assert "Trisphuta" in payload["blocked_layers"]
assert payload["supporting_indicators"]["sphuta"]["status"] == "partial"
assert "Kunda" in payload["blocked_layers"]
def test_cli_prashna_blocks_legacy_approximation_modes():
+23
View File
@@ -0,0 +1,23 @@
from scripts.prashna_sphuta import calculate_sphuta_evidence
def test_sphuta_formula_evidence_uses_exact_gulika_input() -> None:
result = calculate_sphuta_evidence(
ascendant_longitude=10,
planet_longitudes={"Moon": 20, "Sun": 30, "Rahu": 40},
gulika_longitude=50,
)
assert result["status"] == "partial"
assert result["points"] == {"trisphuta": 80.0, "catusphuta": 110.0, "pancasphuta": 150.0}
def test_sphuta_evidence_blocks_missing_required_planet() -> None:
result = calculate_sphuta_evidence(
ascendant_longitude=10,
planet_longitudes={"Moon": 20, "Sun": 30},
gulika_longitude=50,
)
assert result["status"] == "blocked"
assert result["missing"] == ["Rahu"]