Files
Jyotisha/scripts/marriage_match_api.py
T
jesse-uxGrok (agent loop)Cursor
cf063834ca feat(synastry): store the upstream seven-item marriage match
Move the bde5d615 engine and full-synastry skill in unchanged, save both profiles without billing or a page, and retire the old synastry module.

Checked targeted pytest, tsc 0, lint 0 errors, service tests 6/6, and test:db 73 pass / 13 fail matching the Windows known list.

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 16:44:05 +08:00

107 lines
3.6 KiB
Python

"""HTTP-facing marriage match and full synastry dossier.
The dossier endpoint caches on the two birth payloads plus ``GITHUB_SHA``,
the same scratch helper the report reference cache uses. A missing or
``unknown`` commit id skips the cache.
"""
from __future__ import annotations
import os
from typing import Any
from marriage_match import build_marriage_match_dossier
from synastry_full_dossier import RULES_VERSION, build_synastry_full_dossier
try:
from scripts import consult_full_data_cache as _cache
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution
import consult_full_data_cache as _cache
CACHE_NAME = "synastry_full_dossier_cache"
_BIRTH_KEYS = ("year", "month", "day", "hour", "minute", "lat", "lon", "tz")
class MarriageMatchInputError(ValueError):
"""The caller sent a shape this product does not accept."""
def compute_marriage_match(payload: dict[str, Any]) -> dict[str, Any]:
"""Snapshot-only product. Birth details are not accepted here."""
if not isinstance(payload, dict):
raise MarriageMatchInputError("request body must be an object")
person_a = payload.get("person_a")
person_b = payload.get("person_b")
if not isinstance(person_a, dict) or not isinstance(person_b, dict):
raise MarriageMatchInputError("person_a and person_b must be chart snapshots")
profile_id = payload.get("profile_id") or "practitioner_consensus_v1"
if not isinstance(profile_id, str) or not profile_id.strip():
raise MarriageMatchInputError("profile_id must be a string")
try:
return build_marriage_match_dossier(
person_a,
person_b,
profile_id=profile_id.strip(),
)
except ValueError as exc:
raise MarriageMatchInputError(str(exc)) from exc
def _birth(value: Any, label: str) -> dict[str, Any]:
if not isinstance(value, dict):
raise MarriageMatchInputError(f"{label} must be an object")
missing = [key for key in _BIRTH_KEYS if value.get(key) is None]
if missing:
raise MarriageMatchInputError(f"{label} is missing {', '.join(missing)}")
return value
def _birth_identity(body: dict[str, Any]) -> dict[str, Any]:
node = body.get("node_mode", body.get("nodeMode", "mean"))
return {
"year": body.get("year"),
"month": body.get("month"),
"day": body.get("day"),
"hour": body.get("hour"),
"minute": body.get("minute"),
"second": body.get("second", 0),
"lat": body.get("lat"),
"lon": body.get("lon"),
"tz": body.get("tz"),
"ayanamsa": body.get("ayanamsa", "lahiri"),
"node_mode": node or "mean",
}
def _commit_sha() -> str:
return os.environ.get("GITHUB_SHA") or ""
def compute_synastry_full_dossier(handler: Any, body: dict[str, Any]) -> dict[str, Any]:
if not isinstance(body, dict):
raise MarriageMatchInputError("request body must be an object")
person_a = _birth(body.get("person_a"), "person_a")
person_b = _birth(body.get("person_b"), "person_b")
sha = _commit_sha()
def build(_identity: dict[str, Any]) -> dict[str, Any]:
return build_synastry_full_dossier(handler, person_a, person_b)
if sha in ("", "unknown"):
return build({})
cached = _cache.get_or_build(
{
"person_a": _birth_identity(person_a),
"person_b": _birth_identity(person_b),
"github_sha": sha,
"rules_version": RULES_VERSION,
},
build,
cache_name=CACHE_NAME,
)
packet = cached["packet"]
if not isinstance(packet, dict):
raise MarriageMatchInputError("cached dossier was not an object")
return packet