런타임 계약과 학습자 흐름 보강

This commit is contained in:
Yun Chan 2026-06-29 08:12:14 +09:00
parent f456b8997a
commit 206018b088
56 changed files with 4306 additions and 1008 deletions

View file

@ -9,7 +9,7 @@ from unittest.mock import patch
from .deps import Principal, Role
from . import session_persistence
from .routes import sessions
from .services import evaluator
from .services import evaluator, session_metrics
from .services import persona as persona_service
from .services import state_machine
from .store import InProcSession
@ -45,6 +45,16 @@ class FakeAcquire:
class EvaluationPersistenceMappingTest(unittest.TestCase):
def test_session_metrics_prefers_rehydrated_technique_label_ko(self) -> None:
ev = {
"techniques": [
{"code": "empathy", "label_ko": "공감", "label": "legacy empathy"},
{"code": "open_question"},
]
}
self.assertEqual(session_metrics.turn_techniques(ev), ["공감", "open_question"])
def test_fast_evaluator_masks_client_reply_before_prompting(self) -> None:
card = persona_service.P1
state = state_machine.init_state(params=card.openness_params())
@ -106,6 +116,65 @@ class EvaluationPersistenceMappingTest(unittest.TestCase):
self.assertEqual(rows["technique:empathy"]["rationale"], "감정을 명시적으로 반영했다.")
self.assertEqual(rows["client_state:affect_contact"]["rationale"], "내담자가 감정을 언급했다.")
def test_alternative_rows_accept_string_and_dict_shapes(self) -> None:
rows = session_persistence._evaluation_alternative_rows(
{
"alternative_utterances": [
"감정을 먼저 반영해 보세요.",
{"text": "조언 전에 의미를 확인해 보세요.", "rationale": "성급한 해결 방지"},
{"suggestion": "침묵을 허용해 보세요."},
{"rationale": "빈 제안은 저장하지 않음"},
]
}
)
self.assertEqual(
rows,
[
{"suggestion": "감정을 먼저 반영해 보세요.", "rationale": None},
{"suggestion": "조언 전에 의미를 확인해 보세요.", "rationale": "성급한 해결 방지"},
{"suggestion": "침묵을 허용해 보세요.", "rationale": None},
],
)
def test_session_evaluation_write_from_result_preserves_payload_shape(self) -> None:
result = evaluator.SessionEvaluation(
session_id="session-1",
stage="explore",
scope="session_end",
turns_evaluated=2,
)
write = session_persistence.SessionEvaluationWrite.from_result(
session_id="session-1",
learner_id="learner-1",
result=result,
)
self.assertEqual(write.status, "ready")
self.assertEqual(write.source, "engine")
self.assertEqual(write.scope, "session_end")
self.assertEqual(write.stage, "explore")
self.assertEqual(write.payload, result.to_dict())
self.assertNotIn("error", write.payload)
self.assertIsNone(write.error)
def test_session_evaluation_write_from_error_preserves_fallback_shape(self) -> None:
write = session_persistence.SessionEvaluationWrite.from_error(
session_id="session-1",
learner_id="learner-1",
scope="session_end",
stage="explore",
error=RuntimeError("engine timeout"),
)
self.assertEqual(write.status, "error")
self.assertEqual(write.source, "engine")
self.assertEqual(write.scope, "session_end")
self.assertEqual(write.stage, "explore")
self.assertEqual(write.payload, {})
self.assertEqual(write.error, "engine timeout")
def test_rebuild_turn_evaluation_restores_review_shape(self) -> None:
rebuilt = session_persistence._rebuild_turn_evaluations(
[("11111111-1111-1111-1111-111111111111", 2, "탐색")],