SSE: publish uncompleted, child-day-complete and missed (#155)

This commit was merged in pull request #155.
This commit is contained in:
2026-09-22 23:25:06 -04:00
parent a07ce1b2f2
commit d4a55b6b40
5 changed files with 311 additions and 6 deletions
+14 -1
View File
@@ -69,6 +69,7 @@ from api.nestquest_core import (
core_settings,
core_snapshot,
)
from api import transitions as api_transitions
#: The instance DAO, imported on the app's core copy for the
#: child-mismatch guard on the complete route.
@@ -283,13 +284,14 @@ async def panel_complete_instance(
detail="Quest instance not found for this child",
)
now = _local_now()
try:
result = await core_completion.complete_instance(
database,
instance_id,
actor_source="panel",
actor_child_id=body.actor_child_id,
now=_local_now(),
now=now,
)
except ValueError as error:
message = str(error)
@@ -312,6 +314,17 @@ async def panel_complete_instance(
database, instance_id, was_on_time=result.was_on_time
)
request.app.state.publisher.publish(event_type, payload)
# THEN evaluate the day-complete rule off the completed event's
# payload (the core builder decides when the child's whole day
# is cleared — a zero-quest day and a non-today instance never
# fire it). ``today`` comes from the route's single clock read
# so the calendar date can never disagree with the completion's.
await api_transitions.publish_child_day_complete(
database,
payload,
now.date(),
request.app.state.publisher,
)
return {"status": str(result)}
+83
View File
@@ -0,0 +1,83 @@
"""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",
]