세션 계약 SSOT 정리
This commit is contained in:
parent
1881fe50bd
commit
17b114f647
8 changed files with 43 additions and 47 deletions
|
|
@ -31,6 +31,7 @@ from ..store import InProcSession, TurnRecord, store
|
||||||
router = APIRouter(prefix="/sessions", tags=["sessions"])
|
router = APIRouter(prefix="/sessions", tags=["sessions"])
|
||||||
|
|
||||||
TheoryMode = Literal["humanistic", "cbt", "integrative"]
|
TheoryMode = Literal["humanistic", "cbt", "integrative"]
|
||||||
|
StageLabel = Literal["라포", "탐색", "개입", "정리"]
|
||||||
EndStateValue = str | int | float | bool | None | dict[str, float]
|
EndStateValue = str | int | float | bool | None | dict[str, float]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ class SessionStartResponse(BaseModel):
|
||||||
session_id: str
|
session_id: str
|
||||||
case_id: str
|
case_id: str
|
||||||
session_no: int
|
session_no: int
|
||||||
stage: str
|
stage: StageLabel
|
||||||
effective_openness: float
|
effective_openness: float
|
||||||
recall_summary: Optional[str] = None
|
recall_summary: Optional[str] = None
|
||||||
degraded: bool = False
|
degraded: bool = False
|
||||||
|
|
@ -61,7 +62,7 @@ class CrisisResourceResponse(BaseModel):
|
||||||
|
|
||||||
class TurnResponse(BaseModel):
|
class TurnResponse(BaseModel):
|
||||||
turn_seq: int
|
turn_seq: int
|
||||||
stage: str
|
stage: StageLabel
|
||||||
effective_openness: float
|
effective_openness: float
|
||||||
client_reply: Optional[str] = None
|
client_reply: Optional[str] = None
|
||||||
safety_flagged: bool = False
|
safety_flagged: bool = False
|
||||||
|
|
@ -83,7 +84,7 @@ class LearnerSessionSummary(BaseModel):
|
||||||
persona_name: str
|
persona_name: str
|
||||||
session_no: int
|
session_no: int
|
||||||
status: Literal["active", "ended"]
|
status: Literal["active", "ended"]
|
||||||
stage: str
|
stage: StageLabel
|
||||||
turn_count: int
|
turn_count: int
|
||||||
learner_turn_count: int
|
learner_turn_count: int
|
||||||
client_turn_count: int
|
client_turn_count: int
|
||||||
|
|
@ -100,7 +101,7 @@ class LearnerSessionsResponse(BaseModel):
|
||||||
class SessionDetailTurn(BaseModel):
|
class SessionDetailTurn(BaseModel):
|
||||||
turn_seq: int
|
turn_seq: int
|
||||||
speaker: Literal["learner", "client"]
|
speaker: Literal["learner", "client"]
|
||||||
stage: str
|
stage: StageLabel
|
||||||
text: str
|
text: str
|
||||||
created_at: str
|
created_at: str
|
||||||
|
|
||||||
|
|
@ -112,7 +113,7 @@ class SessionDetailResponse(BaseModel):
|
||||||
persona_name: str
|
persona_name: str
|
||||||
theory_mode: str
|
theory_mode: str
|
||||||
status: Literal["active", "ended"]
|
status: Literal["active", "ended"]
|
||||||
stage: str
|
stage: StageLabel
|
||||||
effective_openness: float
|
effective_openness: float
|
||||||
started_at: str
|
started_at: str
|
||||||
ended_at: str | None = None
|
ended_at: str | None = None
|
||||||
|
|
|
||||||
|
|
@ -1305,8 +1305,11 @@ export interface components {
|
||||||
session_id: string;
|
session_id: string;
|
||||||
/** Session No */
|
/** Session No */
|
||||||
session_no: number;
|
session_no: number;
|
||||||
/** Stage */
|
/**
|
||||||
stage: string;
|
* Stage
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
stage: "라포" | "탐색" | "개입" | "정리";
|
||||||
/** Started At */
|
/** Started At */
|
||||||
started_at: string;
|
started_at: string;
|
||||||
/**
|
/**
|
||||||
|
|
@ -1762,8 +1765,11 @@ export interface components {
|
||||||
review_ready: boolean;
|
review_ready: boolean;
|
||||||
/** Session Id */
|
/** Session Id */
|
||||||
session_id: string;
|
session_id: string;
|
||||||
/** Stage */
|
/**
|
||||||
stage: string;
|
* Stage
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
stage: "라포" | "탐색" | "개입" | "정리";
|
||||||
/** Started At */
|
/** Started At */
|
||||||
started_at: string;
|
started_at: string;
|
||||||
/**
|
/**
|
||||||
|
|
@ -1785,8 +1791,11 @@ export interface components {
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
speaker: "learner" | "client";
|
speaker: "learner" | "client";
|
||||||
/** Stage */
|
/**
|
||||||
stage: string;
|
* Stage
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
stage: "라포" | "탐색" | "개입" | "정리";
|
||||||
/** Text */
|
/** Text */
|
||||||
text: string;
|
text: string;
|
||||||
/** Turn Seq */
|
/** Turn Seq */
|
||||||
|
|
@ -1941,8 +1950,11 @@ export interface components {
|
||||||
session_id: string;
|
session_id: string;
|
||||||
/** Session No */
|
/** Session No */
|
||||||
session_no: number;
|
session_no: number;
|
||||||
/** Stage */
|
/**
|
||||||
stage: string;
|
* Stage
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
stage: "라포" | "탐색" | "개입" | "정리";
|
||||||
};
|
};
|
||||||
/** TeacherDashboardResponse */
|
/** TeacherDashboardResponse */
|
||||||
TeacherDashboardResponse: {
|
TeacherDashboardResponse: {
|
||||||
|
|
@ -2207,8 +2219,11 @@ export interface components {
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
safety_flagged: boolean;
|
safety_flagged: boolean;
|
||||||
/** Stage */
|
/**
|
||||||
stage: string;
|
* Stage
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
stage: "라포" | "탐색" | "개입" | "정리";
|
||||||
/** Turn Seq */
|
/** Turn Seq */
|
||||||
turn_seq: number;
|
turn_seq: number;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ export const authApi = {
|
||||||
withdrawConsent: () => api.del<ConsentResponse>("/auth/consent"),
|
withdrawConsent: () => api.del<ConsentResponse>("/auth/consent"),
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SessionStage = "라포" | "탐색" | "개입" | "정리";
|
export type SessionStage = ApiSchema<"SessionStartResponse">["stage"];
|
||||||
|
|
||||||
/** GET /personas — personas.py PersonaSummary */
|
/** GET /personas — personas.py PersonaSummary */
|
||||||
export type PersonaSummary = ApiSchema<"PersonaSummary">;
|
export type PersonaSummary = ApiSchema<"PersonaSummary">;
|
||||||
|
|
@ -178,15 +178,7 @@ export type PersonaDraftPayload = ApiSchema<"PersonaDraftPayload">;
|
||||||
export type PersonaDraftDetail = ApiSchema<"PersonaDraftDetail">;
|
export type PersonaDraftDetail = ApiSchema<"PersonaDraftDetail">;
|
||||||
|
|
||||||
/** POST /sessions — sessions.py SessionStartResponse */
|
/** POST /sessions — sessions.py SessionStartResponse */
|
||||||
export interface SessionStartResponse {
|
export type SessionStartResponse = ApiSchema<"SessionStartResponse">;
|
||||||
session_id: string;
|
|
||||||
case_id: string;
|
|
||||||
session_no: number;
|
|
||||||
stage: SessionStage;
|
|
||||||
effective_openness: number;
|
|
||||||
recall_summary: string | null;
|
|
||||||
degraded: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** POST /sessions/{id}/turn — sessions.py TurnResponse */
|
/** POST /sessions/{id}/turn — sessions.py TurnResponse */
|
||||||
export type TurnResponse = ApiSchema<"TurnResponse">;
|
export type TurnResponse = ApiSchema<"TurnResponse">;
|
||||||
|
|
@ -201,20 +193,7 @@ export type LearnerSessionsResponse = ApiSchema<"LearnerSessionsResponse">;
|
||||||
|
|
||||||
export type SessionDetailTurn = ApiSchema<"SessionDetailTurn">;
|
export type SessionDetailTurn = ApiSchema<"SessionDetailTurn">;
|
||||||
|
|
||||||
export interface SessionDetailResponse {
|
export type SessionDetailResponse = ApiSchema<"SessionDetailResponse">;
|
||||||
session_id: string;
|
|
||||||
case_id: string;
|
|
||||||
persona_code: string;
|
|
||||||
persona_name: string;
|
|
||||||
theory_mode: string;
|
|
||||||
status: "active" | "ended";
|
|
||||||
stage: SessionStage;
|
|
||||||
effective_openness: number;
|
|
||||||
started_at: string;
|
|
||||||
ended_at: string | null;
|
|
||||||
turns: SessionDetailTurn[];
|
|
||||||
review_ready: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ReviewClient = ApiSchema<"ReviewClient">;
|
export type ReviewClient = ApiSchema<"ReviewClient">;
|
||||||
export type ReviewTechnique = ApiSchema<"ReviewTechnique">;
|
export type ReviewTechnique = ApiSchema<"ReviewTechnique">;
|
||||||
|
|
|
||||||
|
|
@ -587,7 +587,7 @@ export default function Session() {
|
||||||
setStage(detail.stage);
|
setStage(detail.stage);
|
||||||
setOpenness(detail.effective_openness);
|
setOpenness(detail.effective_openness);
|
||||||
setUtterances(
|
setUtterances(
|
||||||
detail.turns.map((turn) => ({
|
(detail.turns ?? []).map((turn) => ({
|
||||||
id: nextId(),
|
id: nextId(),
|
||||||
speaker: turn.speaker === "client" ? "client" : "learner",
|
speaker: turn.speaker === "client" ? "client" : "learner",
|
||||||
text: turn.text,
|
text: turn.text,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Vignette Handoff
|
# Vignette Handoff
|
||||||
|
|
||||||
> Updated: 2026-06-27 18:39 KST. 새 세션은 이 문서와 `docs/DESIGN_CONCEPT.md`를 먼저 읽고 이어가면 된다.
|
> Updated: 2026-06-27 18:48 KST. 새 세션은 이 문서와 `docs/DESIGN_CONCEPT.md`를 먼저 읽고 이어가면 된다.
|
||||||
|
|
||||||
## 현재 상태
|
## 현재 상태
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
- X2 예산 경고/저비용 평가 라우팅: `ADMIN_USAGE_BUDGET_USD`가 0보다 크면 `/admin/usage`가 budget 상태(ok/warn/exceeded)를 반환하고 `/admin`이 예산 배너를 표시한다. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL`을 설정하면 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달한다. 비우면 기존 gateway default 라우팅을 유지한다.
|
- X2 예산 경고/저비용 평가 라우팅: `ADMIN_USAGE_BUDGET_USD`가 0보다 크면 `/admin/usage`가 budget 상태(ok/warn/exceeded)를 반환하고 `/admin`이 예산 배너를 표시한다. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL`을 설정하면 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달한다. 비우면 기존 gateway default 라우팅을 유지한다.
|
||||||
- H4 LLM call audit + 동의 게이트: 상담 생성(generate/stream)과 fast/deep 평가의 외부 LLM 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency metadata만 적재한다. prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log`가 8→11로 3행 증가했다. 이번 패스에서 `app_user.consent_at` 기반 learner 동의 수락/철회 API와 세션 시작/voice dev persona 시작 하드게이트를 추가했다. 한국어 이름/기관 NER와 guardian/legal 서명 evidence는 후속.
|
- H4 LLM call audit + 동의 게이트: 상담 생성(generate/stream)과 fast/deep 평가의 외부 LLM 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency metadata만 적재한다. prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log`가 8→11로 3행 증가했다. 이번 패스에서 `app_user.consent_at` 기반 learner 동의 수락/철회 API와 세션 시작/voice dev persona 시작 하드게이트를 추가했다. 한국어 이름/기관 NER와 guardian/legal 서명 evidence는 후속.
|
||||||
- RAG warm 동시성: E2E가 여러 세션을 빠르게 만들 때 BGE-M3 embedder가 동시에 지연 로드되어 `tqdm` lock 예외와 API health/dev-login timeout이 반복됐다. `rag.py` embedder load/encode를 process-wide `RLock`으로 직렬화하고, `sessions.py` warm task를 semaphore 1개로 제한했다. 최신 검증: `layout-visual-gate + session-layout` 11 passed.
|
- RAG warm 동시성: E2E가 여러 세션을 빠르게 만들 때 BGE-M3 embedder가 동시에 지연 로드되어 `tqdm` lock 예외와 API health/dev-login timeout이 반복됐다. `rag.py` embedder load/encode를 process-wide `RLock`으로 직렬화하고, `sessions.py` warm task를 semaphore 1개로 제한했다. 최신 검증: `layout-visual-gate + session-layout` 11 passed.
|
||||||
- 계약 SSOT 3차: `apps/web/src/lib/api.ts`의 수기 DTO 중 auth, persona, admin/user/engine, review leaf, teacher safety/growth leaf, learner sessions, session review/worksheet aggregate, teacher dashboard aggregate 타입을 `apps/web/src/lib/api.gen.ts`의 `ApiSchema` alias로 전환했다. generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. 이번 패스에서 `.github/workflows/api-contract.yml`을 추가해 PR/master push에서 `npm run check:api-types`가 FastAPI OpenAPI와 generated type drift를 막는다. SessionDetail/SessionStart 계열 stage enum 정리는 후속. 검증: `npm run check:api-types`, `npm run typecheck`, `npm run build`, learner/session-review/teacher/layout focused E2E 32 passed.
|
- 계약 SSOT 4차: `apps/web/src/lib/api.ts`의 수기 DTO 중 auth, persona, admin/user/engine, review leaf, teacher safety/growth leaf, learner sessions, session review/worksheet aggregate, teacher dashboard aggregate에 이어 `SessionStartResponse`, `SessionDetailResponse`, `SessionDetailTurn`까지 `apps/web/src/lib/api.gen.ts`의 `ApiSchema` alias로 전환했다. 백엔드 세션 응답 stage는 `StageLabel` enum(`라포|탐색|개입|정리`)으로 고정했고, generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. `.github/workflows/api-contract.yml`은 PR/master push에서 `npm run check:api-types`로 FastAPI OpenAPI와 generated type drift를 막는다. 검증: `npm run check:api-types`, `npm run typecheck`, `npm run build`, `python -m pytest app/test_session_turn_persistence.py -q` 16 passed, learner/session-review/teacher/layout focused E2E 32 passed.
|
||||||
- 턴 런타임 리팩터 2차: `turn_runtime.finalize_completed_turn`으로 REST submit/stream/voice WS의 `record_completed_turn` + `record_safety_event` 호출쌍을 공통화했다. gateway `/v1/stream` route 레벨에서 `token/done/error` SSE 프레임 contract tests를 추가했다. 검증: focused backend 40 passed.
|
- 턴 런타임 리팩터 2차: `turn_runtime.finalize_completed_turn`으로 REST submit/stream/voice WS의 `record_completed_turn` + `record_safety_event` 호출쌍을 공통화했다. gateway `/v1/stream` route 레벨에서 `token/done/error` SSE 프레임 contract tests를 추가했다. 검증: focused backend 40 passed.
|
||||||
- Phase3 artifact checker 강화: `scripts/check-phase3-artifacts.py`가 CSV enum, KPI report 필수 field, approved export의 PII pass·κ/ICC·withdrawn exclusion·consent scope·file sha256을 검증한다. `app/test_phase3_artifact_checker.py` 5 tests 추가. 실제 파일럿 evidence와 steward/legal/IAA gate는 그대로 외부 의존이다.
|
- Phase3 artifact checker 강화: `scripts/check-phase3-artifacts.py`가 CSV enum, KPI report 필수 field, approved export의 PII pass·κ/ICC·withdrawn exclusion·consent scope·file sha256을 검증한다. `app/test_phase3_artifact_checker.py` 5 tests 추가. 실제 파일럿 evidence와 steward/legal/IAA gate는 그대로 외부 의존이다.
|
||||||
- 세션 종료 UX/dark theme SSOT 정리: 드래그형 `SlideToEnd`를 명시 확인 다이얼로그로 교체하고, Topbar/Settings theme 토글을 `lib/theme.ts` 단일 경로로 통합했다. 저장값이 없으면 dark를 기본으로 두고 앱 부팅 시 theme를 먼저 적용해 초기 flash를 줄인다. API 기본 preference `system`은 Settings에서 light로 오해하지 않고 현재 초기 테마를 따른다. 회기 리뷰·설정 포함 주요 페이지의 흰 섹션 잔재도 dark 작업면으로 맞췄다. 검증: `npm run typecheck`, `npm run build`, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/settings.spec.ts e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1` 22 passed.
|
- 세션 종료 UX/dark theme SSOT 정리: 드래그형 `SlideToEnd`를 명시 확인 다이얼로그로 교체하고, Topbar/Settings theme 토글을 `lib/theme.ts` 단일 경로로 통합했다. 저장값이 없으면 dark를 기본으로 두고 앱 부팅 시 theme를 먼저 적용해 초기 flash를 줄인다. API 기본 preference `system`은 Settings에서 light로 오해하지 않고 현재 초기 테마를 따른다. 회기 리뷰·설정 포함 주요 페이지의 흰 섹션 잔재도 dark 작업면으로 맞췄다. 검증: `npm run typecheck`, `npm run build`, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/settings.spec.ts e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1` 22 passed.
|
||||||
|
|
|
||||||
|
|
@ -636,8 +636,8 @@
|
||||||
<div class="scard-body"><div class="kv"><b>현재</b><p><code>app/turn_runtime.py</code>를 추가해 <code>load_owned_session</code>, <code>record_completed_turn</code>, <code>append_completed_turn</code>, <code>update_session_state</code>, <code>record_safety_event</code>를 공통화했다. 이번 패스에서 <code>finalize_completed_turn</code>을 추가해 <code>record_completed_turn</code> + <code>record_safety_event</code> 호출쌍을 REST submit/stream과 voice WS 라우트에서 제거했다. stream fast-loop 평가 누락, voice 세션 recall cache, visible_to/evaluator turn 분리는 보존.</p></div><div class="kv"><b>검증</b><p><code>pytest app/test_session_turn_persistence.py app/test_voice_ws.py engine_gateway/test_gateway_model.py app/test_phase3_artifact_checker.py app/test_dataset_export.py -q</code> 40 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>더 큰 <code>finalize_turn</code>/<code>SessionRepository</code> 경계, 장기적으로는 세션 저장소 인터페이스 SSOT화가 남아 있다.</p></div></div>
|
<div class="scard-body"><div class="kv"><b>현재</b><p><code>app/turn_runtime.py</code>를 추가해 <code>load_owned_session</code>, <code>record_completed_turn</code>, <code>append_completed_turn</code>, <code>update_session_state</code>, <code>record_safety_event</code>를 공통화했다. 이번 패스에서 <code>finalize_completed_turn</code>을 추가해 <code>record_completed_turn</code> + <code>record_safety_event</code> 호출쌍을 REST submit/stream과 voice WS 라우트에서 제거했다. stream fast-loop 평가 누락, voice 세션 recall cache, visible_to/evaluator turn 분리는 보존.</p></div><div class="kv"><b>검증</b><p><code>pytest app/test_session_turn_persistence.py app/test_voice_ws.py engine_gateway/test_gateway_model.py app/test_phase3_artifact_checker.py app/test_dataset_export.py -q</code> 40 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>더 큰 <code>finalize_turn</code>/<code>SessionRepository</code> 경계, 장기적으로는 세션 저장소 인터페이스 SSOT화가 남아 있다.</p></div></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="doing" data-cat="리팩터·정리" data-owner="0">
|
<article class="scard" data-status="doing" data-cat="리팩터·정리" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">REFACTOR 2 3차 DONE</span><span class="scard-mid"><span class="scard-title">백엔드 Pydantic ↔ 프론트 <code>lib/api.ts</code> 계약 SSOT화</span><span class="scard-sum">OpenAPI→<code>api.gen.ts</code> 생성 + stale 체크 게이트. 주요 수기 DTO를 생성 타입 alias로 축소.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-done">REFACTOR 2 4차 DONE</span><span class="scard-mid"><span class="scard-title">백엔드 Pydantic ↔ 프론트 <code>lib/api.ts</code> 계약 SSOT화</span><span class="scard-sum">OpenAPI→<code>api.gen.ts</code> 생성 + CI stale 체크 게이트. 세션 계열 수기 DTO도 생성 타입 alias로 축소.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
<div class="scard-body"><div class="kv"><b>현재</b><p><code>apps/api/scripts/export-openapi.py</code>와 <code>apps/web/scripts/generate-api-types.mjs</code>로 FastAPI OpenAPI를 <code>apps/web/src/lib/api.gen.ts</code>에 생성한다. <code>npm run check:api-types</code>가 재생성 누락을 잡는다. <code>MeResponse</code>, <code>AuthConfigResponse</code>, <code>ConsentResponse</code>, <code>PersonaSummary</code>, <code>PersonaReviewSummary</code>, admin/user/engine DTO, review leaf DTO, <code>CrisisResourceResponse</code>, teacher safety/growth leaf DTO에 이어 이번 패스에서 <code>LearnerSessionsResponse</code>, <code>SessionReviewResponse</code>, worksheet aggregate, <code>TeacherDashboardResponse</code>, <code>TeacherSessionSummary</code>, <code>TeacherLearnerGrowth</code>도 <code>ApiSchema<...></code> alias로 전환했다. generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. <code>.github/workflows/api-contract.yml</code>은 PR/master push에서 같은 stale check를 실행해 OpenAPI type drift를 CI에서 막는다.</p></div><div class="kv"><b>검증</b><p><code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code> passed. 영향 범위 E2E: <code>learner.spec.ts</code> + <code>session-review.spec.ts</code> + <code>teacher.spec.ts</code> + <code>layout-visual-gate.spec.ts</code> 32 passed.</p></div><div class="kv k-warn"><b>잔여</b><p><code>SessionDetail</code>/<code>SessionStart</code> 계열은 backend stage enum/guard를 먼저 정리한 뒤 생성 타입 alias로 전환한다.</p></div></div>
|
<div class="scard-body"><div class="kv"><b>현재</b><p><code>apps/api/scripts/export-openapi.py</code>와 <code>apps/web/scripts/generate-api-types.mjs</code>로 FastAPI OpenAPI를 <code>apps/web/src/lib/api.gen.ts</code>에 생성한다. <code>npm run check:api-types</code>가 재생성 누락을 잡는다. <code>MeResponse</code>, <code>AuthConfigResponse</code>, <code>ConsentResponse</code>, <code>PersonaSummary</code>, <code>PersonaReviewSummary</code>, admin/user/engine DTO, review leaf DTO, <code>CrisisResourceResponse</code>, teacher safety/growth leaf DTO, <code>LearnerSessionsResponse</code>, <code>SessionReviewResponse</code>, worksheet aggregate, <code>TeacherDashboardResponse</code>, <code>TeacherSessionSummary</code>, <code>TeacherLearnerGrowth</code>에 이어 <code>SessionStartResponse</code>, <code>SessionDetailResponse</code>, <code>SessionDetailTurn</code>도 <code>ApiSchema<...></code> alias로 전환했다. 세션 응답 stage는 backend <code>StageLabel</code> enum으로 고정해 OpenAPI가 <code>라포|탐색|개입|정리</code> union을 생성한다. generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. <code>.github/workflows/api-contract.yml</code>은 PR/master push에서 같은 stale check를 실행해 OpenAPI type drift를 CI에서 막는다.</p></div><div class="kv"><b>검증</b><p><code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code> passed. <code>python -m pytest app/test_session_turn_persistence.py -q</code> 16 passed. 영향 범위 E2E: <code>learner.spec.ts</code> + <code>session-review.spec.ts</code> + <code>teacher.spec.ts</code> + <code>layout-visual-gate.spec.ts</code> 32 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>평가 조회·리뷰 도식·교수 콘솔 등 아직 자유 문자열로 남긴 stage-like 필드는 각 도메인 의미를 확인한 뒤 별도 축소한다.</p></div></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="done" data-cat="리팩터·정리" data-owner="0">
|
<article class="scard" data-status="done" data-cat="리팩터·정리" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">REFACTOR 3 일부</span><span class="scard-mid"><span class="scard-title">죽은 확장점/계약 정리 — LogHook·tier 제거 적용</span><span class="scard-sum">caller 0 타입·분기·export 제거. 잔여는 패치 문서에 스테이징. 전체 API 119.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-done">REFACTOR 3 일부</span><span class="scard-mid"><span class="scard-title">죽은 확장점/계약 정리 — LogHook·tier 제거 적용</span><span class="scard-sum">caller 0 타입·분기·export 제거. 잔여는 패치 문서에 스테이징. 전체 API 119.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
|
|
@ -783,7 +783,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
|
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
|
||||||
<tr><td>API typegen SSOT</td><td><code>npm run check:api-types</code></td><td>Passed; FastAPI OpenAPI → <code>src/lib/api.gen.ts</code> stale check</td></tr>
|
<tr><td>API typegen SSOT</td><td><code>npm run check:api-types</code></td><td>Passed; FastAPI OpenAPI → <code>src/lib/api.gen.ts</code> stale check</td></tr>
|
||||||
<tr><td>Contract SSOT aggregate DTO</td><td><code>npm run typecheck</code> / <code>npm run build</code> / <code>learner + session-review + teacher + layout gate</code></td><td>Passed; learner sessions, session review/worksheet, teacher dashboard aggregates now use generated <code>ApiSchema</code> aliases with UI array fallback. Focused E2E 32 passed. <code>.github/workflows/api-contract.yml</code> now runs <code>npm run check:api-types</code> on API/Web contract changes.</td></tr>
|
<tr><td>Contract SSOT aggregate DTO</td><td><code>npm run typecheck</code> / <code>npm run build</code> / <code>session persistence</code> / <code>learner + session-review + teacher + layout gate</code></td><td>Passed; learner sessions, session review/worksheet, teacher dashboard, session start/detail DTOs now use generated <code>ApiSchema</code> aliases with UI fallback. Stage responses are OpenAPI enum unions. Focused E2E 32 passed. <code>.github/workflows/api-contract.yml</code> now runs <code>npm run check:api-types</code> on API/Web contract changes.</td></tr>
|
||||||
<tr><td>Backend pytest</td><td><code>python -m pytest app/ -q</code></td><td>130 passed</td></tr>
|
<tr><td>Backend pytest</td><td><code>python -m pytest app/ -q</code></td><td>130 passed</td></tr>
|
||||||
<tr><td>X2 evaluator model routing</td><td><code>python -m pytest app/test_evaluator_model_routing.py app/test_runtime_policy.py -q</code></td><td>23 passed; fast/deep evaluator model override and blank-default routing covered</td></tr>
|
<tr><td>X2 evaluator model routing</td><td><code>python -m pytest app/test_evaluator_model_routing.py app/test_runtime_policy.py -q</code></td><td>23 passed; fast/deep evaluator model override and blank-default routing covered</td></tr>
|
||||||
<tr><td>C1 case worksheet</td><td><code>python -m pytest app/test_session_turn_persistence.py -q</code> / <code>session-review.spec.ts</code></td><td>14 passed; session review exposes transcript-grounded <code>caseWorksheet</code> and UI evidence jump</td></tr>
|
<tr><td>C1 case worksheet</td><td><code>python -m pytest app/test_session_turn_persistence.py -q</code> / <code>session-review.spec.ts</code></td><td>14 passed; session review exposes transcript-grounded <code>caseWorksheet</code> and UI evidence jump</td></tr>
|
||||||
|
|
|
||||||
|
|
@ -375,7 +375,8 @@ React 19 + Vite. 라우팅은 `apps/web/src/App.tsx`(react-router-dom).
|
||||||
- 도메인 헬퍼: `sessionApi`(list/get/start/turn/end/review/stream), `personaApi`, `personaReviewApi`,
|
- 도메인 헬퍼: `sessionApi`(list/get/start/turn/end/review/stream), `personaApi`, `personaReviewApi`,
|
||||||
`adminApi`, `adminEngineApi`, `teacherApi`, `userApi`. 주요 응답 타입은 FastAPI OpenAPI에서 생성한
|
`adminApi`, `adminEngineApi`, `teacherApi`, `userApi`. 주요 응답 타입은 FastAPI OpenAPI에서 생성한
|
||||||
`src/lib/api.gen.ts`의 `ApiSchema<...>` alias를 사용하고, generated optional 배열은 화면 렌더링 계층에서
|
`src/lib/api.gen.ts`의 `ApiSchema<...>` alias를 사용하고, generated optional 배열은 화면 렌더링 계층에서
|
||||||
빈 배열 fallback으로 흡수한다.
|
빈 배열 fallback으로 흡수한다. 세션 시작/상세/턴 응답의 stage는 backend `StageLabel`
|
||||||
|
enum(`라포|탐색|개입|정리`)에서 생성한 union을 `SessionStage`로 사용한다.
|
||||||
- dev 환경: `vite.config`가 `/api` → `http://127.0.0.1:8000` 프록시(`/api` 프리픽스 제거).
|
- dev 환경: `vite.config`가 `/api` → `http://127.0.0.1:8000` 프록시(`/api` 프리픽스 제거).
|
||||||
배포 호스트(`vignette.chanpaca.net`, `*.pages.dev`)에서는 `api-vignette.chanpaca.net`을 직접 가리킨다.
|
배포 호스트(`vignette.chanpaca.net`, `*.pages.dev`)에서는 `api-vignette.chanpaca.net`을 직접 가리킨다.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
- P1 서연 음성 아트 PoC: Higgs v3 무참조 synthetic seed + 5개 정서/속도 변주(mp3/wav) 생성. 실존 reference voice 미사용. `/voice/ws`에는 dev-only sample TTS provider로 연결했고 live text_turn smoke에서 `p1-sample-poc` binary 20 chunks/78,573 bytes 수신 확인. 이 provider는 TTS override만 담당한다(마이크/STT는 OpenAI 키 필요). 프로덕션 탑재는 라이선스/권리 결정 전 금지.
|
- P1 서연 음성 아트 PoC: Higgs v3 무참조 synthetic seed + 5개 정서/속도 변주(mp3/wav) 생성. 실존 reference voice 미사용. `/voice/ws`에는 dev-only sample TTS provider로 연결했고 live text_turn smoke에서 `p1-sample-poc` binary 20 chunks/78,573 bytes 수신 확인. 이 provider는 TTS override만 담당한다(마이크/STT는 OpenAI 키 필요). 프로덕션 탑재는 라이선스/권리 결정 전 금지.
|
||||||
- 부수: RLS smoke 스크립트 cleanup RLS 컨텍스트 버그 수정, `vignette_app`(NOBYPASSRLS) dev 역할 프로비저닝.
|
- 부수: RLS smoke 스크립트 cleanup RLS 컨텍스트 버그 수정, `vignette_app`(NOBYPASSRLS) dev 역할 프로비저닝.
|
||||||
- 평가 SSOP 2차: `TurnRecord.evaluation`을 `app.feedback_scores`/`turn_technique`/`turn_client_state`/`supervisor_comment`/`alternative_utterance`로 정규화 적재·복원하고, submit/stream/voice 저장 경로를 `app.turn_runtime`으로 공통화했다. 원시 평가 테이블은 evaluator/admin RLS로 제한하고 서버가 evaluator 컨텍스트로 적재한다. `app.turns` insert는 `RETURNING id` 확인으로 silent success 제거. **검증: `pytest app/test_evaluation_persistence.py -q` 6 passed, 관련 회귀 45 passed, `pytest app/` 119 passed, `pytest engine_gateway/` 7 passed.**
|
- 평가 SSOP 2차: `TurnRecord.evaluation`을 `app.feedback_scores`/`turn_technique`/`turn_client_state`/`supervisor_comment`/`alternative_utterance`로 정규화 적재·복원하고, submit/stream/voice 저장 경로를 `app.turn_runtime`으로 공통화했다. 원시 평가 테이블은 evaluator/admin RLS로 제한하고 서버가 evaluator 컨텍스트로 적재한다. `app.turns` insert는 `RETURNING id` 확인으로 silent success 제거. **검증: `pytest app/test_evaluation_persistence.py -q` 6 passed, 관련 회귀 45 passed, `pytest app/` 119 passed, `pytest engine_gateway/` 7 passed.**
|
||||||
- 계약 SSOT 3차: FastAPI OpenAPI를 `apps/web/src/lib/api.gen.ts`로 생성하는 `npm run generate:api-types`와 stale 체크용 `npm run check:api-types`를 유지한다. `TurnResponse`/`SessionEndResponse`에 이어 `MeResponse`, `AuthConfigResponse`, `ConsentResponse`, `PersonaSummary`, `PersonaReviewSummary`, admin/user/engine DTO, review leaf DTO, `CrisisResourceResponse`, teacher safety/growth leaf DTO, `LearnerSessionsResponse`, `SessionReviewResponse`, worksheet aggregate, `TeacherDashboardResponse`, `TeacherSessionSummary`, `TeacherLearnerGrowth`를 생성 타입 alias로 전환했다. generated optional/default 차이는 UI form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. 이번 패스에서 `.github/workflows/api-contract.yml`을 추가해 PR/master push에서 `npm run check:api-types`가 drift를 막는다. SessionDetail/SessionStart 계열의 stage enum 정리는 후속. **검증: `npm run check:api-types`, `npm run typecheck`, `npm run build`, `learner.spec.ts + session-review.spec.ts + teacher.spec.ts + layout-visual-gate.spec.ts` 32 passed.**
|
- 계약 SSOT 4차: FastAPI OpenAPI를 `apps/web/src/lib/api.gen.ts`로 생성하는 `npm run generate:api-types`와 stale 체크용 `npm run check:api-types`를 유지한다. `TurnResponse`/`SessionEndResponse`에 이어 `MeResponse`, `AuthConfigResponse`, `ConsentResponse`, `PersonaSummary`, `PersonaReviewSummary`, admin/user/engine DTO, review leaf DTO, `CrisisResourceResponse`, teacher safety/growth leaf DTO, `LearnerSessionsResponse`, `SessionReviewResponse`, worksheet aggregate, `TeacherDashboardResponse`, `TeacherSessionSummary`, `TeacherLearnerGrowth`, `SessionStartResponse`, `SessionDetailResponse`, `SessionDetailTurn`를 생성 타입 alias로 전환했다. 세션 응답 stage는 `StageLabel` enum으로 OpenAPI에 고정했고, generated optional/default 차이는 UI form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. `.github/workflows/api-contract.yml`은 PR/master push에서 `npm run check:api-types` drift를 막는다. **검증: `npm run check:api-types`, `npm run typecheck`, `npm run build`, `python -m pytest app/test_session_turn_persistence.py -q` 16 passed, `learner.spec.ts + session-review.spec.ts + teacher.spec.ts + layout-visual-gate.spec.ts` 32 passed.**
|
||||||
- 위기게이트 109 1차: 실제 자해·자살 신호는 LLM/엔진 호출 전 중단하고 `crisis_resource(109)`와 `conversation_stopped`를 REST/SSE/voice 응답에 싣는다. `app.safety_events` detail 적재와 교수자 대시보드 안전 알림 큐까지 연결했다. 안전 이벤트 DB insert payload도 회귀화했다. 임상 문안, 실시간 push/메일, 지역 자원 확장은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 20 passed, `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed.**
|
- 위기게이트 109 1차: 실제 자해·자살 신호는 LLM/엔진 호출 전 중단하고 `crisis_resource(109)`와 `conversation_stopped`를 REST/SSE/voice 응답에 싣는다. `app.safety_events` detail 적재와 교수자 대시보드 안전 알림 큐까지 연결했다. 안전 이벤트 DB insert payload도 회귀화했다. 임상 문안, 실시간 push/메일, 지역 자원 확장은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 20 passed, `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed.**
|
||||||
- 교수자 성장 추적 1차: `teacher_dashboard`가 정규화된 턴 평가를 기반으로 학습자별 적절성·라포·기술 사용 추이를 집계하고, 교수 콘솔에 최근 회기/항목별 성장 카드와 안전 알림 큐를 함께 표시한다. **검증: `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed, `pytest app/` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`.**
|
- 교수자 성장 추적 1차: `teacher_dashboard`가 정규화된 턴 평가를 기반으로 학습자별 적절성·라포·기술 사용 추이를 집계하고, 교수 콘솔에 최근 회기/항목별 성장 카드와 안전 알림 큐를 함께 표시한다. **검증: `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed, `pytest app/` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`.**
|
||||||
- 페르소나 저작 CRUD 1차: teacher/admin 전용 `POST/GET/PUT /personas/drafts` 경로를 추가해 draft 생성·조회·편집·검수요청을 열고, 교수 콘솔에 JSON 초안 작성/불러오기/수정 UI를 연결했다. 승인된 카드만 학습자 catalog에 노출하는 기존 경계는 유지한다. P4~P7 실제 임상 콘텐츠 입력은 후속. **검증: `pytest app/test_persona_review.py -q` 19 passed, `pytest app/` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`.**
|
- 페르소나 저작 CRUD 1차: teacher/admin 전용 `POST/GET/PUT /personas/drafts` 경로를 추가해 draft 생성·조회·편집·검수요청을 열고, 교수 콘솔에 JSON 초안 작성/불러오기/수정 UI를 연결했다. 승인된 카드만 학습자 catalog에 노출하는 기존 경계는 유지한다. P4~P7 실제 임상 콘텐츠 입력은 후속. **검증: `pytest app/test_persona_review.py -q` 19 passed, `pytest app/` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`.**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue