Move the three local glue modules into scripts/marriage_match_service and point the invocation matrix at marriage_match.py. The upstream engine file stays where it is. Checked the file-count and oracle contracts, the marriage-match tests, and a Windows full pytest. The 176 failures are python3-missing and path-separator noise; the named contracts passed. Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
5.2 KiB
Python
135 lines
5.2 KiB
Python
"""Marriage-match API glue: order, cache, and route registration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SCRIPTS = ROOT / "scripts"
|
|
if str(SCRIPTS) not in sys.path:
|
|
sys.path.insert(0, str(SCRIPTS))
|
|
|
|
from marriage_match import build_marriage_match_dossier
|
|
from marriage_match_service.api import (
|
|
CACHE_NAME,
|
|
MarriageMatchInputError,
|
|
compute_marriage_match,
|
|
compute_synastry_full_dossier,
|
|
)
|
|
import marriage_match_service.api as api
|
|
from marriage_match_service.full_dossier import RULES_VERSION
|
|
import api_heavy_compute_gate
|
|
|
|
|
|
def chart(asc, moon, venus, mars):
|
|
return {
|
|
"ascendant": {"longitude": asc},
|
|
"planets": {
|
|
"Moon": {"longitude": moon},
|
|
"Venus": {"longitude": venus},
|
|
"Mars": {"longitude": mars},
|
|
"Saturn": {"longitude": 210},
|
|
"Sun": {"longitude": 120},
|
|
"Rahu": {"longitude": 300},
|
|
"Ketu": {"longitude": 120},
|
|
},
|
|
}
|
|
|
|
|
|
def _pressure_signature(report):
|
|
people = []
|
|
for key in ("person_a", "person_b"):
|
|
rows = report["papasamya"][key]["active_placements"]
|
|
people.append(tuple(sorted(
|
|
(row["planet"], row["reference"], row["house"]) for row in rows
|
|
)))
|
|
return tuple(sorted(people))
|
|
|
|
|
|
def test_swapped_people_keep_pairing_kuja_and_pressure():
|
|
left = chart(10, 40, 70, 190)
|
|
right = chart(100, 130, 160, 250)
|
|
forward = build_marriage_match_dossier(left, right)
|
|
reverse = build_marriage_match_dossier(right, left)
|
|
assert forward["ashtakoota_36"]["international_core"]["scores"] == reverse["ashtakoota_36"]["international_core"]["scores"]
|
|
assert forward["ashtakoota_36"]["international_core"]["total_score"] == reverse["ashtakoota_36"]["international_core"]["total_score"]
|
|
assert forward["kuja_mangal_dosha"]["outcome"] == reverse["kuja_mangal_dosha"]["outcome"]
|
|
assert forward["kuja_mangal_dosha"]["person_a"]["active_reference_count"] == reverse["kuja_mangal_dosha"]["person_b"]["active_reference_count"]
|
|
assert _pressure_signature(forward) == _pressure_signature(reverse)
|
|
|
|
|
|
def test_snapshot_endpoint_rejects_a_non_object():
|
|
with pytest.raises(MarriageMatchInputError):
|
|
compute_marriage_match({"person_a": []})
|
|
|
|
|
|
def test_disk_cache_reuses_one_build(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("GITHUB_SHA", "marriage-match-cache-test")
|
|
def cache_dir(name: str = "consult_full_data_cache"):
|
|
path = tmp_path / name
|
|
path.mkdir(parents=True, exist_ok=True)
|
|
return path
|
|
|
|
monkeypatch.setattr(api._cache, "cache_dir", cache_dir)
|
|
calls = {"n": 0}
|
|
|
|
def fake(_handler, _person_a, _person_b):
|
|
calls["n"] += 1
|
|
return {"rules_version": RULES_VERSION, "dossier": {"schema": "fixture"}, "raman_marriage_match": {}}
|
|
|
|
monkeypatch.setattr(
|
|
"marriage_match_service.api.build_synastry_full_dossier",
|
|
fake,
|
|
)
|
|
body = {
|
|
"person_a": {"year": 1990, "month": 1, "day": 2, "hour": 3, "minute": 4, "lat": 1, "lon": 2, "tz": 8},
|
|
"person_b": {"year": 1991, "month": 5, "day": 6, "hour": 7, "minute": 8, "lat": 3, "lon": 4, "tz": 8},
|
|
}
|
|
first = compute_synastry_full_dossier(object(), body)
|
|
second = compute_synastry_full_dossier(object(), body)
|
|
assert first == second
|
|
assert calls["n"] == 1
|
|
assert list((tmp_path / CACHE_NAME).glob("*.json"))
|
|
|
|
|
|
def test_missing_commit_does_not_reuse_the_disk_cache(monkeypatch):
|
|
monkeypatch.delenv("GITHUB_SHA", raising=False)
|
|
calls = {"n": 0}
|
|
|
|
def fake(_handler, _person_a, _person_b):
|
|
calls["n"] += 1
|
|
return {"rules_version": RULES_VERSION, "dossier": {}, "raman_marriage_match": {}}
|
|
|
|
monkeypatch.setattr("marriage_match_service.api.build_synastry_full_dossier", fake)
|
|
body = {
|
|
"person_a": {"year": 1990, "month": 1, "day": 2, "hour": 3, "minute": 4, "lat": 1, "lon": 2, "tz": 8},
|
|
"person_b": {"year": 1991, "month": 5, "day": 6, "hour": 7, "minute": 8, "lat": 3, "lon": 4, "tz": 8},
|
|
}
|
|
compute_synastry_full_dossier(object(), body)
|
|
compute_synastry_full_dossier(object(), body)
|
|
assert calls["n"] == 2
|
|
|
|
|
|
def test_routes_are_registered_and_the_dossier_is_heavy():
|
|
source = (ROOT / "scripts" / "jyotish_api_server.py").read_text(encoding="utf-8")
|
|
assert "elif path == '/api/marriage-match':" in source
|
|
assert "elif path == '/api/synastry_full_dossier':" in source
|
|
assert "/api/synastry_full_dossier" in api_heavy_compute_gate.HEAVY_COMPUTE_PATHS
|
|
assert "authorizeUsage" not in (ROOT / "frontend" / "src" / "lib" / "marriage-match-service.ts").read_text(encoding="utf-8")
|
|
assert not (ROOT / "scripts" / "synastry.py").exists()
|
|
|
|
|
|
def test_snapshot_golden_matches_the_engine():
|
|
fixture_path = ROOT / "frontend" / "tests" / "fixtures" / "marriage-match-snapshot.json"
|
|
payload = {"person_a": chart(12, 48, 96, 200), "person_b": chart(140, 210, 30, 300)}
|
|
result = compute_marriage_match(payload)
|
|
if os.environ.get("WRITE_MARRIAGE_MATCH_GOLDEN") == "1":
|
|
fixture_path.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
stored = json.loads(fixture_path.read_text(encoding="utf-8"))
|
|
assert result == stored
|