운영 화면과 회기 리뷰 UI 갱신
This commit is contained in:
parent
e7ebb38177
commit
3a9f70a97b
18 changed files with 2210 additions and 137 deletions
|
|
@ -1,7 +1,12 @@
|
|||
import type { Page } from "@playwright/test";
|
||||
import type { SessionReviewResponse } from "../src/lib/api";
|
||||
import type {
|
||||
SessionReviewResponse,
|
||||
UserPrepostMeasureItem,
|
||||
UserPrepostMeasuresResponse,
|
||||
} from "../src/lib/api";
|
||||
|
||||
export const FILLED_REVIEW_SESSION_ID = "filled-review-visual";
|
||||
export const EMPTY_REVIEW_SESSION_ID = "empty-review-visual";
|
||||
|
||||
export function filledReviewResponse(
|
||||
sessionId: string = FILLED_REVIEW_SESSION_ID,
|
||||
|
|
@ -67,7 +72,10 @@ export function filledReviewResponse(
|
|||
{ kind: "reflect", label: "감정 반영" },
|
||||
{ kind: "explore", label: "개방 질문" },
|
||||
],
|
||||
nonverbal: [{ kind: "pace", label: "말 속도", detail: "안정" }],
|
||||
nonverbal: [
|
||||
{ kind: "pace", label: "말 속도", detail: "안정" },
|
||||
{ kind: "paralinguistic", label: "음성 단서", detail: "한숨 감지 · 신뢰도 82%" },
|
||||
],
|
||||
note: {
|
||||
author: "AI 슈퍼바이저",
|
||||
tone: "ai",
|
||||
|
|
@ -239,3 +247,176 @@ export async function routeFilledSessionReview(
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function prepostMeasuresResponse(
|
||||
measures: UserPrepostMeasureItem[] = [
|
||||
{
|
||||
measure_id: "prepost-self-efficacy-pre",
|
||||
pilot_id: "phase3-pilot-draft",
|
||||
measure_name: "self_efficacy",
|
||||
timepoint: "pre",
|
||||
raw_score: 3,
|
||||
min_score: 1,
|
||||
max_score: 5,
|
||||
normalized_score: 0.5,
|
||||
instrument_version: "pilot-prepost-scaffold-2026-06-28",
|
||||
item_count: 1,
|
||||
collected_at: 1782662400,
|
||||
updated_at: 1782662400,
|
||||
},
|
||||
{
|
||||
measure_id: "prepost-self-efficacy-post",
|
||||
pilot_id: "phase3-pilot-draft",
|
||||
measure_name: "self_efficacy",
|
||||
timepoint: "post",
|
||||
raw_score: 4,
|
||||
min_score: 1,
|
||||
max_score: 5,
|
||||
normalized_score: 0.75,
|
||||
instrument_version: "pilot-prepost-scaffold-2026-06-28",
|
||||
item_count: 1,
|
||||
collected_at: 1782662400,
|
||||
updated_at: 1782662400,
|
||||
},
|
||||
],
|
||||
): UserPrepostMeasuresResponse {
|
||||
const completePairs = new Set<string>();
|
||||
for (const measureName of ["self_efficacy", "skill_proficiency", "training_satisfaction"]) {
|
||||
const hasPre = measures.some(
|
||||
(item) => item.measure_name === measureName && item.timepoint === "pre",
|
||||
);
|
||||
const hasPost = measures.some(
|
||||
(item) => item.measure_name === measureName && item.timepoint === "post",
|
||||
);
|
||||
if (hasPre && hasPost) completePairs.add(measureName);
|
||||
}
|
||||
return {
|
||||
source: "database",
|
||||
durable: true,
|
||||
pilot_id: "phase3-pilot-draft",
|
||||
required_measure_names: [
|
||||
"self_efficacy",
|
||||
"skill_proficiency",
|
||||
"training_satisfaction",
|
||||
],
|
||||
required_timepoints: ["pre", "post"],
|
||||
complete_measure_pairs: completePairs.size,
|
||||
measures,
|
||||
generated_at: 1782662400,
|
||||
};
|
||||
}
|
||||
|
||||
export async function routePrepostMeasures(page: Page) {
|
||||
let measures = prepostMeasuresResponse().measures;
|
||||
|
||||
await page.route("**/api/users/me/prepost-measures**", async (route) => {
|
||||
const request = route.request();
|
||||
if (request.method() === "GET") {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(prepostMeasuresResponse(measures)),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method() === "PUT") {
|
||||
const body = request.postDataJSON() as {
|
||||
measure_name: UserPrepostMeasureItem["measure_name"];
|
||||
timepoint: UserPrepostMeasureItem["timepoint"];
|
||||
raw_score: number;
|
||||
min_score?: number;
|
||||
max_score?: number;
|
||||
instrument_version?: string;
|
||||
item_count?: number;
|
||||
pilot_id?: string;
|
||||
};
|
||||
const now = 1782666000;
|
||||
const next: UserPrepostMeasureItem = {
|
||||
measure_id: `prepost-${body.measure_name}-${body.timepoint}`,
|
||||
pilot_id: body.pilot_id ?? "phase3-pilot-draft",
|
||||
measure_name: body.measure_name,
|
||||
timepoint: body.timepoint,
|
||||
raw_score: body.raw_score,
|
||||
min_score: body.min_score ?? 1,
|
||||
max_score: body.max_score ?? 5,
|
||||
normalized_score:
|
||||
(body.raw_score - (body.min_score ?? 1)) / ((body.max_score ?? 5) - (body.min_score ?? 1)),
|
||||
instrument_version: body.instrument_version ?? "pilot-prepost-scaffold-2026-06-28",
|
||||
item_count: body.item_count ?? 1,
|
||||
collected_at: now,
|
||||
updated_at: now,
|
||||
};
|
||||
measures = measures
|
||||
.filter(
|
||||
(item) =>
|
||||
!(
|
||||
item.measure_name === next.measure_name &&
|
||||
item.timepoint === next.timepoint &&
|
||||
item.instrument_version === next.instrument_version
|
||||
),
|
||||
)
|
||||
.concat(next);
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(next),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fallback();
|
||||
});
|
||||
}
|
||||
|
||||
export function emptyReviewResponse(
|
||||
sessionId: string = EMPTY_REVIEW_SESSION_ID,
|
||||
): SessionReviewResponse {
|
||||
const response = filledReviewResponse(sessionId);
|
||||
return {
|
||||
...response,
|
||||
date: "2026-06-28",
|
||||
durationLabel: "0분",
|
||||
durationSeconds: 0,
|
||||
reachedPhase: "라포",
|
||||
sessionSignal: "종료됨",
|
||||
supervisorState: "평가 대기",
|
||||
summary:
|
||||
"축어록이 아직 저장되지 않아 평가 근거를 생성하지 않았습니다. 빈 상태에서는 임의의 강점이나 개선점을 표시하지 않습니다.",
|
||||
phases: [],
|
||||
phaseAxis: [],
|
||||
valenceAxis: [],
|
||||
clientValence: [],
|
||||
counselorBaseline: [],
|
||||
turns: [],
|
||||
rubric: [],
|
||||
goodMoments: [],
|
||||
growthPoints: [],
|
||||
caseWorksheet: {
|
||||
status: "empty",
|
||||
generatedBy: "rule-based transcript extractor",
|
||||
savedAt: null,
|
||||
sections: [],
|
||||
limitations: ["축어록이 없어 사례개념화 워크시트를 생성하지 않았습니다."],
|
||||
},
|
||||
nextLine: null,
|
||||
clientFeedback: null,
|
||||
audioUrl: null,
|
||||
pdfExportUrl: null,
|
||||
degraded: false,
|
||||
reviewReady: false,
|
||||
};
|
||||
}
|
||||
|
||||
export async function routeEmptySessionReview(
|
||||
page: Page,
|
||||
sessionId: string = EMPTY_REVIEW_SESSION_ID,
|
||||
) {
|
||||
await page.route(`**/api/sessions/${sessionId}/review`, async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(emptyReviewResponse(sessionId)),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue