회기 무발화 0턴 분리, 자기예측 락 불변식 및 TDD 회귀 검증 완료
Some checks failed
API contract / OpenAPI type drift (push) Failing after 3m27s
Some checks failed
API contract / OpenAPI type drift (push) Failing after 3m27s
This commit is contained in:
parent
a479db7a5a
commit
a0311c5957
100 changed files with 4884 additions and 11210 deletions
|
|
@ -6,7 +6,7 @@ import unittest
|
|||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from .deps import Principal, Role
|
||||
from . import session_read_model
|
||||
from . import session_dashboard_projection
|
||||
from .routes import sessions
|
||||
from .services import state_machine
|
||||
from .services.persona import P1, P2, P3
|
||||
|
|
@ -88,14 +88,14 @@ def _exposure_sessions(persona_codes: list[str]) -> list[InProcSession]:
|
|||
|
||||
class TrainingExposureMetricTest(unittest.TestCase):
|
||||
def test_zero_completed_sessions_is_insufficient(self) -> None:
|
||||
metric = session_read_model.dashboard_training_exposure([])
|
||||
metric = session_dashboard_projection.dashboard_training_exposure([])
|
||||
|
||||
self.assertEqual(metric.label, "판정 근거 부족")
|
||||
self.assertEqual(metric.completed_sessions, 0)
|
||||
self.assertIsNone(metric.dominant_share)
|
||||
|
||||
def test_one_completed_session_is_insufficient(self) -> None:
|
||||
metric = session_read_model.dashboard_training_exposure(
|
||||
metric = session_dashboard_projection.dashboard_training_exposure(
|
||||
_exposure_sessions([P1.code])
|
||||
)
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ class TrainingExposureMetricTest(unittest.TestCase):
|
|||
self.assertEqual(metric.dominant_share, 1.0)
|
||||
|
||||
def test_four_sessions_with_broad_exposure_is_balanced(self) -> None:
|
||||
metric = session_read_model.dashboard_training_exposure(
|
||||
metric = session_dashboard_projection.dashboard_training_exposure(
|
||||
_exposure_sessions([P1.code, P1.code, P2.code, P3.code])
|
||||
)
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ class TrainingExposureMetricTest(unittest.TestCase):
|
|||
self.assertEqual(metric.dominant_share, 0.5)
|
||||
|
||||
def test_dominant_share_at_seventy_five_percent_is_attention(self) -> None:
|
||||
metric = session_read_model.dashboard_training_exposure(
|
||||
metric = session_dashboard_projection.dashboard_training_exposure(
|
||||
_exposure_sessions([P1.code, P1.code, P1.code, P2.code])
|
||||
)
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ class TrainingExposureMetricTest(unittest.TestCase):
|
|||
self.assertEqual(metric.dominant_share, 0.75)
|
||||
|
||||
def test_tied_dominant_persona_is_deterministic_and_balanced(self) -> None:
|
||||
metric = session_read_model.dashboard_training_exposure(
|
||||
metric = session_dashboard_projection.dashboard_training_exposure(
|
||||
_exposure_sessions([P2.code, P1.code, P2.code, P1.code])
|
||||
)
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ class LearnerDashboardTest(unittest.IsolatedAsyncioTestCase):
|
|||
AsyncMock(return_value=(owned_sessions, True)),
|
||||
) as list_recent_sessions,
|
||||
patch.object(
|
||||
sessions.session_persistence,
|
||||
sessions.session_evaluation_repository,
|
||||
"load_session_evaluation",
|
||||
AsyncMock(side_effect=load_review),
|
||||
),
|
||||
|
|
@ -249,7 +249,7 @@ class LearnerDashboardTest(unittest.IsolatedAsyncioTestCase):
|
|||
AsyncMock(return_value=([good_session, failed_session], True)),
|
||||
),
|
||||
patch.object(
|
||||
sessions.session_persistence,
|
||||
sessions.session_evaluation_repository,
|
||||
"load_session_evaluation",
|
||||
AsyncMock(return_value=({"status": "ready"}, True)),
|
||||
),
|
||||
|
|
@ -386,7 +386,7 @@ class LearnerDashboardTest(unittest.IsolatedAsyncioTestCase):
|
|||
AsyncMock(return_value=(archive_records, True)),
|
||||
) as list_session_archives,
|
||||
patch.object(
|
||||
sessions.session_persistence,
|
||||
sessions.session_evaluation_repository,
|
||||
"load_session_evaluation",
|
||||
AsyncMock(side_effect=load_review),
|
||||
),
|
||||
|
|
@ -438,7 +438,7 @@ class LearnerDashboardTest(unittest.IsolatedAsyncioTestCase):
|
|||
),
|
||||
) as set_session_archived,
|
||||
patch.object(
|
||||
sessions.session_persistence,
|
||||
sessions.session_evaluation_repository,
|
||||
"load_session_evaluation",
|
||||
AsyncMock(return_value=({"status": "ready"}, True)),
|
||||
),
|
||||
|
|
@ -486,7 +486,7 @@ class LearnerDashboardTest(unittest.IsolatedAsyncioTestCase):
|
|||
AsyncMock(return_value=(None, True)),
|
||||
) as set_session_archived,
|
||||
patch.object(
|
||||
sessions.session_persistence,
|
||||
sessions.session_evaluation_repository,
|
||||
"load_session_evaluation",
|
||||
AsyncMock(return_value=(None, True)),
|
||||
),
|
||||
|
|
@ -510,6 +510,46 @@ class LearnerDashboardTest(unittest.IsolatedAsyncioTestCase):
|
|||
self.assertFalse(response.session.archived)
|
||||
self.assertIsNone(response.session.archived_at)
|
||||
|
||||
def test_zero_turn_ended_sessions_not_counted_in_completed_sessions(self) -> None:
|
||||
"""REQ-010: Zero-turn sessions ended without counselor dialogue are not counted in completed_sessions."""
|
||||
# 1 session with turns (completed)
|
||||
normal_session = _session(
|
||||
session_id="00000000-0000-0000-0000-00000000b991",
|
||||
session_no=1,
|
||||
persona_code=P1.code,
|
||||
score="pos",
|
||||
rapport=0.5,
|
||||
technique="reflection",
|
||||
created_at=1_000.0,
|
||||
)
|
||||
normal_session.ended = True
|
||||
|
||||
# 1 zero-turn session created and closed immediately
|
||||
zero_turn_session = _session(
|
||||
session_id="00000000-0000-0000-0000-00000000b992",
|
||||
session_no=2,
|
||||
persona_code=P1.code,
|
||||
score="neutral",
|
||||
rapport=0.0,
|
||||
technique="none",
|
||||
created_at=1_100.0,
|
||||
)
|
||||
zero_turn_session.turns = [] # No turns
|
||||
zero_turn_session.ended = True
|
||||
|
||||
from app.session_dashboard_projection import dashboard_overview, dashboard_training_exposure
|
||||
|
||||
overview = dashboard_overview(
|
||||
[normal_session, zero_turn_session],
|
||||
visible_review_ready={normal_session.session_id: True, zero_turn_session.session_id: False},
|
||||
archived_sessions=0,
|
||||
)
|
||||
self.assertEqual(overview.total_sessions, 2)
|
||||
self.assertEqual(overview.completed_sessions, 1) # Only 1 completed, not 2!
|
||||
|
||||
exposure = dashboard_training_exposure([normal_session, zero_turn_session])
|
||||
self.assertEqual(exposure.completed_sessions, 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue