What
services/quiz_signals.py::_tutor_recency answers "when did the tutor last cover THIS concept?"
by reading sessions (owner + offering scoped, bounded) and then scanning the
graph_update_json column of the messages in those sessions for a matching concept_name.
node_mastery_events is the better primary read for that question:
Why this is a follow-up and not a drop-in
The reviewer who proposed it and the one who checked it disagreed, and the checker was right —
it is a supplement, not a replacement:
- Unclassified turns write a NULL
event_type. A mastery event exists but does not identify
itself as a tutor turn, so "last tutored" cannot be read off it without deciding what a NULL
means. - A new-node introduction writes no mastery event at all. The first time the tutor introduces
a concept — often the most interesting "we just covered this" case — there is nothing in
node_mastery_events to find.
So a straight swap would trade one silent miss for two different ones. The work is to decide the
combination: node_mastery_events as the primary read (cheap, indexed, keyspace-proof) with the
sessions/messages scan kept as the fallback that covers introductions and unclassified turns —
and to pin both halves with tests that fail against the primary read alone.
Where
backend/services/quiz_signals.py::_tutor_recency (and _TUTOR_SESSION_SCAN /
_TUTOR_MESSAGE_SCAN, which exist only to bound the current shape)backend/tests/test_quiz_signals.py::TestTutorRecency
Raised in the #592 merge-gate review (finding C14) against #556 / epic #537. Not blocking:
the current read is correct, just more expensive and more fragile than it needs to be.
What
services/quiz_signals.py::_tutor_recencyanswers "when did the tutor last cover THIS concept?"by reading
sessions(owner + offering scoped, bounded) and then scanning thegraph_update_jsoncolumn of the messages in those sessions for a matchingconcept_name.node_mastery_eventsis the better primary read for that question:(node_id, created_at)— the exact shape of the query;and the feat(quiz): land the two deferred H4 signals at the scope the data supports (#556) #592 merge-gate review were about;
_velocityin the same module already reads it, so the round trip is arguably already paid for.Why this is a follow-up and not a drop-in
The reviewer who proposed it and the one who checked it disagreed, and the checker was right —
it is a supplement, not a replacement:
event_type. A mastery event exists but does not identifyitself as a tutor turn, so "last tutored" cannot be read off it without deciding what a NULL
means.
a concept — often the most interesting "we just covered this" case — there is nothing in
node_mastery_eventsto find.So a straight swap would trade one silent miss for two different ones. The work is to decide the
combination:
node_mastery_eventsas the primary read (cheap, indexed, keyspace-proof) with thesessions/messagesscan kept as the fallback that covers introductions and unclassified turns —and to pin both halves with tests that fail against the primary read alone.
Where
backend/services/quiz_signals.py::_tutor_recency(and_TUTOR_SESSION_SCAN/_TUTOR_MESSAGE_SCAN, which exist only to bound the current shape)backend/tests/test_quiz_signals.py::TestTutorRecencyRaised in the #592 merge-gate review (finding C14) against #556 / epic #537. Not blocking:
the current read is correct, just more expensive and more fragile than it needs to be.