feat(reports): add professional reference export
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
"""Public professional-reference export assembly for the Jyotish API.
|
||||
|
||||
This boundary performs no writing-agent, persistence, billing, or telemetry work.
|
||||
It validates the public export request, reuses the handler's one full-reading
|
||||
calculation, and delegates packet assembly/rendering to ``jyotish_engine``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from importlib import import_module
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
|
||||
class ProfessionalReportReferenceInputError(ValueError):
|
||||
"""The professional-reference request is outside the public contract."""
|
||||
|
||||
|
||||
def _normalize_format(value: Any) -> str:
|
||||
if value is None:
|
||||
return "json"
|
||||
if not isinstance(value, str):
|
||||
raise ProfessionalReportReferenceInputError("format must be json or markdown")
|
||||
normalized = value.strip().lower()
|
||||
if normalized not in {"json", "markdown"}:
|
||||
raise ProfessionalReportReferenceInputError("format must be json or markdown")
|
||||
return normalized
|
||||
|
||||
|
||||
def _normalize_packs(value: Any) -> list[str]:
|
||||
if value is None:
|
||||
return []
|
||||
if isinstance(value, str):
|
||||
raw_items = value.split(",")
|
||||
elif isinstance(value, list):
|
||||
if any(not isinstance(item, str) for item in value):
|
||||
raise ProfessionalReportReferenceInputError("packs must contain only strings")
|
||||
raw_items = value
|
||||
else:
|
||||
raise ProfessionalReportReferenceInputError("packs must be a string or array of strings")
|
||||
|
||||
selected: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for item in raw_items:
|
||||
pack_id = item.strip()
|
||||
if pack_id and pack_id not in seen:
|
||||
selected.append(pack_id)
|
||||
seen.add(pack_id)
|
||||
return selected
|
||||
|
||||
|
||||
def _load_engine():
|
||||
try:
|
||||
return import_module("scripts.jyotish_engine")
|
||||
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution path
|
||||
return import_module("jyotish_engine")
|
||||
|
||||
|
||||
def _export_args(birth: dict[str, Any]) -> SimpleNamespace:
|
||||
normalized_birth = {
|
||||
**birth,
|
||||
"hour": int(birth["hour"]),
|
||||
"minute": int(birth["minute"]),
|
||||
"second": int(birth.get("second", 0)),
|
||||
}
|
||||
return SimpleNamespace(
|
||||
**normalized_birth,
|
||||
age=None,
|
||||
target_year=None,
|
||||
visual_chart_observations=None,
|
||||
startrack_language_bridge=False,
|
||||
)
|
||||
|
||||
|
||||
def build_professional_report_reference(handler, body: dict[str, Any], *, engine=None) -> dict[str, Any]:
|
||||
"""Build one JSON or Markdown response from one reused full-reading result."""
|
||||
if not isinstance(body, dict):
|
||||
raise ProfessionalReportReferenceInputError("request body must be an object")
|
||||
output_format = _normalize_format(body.get("format"))
|
||||
packs = _normalize_packs(body.get("packs"))
|
||||
|
||||
birth = handler._high_rigor_birth_payload(body)
|
||||
full_reading = handler._compute_full_reading_for_thematic(birth)
|
||||
resolved_engine = engine or _load_engine()
|
||||
try:
|
||||
packet = resolved_engine.build_professional_report_reference_packet(
|
||||
full_reading,
|
||||
_export_args(birth),
|
||||
packs,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise ProfessionalReportReferenceInputError(str(exc)) from exc
|
||||
|
||||
if output_format == "markdown":
|
||||
return {
|
||||
"format": "markdown",
|
||||
"markdown": resolved_engine.render_pl9_markdown(packet),
|
||||
}
|
||||
return {
|
||||
"format": "json",
|
||||
"report": packet,
|
||||
}
|
||||
Reference in New Issue
Block a user