세션 평가와 교수자 분석 보강
This commit is contained in:
parent
5c4ac04e06
commit
fe2796f05a
51 changed files with 4928 additions and 240 deletions
|
|
@ -447,6 +447,49 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
|
|||
legacy_history = await sessions.list_live_coach_history(sess.session_id, principal)
|
||||
self.assertIsNone(legacy_history.events[0].stage)
|
||||
|
||||
async def test_live_coach_persistence_failure_is_not_swallowed(self) -> None:
|
||||
principal = _principal()
|
||||
sess = _session(principal)
|
||||
suggestion = live_coach.LiveCoachSuggestion(
|
||||
status="ready",
|
||||
tone="pos",
|
||||
focus="emotion",
|
||||
title="감정 반영이 선명합니다",
|
||||
message="학습자가 내담자의 감정을 먼저 되짚었습니다.",
|
||||
)
|
||||
save_error = sessions.HTTPException(
|
||||
status_code=sessions.status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="live coach event save persistence unavailable; runtime fallback is disabled in prod",
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
sessions,
|
||||
"_retrieve_live_coach_grounding",
|
||||
AsyncMock(return_value=[]),
|
||||
), patch.object(
|
||||
sessions.live_coach,
|
||||
"generate_live_coaching",
|
||||
AsyncMock(return_value=suggestion),
|
||||
), patch.object(
|
||||
session_persistence,
|
||||
"save_live_coach_event",
|
||||
AsyncMock(side_effect=save_error),
|
||||
) as save_event:
|
||||
with self.assertRaises(sessions.HTTPException) as caught:
|
||||
await sessions.live_coach_turn(
|
||||
sess.session_id,
|
||||
sessions.LiveCoachRequest(
|
||||
learner_text="그 마음이 컸겠네요.",
|
||||
client_reply="네, 아무도 몰라주는 것 같았어요.",
|
||||
turn_seq=1,
|
||||
),
|
||||
principal,
|
||||
)
|
||||
|
||||
self.assertEqual(caught.exception.status_code, 503)
|
||||
self.assertIn("live coach event save", str(caught.exception.detail))
|
||||
save_event.assert_awaited_once()
|
||||
|
||||
async def test_live_coach_event_normalizes_legacy_stage_values(self) -> None:
|
||||
suggestion = live_coach.LiveCoachSuggestion(
|
||||
status="degraded",
|
||||
|
|
@ -987,6 +1030,11 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
|
|||
"category": "speech_activity",
|
||||
"start_ms": 100,
|
||||
},
|
||||
{
|
||||
"event_type": "silence",
|
||||
"category": "timing",
|
||||
"duration_ms": 1234,
|
||||
},
|
||||
{
|
||||
"event_type": "background_noise",
|
||||
"category": "audio_quality",
|
||||
|
|
@ -1021,6 +1069,7 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
|
|||
)
|
||||
self.assertEqual(learner_turn.nonverbal[0].label, "침묵")
|
||||
self.assertEqual(learner_turn.nonverbal[0].detail, "1.2초")
|
||||
self.assertEqual([event.kind for event in learner_turn.nonverbal].count("silence"), 1)
|
||||
self.assertEqual(learner_turn.nonverbal[1].detail, "분당 420자")
|
||||
self.assertEqual(learner_turn.nonverbal[4].label, "음성 단서")
|
||||
self.assertEqual(learner_turn.nonverbal[4].detail, "한숨 감지 · 신뢰도 82%")
|
||||
|
|
@ -1191,6 +1240,66 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
|
|||
self.assertIn("주호소", response.teacherReview.worksheetNote)
|
||||
self.assertEqual(response.teacherReview.worksheetReviewedAt, "2026-06-27T10:05:00Z")
|
||||
|
||||
async def test_review_surfaces_session_evaluation_error_record(self) -> None:
|
||||
principal = _principal()
|
||||
sess = _session(principal)
|
||||
sess.ended = True
|
||||
sess.ended_at = sess.created_at + 180
|
||||
sess.turns.extend(
|
||||
[
|
||||
TurnRecord(
|
||||
turn_seq=1,
|
||||
speaker="learner",
|
||||
stage=sess.state.stage.value,
|
||||
text="많이 지쳐 보였어요. 지금 제일 버거운 마음이 뭔가요?",
|
||||
text_masked="많이 지쳐 보였어요. 지금 제일 버거운 마음이 뭔가요?",
|
||||
created_at=sess.created_at + 1,
|
||||
),
|
||||
TurnRecord(
|
||||
turn_seq=2,
|
||||
speaker="client",
|
||||
stage=sess.state.stage.value,
|
||||
text="그냥 아무것도 하고 싶지 않아요.",
|
||||
text_masked="그냥 아무것도 하고 싶지 않아요.",
|
||||
created_at=sess.created_at + 2,
|
||||
),
|
||||
]
|
||||
)
|
||||
error_record = session_persistence.SessionEvaluationWrite.from_error(
|
||||
session_id=sess.session_id,
|
||||
learner_id=principal.user_id,
|
||||
scope="session_end",
|
||||
stage="정리",
|
||||
error="session evaluation timeout after 45s",
|
||||
).cache_record()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
sessions.session_persistence,
|
||||
"load_session",
|
||||
AsyncMock(return_value=sess),
|
||||
),
|
||||
patch.object(
|
||||
sessions.session_persistence,
|
||||
"load_case_worksheet",
|
||||
AsyncMock(return_value=(None, False)),
|
||||
),
|
||||
patch.object(
|
||||
sessions.session_persistence,
|
||||
"load_session_evaluation",
|
||||
AsyncMock(return_value=(error_record, True)),
|
||||
),
|
||||
):
|
||||
response = await sessions.get_session_review(sess.session_id, principal)
|
||||
|
||||
self.assertFalse(response.reviewReady)
|
||||
self.assertTrue(response.degraded)
|
||||
self.assertEqual(response.supervisorState, "평가 실패")
|
||||
self.assertIn("session evaluation timeout after 45s", response.summary)
|
||||
self.assertEqual(response.rubric, [])
|
||||
self.assertEqual(response.goodMoments, [])
|
||||
self.assertEqual(response.growthPoints, [])
|
||||
|
||||
async def test_learner_can_save_case_formulation_worksheet(self) -> None:
|
||||
principal = _principal()
|
||||
sess = _session(principal)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue