세션 평가·라이브코치·교수자 분석 라운드 마감 + 문서 정리 + 코드품질 리팩터
- 누적 작업트리 커밋: 회기 평가 복구·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:
parent
7c41c3ce79
commit
778e8526d4
108 changed files with 6457 additions and 455 deletions
|
|
@ -242,6 +242,80 @@ test.describe("session review", () => {
|
|||
expect(reviewAttempts).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
test("keeps polling long-running session evaluation until the ready review arrives", async ({
|
||||
page,
|
||||
}) => {
|
||||
await routeSupervisorCapableLearner(page);
|
||||
await routePrepostMeasures(page);
|
||||
|
||||
const sessionId = "review-delayed-ready";
|
||||
let reviewAttempts = 0;
|
||||
const readyAtAttempt = 10;
|
||||
await page.route(`**/api/sessions/${sessionId}/review`, async (route) => {
|
||||
reviewAttempts += 1;
|
||||
const ready = {
|
||||
...filledReviewResponse(sessionId),
|
||||
summary: "평가 AI가 늦게 완료된 deep-loop 결과를 반영했습니다.",
|
||||
};
|
||||
const pending = {
|
||||
...ready,
|
||||
supervisorState: "평가 대기",
|
||||
summary: "평가 AI가 긴 회기 축어록을 분석 중입니다.",
|
||||
rubric: [],
|
||||
goodMoments: [],
|
||||
growthPoints: [],
|
||||
degraded: true,
|
||||
reviewReady: false,
|
||||
};
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(reviewAttempts >= readyAtAttempt ? ready : pending),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto(`/learn/session/${sessionId}/review`);
|
||||
|
||||
await expect(page.getByText("평가 AI가 늦게 완료된 deep-loop 결과를 반영했습니다.")).toBeVisible({
|
||||
timeout: 20_000,
|
||||
});
|
||||
await expect(page.getByText("평가 완료")).toBeVisible();
|
||||
expect(reviewAttempts).toBeGreaterThanOrEqual(readyAtAttempt);
|
||||
});
|
||||
|
||||
test("does not offer manual AI retry while session evaluation is still pending", async ({
|
||||
page,
|
||||
}) => {
|
||||
await routeSupervisorCapableLearner(page);
|
||||
await routePrepostMeasures(page);
|
||||
|
||||
const sessionId = "review-pending-no-manual-retry";
|
||||
await page.route(`**/api/sessions/${sessionId}/review`, async (route) => {
|
||||
const pending = {
|
||||
...filledReviewResponse(sessionId),
|
||||
supervisorState: "평가 대기",
|
||||
summary: "평가 AI가 저장된 축어록을 분석 중입니다.",
|
||||
rubric: [],
|
||||
goodMoments: [],
|
||||
growthPoints: [],
|
||||
degraded: true,
|
||||
reviewReady: false,
|
||||
};
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(pending),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto(`/teach/session/${sessionId}/review`);
|
||||
|
||||
await expect(page.getByText("평가 대기", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("평가 AI가 저장된 축어록을 분석 중입니다.")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "AI 평가 재시도" })).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "검토 완료" })).toBeDisabled();
|
||||
});
|
||||
|
||||
test("lets supervisor retry a failed AI session evaluation", async ({ page }) => {
|
||||
await routeSupervisorCapableLearner(page);
|
||||
await routePrepostMeasures(page);
|
||||
|
|
@ -303,11 +377,13 @@ test.describe("session review", () => {
|
|||
await page.goto(`/teach/session/${sessionId}/review`);
|
||||
await expect(page.getByText("평가 실패")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "AI 평가 재시도" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "검토 완료" })).toBeDisabled();
|
||||
|
||||
await page.getByRole("button", { name: "AI 평가 재시도" }).click();
|
||||
|
||||
await expect(page.getByText("평가 완료")).toBeVisible();
|
||||
await expect(page.getByText("평가 실패")).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "검토 완료" })).toBeEnabled();
|
||||
expect(reevaluateRequests).toBe(1);
|
||||
});
|
||||
|
||||
|
|
@ -483,5 +559,13 @@ test.describe("session review", () => {
|
|||
|
||||
await expect(page.getByText("2/3 쌍")).toBeVisible();
|
||||
await expect(page.locator(".sr-prepost__actions")).toContainText("저장된 값 기준");
|
||||
|
||||
const prepostCard = page.locator(".sr-card--prepost");
|
||||
await prepostInputs.nth(0).fill("");
|
||||
await expect(prepostInputs.nth(0)).toHaveAttribute("aria-invalid", "true");
|
||||
await expect(prepostCard.locator(".sr-prepost__actions")).toContainText("저장되지 않은 입력 있음");
|
||||
await expect(prepostCard.locator(".sr-prepost__actions")).not.toContainText("저장된 값 기준");
|
||||
await expect(prepostCard.getByRole("alert")).toContainText("기존 값을 비우려면 새 점수를 입력해 주세요.");
|
||||
await expect(prepostCard.getByRole("button", { name: "점수 저장" })).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue