세션 평가·라이브코치·교수자 분석 라운드 마감 + 문서 정리 + 코드품질 리팩터

- 누적 작업트리 커밋: 회기 평가 복구·durable 저장, 라이브 코치 이력/근거, 교수자 학생분석, 음성 비언어 메타, PII 마스킹, 운영 티켓/헬스 등
- 문서: 완료 기록 docs/archive/ 냉동 보관, docs/ 단일 인덱스(docs/README.md)+통합 TODO(docs/TODO.md)로 정리
- 리팩터(행위 보존): Stage enum SSOT(taxonomy 소유·state_machine re-export), store recent/masked_turns 중복 제거, speaker_ko_label 단일 헬퍼, _list_sessions N+1 제거(state/turns 배치 + 턴평가 하이드레이션 배치)
- 검증: 백엔드 pytest 352 passed, _list_sessions E2E chromium-single-run 2 passed
This commit is contained in:
Yun Chan 2026-07-02 02:50:36 +09:00
parent 7c41c3ce79
commit 778e8526d4
108 changed files with 6457 additions and 455 deletions

View file

@ -660,7 +660,21 @@ test.describe("teacher console", () => {
const analysisPanel = page.locator('[data-learner-analysis-panel="true"]');
await expect(analysisPanel).toBeVisible();
await expect(analysisPanel.getByRole("tab", { name: /^페르소나별 회기/ })).toHaveAttribute(
"aria-selected",
"true",
);
await expect(analysisPanel.locator('[data-learner-persona-group="true"]')).toHaveCount(2);
await expect(analysisPanel.locator('[data-learner-session-row="true"]')).toHaveCount(0);
const personaGroup = analysisPanel
.locator('[data-learner-persona-group="true"]')
.filter({ hasText: "민재" });
await personaGroup.getByRole("button", { name: /P2 민재 회기 펼치기/ }).click();
await expect(analysisPanel.locator('[data-learner-persona-sessions="true"]')).toBeVisible();
await expect(
analysisPanel.locator('[data-learner-persona-sessions="true"]').getByText(`${learnerB}-session-2`),
).toBeVisible();
await expect(analysisPanel.locator('[data-learner-session-row="true"]')).toHaveCount(1);
await analysisPanel.getByRole("tab", { name: /^전체 회기/ }).click();
await expect(analysisPanel.locator('[data-learner-session-row="true"]')).toHaveCount(2);
await expect(analysisPanel.getByText(`${learnerB}-session-2`)).toBeVisible();
@ -766,6 +780,89 @@ test.describe("teacher console", () => {
await expectNoHorizontalOverflow(page);
});
test("surfaces failed AI session evaluation in the teacher pending queue", async ({
page,
}) => {
const sessionId = "teacher-failed-ai-evaluation-session";
await page.route("**/api/teacher/dashboard", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
source: "database",
cohort_label: "E2E cohort",
total_learners: 1,
active_sessions: 0,
ended_sessions: 1,
learner_growth: [],
safety_alerts: [],
pending_reviews: [
{
session_id: sessionId,
learner_id: "00000000-0000-0000-0000-000000000116",
learner_label: "하린",
persona_code: "P6",
persona_name: "하린",
session_no: 6,
status: "ended",
stage: "정리",
turn_count: 18,
learner_turn_count: 9,
client_turn_count: 9,
started_at: "2026-06-30T05:00:00Z",
ended_at: "2026-06-30T06:42:33Z",
review_status: "pending",
review_note: null,
reviewed_at: null,
evaluation_status: "error",
review_ready: false,
supervisor_state: "평가 실패",
evaluation_error: "session evaluation timeout after 45s",
},
],
recent_sessions: [],
message: "교수자 검토 대기 회기가 있습니다.",
}),
}),
);
await page.route("**/api/personas/review", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: "[]",
}),
);
await page.route("**/api/auth/me", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
user_id: "00000000-0000-0000-0000-000000000901",
email: "teacher@hs.ac.kr",
role: "teacher",
display_name: "E2E Teacher",
admin_access: false,
super_admin: false,
account_status: "approved",
approval_required: false,
cohort_ids: [],
consent_at: 1782820000,
onboarding_completed_at: 1782820001,
nickname: "E2E Teacher",
self_introduction: "",
avatar_url: "",
}),
}),
);
await page.goto("/teach");
const row = page.getByRole("button", { name: /하린 P6 회기 상세 검토/ });
await expect(row).toBeVisible();
await expect(row).toContainText("평가 실패");
await expect(row).toContainText("검토 대기");
});
test("opens recent ended and active sessions from the teacher history @single-run", async ({
page,
}) => {