"""채점(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")