| """Transactional learning-event ledger helpers. |
| |
| Domain rows such as QuizAttempt, LessonProgress and RevisionItem remain the |
| source of truth. This ledger only records the meaningful action and the |
| domain record that caused it, in the same database transaction. |
| """ |
| from __future__ import annotations |
|
|
| from typing import Any |
|
|
| from sqlalchemy import select |
| from sqlalchemy.orm import Session |
|
|
| from app.models.learning_state import LearningEvent |
|
|
|
|
| def add_learning_event( |
| db: Session, |
| *, |
| user_id: str, |
| event_type: str, |
| entity_type: str, |
| entity_id: str, |
| idempotency_key: str, |
| subject_id: str | None = None, |
| chapter_id: str | None = None, |
| topic_key: str | None = None, |
| event_data: dict[str, Any] | None = None, |
| ) -> LearningEvent: |
| """Stage one idempotent event in the caller's current transaction.""" |
|
|
| existing = db.scalar( |
| select(LearningEvent).where( |
| LearningEvent.user_id == user_id, |
| LearningEvent.idempotency_key == idempotency_key, |
| ) |
| ) |
| if existing is not None: |
| return existing |
|
|
| event = LearningEvent( |
| user_id=user_id, |
| event_type=event_type, |
| entity_type=entity_type, |
| entity_id=entity_id, |
| idempotency_key=idempotency_key, |
| subject_id=subject_id, |
| chapter_id=chapter_id, |
| topic_key=topic_key, |
| event_data=event_data or {}, |
| ) |
| db.add(event) |
| return event |
|
|