add auditable Swiss gulika calculator
This commit is contained in:
@@ -89,6 +89,7 @@ For large architecture or release work, also read:
|
||||
| ERR-056 | A WorkBuddy checkout of the same remote diverged substantially from the active source branch and can be mistaken for a mergeable mirror. | active | Read `whole_machine_fragment_sweep_2026_07_14.md`; do not copy or merge it without explicit commit-level review on a separate branch. |
|
||||
| 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; registry is `partial` and exposes only the actual calculator until external numeric parity and chart integration are complete. |
|
||||
|
||||
## Fragment Sweep Command Set
|
||||
|
||||
|
||||
@@ -420,7 +420,7 @@
|
||||
"upagraha",
|
||||
"event"
|
||||
],
|
||||
"status": "blocked",
|
||||
"status": "partial",
|
||||
"knowledge_refs": [
|
||||
"references/prashna-complete-guide.md"
|
||||
],
|
||||
@@ -430,17 +430,16 @@
|
||||
"full-reading"
|
||||
],
|
||||
"output_paths": [
|
||||
"modules.chart.upagraha",
|
||||
"scripts/prashna.py:calc_gulika_simple"
|
||||
"scripts/gulika.py:calculate_gulika"
|
||||
],
|
||||
"audit_label": "Upagraha/Gulika/Maandi",
|
||||
"missing_impact": "Sensitive malefic shadow points are unavailable, weakening pressure, delay and vulnerability interpretation.",
|
||||
"missing_impact": "Swiss day/night and Prasna Marga Ghatika Gulika are available as supporting evidence; Maandi, other Upagrahas and external numeric parity 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."
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Swiss-Ephemeris Gulika calculator using the Prasna Marga Ghatika table."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
import swisseph as swe
|
||||
|
||||
try:
|
||||
from saham_daynight import determine_daytime
|
||||
except ImportError:
|
||||
from scripts.saham_daynight import determine_daytime
|
||||
|
||||
|
||||
# Monday=0, matching datetime.weekday(). Values are the end of Saturn's share
|
||||
# measured in Ghatika from the relevant sunrise/sunset (30 Ghatika per period).
|
||||
GHATIKA_END = {
|
||||
0: {"day": 22, "night": 6},
|
||||
1: {"day": 18, "night": 2},
|
||||
2: {"day": 14, "night": 26},
|
||||
3: {"day": 10, "night": 22},
|
||||
4: {"day": 6, "night": 18},
|
||||
5: {"day": 2, "night": 14},
|
||||
6: {"day": 26, "night": 10},
|
||||
}
|
||||
|
||||
|
||||
def _sidereal_ascendant(jd_ut: float, lat: float, lon: float) -> float:
|
||||
swe.set_sid_mode(swe.SIDM_LAHIRI)
|
||||
cusps, ascmc = swe.houses_ex(jd_ut, lat, lon, b"P", swe.FLG_SIDEREAL)
|
||||
return float(ascmc[0]) % 360
|
||||
|
||||
|
||||
def calculate_gulika(
|
||||
moment: datetime,
|
||||
*,
|
||||
lat: float,
|
||||
lon: float,
|
||||
tz: float,
|
||||
) -> dict[str, Any]:
|
||||
"""Return Gulika from local moment/location using Swiss sunrise and sunset."""
|
||||
daynight = determine_daytime(moment, lat=lat, lon=lon, tz=tz)
|
||||
is_day = bool(daynight["is_daytime"])
|
||||
period = "day" if is_day else "night"
|
||||
ghatika_end = GHATIKA_END[moment.weekday()][period]
|
||||
start_jd = daynight["sunrise_jd_ut"] if is_day else daynight["sunset_jd_ut"]
|
||||
end_jd = daynight["sunset_jd_ut"] if is_day else daynight["sunrise_jd_ut"] + 1.0
|
||||
if end_jd <= start_jd:
|
||||
end_jd += 1.0
|
||||
segment_jd = start_jd + (end_jd - start_jd) * (ghatika_end / 30.0)
|
||||
longitude = _sidereal_ascendant(segment_jd, float(lat), float(lon))
|
||||
return {
|
||||
"scope": "gulika_prasna_marga",
|
||||
"status": "partial",
|
||||
"longitude": round(longitude, 6),
|
||||
"sign_idx": int(longitude / 30) % 12,
|
||||
"degree_in_sign": round(longitude % 30, 6),
|
||||
"period": period,
|
||||
"weekday": moment.weekday(),
|
||||
"ghatika_end": ghatika_end,
|
||||
"segment_jd_ut": segment_jd,
|
||||
"daynight_evidence": daynight,
|
||||
"ayanamsa": "lahiri",
|
||||
"rule_source": "references/prashna-complete-guide.md#3.5",
|
||||
"boundary": "Formula is implemented from the local classical guide; external JHora/PyJHora numeric parity remains required before enabling Sphuta or verdict layers.",
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
from datetime import datetime
|
||||
|
||||
from scripts.gulika import GHATIKA_END, calculate_gulika
|
||||
|
||||
|
||||
def test_gulika_uses_prasna_marga_weekday_table() -> None:
|
||||
assert GHATIKA_END[6] == {"day": 26, "night": 10}
|
||||
assert GHATIKA_END[0] == {"day": 22, "night": 6}
|
||||
|
||||
|
||||
def test_gulika_returns_sidereal_segment_ascendant_with_audit_trace() -> None:
|
||||
result = calculate_gulika(datetime(1990, 6, 15, 12, 0), lat=39.9042, lon=116.4074, tz=8)
|
||||
|
||||
assert result["status"] == "partial"
|
||||
assert 0 <= result["longitude"] < 360
|
||||
assert result["ghatika_end"] in range(0, 31)
|
||||
assert result["rule_source"].endswith("#3.5")
|
||||
Reference in New Issue
Block a user