"""# Summary Appointment-reminder pipeline family — concrete stages, the wired root, and its registration. The concrete subclass re-annotates each slot with a stage implementation; the worker wires the tree once per run with the CRUD services injected by slot name. """ import logging from pathlib import Path from typing import Any from pipelines.appointment_reminder.abc import ( AppointmentReminderPipelinePort, ComposePort, DeliverPort, LoadPort, ) from pipelines.shared.loader import build_pipeline from pipelines.shared.registry import register logger = logging.getLogger(__name__) class AppointmentLoader(LoadPort): """Load an appointment through the appointment CRUD service. Notes: The service instance is injected by the worker (``appointment_svc``). """ appointment_svc: Any async def load(self, appointment_id: int) -> dict[str, Any]: doc = await self.appointment_svc.read(appointment_id) if doc is None: raise ValueError(f"appointment {appointment_id} not found") return doc.model_dump(mode="json", by_alias=True) class ReminderComposer(ComposePort): """Compose the reminder text from the family's pattern fragments. Notes: ``registry`` is the merged pattern map injected by the shared loader. """ registry: Any def compose(self, appointment: dict[str, Any], pet: dict[str, Any] | None, parent: dict[str, Any] | None) -> dict[str, Any]: pattern = (self.registry or {}).get("reminder", {}) pet_name = (pet or {}).get("name", "your pet") parent_name = (parent or {}).get("name", "there") to = (parent or {}).get("email", "") subject = pattern.get("subject", "Appointment reminder") body = pattern.get( "body", "Hi {parent_name}, {pet_name} has an appointment on {date}.", ).format( parent_name=parent_name, pet_name=pet_name, date=appointment.get("date", ""), ) return {"to": to, "subject": subject, "body": body} class LogDeliver(DeliverPort): """Deliver the reminder by logging it (swap for an email/SMS adaptor).""" async def deliver(self, reminder: dict[str, Any]) -> dict[str, Any]: logger.info("REMINDER to=%s subject=%s body=%s", reminder.get("to"), reminder.get("subject"), reminder.get("body")) reminder["delivered"] = "log" return reminder class AppointmentReminderPipeline(AppointmentReminderPipelinePort): """Concrete appointment-reminder family — the dependency decisions live here.""" loader: AppointmentLoader compose: ReminderComposer deliver: LogDeliver async def run(self, appointment_id: int) -> dict[str, Any]: appointment = await self.loader.load(appointment_id) pet = appointment.get("pet") if isinstance(appointment.get("pet"), dict) else None parent = None if pet and pet.get("parents"): first = pet["parents"][0] parent = first if isinstance(first, dict) else None reminder = self.compose.compose(appointment, pet, parent) return await self.deliver.deliver(reminder) def get_appointment_reminder_pipeline(appointment_svc) -> AppointmentReminderPipeline: """Wire and return the appointment-reminder pipeline through the shared loader. Args: appointment_svc: Appointment CRUD service injected into the load stage. Returns: AppointmentReminderPipeline: The wired pipeline root. """ resources = Path(__file__).parent / "resources" return build_pipeline( AppointmentReminderPipeline, resources_dir=resources, instances={"appointment_svc": appointment_svc}, ) register("appointment_reminder", "default", AppointmentReminderPipeline)