The panel stream so far carried only quest-completed. This adds the three remaining documented transitions (§3) so the panel UI can react to reversals, full days, and nightly misses through the same ONE publisher: - core/events.py gains build_quest_missed_event, mirroring the sweep's hand-built payload exactly (shared _instance_payload shape), so the API plane publishes the same shape the integration sweep fires. - api/transitions.py adds the reusable publish helpers the later uncomplete route (da0226b3) and the API-side missed sweep will call; payloads are always built by the core builders, never hand-built. - The panel complete route now evaluates the day-complete rule on an actual transition, off the completed event's payload and the route's single clock read; the core builder still decides, so a zero-quest day and a non-today instance never fire it and a no-op re-complete publishes nothing. - Tests drive all three transitions through the real SSE stream (server-loop scheduling for the not-yet-routed helpers) and assert every documented payload field.
84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
"""Reusable SSE transition publishers for the API service (task 44c93cfd).
|
|
|
|
Each helper wraps the ONE publisher on ``app.state.publisher`` with a
|
|
single documented transition: it builds the payload through the core
|
|
event builders (``nestquest_core.events``) — never hand-built here —
|
|
and fans it out with :meth:`~api.events.TransitionPublisher.publish`.
|
|
|
|
The uncomplete route (follow-up task da0226b3) and the API-side missed
|
|
sweep do not exist yet; these helpers are the functions they will call,
|
|
and the SSE tests exercise them directly. Fan-out stays fire-and-forget
|
|
(see api/events.py): a slow subscriber drops the event and recovers by
|
|
polling the snapshot.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import datetime
|
|
|
|
from api.nestquest_core import core_events
|
|
|
|
|
|
async def publish_quest_uncompleted(
|
|
database: object, instance_id: int, publisher: object
|
|
) -> None:
|
|
"""Publish ONE ``nestquest_quest_uncompleted`` event for an actual
|
|
reversal of ``instance_id``.
|
|
|
|
The payload is built by the core builder
|
|
(:func:`nestquest_core.events.build_quest_uncompleted_events`);
|
|
this helper only picks the single event out of the builder's list
|
|
and publishes it.
|
|
"""
|
|
events = await core_events.build_quest_uncompleted_events(
|
|
database, instance_id
|
|
)
|
|
for event_type, payload in events:
|
|
publisher.publish(event_type, payload)
|
|
|
|
|
|
async def publish_quest_missed(
|
|
database: object, instance_id: int, publisher: object
|
|
) -> None:
|
|
"""Publish ONE ``nestquest_quest_missed`` event for ``instance_id``.
|
|
|
|
The payload is built by the core builder
|
|
(:func:`nestquest_core.events.build_quest_missed_event`), which
|
|
mirrors the documented sweep payload exactly (§3 of
|
|
design/ENTITIES-AND-SERVICES.md).
|
|
"""
|
|
event_type, payload = await core_events.build_quest_missed_event(
|
|
database, instance_id
|
|
)
|
|
publisher.publish(event_type, payload)
|
|
|
|
|
|
async def publish_child_day_complete(
|
|
database: object,
|
|
completed_payload: dict,
|
|
today: datetime.date,
|
|
publisher: object,
|
|
) -> bool:
|
|
"""Publish ``nestquest_child_day_complete`` when a completion
|
|
cleared the child's whole day; return whether one was published.
|
|
|
|
The day-complete rule lives entirely in the core builder
|
|
(:func:`nestquest_core.events.build_child_day_complete_event`):
|
|
it returns ``None`` when the day did not clear (a zero-quest day
|
|
or a non-today instance) and this helper publishes nothing then.
|
|
"""
|
|
event = await core_events.build_child_day_complete_event(
|
|
database, completed_payload, today=today
|
|
)
|
|
if event is None:
|
|
return False
|
|
event_type, payload = event
|
|
publisher.publish(event_type, payload)
|
|
return True
|
|
|
|
|
|
__all__ = [
|
|
"publish_child_day_complete",
|
|
"publish_quest_missed",
|
|
"publish_quest_uncompleted",
|
|
]
|