Files
hyungi_document_server/app/services/study/outcome.py
T
hyungi 8d3b648b5f feat(ingest): P2 DS write-back — /ingest/study/attempts 멱등 finalize 재생 (study→viewer)
뷰어 로컬 풀이 세션을 DS 로 흘려 학습엔진(SR/pattern/오답/4-A·4-B) 재생. 기본 inert(flag off).
- 마이그 373~376: study_quiz_sessions 에 finalized_at(멱등 마커)·client_session_uuid·source
  + UNIQUE(client_session_uuid, study_topic_id) partial.
- outcome.py derive_outcome = 채점 단일 소스(라이브 submit_attempt 도 이걸로 리팩터 → 정오 어휘
  한 곳, ingest 는 raw 신호 selected+unsure 만 싣고 DS 산출 = '무수정 재생' 성립).
- ingest_study.py: Bearer(VIEWER_SYNC_TOKEN)+study_ingest_enabled gate. pub_id→source_id→question
  해소(graceful skip)·principal=question.user_id(mixed 거부)·topic 별 DS 세션(source=viewer·uuid)
  생성+attempt+finalize_session 무수정 재생+finalized_at, 1-tx 원자. uuid 존재=already_ingested
  캐시반환(멱등 → at-least-once 재전송에도 SR 이중 advance 0).
- config study_ingest_enabled + compose 매핑 + main 등록.

검증: py_compile·ephemeral 마이그(373~376 라이브스키마 위 클린)·single-statement. 배포 후
합성 세션 멱등/무이중SR 실측 예정. 배포=inert(STUDY_INGEST_ENABLED 미설정=503).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 07:27:34 +09:00

26 lines
1.3 KiB
Python

"""채점(outcome) 산출 단일 소스 (study-to-viewer P2).
라이브 attempt 엔드포인트(submit_attempt)와 뷰어 ingest 가 **동일 함수**로 채점 →
정오 어휘가 한 곳(서버)에서 결정(plan r2: ingest 는 raw 신호 selected+unsure 만 싣고
DS 가 산출 = '무수정 재생'을 실제로 성립시키는 형태). correct_choice 는 항상 현재 DB 값.
규칙(라이브 study_questions.py:1008-1020 동일):
is_unsure=True → (None, False, 'unsure') # unsure 가 정오 override, selected 폐기
selected None → ValueError # 선택 없고 unsure 도 아니면 무효(엔드포인트가 처리)
그 외 → selected==correct → (selected, is_correct, 'correct'|'wrong')
"""
from __future__ import annotations
def derive_outcome(
selected_choice: int | None, is_unsure: bool, correct_choice: int
) -> tuple[int | None, bool, str]:
"""(selected, is_correct, outcome) 반환. skipped 는 여기서 안 나옴(선택 없으면 호출측이 거부/skip)."""
if is_unsure:
return None, False, "unsure"
if selected_choice is None:
raise ValueError("selected_choice (1~4) 또는 is_unsure=true 가 필요합니다")
is_correct = selected_choice == correct_choice
return selected_choice, is_correct, ("correct" if is_correct else "wrong")