The pair page and reader replace the leftover chat draft, and the same pair is not charged again. Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
123 lines
4.5 KiB
Python
123 lines
4.5 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 marriage_match_service.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 check_synastry_reader(body: dict[str, Any]) -> dict[str, Any]:
|
|
"""Relationship-language gate. The body is the delivery plus the section inputs."""
|
|
if not isinstance(body, dict):
|
|
raise MarriageMatchInputError("request body must be an object")
|
|
from marriage_match_service.synastry_full_reader_gate import validate_synastry_reader
|
|
|
|
delivery = body.get("delivery")
|
|
section_inputs = body.get("sectionInputs")
|
|
language = body.get("language") or "zh"
|
|
if not isinstance(delivery, dict) or not isinstance(language, str):
|
|
raise MarriageMatchInputError("delivery and language are required")
|
|
if section_inputs is not None and not isinstance(section_inputs, dict):
|
|
raise MarriageMatchInputError("sectionInputs must be an object")
|
|
return validate_synastry_reader(delivery, section_inputs or {}, language)
|
|
|
|
|
|
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
|