전 저장소 리팩터링과 SSOT 정비
This commit is contained in:
parent
14ecbd4e7d
commit
3dfddcac6f
173 changed files with 19679 additions and 6952 deletions
|
|
@ -50,6 +50,30 @@ status: 통합본 v1
|
|||
|
||||
이유: **세이지-틸**은 임상적 차분함(자연·회복·안정)과 따뜻함(흙·식물)을 동시에 품으면서, 한국 상담/심리 분야에서 과용되지 않아 차별화된다. 의료 블루는 흔하고 차갑다.
|
||||
|
||||
### 0.3 구현 소유권 계약
|
||||
|
||||
이 문서는 디자인 결정의 원본이고, 구현 권위는 아래 순서로만 내려간다. 같은 결정을 페이지마다 다시 정의하지 않는다.
|
||||
|
||||
| 계층 | 단일 소유자 | 허용 범위 | 금지 |
|
||||
|---|---|---|---|
|
||||
| 전역 토큰·테마 | `apps/web/src/styles/tokens.css` | 중성 팔레트, 의미색, 타이포, 간격, radius, light/dark | 페이지 CSS의 전역 토큰 재정의 |
|
||||
| UI 프리미티브 | `apps/web/src/components/ui/` | Button, Surface, Badge, Field, EmptyState 같은 의미 단위 | 페이지 안에 같은 프리미티브 재구현 |
|
||||
| 앱 크롬·레이아웃 | `apps/web/src/components/shell/` | topbar, sidebar, main 폭, safe area | 페이지 CSS가 `.vg-topbar/.vg-nav/.vg-main/.vg-shell` 재정의 |
|
||||
| 페이지·기능 | `apps/web/src/pages/` | 해당 업무 흐름의 배치와 상태 표현 | 공통 셸·전역 테마 소유권 침범 |
|
||||
| 집중 화면 예외 | `pages/session/session.css`, 인증·아바타 전용 CSS | 세션의 몰입형 dark stage, 실제 아트 합성에 필요한 국소 색 | 예외 토큰을 다른 페이지로 전파 |
|
||||
|
||||
현재 제품은 기존 브랜드와 IA를 보존하는 신뢰 우선 제품 UI다. 디자인 다이얼은
|
||||
`DESIGN_VARIANCE 4 / MOTION_INTENSITY 3 / VISUAL_DENSITY 6`으로 고정한다. 모션은 상태 전환과
|
||||
조작 피드백에만 쓰고, 데이터 밀도는 카드 중첩보다 여백·그룹·얇은 구분선으로 제어한다.
|
||||
|
||||
새 시각 규칙은 다음 순서로 결정한다.
|
||||
|
||||
1. 이 문서에 이미 있는 원칙인지 확인한다.
|
||||
2. 전역 결정이면 `tokens.css`, 공통 의미 단위면 `components/ui`, 앱 크롬이면 `components/shell`에 둔다.
|
||||
3. 페이지 전용 결정만 페이지 CSS에 둔다. 새 raw color가 필요하면 기존 의미 토큰으로 표현할 수 없는 이유를 주석으로 남긴다.
|
||||
4. 라우트 화면은 `React.lazy`로 분리하고 `Suspense` 로딩·오류 경계를 유지한다. 초기 진입 번들에 모든 역할 화면을 다시 합치지 않는다.
|
||||
5. `npm run check:design-ssot`, 타입체크, 프로덕션 빌드와 해당 시각 E2E를 통과해야 완료다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 핵심 디자인 원칙 (신성불가침 5개)
|
||||
|
|
@ -479,6 +503,13 @@ function useAvatarMotion(state, affect, analyser) {
|
|||
|
||||
핵심 긴장: **몰입 vs 평가 가시성.** 실시간 피드백이 라포 형성을 방해하면 안 되고, 평가가 사후로만 묶이면 학습 효과가 떨어진다. 해법 = **2단(two-tier) 피드백 모델** — 실시간은 "신호(signal)" 수준, 정밀 평가는 "회기말 리뷰"로.
|
||||
|
||||
### 5.0 회기 시작 전 준비 화면
|
||||
|
||||
- 헤더와 준비 본문은 같은 가용 폭을 사용한다. 별도의 고정 카드 폭으로 페이지 축을 끊지 않는다.
|
||||
- 데스크톱은 내담자 요약·회기 설정·진행 초점의 3열이되, 열은 고정 px가 아니라 가용 폭에 비례해 확장한다.
|
||||
- 진행 초점은 장식용 문구가 아니라 현재 단계, 선택 이론의 대화 기준, 이번 목표, 시간·안전 운영 기준을 즉시 확인하는 동적 브리핑이다.
|
||||
- 좁은 화면에서는 회기 시작이라는 단일 의도를 지키기 위해 진행 초점을 숨기고 핵심 설정과 시작 버튼을 우선한다.
|
||||
|
||||
### 5.1 3-column 골격 (데스크탑 ≥1280px)
|
||||
|
||||
```
|
||||
|
|
@ -678,6 +709,7 @@ body[data-role="admin"] { --accent:#5B5F6B; --accent-bright:#7D818E; --acce
|
|||
|
||||
- 톱바 56px `background:var(--surface); border-bottom:1px solid var(--hair);` (그림자 없음).
|
||||
- 좌측 네비 기본 72px 아이콘 전용(Lucide stroke 1.75), hover/포커스 시 220px 슬라이드(160ms). 활성 = 아이콘 `color:var(--accent)` + **3px 강조바 금지** → 아이콘 배경 `var(--accent-tint)` `border-radius:8px` 알약형(네비 알약만 8px 관용).
|
||||
- 앱 셸은 뷰포트 높이의 고정 프레임이다. 톱바와 GNB는 화면에 남고, 라우트 콘텐츠를 담는 메인 영역만 독립적으로 세로 스크롤한다. GNB 장식은 네비 자체의 `background` 레이어로만 합성하며 문서 위에 절대 위치 의사요소로 띄우지 않는다.
|
||||
- **역할 컨텍스트 라벨**: 톱바 좌측, `font-family:var(--font-num); font-size:13px; letter-spacing:0.04em; color:var(--accent);` — "학습 대시보드"/"교수 콘솔"/"운영 콘솔". "지금 누구로 보고 있나"의 유일하고 조용한 신호.
|
||||
- **권한 표현 원칙: 권한 없으면 DOM에서 제거.** disabled 회색처리도 안 함 — 존재를 숨김. 화면이 깨끗해지고 "내가 못 하는 것" 노이즈 제거. 임상 데이터 프라이버시(관리자는 학생 개별 성장곡선·transcript 기본 접근 불가, 메뉴 자체 렌더 안 함).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Vignette Handoff
|
||||
|
||||
> Updated: 2026-07-03 KST. 새 세션은 이 문서와 `docs/DESIGN_CONCEPT.md`를 먼저 읽고 이어가면 된다.
|
||||
> Updated: 2026-07-13 KST. 새 세션은 이 문서와 `docs/DESIGN_CONCEPT.md`를 먼저 읽고 이어가면 된다.
|
||||
|
||||
## 현재 상태
|
||||
|
||||
|
|
@ -10,8 +10,8 @@
|
|||
- 로컬 engine gateway 기본 포트: `http://127.0.0.1:9099`
|
||||
- 공개 웹: `https://vignette.chanpaca.net`
|
||||
- 공개 API: `https://api-vignette.chanpaca.net`
|
||||
- 최신 앱 배포 소스: 2026-07-03 client diagnostics rollout.
|
||||
- 최신 Cloudflare Pages production deploy: 2026-07-03 manual deploy `https://a948284e.vignette-b1q.pages.dev`, branch `main`, custom domain assets `assets/index-R5KK7hZI.js` + `assets/index-DzdAkRsn.css`, boot diagnostic HTML 및 `/client-diagnostics` 송신 포함.
|
||||
- 최신 앱 배포 소스: 2026-07-13 생성 이미지/PSD 래스터 아바타 비활성화와 SVG 도형 기반 파라미터 리그 복귀(관리자 복원 탭 스크롤 복구 포함).
|
||||
- 최신 Cloudflare Pages production deploy: 2026-07-13 manual deploy `https://abe0a142.vignette-b1q.pages.dev`, branch `main`, custom domain assets `assets/index-DKQt25EP.js` + `assets/index-DzdAkRsn.css`. 앞선 관리자 전용 rollback 뒤 소유자 요청으로 SVG 아바타 복귀본을 다시 공식 배포했고, 커스텀 도메인 공개 번들에서 P1 데스크톱·모바일 SVG 렌더와 래스터 DOM 0개를 확인했다.
|
||||
- Google OAuth 허용 이메일 도메인: `hs.ac.kr`, `twentyoz.kr`
|
||||
- 최신 백엔드 회귀: `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -m pytest app/ -q` → `305 passed`
|
||||
- X1 재귀학습 export 1차: `scripts/export-recursive-dataset.py` 기본 read-only dry-run, `--write-dataset` 명시 시에만 `ds.*` write, approved export는 steward/legal/IAA gate 없으면 거부.
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
- 턴 런타임 리팩터 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는 그대로 외부 의존이다.
|
||||
- 세션 종료 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.
|
||||
- PSD v2 기반 P1 아바타 보정: P1 기본 아바타는 컨셉 보드 크롭이 아니라 사용자 제공 `라투디 여캐_ver2.psd`에서 추출한 `seoyeon-live2d-psd-v2`를 사용한다. `sad` 표정은 PSD의 울상 눈썹, 우는 입, 눈물 레이어를 별도 파츠로 합성한다. active session 데스크톱 스테이지는 `상태 라벨 / 아바타 / 현재 상태` 3행 구조를 유지한다. 재현 스크립트와 소스 복사본은 `docs/avatar-art/seoyeon/live2d-psd-v2/`, 앱 자산은 `apps/web/public/avatar/seoyeon-live2d-psd-v2/parts/`에 있다.
|
||||
- 아바타 렌더 기준: 사용자 피드백에 따라 서연 P1과 P4~P7의 생성 이미지/PSD 래스터 파츠 연결을 전부 끊고, 모든 실제 화면과 dev 미리보기에서 기존 SVG 도형 기반 파라미터 리그만 렌더링한다. `RasterBust`와 래스터 원본·파츠는 재검토용으로 보존하지만 앱에서 로드하지 않는다. active session 데스크톱 스테이지의 `상태 라벨 / 아바타 / 현재 상태` 3행 구조는 유지한다.
|
||||
- M3 인증 claim 1차: Google/SAML/dev-login이 설정 기반 cohort map과 SAML cohort claim을 `cohort_ids`로 넘기고, 관리 사용자 `external_id`는 provider subject 기반(`google:`/`saml:`/`dev:`)으로 저장한다. 운영 SAML 서명검증·기관 claim schema·deprovisioning audit은 후속.
|
||||
- 페르소나 저작/P4~P7 적재 2차: `data/personas/P4.json`~`P7.json`을 저장소 관리 `PersonaCard`로 읽어 `materialize_seed_personas()`와 seed fallback catalog에 포함했다. 검증: `python -m pytest app/test_persona_review.py -q` 22 passed, 로컬 dev DB materialize 결과 `approved_codes=P1,P2,P3,P4,P5,P6,P7`, 인증된 로컬 `GET /personas`도 `P1..P7` 반환.
|
||||
- Google OAuth 진단 1차: provider callback error는 `access_denied`/`provider_error`로 분리하고, 로그인 화면은 실패 reason code를 함께 표시한다. 실제 Google 계정 완료 proof는 아직 owner 로그인/storageState가 필요하다.
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
| 문서 | 역할 |
|
||||
|---|---|
|
||||
| [`guides/collaboration-for-researchers.md`](./guides/collaboration-for-researchers.md) | 비개발자(연구팀·디자인 담당)용 GitHub 협업 가이드 — 브랜치·PR·문구 수정 흐름·금지 사항 |
|
||||
| [`guides/local-development.md`](./guides/local-development.md) | 로컬 개발 환경 구축·실행 (1-커맨드 dev 스택) |
|
||||
| [`guides/architecture.md`](./guides/architecture.md) | 시스템 아키텍처(엔진/오케스트레이터/저항/마스킹/음성/평가/데이터) |
|
||||
| [`guides/testing.md`](./guides/testing.md) | 테스트·검증(pytest, typecheck, Playwright E2E 게이트) |
|
||||
|
|
@ -45,12 +46,18 @@
|
|||
| 문서 | 역할 |
|
||||
|---|---|
|
||||
| [`ops/public-runtime-watchdog.md`](./ops/public-runtime-watchdog.md) | 공개 런타임 워치독 설치·복구 절차(런북) |
|
||||
| [`ops/server-migration-runbook-draft.md`](./ops/server-migration-runbook-draft.md) | 서버 이관 런북 초안(자택→학교 정보처) — 이식성 이슈·DB dump/restore·엔진 모드 전환·검증 (초안) |
|
||||
| [`ops/pilot-usage-guide-draft.md`](./ops/pilot-usage-guide-draft.md) | 파일럿 사용 지침 초안(대학원생 ~20명) — 접속·시간 기반 회기·라이브 코칭·유의사항 (초안) |
|
||||
| [`ops/tailscale-vnet-runtime-2026-06-27.md`](./ops/tailscale-vnet-runtime-2026-06-27.md) | Tailnet 런타임 + 공개 vnet 외부 게이트 추적 |
|
||||
| [`ops/hanshin-data-governance-gate.md`](./ops/hanshin-data-governance-gate.md) | 한신대 데이터/SSO 외부 증거 게이트 체크리스트 |
|
||||
| [`ops/hanshin-feedback-improvement-plan-2026-07-03.md`](./ops/hanshin-feedback-improvement-plan-2026-07-03.md) | 한신대 실사용 오류분석 기반 개선 계획(P0~P4·레드팀 반영) |
|
||||
| [`ops/hanshin-meeting-actions-2026-07-13.md`](./ops/hanshin-meeting-actions-2026-07-13.md) | 2026-07-13 한신대 회의 결정 → 구현 스펙(P1~P5, 불변 요구사항) |
|
||||
| [`ops/session-progress-gauge-design-2026-07-14.md`](./ops/session-progress-gauge-design-2026-07-14.md) | P2 회기 누적 게이지·수치 상세 설계 1p |
|
||||
| [`ops/tagged-data-utilization-2026-07-15.md`](./ops/tagged-data-utilization-2026-07-15.md) | 연구팀 태깅 축어록 4종 활용 — KB 등록·taxonomy 1.1.0·P8~P11 초안 실행 기록 + 임상팀 게이트 후속 계획 |
|
||||
| [`redteam/hanshin-feedback-plan-redteam-2026-07-03.md`](./redteam/hanshin-feedback-plan-redteam-2026-07-03.md) | 한신대 개선 계획 적대 검토(컨텍스트·privacy·fallback·임상 설명 위험) |
|
||||
| [`redteam/hanshin-feedback-plan-counter-redteam-2026-07-03.md`](./redteam/hanshin-feedback-plan-counter-redteam-2026-07-03.md) | 한신대 개선 계획 대레드팀 검토(과잉 차단 완화·1차 MVP 재정렬) |
|
||||
| [`ops/code-quality-research-2026-06-26.md`](./ops/code-quality-research-2026-06-26.md) | 코드품질 분석 + 미실행 리팩터 로드맵 |
|
||||
| [`ops/refactor-governance-2026-07-15.md`](./ops/refactor-governance-2026-07-15.md) | 전 저장소 구조 감사·P1~P8 리팩터·중복/번들/SSOT 게이트 실행 증거 |
|
||||
| [`ops/postgres-rls-audit-smoke.md`](./ops/postgres-rls-audit-smoke.md) | RLS·감사로그 격리 스모크 절차(재현 런북) |
|
||||
| [`ops/engine-rss-smoke-2026-06-28.md`](./ops/engine-rss-smoke-2026-06-28.md) · [`ops/resistance-openness-db-smoke-2026-06-28.md`](./ops/resistance-openness-db-smoke-2026-06-28.md) | 대시보드가 인용하는 live 실측 증거 |
|
||||
| [`ops/layout-research-2026-06-27/persona-dashboard-layout-guideline.md`](./ops/layout-research-2026-06-27/persona-dashboard-layout-guideline.md) | 페르소나 스튜디오·대시보드 레이아웃 원칙 |
|
||||
|
|
@ -76,7 +83,7 @@
|
|||
|
||||
| 문서 | 역할 |
|
||||
|---|---|
|
||||
| [`ops/handoff-avatar-seoyeon-2026-06-27.md`](./ops/handoff-avatar-seoyeon-2026-06-27.md) | 서연(P1) 아바타 작업 핸드오프(별도 세션 진행) |
|
||||
| [`ops/handoff-avatar-seoyeon-2026-06-27.md`](./ops/handoff-avatar-seoyeon-2026-06-27.md) | 서연(P1) 래스터 아바타 보류 상태·보존 파이프라인 핸드오프 |
|
||||
| [`avatar-art/personas/README.md`](./avatar-art/personas/README.md) | P4~P7 Live2D 파츠 생성 규칙 |
|
||||
| `avatar-art/` | 페르소나별 파츠·재현 스크립트·QA 자산(활성 파이프라인) |
|
||||
|
||||
|
|
|
|||
33
docs/TODO.md
33
docs/TODO.md
|
|
@ -76,6 +76,10 @@
|
|||
## F. 임상팀 콘텐츠 [임상] (외부 소유)
|
||||
> 출처: `ops/source-docs-gap-analysis-2026-06-26.md` · 백로그 B0. 코드 구조는 선제 구축, 문안·기준은 외부 정의.
|
||||
|
||||
- [ ] **태깅 축어록 4종 후속 게이트** — `ops/tagged-data-utilization-2026-07-15.md` §3 소유:
|
||||
golden set P8~P11, 다회기 스키마 격상, gestalt/dbt 이론모드 신설(소유자 결정), P1~P7 보강,
|
||||
사례개념화 루브릭 draft, P8~P11 초안 검수·승인(특히 P9 자해 페르소나는 안전장치 실측 선행).
|
||||
|
||||
- [ ] C1 사례개념화 **확정 루브릭 콘텐츠** + AI 추출/채점 calibration (현재 scaffold_only).
|
||||
- [ ] CBT 체인·이론부합 루브릭.
|
||||
- [ ] 위기개입 프로토콜 임상 문안.
|
||||
|
|
@ -87,8 +91,35 @@
|
|||
- [ ] **운영 티켓 자동 분류·처리** — Claude Recipe headless 자동 수정 후보, 관리자 승인 후 이슈 등록·PR/작업 스레드 생성,
|
||||
처리 결과 audit trail 확장. 자동 수정은 운영자 승인 전까지 실행하지 않는다.
|
||||
|
||||
## H. 2026-07-13 한신대 회의 결정 구현 [구현] (파일럿 게이트 — 7월 내)
|
||||
> 출처: `ops/hanshin-meeting-actions-2026-07-13.md` (요구사항 상세·완료기준은 그 문서가 소유).
|
||||
> 데드라인: **7월 내 배포 → 7월 말~8월 초 파일럿(대학원생 ~20명)**. 9~10월 연구팀 점진 이관.
|
||||
|
||||
- [x] **P1. 회기 종료 시간 기반 전환** — 구현 완료(2026-07-14). 60분 시간 기반(`SESSION_DURATION_MINUTES`) +
|
||||
10분 전 알람 바 + 시간 만료 정리 유도 다이얼로그 + 유예(+10분) 후 서버 턴 409 거부 + 시작 전 목표 1~4개
|
||||
선택(`goal_stages` 계약, DDL·런타임 가드 포함 — 회의 권장 2개 수준, 소유자 지시 2026-07-15로 4개까지 허용).
|
||||
목표 달성해도 시간 내 계속 진행. 상태머신은 채점·표시용 유지.
|
||||
**검증**: 백엔드 pytest 393 pass, 1분 회기 실측(알람→만료 다이얼로그→409), session-layout 8/8, layout-visual-gate 12/12.
|
||||
- [x] **P2. 회기 계획·달성도 게이지** — 구현 완료(2026-07-14). 설계 1p `ops/session-progress-gauge-design-2026-07-14.md`.
|
||||
단계별 누적 게이지(rapport_credit 파생, 회기 간 ×0.7 이월로 누적) + 상세 수치(방어/개방도/라포 누적+이번 회기 증가분)
|
||||
를 세션 화면·상세 응답·스트림 done·대시보드 페르소나 카드(rapport_percent)에 노출. 계획서·프로토콜 첨부는 기존
|
||||
`/personas/sources`(+신규 엑셀 업로드)로 흡수. **검증**: 2회기 carry 실측(65%→46% 시작 = ×0.7 정확).
|
||||
- [~] **P3. 파일럿 인프라 대비** — 부하 검증 완료: `scripts/load-test-sessions.py`, 20 동시 세션 생성 20/20 성공
|
||||
(p95 125ms, RAG warm 병목 없음), 5 동시 실턴은 p50 55s로 **claude_cli 상주 풀이 병목** → 파일럿 전
|
||||
`ENGINE_MODE=claude_api` 전환 권고. 이관 절차 초안 `ops/server-migration-runbook-draft.md`, 파일럿 사용 지침
|
||||
`ops/pilot-usage-guide-draft.md`. **남은 것**: 실제 서버 이관(연구팀·정보처 협의) — 외부 대기.
|
||||
- [x] **P4. 데이터 업로드 흐름 검증** — 자유 양식 엑셀 업로드 경로가 없어서 신규 구축(2026-07-14):
|
||||
`POST /personas/sources/upload`(xlsx/xlsm/csv → 텍스트 결정론 변환 `services/tabular_ingest.py` → 기존 마스킹·
|
||||
hash-only 증거·sanitized chunk 경로 재사용, 원본 바이트 미저장) + PersonaStudio 파일 첨부 xlsx 지원.
|
||||
**검증**: 파란 라벨 샘플 실업로드(NAME 마스킹·chunk 색인·임베딩 OK, raw hash-only 확인), 방어 테스트 9종
|
||||
(.xls 거부·cp949 폴백·손상/빈 파일) pass.
|
||||
- [~] **P5. 협업 체계** — 비개발자용 가이드 `guides/collaboration-for-researchers.md` 작성 완료(문구 수정 프로세스 포함).
|
||||
**남은 것**: 저장소 초대(소유자 직접 실행).
|
||||
- ⏸️ **외부 대기(구현 착수 금지)**: '실패(게임오버)' 정의 / 검수 표준화 항목 / AI 평가 노출 정책 / 페르소나 추가 제작 — 전부 연구팀 몫, 수신 시 반영.
|
||||
|
||||
---
|
||||
|
||||
## 지금 임계경로 (한 줄)
|
||||
납품(공개 데모)의 핵심 블로커는 **A(공개 `/turn` proof + 배포 secrets)**. 그 다음이 **B(한신대 거버넌스)**와
|
||||
**C(Phase 3 파일럿)**. D~G는 품질·확장 작업으로 게이트가 아니다. 상세 상태·검증 증거는 SSOT 대시보드가 소유한다.
|
||||
**C(Phase 3 파일럿)** — C는 2026-07-13 회의로 **7월 말~8월 초 파일럿 일정 확정**, 그 전제로 **H의 P1(회기 종료 시간 기반)**이
|
||||
새 구현 게이트다. D~G는 품질·확장 작업으로 게이트가 아니다. 상세 상태·검증 증거는 SSOT 대시보드가 소유한다.
|
||||
|
|
|
|||
33
docs/design/darkmode-2026-07-14/README.md
Normal file
33
docs/design/darkmode-2026-07-14/README.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# 다크모드 채도 완화 리디자인 (2026-07-14)
|
||||
|
||||
> 배경: 2026-07-13 한신대 회의 피드백 — "모든 녹색 다크모드가 너무 채도가 높다".
|
||||
> gpt-image-2(imagegen2) 시안 3종을 만들고, A(따뜻한 차콜+마른 세이지)를 기본으로
|
||||
> B의 앰버 경고 톤을 혼합해 실제 토큰에 반영했다.
|
||||
|
||||
## 시안
|
||||
|
||||
| 파일 | 방향 | 채택 |
|
||||
|---|---|---|
|
||||
| `darkmode-a.png` | 저녁의 상담실 — 따뜻한 차콜 + 마른 세이지(#7da294대) | ✅ 기본 채택 |
|
||||
| `darkmode-b.png` | 근단색 그레이-유칼립투스 + 앰버 시간 알람 바 | ✅ 경고 톤만 채택 |
|
||||
| `darkmode-c.png` | 따뜻한 잉크 대시보드 + 누적 단계 게이지 표현 | 게이지 표현 참조 |
|
||||
| `implemented-session.png` | 실제 구현된 세션 화면 (컬랩싱 패널 + P2 게이지 포함) | 결과 |
|
||||
| `implemented-learn.png` | 실제 구현된 학습자 대시보드 | 결과 |
|
||||
|
||||
## 토큰 변경 요약 (다크 블록만 — 라이트 모드 무변경)
|
||||
|
||||
| 토큰 | 이전(쨍한 민트) | 이후(마른 세이지) |
|
||||
|---|---|---|
|
||||
| `--accent` | `#6fb3a4` | `#7da294` |
|
||||
| `--accent-deep` | `#84c2b4` | `#92b3a6` |
|
||||
| `--accent-bright` | `#6fb3a4` | `#8db1a4` |
|
||||
| `--pos-text` | `#7fd0a0` | `#9cbfa4` |
|
||||
| `--pos-solid` | `#5fb682` | `#6f9a7e` |
|
||||
| 세션 국소 `--accent` | `#79bfae` | `#83a89b` |
|
||||
|
||||
하드코딩 잔여 색(고채도 틸)은 `session.css`/`shell.css`/`login.css`/`settings.css`/
|
||||
`learner-home.css`/`session-review.css`에서 일괄 저채도 값으로 치환했다
|
||||
(예: `#6fc6a8→#7da294`, `#1d7169→#29564d`, `rgba(111,179,164,…)→rgba(125,162,148,…)`).
|
||||
|
||||
검증: `layout-visual-gate.spec.ts` 12/12 · `session-layout.spec.ts` 8/8 (다크 테마 어서션 포함).
|
||||
색상값 회귀는 게이트가 잡지 않으므로, 채도를 되돌릴 때는 이 문서와 시안을 근거로 소유자 확인을 거친다.
|
||||
BIN
docs/design/darkmode-2026-07-14/darkmode-a.png
Normal file
BIN
docs/design/darkmode-2026-07-14/darkmode-a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
docs/design/darkmode-2026-07-14/darkmode-b.png
Normal file
BIN
docs/design/darkmode-2026-07-14/darkmode-b.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
docs/design/darkmode-2026-07-14/darkmode-c.png
Normal file
BIN
docs/design/darkmode-2026-07-14/darkmode-c.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
docs/design/darkmode-2026-07-14/implemented-learn.png
Normal file
BIN
docs/design/darkmode-2026-07-14/implemented-learn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 249 KiB |
BIN
docs/design/darkmode-2026-07-14/implemented-session.png
Normal file
BIN
docs/design/darkmode-2026-07-14/implemented-session.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 478 KiB |
BIN
docs/design/darkmode-2026-07-14/review-redesigned-2026-07-15.png
Normal file
BIN
docs/design/darkmode-2026-07-14/review-redesigned-2026-07-15.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 643 KiB |
File diff suppressed because one or more lines are too long
|
|
@ -25,6 +25,8 @@ Vignette(AI 심리상담 시뮬레이션 훈련 플랫폼)의 시스템 아키
|
|||
자살 수단/방법 정보는 출력 가드레일이 차단한다.
|
||||
- **주입형(hook) 경계**: 오케스트레이터는 평가/로깅 함수를 *주입*받는다. `services/orchestrator.py`는
|
||||
`services/evaluator.py`를 import 하지 않는다(소유권 분리).
|
||||
- **LLM 감사 경계**: evaluator/orchestrator의 생성 시간·토큰·비용 기록은 `services/llm_audit.py`의
|
||||
`generate_with_audit()`가 소유한다. 생성 성공을 감사 저장 성공으로 오인하지 않고 호출자가 degraded 의미를 결정한다.
|
||||
- **degraded 폴백**: DB(단일 SoR)가 없어도 `app/store.py` in-memory 미러로 1턴이 돈다.
|
||||
|
||||
### 1.1 모노레포 레이아웃
|
||||
|
|
@ -254,8 +256,17 @@ RBAC×AIView로 차단된다. 이 모듈은 평가 신호만 산출한다.
|
|||
형태가 아니라 `quota`와 `credit_events`를 포함한 짧은 카드 계약이다. UI는 코칭 아바타 말풍선,
|
||||
근거 모달, 발화별 코칭 이력 오버레이로 표시한다.
|
||||
- 회기별 코칭 기회는 기본/최대 3개다. `POST /sessions/{id}/live-coach` 사용 시 `use/-1` 이벤트를 남기고,
|
||||
턴 평가가 `appropriateness=pos`, `rapport_signal>=0.35`, 그리고 단계 전환 또는 `effective_openness`
|
||||
상승을 동시에 만족하면 `recharge/+1` 이벤트로 1개를 재충전한다(최대 3개). 사용권 없음은 409로 막는다.
|
||||
충전은 2026-07-14 재설계된 게이트를 쓴다(`turn_runtime.should_recharge_live_coach_credit`):
|
||||
① 성과 충전 — `appropriateness=pos` + `rapport_signal>=0.15` + (단계 전환 또는 개방도 +0.01),
|
||||
또는 `neutral` + `rapport_signal>=0.35` + 개방도 상승 ② 페이싱 충전 — 평가 신호와 무관하게
|
||||
6턴마다 1개(평가 실패여도 충전, 순감 구조 방지). 사용권 없음은 409로 막는다.
|
||||
- 프론트 트리거: 코칭 화면(`feedbackMode=coached`)이 아닐 때 완료된 턴은 기회를 소모하지 않고
|
||||
`pendingCoachTurn`으로 대기하며, 컨트롤바 위 컨텍스추얼 넛지 칩("방금 발화에 코치 제안이 있어요")으로
|
||||
안내한다. 코칭 화면을 열면 대기 턴에 대한 코칭을 즉시 요청한다.
|
||||
- 코칭 프롬프트에는 이번 회기 목표(`goal_stages`)와 직전 코칭 2건(title/focus)을 주입해
|
||||
목표 정렬·조언 반복 방지를 유도하고, 규칙 폴백도 단계별 기본 다음 발화를 쓴다.
|
||||
- `record_llm_call_audit`는 dev degraded(무DB) 기동을 "기록 실패"로 보지 않는다
|
||||
(`runtime_fallback_allowed()`면 True). durable 환경의 실제 기록 실패만 코칭을 degraded로 강등한다.
|
||||
- 전달된 코칭과 충전/사용 기록은 `app.live_coach_events`에 저장된다. 원문 축어록을 중복 저장하지 않고
|
||||
PII 마스킹된 짧은 learner/client excerpt, 코칭 payload, `event_type/credit_delta/credit_balance/reason`만
|
||||
저장한다. DB 미가용 dev에서는 런타임 캐시로 폴백한다.
|
||||
|
|
@ -339,6 +350,14 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
|
|||
기본값을 쓰되 학습자가 `humanistic`/`cbt`/`integrative` 중 하나를 명시 선택해 기존
|
||||
`theory_mode` 계약으로 보낸다. DB가 없으면
|
||||
`runtime_fallback_allowed()` 확인 후 `store.create(...)`로 in-proc 생성.
|
||||
2026-07-13 한신대 회의 P1 반영: 요청에 이번 회기 목표 단계 `goal_stages`(StageLabel 1~4개 —
|
||||
회의 권장은 2개 수준, 소유자 지시(2026-07-15)로 4개까지 허용. 중복 제거·최대 4 검증,
|
||||
`app.sessions.session_goals` JSONB 저장)를 받고, 응답에
|
||||
`started_at`/`goal_stages`/`duration_limit_seconds`/`warning_before_end_seconds`를 돌려준다.
|
||||
회기 종료는 단계 완수가 아니라 **시간 기반**이다 — 프론트가 60분 타이머·10분 전 알람·만료 시
|
||||
정리 유도를 주도하고, 서버는 제한+유예(`SESSION_DURATION_MINUTES`+`SESSION_OVERTIME_GRACE_MINUTES`)
|
||||
초과 시 신규 턴을 `409 session_time_over`로 거부한다(`_ensure_turn_time_allowed`, 음성 WS 동일).
|
||||
목표를 달성해도 시간 내에는 계속 진행한다(상태머신은 채점·표시용으로만 유지).
|
||||
- `POST /sessions/{id}/turn` — 동기 경로. `prepare_turn` → `run_turn_generate` →
|
||||
학습자 발화 turn(speaker=`counselor`, 원문 `text` + `text_masked`) + 내담자 응답 turn(speaker=`client`)
|
||||
순차 append → 상태 갱신. `EngineError`는 503으로 변환.
|
||||
|
|
@ -355,6 +374,14 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
|
|||
임베딩 모델이 없으면 BM25-only degraded 색인으로 진행한다.
|
||||
- `POST /sessions/{id}/end` — `memory.make_carry_over(...)` → 세션 종료 + carry 준비 →
|
||||
`_schedule_session_evaluation(sess)`로 deep-loop 평가를 비동기 태스크로 발사.
|
||||
- 진행도 파생(P2, 2026-07-14): `session_read_model.build_session_progress(state, prev_rapport_credit,
|
||||
goal_stages)`가 상태머신 수치를 학습자-안전 %로 파생한다 — 단계별 누적 게이지(현재 단계는
|
||||
`rapport_credit / STAGE_ADVANCE_RAPPORT` 기준, 지나온 단계 100, 전이 대기 99 캡), 라포 누적
|
||||
(`/0.55` 전 주기 기준)과 이번 회기 증가분(`prev_rapport_credit` 대비), 방어(저항)·유효 개방도 %.
|
||||
노출 지점: `SessionDetailResponse.progress`, `TurnResponse.progress`, 스트림 `done` payload,
|
||||
음성 WS `reply`, 대시보드 `persona_progress[].rapport_percent`. rapport_credit이 회기 간 ×0.7
|
||||
이월되므로 게이지가 회기를 건너 누적된다(초심 상담자 학습 신호 — 회의 P2 의도). ideation·CCD는
|
||||
계속 비노출.
|
||||
- `GET /sessions/dashboard` — 학습자 본인 세션만 `include_turn_evaluation=true`로 집계해
|
||||
`overview/growth/persona_progress/achievements/recent_feedback`를 반환한다. `overview.archived_sessions`는
|
||||
학습자별 보관 상태(`app.session_archive_state`) 기준 카운트이며, 보관된 종료 회기는 리뷰 대기 행동 큐에서
|
||||
|
|
@ -494,8 +521,16 @@ HTTP error mapping을 유지한다.
|
|||
- `get_approved_persona(code)` / `list_approved_personas()` — 승인된 최신 버전 조회(AI 컨텍스트).
|
||||
- `get_catalog_persona(code)` — 승인 카드 조회, 실패 시 `settings.allow_seed_persona_fallback`이면
|
||||
`seed_fallback_persona`(degraded=True)로 폴백.
|
||||
- 페르소나 스튜디오: `/teach/personas`가 teacher/admin 전용 저작 작업면이다. 교수 콘솔은 진입점/triage만 맡고,
|
||||
실제 저작은 개요·임상·저항·회기·말투·안전·프롬프트 탭으로 분리한다.
|
||||
- 페르소나 워크스페이스: `/teach/personas`는 teacher/admin 전용 운영·저작 작업면이다. 상단 horizontal tab이
|
||||
`대시보드`(공개/활성 페르소나·학습 인원·세션·평가/라포·검수 요약), `카탈로그`(공개 규칙·구성·버전),
|
||||
`페르소나`(검색/필터 가능한 전체 table + 내부 `검수 현황` 탭)를 나눈다. 행을 누르면 소개·학습 현황·설정
|
||||
drilldown으로 들어가며, 수정/신규 작성은 URL query 상태의 독립 작업면에서 자료→초안→설정→검토 4단계를
|
||||
사용한다. 작성 중간 저장은 브라우저 임시 저장을 항상 남기고, 기본 식별자가 채워지면 기존 draft API에도 저장한다.
|
||||
구조화 편집 자체는 개요·임상·저항·회기·말투·안전·프롬프트 탭 계약을 유지한다.
|
||||
- 자유 양식 표 업로드(2026-07-13 회의 P4): `POST /personas/sources/upload`는 xlsx/xlsm/csv 파일을
|
||||
받아 `services/tabular_ingest.extract_tabular_text()`로 결정론 평탄화(라벨: 값 쌍, 시트 구분,
|
||||
cp949 폴백, .xls 거부)한 뒤 아래 `/personas/sources` 등록 경로를 그대로 재사용한다.
|
||||
업로드 원본 바이트는 핸들러 메모리에서만 파싱하고 저장하지 않는다(원본 파기 — hash-only 증거만 남음).
|
||||
- RAG 첨부 SSOT: `POST /personas/sources`는 첨부/붙여넣기 자료를 `mask_pii()` 후
|
||||
raw 원문 hash-only 증거는 `kb.raw_source_artifact`에 따로 기록하고, sanitized 파생본만
|
||||
`kb.source/document/chunk`에 evaluator 전용 근거(`visible_to=['evaluator']`, `sensitivity=2`)로 등록한다.
|
||||
|
|
@ -567,12 +602,16 @@ React 19 + Vite. 라우팅은 `apps/web/src/App.tsx`(react-router-dom).
|
|||
| `/teach/personas` | PersonaStudio(페르소나 저작·검수) | teacher/admin |
|
||||
| `/teach/session/:sessionId/review` | SessionReview(교수자 읽기 전용 회기 검토) | teacher/admin |
|
||||
| `/admin` | Admin(운영 홈) | admin |
|
||||
| `/admin/ai` | AdminAi(AI 엔진 설정, 기간별 DB 비용·토큰·계량 커버리지, provider/model 원장, 평가 캐시 효율) | admin |
|
||||
| `/admin/users` | Admin(사용자 관리) | admin |
|
||||
| `/admin/access` | Admin(접근 권한) | admin |
|
||||
| `/admin/tickets` | Admin(운영 티켓 처리 큐: 접수, 필터, 카테고리 큐, 우선순위, 수동 상태 변경 감사) | admin |
|
||||
| `/settings` | Settings | 3역할 공통 |
|
||||
|
||||
- `RequireAuth`가 `lib/auth`의 AuthContext로 가드하고, 권한 불일치 시 역할 홈(`roleHomePath`)으로 보낸다.
|
||||
- `App.tsx`는 모든 역할 페이지를 `lazy()`로 로드하고 공통 `Suspense` 부트 경계를 사용한다. 페이지별 순수 표시 계산은
|
||||
`pages/*/model.ts`, 브라우저 음성 캡처는 `pages/session/voiceCapture.ts`, 페르소나 이름·난도·아바타 팔레트는
|
||||
`lib/personaViewModel.ts`가 소유해 라우트 컴포넌트의 API/상태/렌더 책임과 분리한다.
|
||||
- API 클라이언트 `src/lib/api.ts`:
|
||||
- `apiFetch`/`api`(get/post/put/del) — 표준 JSON, 에러는 `ApiError`로 정규화, 항상 `credentials:"include"`.
|
||||
- SSE: `openSessionStream(sessionId, text, handlers)` — `fetch` 스트림을 직접 라인 파싱해
|
||||
|
|
@ -593,6 +632,8 @@ React 19 + Vite. 라우팅은 `apps/web/src/App.tsx`(react-router-dom).
|
|||
(예: `pages/login/login.css`, `pages/learner-home.css`, `pages/session/session.css`).
|
||||
TSX 안에 `<style>{..._CSS}</style>` template literal을 두지 않는다. 새 화면은 스타일을
|
||||
페이지/기능 CSS로 분리하고, 반복 JSX는 작은 presentational component로 빼서 인증·데이터 상태 로직과 섞지 않는다.
|
||||
- 공통 UI/셸 CSS의 raw color는 0개를 강제한다. 몰입형 세션·인증·아바타 아트처럼 전역 의미 토큰이 아닌 국소 색은
|
||||
`check:design-ssot`의 파일별 고정 예산을 넘길 수 없어 예외가 다른 화면으로 확산되지 않는다.
|
||||
- 로그인 진입 화면은 `pages/login/LoginBrand.tsx`, `LoginPanel.tsx`, `login.css`로 분리되어
|
||||
`Login.tsx`는 OAuth/dev-login 상태와 라우팅만 소유한다.
|
||||
|
||||
|
|
@ -728,8 +769,9 @@ DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
|
|||
- 프론트의 최초 진입 경로(`initialPathForUser`)는 pending이면 `/pending`, 온보딩 미완료 일반 사용자는
|
||||
`/onboarding`, 관리자 콘솔 접근권이 있는 사용자는 기본 역할이 learner/teacher여도 `/admin`을 우선한다.
|
||||
역할 전환용 `roleHomePath`는 그대로 역할별 홈(`/learn`, `/teach`, `/admin`)만 소유한다.
|
||||
- SPA 라우트 전환은 `App.tsx`가 `pathname` 변경마다 문서 스크롤을 맨 위로 복원한다. 긴 관리자 목록에서
|
||||
짧은 `/admin/access` 정책 화면으로 이동할 때 이전 `scrollY`가 유지되어 sticky 셸만 보이는 빈 화면을 막기 위한 계약이다.
|
||||
- SPA 라우트 전환은 `App.tsx`가 `pathname` 변경마다 문서 스크롤을 맨 위로 복원한다. 또한 브라우저
|
||||
`history.scrollRestoration`을 `manual`로 두고 `pageshow`에서도 다시 복원해, 재부팅 뒤 기존 관리자 탭을
|
||||
되살릴 때 이전 `scrollY` 때문에 sticky 셸만 보이고 본문이 화면 위로 밀리는 빈 화면을 막는다.
|
||||
- 관리자 콘솔은 shell만 남고 본문이 비는 silent failure를 허용하지 않는다. `/admin/users` 응답의
|
||||
`cohort_ids`, `active_sessions`, `created_at`와 `/admin/tickets` 응답의 `summary`, `by_status`,
|
||||
`by_priority` 같은 필드가 런타임 계약과 다르면 프론트가 안전한 기본값으로 정규화하고 `관리자 데이터 진단`
|
||||
|
|
|
|||
185
docs/guides/collaboration-for-researchers.md
Normal file
185
docs/guides/collaboration-for-researchers.md
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
# 연구팀·디자인 담당을 위한 협업 가이드 (Git을 처음 쓰는 분께)
|
||||
|
||||
이 문서는 **개발이 처음인 연구팀 대학원생·디자인 담당 학생**이 이 저장소(프로젝트 파일 보관소)에
|
||||
안전하게 참여하기 위한 안내서다. 어려운 용어는 나올 때마다 한 줄로 풀어서 설명한다.
|
||||
천천히 따라오면 되고, 막히면 언제든 단톡방에 물어보면 된다.
|
||||
|
||||
> **먼저 알아둘 3가지**
|
||||
> 1. 여러분은 코드를 몰라도 참여할 수 있다. 대부분은 **웹 브라우저 화면만으로** 가능하다.
|
||||
> 2. 실수해도 프로젝트가 망가지지 않도록 설계돼 있다. 아래 "하지 말아야 할 것"만 지키면 된다.
|
||||
> 3. 문구(화면에 보이는 글자) 수정은 **단톡방에 목록으로 전달하는 것이 가장 쉬운 기본 경로**다.
|
||||
> 직접 고쳐보고 싶은 분을 위한 방법도 뒤에 따로 안내한다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 왜 main(master)에 직접 손대면 안 되나
|
||||
|
||||
먼저 용어부터.
|
||||
|
||||
| 용어 | 한 줄 뜻 |
|
||||
|---|---|
|
||||
| **저장소(repository, 레포)** | 프로젝트의 모든 파일과 수정 이력이 담긴 온라인 보관소 |
|
||||
| **브랜치(branch, 가지)** | 원본을 건드리지 않고 따로 복사해서 작업하는 "작업용 사본" |
|
||||
| **main / master** | 실제 서비스에 반영되는 **원본 줄기**. 손대면 바로 모두에게 영향 |
|
||||
| **커밋(commit)** | "여기까지 이렇게 고쳤다"라고 저장하는 한 번의 기록 |
|
||||
|
||||
**비유로 이해하기.** `main(master)`은 병원의 **정식 환자 차트 원본**이라고 생각하면 된다.
|
||||
누구든 원본에 바로 낙서하듯 고치면, 다른 사람이 그 잘못된 차트를 그대로 믿고 일하게 되고,
|
||||
누가 언제 무엇을 바꿨는지도 뒤엉킨다. 그래서 우리는 항상 **원본을 복사한 작업용 사본(브랜치)** 에서
|
||||
고친 뒤, 담당자가 한 번 확인(리뷰)하고 나서야 원본에 반영한다.
|
||||
|
||||
이렇게 하면 좋은 점:
|
||||
|
||||
- 원본은 항상 **작동하는 상태**로 유지된다 (파일럿·시연 중에 갑자기 화면이 깨지지 않는다).
|
||||
- 실수해도 내 작업용 사본에서만 일어나므로 **되돌리기 쉽다**.
|
||||
- 바꾼 이유와 내용을 담당자가 **한 번 검토**하므로 사고가 미리 걸러진다.
|
||||
|
||||
> 한 줄 요약: **원본(main/master)에 직접 저장(커밋)·업로드(push)하지 않는다. 항상 사본(브랜치) → 확인(리뷰) → 반영 순서.**
|
||||
|
||||
---
|
||||
|
||||
## 2. 시작 준비 — GitHub 계정과 저장소 초대
|
||||
|
||||
우리는 **GitHub**라는 서비스에서 이 저장소를 관리한다. (GitHub = 저장소를 온라인에 두고 여럿이 함께
|
||||
작업하게 해주는 웹사이트.)
|
||||
|
||||
**여러분이 할 일 (체크리스트)**
|
||||
|
||||
- [ ] 1. [github.com](https://github.com) 에서 **무료 계정**을 만든다. (이메일·비밀번호만 있으면 된다)
|
||||
- [ ] 2. 가입에 사용한 **GitHub 아이디(username) 또는 이메일**을 단톡방에 알려준다.
|
||||
- [ ] 3. 소유자(윤찬)가 **여러분을 저장소에 초대**한다. → **초대 실행은 윤찬이 직접 한다. 여러분이 신청하는 것이 아니다.**
|
||||
- [ ] 4. 초대되면 여러분의 가입 이메일로 **초대 메일**이 오거나, GitHub 알림에 초대가 뜬다.
|
||||
- [ ] 5. 메일/알림의 **"Accept invitation(초대 수락)"** 버튼을 누른다. 끝.
|
||||
|
||||
수락하고 나면 저장소 페이지가 열리고, 이제 여러분도 이 프로젝트의 협업자(collaborator)가 된다.
|
||||
|
||||
> 초대가 안 왔거나 버튼을 못 찾겠으면 단톡방에 알려주면 된다. 흔한 일이니 걱정하지 않아도 된다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 실제 수정 흐름 — 웹 브라우저만으로 (권장)
|
||||
|
||||
여기서는 **컴퓨터에 아무것도 설치하지 않고, GitHub 웹사이트 화면만으로** 파일을 고치고 반영 요청까지
|
||||
하는 방법을 설명한다. 대부분의 문구·문서 수정은 이 방법으로 충분하다.
|
||||
|
||||
먼저 이 흐름의 전체 그림:
|
||||
|
||||
```
|
||||
파일 열기 → 편집(연필) 버튼 → 내용 수정 → "Propose changes(변경 제안)"
|
||||
→ 자동으로 새 브랜치(사본) 생성됨 → Pull Request(반영 요청) 작성
|
||||
→ 담당자 리뷰 → 문제 없으면 병합(main에 반영)
|
||||
```
|
||||
|
||||
용어 두 개만 더:
|
||||
|
||||
| 용어 | 한 줄 뜻 |
|
||||
|---|---|
|
||||
| **Pull Request (PR, 풀 리퀘스트)** | "제 사본에서 이렇게 고쳤으니 원본에 반영해 주세요"라는 **반영 요청서** |
|
||||
| **병합(merge, 머지)** | 검토를 통과한 사본의 변경을 원본에 **합치는 것** |
|
||||
|
||||
### 3-1. 단계별 (웹 UI 기준)
|
||||
|
||||
1. **파일을 찾아 연다.** 저장소 페이지에서 폴더를 눌러 들어가 원하는 파일을 클릭한다.
|
||||
(예: 문구는 대개 `apps/web/src/pages/` 폴더 안에 있다 — 4장 참조)
|
||||
2. **연필(✏️) 아이콘을 누른다.** 파일 오른쪽 위에 있다. "Edit this file(이 파일 편집)" 뜻이다.
|
||||
3. **내용을 고친다.** 화면에서 글자를 직접 수정하면 된다.
|
||||
4. 오른쪽 위 **"Commit changes...(변경 저장)"** 초록 버튼을 누른다.
|
||||
5. 작은 창이 뜨면:
|
||||
- **변경 설명**에 무엇을 왜 고쳤는지 한 줄로 적는다. (예: `학습자 홈 안내 문구 오타 수정`)
|
||||
- **반드시 아래쪽 "Create a new branch ... and start a pull request(새 브랜치를 만들어 PR 시작)"** 를 고른다.
|
||||
→ 이걸 고르면 **원본을 건드리지 않고 자동으로 사본(브랜치)이 만들어진다.** (첫 번째 "Commit directly to the master branch(원본에 바로 저장)"는 **고르지 않는다.**)
|
||||
- 브랜치 이름은 자동으로 채워지니 그대로 둬도 된다.
|
||||
- **"Propose changes"** 를 누른다.
|
||||
6. **Pull Request(반영 요청) 화면**이 나온다. 제목·설명을 확인하고(자동으로 채워져 있음)
|
||||
**"Create pull request"** 를 누른다.
|
||||
7. 끝. 이제 **담당자가 리뷰**한다. 단톡방에 "PR 올렸습니다"라고 한 마디 남겨주면 확인이 빨라진다.
|
||||
8. 담당자가 수정 요청을 남기면, 같은 PR에서 파일을 다시 편집해 커밋하면 된다.
|
||||
문제 없으면 담당자가 **병합(merge)** 하여 원본에 반영한다.
|
||||
|
||||
### 3-2. 한눈에 보는 체크리스트
|
||||
|
||||
- [ ] 원하는 파일을 연다
|
||||
- [ ] 연필(✏️) 버튼으로 편집
|
||||
- [ ] "Commit changes" → **새 브랜치 만들기 선택** (원본 직접 저장 아님)
|
||||
- [ ] "Create pull request"로 반영 요청
|
||||
- [ ] 단톡방에 PR 올렸다고 공유
|
||||
- [ ] 리뷰 통과 후 담당자가 병합 → 완료
|
||||
|
||||
---
|
||||
|
||||
## 4. UI 문구(화면 글자) 수정 프로세스
|
||||
|
||||
### 4-1. 기본 경로 — 단톡방으로 목록 전달 (가장 쉽고 빠름)
|
||||
|
||||
화면에 보이는 문구를 고치고 싶을 때 **가장 권장하는 방법**이다.
|
||||
|
||||
1. **바꿀 문구를 목록으로 정리**한다. 아래처럼 "어디의 / 무엇을 / 어떻게" 형태면 개발자가 바로 반영한다.
|
||||
|
||||
| 위치(어느 화면) | 현재 문구 | 바꿀 문구 |
|
||||
|---|---|---|
|
||||
| 학습자 홈 상단 | 회기를 시작하세요 | 오늘의 회기를 시작해 보세요 |
|
||||
| 회기 준비 화면 버튼 | 시작 | 회기 시작 |
|
||||
|
||||
2. 이 목록을 **단톡방에 전달**한다.
|
||||
3. 개발 쪽에서 **단순 텍스트 교체 수준으로 즉시 반영**한다.
|
||||
|
||||
문구 수정은 이 경로가 기본이다. Git을 몰라도 되고, 실수할 일도 없다.
|
||||
|
||||
### 4-2. 직접 고쳐보고 싶은 분을 위한 안내
|
||||
|
||||
3장의 웹 UI 방법으로 직접 PR을 올리고 싶다면, 문구는 주로 아래 위치에 있다.
|
||||
|
||||
- **폴더**: `apps/web/src/pages/`
|
||||
- **파일**: 화면별 `.tsx` 파일 (예: 학습자 홈은 `LearnerHome.tsx`, 회기 화면은 `Session.tsx`,
|
||||
교수자 화면은 `Professor.tsx`)
|
||||
- **문구 형태**: 파일 안에서 **따옴표(" ")로 감싼 한글 글자**가 화면에 보이는 문구다.
|
||||
예: `label: "전체"` 에서 `전체`, `"내담자 정보를 불러오는 중"` 같은 부분.
|
||||
|
||||
**가장 중요한 원칙 — 문자열(따옴표 안 글자)만 바꾸고, 코드 구조는 절대 건드리지 않는다.**
|
||||
|
||||
| 해도 되는 것 (안전) | 하면 안 되는 것 (위험) |
|
||||
|---|---|
|
||||
| 따옴표 **안쪽의 한글 글자**만 수정 | 따옴표 `"` 자체를 지우기 |
|
||||
| `"시작"` → `"회기 시작"` 처럼 글자만 교체 | `label:`, `{ }`, `( )`, `;`, `<` `>` 같은 기호 수정 |
|
||||
| 오타·띄어쓰기·표현 다듬기 | 줄을 통째로 지우거나 위치 옮기기 |
|
||||
| | 영어로 된 코드 부분(`value`, `desc` 등) 수정 |
|
||||
|
||||
> 헷갈리면 무리하지 말고 4-1의 목록으로 전달하면 된다. 그게 더 빠를 때가 많다.
|
||||
> 직접 PR을 올렸다면 단톡방에 알려서 리뷰를 받는다. 병합 전에 담당자가 확인하므로,
|
||||
> 잘못 고쳤어도 원본에 그대로 들어가지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 하지 말아야 할 것 (꼭 지켜주세요)
|
||||
|
||||
| 하지 말 것 | 왜 위험한가 |
|
||||
|---|---|
|
||||
| ❌ **main(master)에 직접 저장·업로드** | 원본이 검토 없이 바뀌어 시연·파일럿 중 화면이 깨질 수 있다. 항상 사본(브랜치)+PR로. |
|
||||
| ❌ **force push(강제 덮어쓰기)** | 남들이 올린 기록까지 지워버릴 수 있다. 이 단어가 나오면 절대 실행하지 말고 문의. |
|
||||
| ❌ **남의 브랜치(사본)를 덮어쓰기·삭제** | 다른 사람이 작업 중인 내용을 날린다. 내 사본만 다룬다. |
|
||||
| ❌ **코드 파일을 대량으로 수정** | 문구 한두 줄이 아니라 구조를 건드리면 프로그램이 멈춘다. 문구는 "문자열만" 원칙. |
|
||||
| ❌ **`.env`·비밀번호·API 키 등 secret 파일 커밋** | 비밀 정보가 온라인에 영구히 노출된다. 이런 파일은 올리지 않는다. |
|
||||
| ❌ **확신 없는 대량 삭제** | 무엇이 사라질지 모른다. 애매하면 먼저 물어본다. |
|
||||
|
||||
> 위 항목 중 하나라도 "이거 해도 되나?" 싶으면 **멈추고 단톡방에 물어보는 것이 정답**이다.
|
||||
> 물어봐서 손해 보는 일은 없다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 도움 요청 채널
|
||||
|
||||
- **막히거나 헷갈릴 때: 단톡방**에 바로 물어본다. 캡처(스크린샷)를 함께 올리면 더 빠르다.
|
||||
- 자주 나오는 질문
|
||||
- "초대가 안 왔어요" → 단톡방에 GitHub 아이디/이메일을 다시 알려준다.
|
||||
- "어떤 버튼을 눌러야 할지 모르겠어요" → 화면 캡처와 함께 물어본다.
|
||||
- "문구만 바꾸고 싶어요" → 4-1의 목록 양식으로 단톡방에 전달하면 개발 쪽에서 반영한다.
|
||||
|
||||
혼자 오래 고민하지 말고, 처음엔 무엇이든 물어보면서 익히면 된다.
|
||||
|
||||
---
|
||||
|
||||
### (선택 심화) 로컬 Git으로 작업하기
|
||||
|
||||
컴퓨터에 직접 도구를 설치해 작업하는 방법도 있지만, **연구팀·디자인 담당은 위 3장의 웹 UI 방법만으로
|
||||
충분하다.** 로컬 개발 환경이 필요한 경우(개발 인력 합류 등)에는 별도 문서
|
||||
[`local-development.md`](./local-development.md)를 참고한다. 이 문서를 처음부터 볼 필요는 없다.
|
||||
|
|
@ -488,8 +488,8 @@ RAG 임베딩/리랭커 의존성은 기본 이미지에 설치하지 않는다.
|
|||
```powershell
|
||||
# 백엔드 (apps/api)
|
||||
cd apps\api
|
||||
python -m pytest app/ -q # 백엔드 기준선 178 pass
|
||||
python -m pytest engine_gateway\ -q # 현재 27 pass
|
||||
python -m pytest app/ -q # 백엔드 기준선 400 pass
|
||||
python -m pytest engine_gateway\ -q # 현재 29 pass
|
||||
|
||||
# 웹 (apps/web)
|
||||
cd apps\web
|
||||
|
|
|
|||
|
|
@ -136,8 +136,8 @@ rg -n "materialize_seed_personas|init_pool|close_pool|--apply|--json" scripts/ma
|
|||
|
||||
```powershell
|
||||
# 백엔드 (작업 디렉터리 apps/api)
|
||||
python -m pytest app/ -q # 백엔드 기준선 178 pass
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 현재 27 pass
|
||||
python -m pytest app/ -q # 백엔드 기준선 400 pass
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 현재 29 pass
|
||||
|
||||
# 프론트 (작업 디렉터리 apps/web)
|
||||
npm run typecheck
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
|||
|
||||
| 검증 | 작업 디렉터리 | 명령 | DB | API(8000) | 웹(5173) | 엔진GW(9099) | 브라우저 | 현재 통과 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 현재 collect-only 349 tests, 최신 focused pass |
|
||||
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 현재 27 pass |
|
||||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-07-15 전체 실행 400 passed |
|
||||
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 현재 29 pass |
|
||||
| API 타입 생성 체크 | `apps/web` | `npm run check:api-types` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||
| 웹 타입체크 | `apps/web` | `npm run typecheck` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||
| 웹 빌드 | `apps/web` | `npm run build` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 194 tests / 19 files |
|
||||
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 215 tests / 20 files |
|
||||
|
||||
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
|
||||
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
|
||||
|
|
@ -42,15 +42,15 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
|||
|
||||
```sh
|
||||
# apps/api
|
||||
python -m pytest app/ -q # 앱 단위 테스트: 현재 collect-only 349 tests, 최신 focused pass
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트 현재 27 pass
|
||||
python -m pytest app/ -q # 앱 단위 테스트: 2026-07-15 전체 실행 400 passed
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트 현재 29 pass
|
||||
```
|
||||
|
||||
수집만 빠르게 확인하려면:
|
||||
|
||||
```sh
|
||||
python -m pytest app/ --collect-only -q # 현재: "349 tests collected"
|
||||
python -m pytest engine_gateway/ --collect-only -q # 현재: "27 tests collected"
|
||||
python -m pytest app/ --collect-only -q # 현재: "400 tests collected" (2026-07-15)
|
||||
python -m pytest engine_gateway/ --collect-only -q # 현재: "29 tests collected"
|
||||
```
|
||||
|
||||
> 참고: 실행 중 `PendingDeprecationWarning: Please use 'import python_multipart'`
|
||||
|
|
@ -221,7 +221,10 @@ python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
|
|||
|
||||
```sh
|
||||
# apps/web — API(8000)와 DB가 떠 있는 상태에서
|
||||
npm run e2e # = playwright test (전체)
|
||||
npm run e2e # 전체: fixture 병렬 단계 → DB/engine 직렬 단계
|
||||
npm run e2e:list # 두 단계의 수집 목록·개수만 확인
|
||||
npm run e2e:parallel # desktop/mobile route-fixture, workers=4
|
||||
npm run e2e:single-run # DB/engine @single-run, workers=1
|
||||
npm run e2e:headed # 브라우저 표시
|
||||
npm run e2e:ui # Playwright UI 모드
|
||||
```
|
||||
|
|
@ -230,9 +233,9 @@ npm run e2e:ui # Playwright UI 모드
|
|||
|
||||
```sh
|
||||
npx playwright test e2e/session-layout.spec.ts
|
||||
npx playwright test --grep "@single-run" # 직렬 실행 시나리오만
|
||||
npx playwright test --project=chromium-single-run --workers=1 --grep "검색어"
|
||||
npx playwright test --grep-invert "@single-run" # 병렬 시나리오만
|
||||
npx playwright test --list # 실행하지 않고 목록·개수만
|
||||
npm run e2e:list # 실행하지 않고 단계별 목록·개수만
|
||||
```
|
||||
|
||||
유용한 오버라이드(`playwright.config.ts` 실측):
|
||||
|
|
@ -245,30 +248,38 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
|||
|
||||
### 3.5 프로젝트(브라우저 프로파일) 구성
|
||||
|
||||
`playwright.config.ts`는 `testDir: ./e2e`, `timeout: 30s`, `expect.timeout: 5s`,
|
||||
`fullyParallel: true`로 다음 프로젝트를 정의한다.
|
||||
`playwright.config.ts`는 `testDir: ./e2e`, `timeout: 30s`, `expect.timeout: 5s`로
|
||||
다음 프로젝트를 정의한다. fixture 프로젝트는 `fullyParallel:true`로 실행한다. `npm run e2e`는
|
||||
로컬 단일 API/DB/engine 자원을 보호하기 위해 desktop/mobile 단계를 `workers=4`로 먼저 끝내고,
|
||||
`chromium-single-run`을 `fullyParallel:false`, `workers=1`로 두 번째 실행한다.
|
||||
|
||||
| 프로젝트 | 뷰포트/디바이스 | 대상 grep |
|
||||
|---|---|---|
|
||||
| `chromium-desktop` | 1440×900 | `@single-run`·`@public-auth` 제외 전부 |
|
||||
| `chromium-mobile` | Pixel 5 | `@single-run`·`@public-auth` 제외 전부 |
|
||||
| `chromium-single-run` | 1280×800 | `@single-run`만(직렬) |
|
||||
| `chromium-single-run` | 1280×800 | `@single-run`만(별도 2단계, workers=1 직렬) |
|
||||
| `chromium-public-auth` | 1440×900 | `E2E_PUBLIC_AUTH=1`일 때만 활성, 공개 사이트 대상 |
|
||||
|
||||
- CI(`process.env.CI`)에서는 `retries: 2`, `workers: 1`, `list`+`html` 리포터를 쓴다.
|
||||
- 로컬 fixture 단계의 기본 worker는 4다. 전체 러너에서만 `E2E_PARALLEL_WORKERS`로 조정할 수 있고,
|
||||
12-worker 전면 병렬은 단일 FastAPI/DB가 포화되어 서로 무관한 요청 timeout을 만들므로 기준선으로 쓰지 않는다.
|
||||
- `npx playwright test`를 직접 실행하면 로컬에서 세 프로젝트가 같은 worker pool에 섞일 수 있다.
|
||||
전체 게이트는 반드시 `npm run e2e`, focused DB/engine 게이트는 프로젝트와 worker를 명시한
|
||||
`npx playwright test --project=chromium-single-run --workers=1 --grep "..."`을 사용한다.
|
||||
- 실패 시 trace(첫 재시도)·스크린샷·비디오를 `node_modules/.tmp/`에 남긴다.
|
||||
|
||||
### 3.6 실측 테스트 개수 (현재)
|
||||
|
||||
`npx playwright test --list` 기준 **총 194 tests / 19 files**.
|
||||
`npx playwright test --list` 기준 **총 215 tests / 20 files**.
|
||||
|
||||
- **병렬 시나리오**: 150 tests (desktop 75 + mobile 75)
|
||||
- **`@single-run` 직렬 시나리오**: 44 tests (DB 영속화·세션 MVP·음성 성공경로·회기말 평가 저장·교수자 명시 재평가 저장·교수자 턴 재평가 저장·교수자 UI 평가 재시도·교수자 사용자별 분석·source pack sync·이론모드 저장·동의 철회 후 voice 차단·브라우저 stream PII 마스킹·음성 transcript 저장 실패 UI 표면화 등)
|
||||
- **병렬 시나리오**: 166 tests (desktop 83 + mobile 83)
|
||||
- **`@single-run` 직렬 시나리오**: 49 tests (DB 영속화·세션 MVP·음성 성공경로·인증 시각 테마·회기말 평가 저장·교수자 명시 재평가 저장·교수자 턴 재평가 저장·교수자 UI 평가 재시도·교수자 사용자별 분석·source pack sync·이론모드 저장·동의 철회 후 voice 차단·브라우저 stream PII 마스킹·음성 transcript 저장 실패 UI 표면화 등)
|
||||
- 2026-07-15 전체 검증: `npm run e2e:parallel` **166/166 passed**(2.5분), `npm run e2e:single-run` **49/49 passed**(23.2분). 합계 **215/215 passed**. AI 튜터 DB 영속화는 정상 provider 응답이 2분을 넘을 수 있어 해당 테스트만 같은 실엔진 묶음의 장시간 기준인 240초를 사용한다.
|
||||
- `e2e/voice-success.spec.ts`는 직접 `/voice/ws` 캐스케이드, Session 텍스트 턴의
|
||||
`POST /voice/speech`, Session 마이크 UI를 함께 검증한다. 브라우저 `<audio>.play()`가 차단된 조건에서도
|
||||
Web Audio buffer source 재생이 시작되는지와 Session 마이크 UI가 `audio_end`에 browser voice
|
||||
activity/silence 메타를 싣는지 확인한다.
|
||||
- 2026-07-13 focused 검증: `PLAYWRIGHT_PORT=5271 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1` **2 passed**. 텍스트 턴 저장→소유 회기/턴 기반 `/voice/speech`→OpenAI speech 요청→Web Audio buffer 재생 시작을 확인하고, 별도 새 회기에서 마이크 PCM/STT→AI reply→TTS chunk/`tts_end` 경로가 유지되는지 검증한다. 백엔드 voice focused는 **32 passed**이며, 운영 키 직접 smoke는 `gpt-4o-mini-tts`가 98,133-byte MP3(11.68초)를 반환했다.
|
||||
- 2026-07-13 focused 검증: `PLAYWRIGHT_PORT=5269 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1 --grep "drives one voice turn"` **1 passed**. 같은 로그인에서 텍스트 턴 저장→소유 회기/턴 기반 `/voice/speech`→OpenAI speech 요청→Web Audio buffer 재생 시작을 확인하고, 별도 새 회기에서 마이크 PCM/STT→AI reply→TTS chunk/`tts_end` 경로가 유지되는지 검증한다. 백엔드 voice focused는 **32 passed**이며, 운영 키 직접 smoke는 `gpt-4o-mini-tts`가 98,133-byte MP3(11.68초)를 반환했다.
|
||||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` **7 passed**. 실제 브라우저 `openSessionStream()` → `/sessions/{id}/stream` → DB-backed `/review` 축어록 저장 경로, AI 튜터 코칭 이력 저장/재로딩, WebSocket `stt_result` 음성 비언어 메타데이터, Session 마이크 UI가 생성한 voice activity/silence 메타, Phase 3 pre/post 점수의 DB-backed 저장/재조회, 그리고 세션 종료 background deep 평가가 durable DB row로 저장되어 교수자 리뷰가 `평가 완료`로 전환되는지 검증한다. AI 튜터 코칭 이력 검증은 `POST /live-coach` 응답과 DB-backed history payload의 `status=ready`, `latency_ms>0`도 확인해 규칙 기반 `degraded` fallback 200 응답이 정상 AI 코칭으로 통과하지 못하게 한다.
|
||||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5238 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "finishes session end evaluation"` **1 passed**. 같은 자동 종료 평가 row가 `/teacher/dashboard`의 `recent_sessions`에서도 `evaluation_status=ready`, `review_ready=true`, `supervisor_state=평가 완료`로 반영되는지 실제 DB/API/엔진으로 검증한다.
|
||||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5237 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "explicit teacher session reevaluation"` **1 passed**. 실제 DB/API/엔진에서 학습자 세션과 턴을 만든 뒤 교수자 `POST /eval/sessions/{id}/reevaluate`가 durable `app.session_evaluation` row를 저장하고, `/eval/.../evaluation` 및 `/review`가 `평가 완료`로 반영되는지 검증한다.
|
||||
|
|
@ -276,6 +287,7 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
|||
- 2026-07-01 추가 DB-backed UI 검증: `PLAYWRIGHT_PORT=5245 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "manual AI evaluation retry"` **1 passed**. admin engine config를 잠깐 실패 endpoint로 바꿔 실제 `app.session_evaluation` 실패 row를 만든 뒤 원복하고, 교수자 리뷰 UI의 `AI 평가 재시도` 버튼 클릭이 실제 `POST /eval/sessions/{id}/reevaluate` → durable ready row → `/review` `평가 완료` → `/teacher/dashboard` `evaluation_status=ready`까지 이어지는지 검증한다.
|
||||
- 2026-07-02 focused 검증: `PLAYWRIGHT_PORT=5255 npx playwright test e2e/kb-source-packs.spec.ts --project=chromium-single-run --workers=1` **1 passed**. 관리자 전용 `POST /kb/live-coach/source-packs/sync`가 인증 없이 401, learner 403, admin 202로 동작하고, 0615 워크북·DSM·공식 상담 지침·자살위험 지침 source pack을 DB-backed evaluator RAG에 sync한 뒤 source-scoped `/kb/eval-grounding`이 503 skip 없이 200을 반환하고 같은 `source_id`만 반환하는지 검증한다.
|
||||
- 2026-07-02 focused 검증: `PLAYWRIGHT_PORT=5259 npx playwright test e2e/session-mvp.spec.ts --project=chromium-desktop --workers=1 --grep "AI tutor|stale empty AI tutor quota|quota exhaustion|degraded AI tutor|voice conversation stop"` **7 passed**. route-fixture UI에서 AI 튜터의 stale empty quota가 서버 `GET /live-coach` 재조회 뒤 실제 잔여 기회로 복구되는지, 최신 quota exhaustion 오류가 이전 코칭 카드에 가려지지 않는지, 음성 `reply.conversation_stopped`가 빈 client reply 대신 109 안전 게이트와 socket close를 유지하고 live-coach를 호출하지 않는지 고정한다. 이 fixture들은 UI 회귀 증거이며 DB-backed 엔진 성공 증거가 아니다.
|
||||
- 2026-07-03 backend focused 검증: `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider apps/api/app/test_session_turn_persistence.py -k "live_coach" -q` **7 passed**. live coach LLM 생성은 성공했지만 `audit.llm_call_log` 저장 확인이 실패한 경우 `status=ready` AI 코칭으로 보이지 않고 규칙 기반 `status=degraded` 코칭으로 내려가는지 검증한다. 기존 DB-backed event 저장 실패 503 회귀와 source metadata/degraded fallback 회귀도 같은 focused 묶음에서 유지한다.
|
||||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5251 npx playwright test e2e/session-mvp.spec.ts --project=chromium-desktop --workers=1 --grep "AI tutor|runtime AI tutor"` **4 passed**. mock MVP 코칭 카드에서 `근거 보기` 모달이 source title, `source_pack` kind, `2026-06-15` version, citation을 표시하는지 검증하고, `status=degraded` route fixture는 카드·근거 모달·이력 모달·턴 `C` 마커가 `대체 코칭`/`AI 응답 대체`를 노출하는지 고정한다. 추가 route fixture는 `GET /live-coach` 실패가 빈 이력으로 보이지 않고 alert로 표시되는지, `persistence_source/source=runtime`이 임시 저장 경고로 표시되는지도 고정한다. 이 fixture들은 UI 회귀 증거이며 DB-backed 엔진 성공 증거가 아니다.
|
||||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5252 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 --grep "pre/post"` **1 passed**. 기존 저장 pre/post 점수를 비운 입력이 `저장된 값 기준`으로 오인되지 않고 invalid 상태, alert, 저장 버튼 disabled로 표면화되는지 검증한다.
|
||||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5248 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1 --grep "pending voice transcript"` **1 passed**. mock WebSocket이 `transcript final` 뒤 `turn_persistence_unavailable` error를 보내면 Session UI가 임시 학습자 발화를 정상 턴으로 확정하지 않고 `저장 실패` 배지와 alert로 표면화하며, 대기 중 내담자 말풍선을 제거하고 텍스트 입력을 복구하는지 검증한다.
|
||||
|
|
@ -299,26 +311,31 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
|||
- 2026-07-01 focused 검증: `PLAYWRIGHT_BASE_URL=http://localhost:5210 npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --grep "shows selected learner analysis"` **1 passed** + `PLAYWRIGHT_BASE_URL=http://localhost:5210 npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --grep "professor (student analysis overview|learner detail analysis)"` **2 passed**. 실제 5210 로컬 스택에서 학생 분석 목록의 사용자 표시명, 상세 기본 페르소나별 회기 탭, 페르소나 행 펼침, 전체 회기 탭 전환, 7개 폭 레이아웃 containment를 검증한다.
|
||||
- 2026-07-01 focused 검증: `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider app/test_teacher_dashboard.py -q` **10 passed**. 교수자 대시보드 API는 `session_persistence.list_all_sessions()`로 전체 담당 세션을 읽고, 학습자 일반 목록은 `list_recent_sessions()`로 최근 100개 제한을 명시해 한 학생의 최신 회기가 다른 학생 분석 목록을 밀어내지 않는지 검증한다. 또한 ended session summary가 `app.session_evaluation`의 `evaluation_status`, `review_ready`, `supervisor_state`, `evaluation_error`를 별도로 싣고, 평가 실패가 수동 review status와 섞이지 않는지 검증한다.
|
||||
- 2026-07-01 focused 검증: `npx playwright test e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --grep "approved admin without onboarding"` **2 passed**. 승인된 `role=admin` 사용자의 세션에 `admin_access=false`, `onboarding_completed_at=null`이 남아도 `/admin`이 `/onboarding`으로 우회하지 않고 관리 콘솔의 `/admin/*` API를 호출하는지 fixture로 고정한다.
|
||||
- 2026-07-15 보태니컬 글래스 UI·SSOT 검증: `npm run check:design-ssot` + `npm run typecheck` + `npm run build` 통과, `npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --reporter=line` 시각 게이트 **15/15**, `npx playwright test e2e/auth-visual.spec.ts --project=chromium-single-run --reporter=line` **1/1**, `npx playwright test e2e/session-layout.spec.ts e2e/learner.spec.ts e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile` **46/46**, 로그인→온보딩 focused desktop/mobile **2/2**. 공통 AppShell GNB, Theme store, Surface variant의 소유권과 전 폭 대시보드/리뷰 탭, 로그인·온보딩 라이트/다크 390/1280px, 고해상도 보태니컬 자산을 함께 고정한다. 라이트 테마도 패널당 복수 굴절 그라데이션, backdrop blur, 헤어라인, 그림자를 computed style로 단언하며 모바일 셸이 보태니컬 배경을 제거하지 않는지 검사한다. 관리자 사용자 표는 semantic table, 정렬 헤더, 1440px 최소 폭과 표 전용 가로 스크롤을 desktop/mobile에서 검증한다.
|
||||
|
||||
레이아웃·시각 회귀 게이트(핵심 합격선):
|
||||
|
||||
| 게이트 | 스펙 | 구성 | 개수 |
|
||||
|---|---|---|---|
|
||||
| 세션 레이아웃 | `e2e/session-layout.spec.ts` | 4 테스트 × (desktop+mobile) | **8 / 8** |
|
||||
| 시각 레이아웃 게이트 | `e2e/layout-visual-gate.spec.ts` | `@single-run`, 12개 화면 × 7개 폭 검사 + 다크 테마 assertion | **12 / 12** |
|
||||
| 시각 레이아웃 게이트 | `e2e/layout-visual-gate.spec.ts` | `@single-run`, 14개 화면 × 7개 폭 검사 + 다크 테마 assertion + 학습 대시보드 라이트 자산 연결 | **14 / 14** |
|
||||
| 인증 테마 게이트 | `e2e/auth-visual.spec.ts` | 로그인·온보딩 × light/dark × 390/1280px, 100vw/100dvh 및 overflow 검사 | **1 / 1** |
|
||||
| 레이아웃 포커스(재설계 화면) | `session-layout`·`session-review`·`admin`·`learner`·`settings`·`teacher`, `@single-run` 제외 | desktop+mobile 병렬 | **54** |
|
||||
|
||||
> `layout-visual-gate`는 7개 폭(390/720/861/900/1024/1280/1440)에서 12개 핵심 화면의 가로
|
||||
> `layout-visual-gate`는 7개 폭(390/720/861/900/1024/1280/1440)에서 14개 핵심 화면(페르소나 운영·작성 단계 포함)의 가로
|
||||
> 오버플로·잘린 컨트롤·다크 테마 적용을 검사하고 전체 페이지 스크린샷을
|
||||
> `node_modules/.tmp/layout-gate/`에 남긴다.
|
||||
> `node_modules/.tmp/layout-gate/`에 남긴다. 학습 대시보드는 추가로 라이트 테마의 카드·사이드바·
|
||||
> 우상단 배경 자산이 실제 computed style에 연결됐는지 확인하고 1200px·390px 라이트 캡처를 남긴 뒤 다크로 복귀한다.
|
||||
> `session-layout`은 회기 전/활성 화면이 뷰포트를 벗어나지 않는지, 우측 패널이 코어 영역을
|
||||
> 침범하지 않는지, 시작 후 실제 `session_id` URL에서 새로고침해도 활성 회기 상세가 유지되는지,
|
||||
> 스트림 실패 시 미저장 전사가 남지 않는지를 검증한다.
|
||||
|
||||
### 3.7 공개 인증 스모크(선택)
|
||||
|
||||
`e2e/public-auth-turn.spec.ts`는 공개 사이트(`https://vignette.chanpaca.net`)를 직접 타격하는
|
||||
옵트인 스모크로, 로컬 웹 서버를 띄우지 않는다. 절차는 `apps/web/e2e/README.md` 참고
|
||||
`e2e/public-auth-turn.spec.ts`와 `e2e/public-admin-visual.spec.ts`는 공개 사이트
|
||||
(`https://vignette.chanpaca.net`)를 직접 타격하는 옵트인 스모크로, 로컬 웹 서버를 띄우지 않는다.
|
||||
관리자 시각 스모크는 운영 홈·사용자·권한·티켓의 본문 노출과 브라우저 `pageshow` 탭 복원 뒤
|
||||
스크롤/heading 가시성을 함께 검증한다. 절차는 `apps/web/e2e/README.md` 참고
|
||||
(`E2E_PUBLIC_AUTH=1`, `npx playwright codegen ... --save-storage`로 인증 상태 캡처 후
|
||||
`E2E_PUBLIC_STORAGE_STATE` 재사용). 캡처한 storage state에는 API 세션 쿠키가 들어 있으니
|
||||
민감 정보로 취급한다.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
스레드 생성, 처리 결과 audit trail 확장은 아직 설계/승인 필요. 담당 그룹 자동 배정·우선순위 escalation·raw/rollup
|
||||
보존기간 같은 운영 정책은 B3에서 이미 결정됨(수동 승인·미도입 고정). 자동 수정은 운영자 승인 전까지 실행하지 않는다.
|
||||
|
||||
> B1 완료 항목(셸 구분선, 세션 종료 UX/다크테마, 아바타 PSD v2, SEO/공유 카드, 레이아웃 정렬, 권한 위임,
|
||||
> B1 완료 항목(셸 구분선, 세션 종료 UX/다크테마, 아바타 SVG 리그 복귀와 래스터 비활성화, SEO/공유 카드, 레이아웃 정렬, 권한 위임,
|
||||
> 메일링, 아카이브 API, TTS voice map, 빈상태 레이아웃, 평가 실패 복구 UX, SSE 저장, live-coach 표면화,
|
||||
> 음성 메타, 위기 게이트 UI, 운영 티켓/헬스 샘플러 등)의 상세·검증 로그는 아카이브 full-history 참조.
|
||||
|
||||
|
|
|
|||
|
|
@ -3,20 +3,17 @@
|
|||
> 다른 세션에서 이어서 작업하기 위한 인계. imagegen(gpt-image-2)+BiRefNet+파츠 분리 리깅 파이프라인 전체를 여기서 참조.
|
||||
> 상세 원칙은 `AGENTS.md` §4.
|
||||
|
||||
> 2026-06-27 19:45 추가: 사용자 제공 PSD `라투디 여캐_ver2.psd`를 기준으로 새 파츠 세트 `seoyeon-live2d-psd-v2`를 만들고 P1 실제 세션 기본값으로 연결했다. 이 세트는 컨셉 보드 크롭이 아니라 PSD 레이어 기반이며, `sad` 표정에서 울상 눈썹·우는 입·눈물 파츠를 별도로 합성한다. `p1-concept`는 기본값으로 쓰지 않는다.
|
||||
> **2026-07-13 상태 변경 — PAUSED:** 사용자 피드백에 따라 서연을 포함한 생성 이미지/PSD 래스터 파츠 리그는 실제 화면에서 전부 비활성화했다. 현재 `ClientAvatar`는 모든 페르소나에서 기존 SVG 도형 기반 파라미터 리그만 렌더링한다. 래스터 렌더러·원본·파츠 자산은 재검토 가능하도록 보존하지만 앱 배선에는 연결하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 0. 한 줄 요약
|
||||
파이프라인·렌더러·dev 미리보기까지 **완성**됐으나, 서연 아트가 **잘못된 참조(Image #1=긴 머리)**로 만들어져, **진짜 목표 Image #2(짧은 갈색 보브, 정면)**로 **재생성**이 남았다.
|
||||
래스터 파츠 파이프라인은 보존 상태로 중단했고, 현재 제품과 dev 미리보기는 기존 SVG 도형 기반 파라미터 리그만 사용한다.
|
||||
|
||||
---
|
||||
|
||||
## 1. ⚠️ 다음 세션이 가장 먼저 할 일 (핵심)
|
||||
1. **목표 재확인**: 진짜 타겟은 **Image #2** = `D:\workspace\GLM\.claude-glm\image-cache\73cbbea0-5c27-4ced-91db-16d7a8df602f\2.png`
|
||||
(단일 캐릭터, **짧은 갈색 보브**, 정면 — 현재 서연의 긴 머리와 다름. 사용자가 "잉??" 한 원인).
|
||||
- 비전(analyze_image) 도구가 다중 패널/캐릭터를 자주 혼동했으므로, **재생성 전 사용자에게 Image #2 해석(짧은 보브, 의상 색 등)을 한 번 확인**할 것.
|
||||
2. **Image #2를 `-i` 참조로 재생성**하면 gpt-image-2가 캐릭터를 그대로 따라함(텍스트 묘사 의존 X). 아트 디렉션은 2.png에 맡긴다.
|
||||
## 1. 재개 조건
|
||||
소유자가 래스터 아바타 재도입을 명시적으로 결정하기 전에는 Image #2 재생성, 파츠 보정, 앱 재연결을 진행하지 않는다. 재개 시에도 먼저 별도 미리보기에서 시각 수용 확인을 받아야 한다.
|
||||
|
||||
## 2. 완료된 것 (재사용 가능)
|
||||
- **이미지 생성**: `~/.codex/imagegen-headless/codex_imagegen.sh`(gpt-image-2, ChatGPT 구독). 검증 완료.
|
||||
|
|
@ -30,17 +27,16 @@
|
|||
- **렌더러**: `apps/web/src/components/avatar/RasterBust.tsx`
|
||||
- 파츠 분리: `base` + `upperface-<표정>`(눈썹+눈) + `eyelid-closed`(깜빡임, 1-blink) + `mouth-<표정>` + `mouth-open`(립싱크, RMS).
|
||||
- 28표정→클러스터 매핑(`VARIANT_FOR_EXPRESSION`) 내장.
|
||||
- **연결 배선**:
|
||||
- `persona.ts`: `AvatarPersona.rasterArtSet?: string` 추가됨.
|
||||
- `Session.tsx`: 현재 실제 세션 기본값은 `PERSONA_AVATAR_LOOKS.P1 = { rasterArtSet: "seoyeon-live2d-psd-v2", expressionBias: "sad", ... }` + avatar 객체에 `rasterArtSet` 전달.
|
||||
- `ClientAvatar.tsx`: `useRaster` 분기(rasterArtSet 있으면 `RasterBust`, 없으면 기존 SVG). `data-render-mode` 속성 추가.
|
||||
- `App.tsx`: `/dev/avatar-preview` 라우트(인증 없음).
|
||||
- **dev 미리보기**: `apps/web/src/pages/AvatarPreview.tsx` 기본값은 `seoyeon-live2d-psd-v2`. `?rig=psb`, `?rig=v3`, `?rig=safe`로 과거 세트 비교 가능.
|
||||
- **현재 연결 상태**:
|
||||
- `persona.ts`: `AvatarPersona.rasterArtSet` 계약 제거.
|
||||
- `Session.tsx`·`LearnerHome.tsx`: P1/P4~P7 래스터 아트셋 배선 제거.
|
||||
- `ClientAvatar.tsx`: 항상 SVG 파라미터 리그 렌더, `data-render-mode="svg"` 고정.
|
||||
- `App.tsx`: `/dev/avatar-preview`는 SVG 파라미터 리그 점검 페이지로 유지.
|
||||
- **dev 미리보기**: `apps/web/src/pages/AvatarPreview.tsx`는 서연 SVG 도형 리그의 대표 표정과 모션만 표시한다.
|
||||
- **스크린샷 도구**: `apps/web/scripts/avatar-shot.mjs` (`BASE_URL=http://localhost:<port> node ...`).
|
||||
- **현재 실제 세션 에셋**: `apps/web/public/avatar/seoyeon-live2d-psd-v2/parts/*.png`. 소스/재현 스크립트는 `docs/avatar-art/seoyeon/live2d-psd-v2/`.
|
||||
- **보존 에셋(Image #1=긴 머리 버전)**: `apps/web/public/avatar/seoyeon/{neutral,sad,tired,anxious,warm,startled,eyes-closed,speaking}.png` + `parts/`, `seoyeon-live2d-psb`. 비교용으로 남기되 P1 기본값은 새 PSD v2다.
|
||||
- **보존 에셋**: `apps/web/public/avatar/seoyeon-live2d-psd-v2/parts/*.png`, `apps/web/public/avatar/seoyeon/`, `seoyeon-live2d-psb`. 소스/재현 스크립트는 `docs/avatar-art/seoyeon/live2d-psd-v2/`. 현재 제품에서는 로드하지 않는다.
|
||||
|
||||
## 3. 남은 작업 (순서대로)
|
||||
## 3. 보류된 작업 (래스터 재도입 결정 전 실행 금지)
|
||||
1. Image #2(2.png) 해석 사용자 확인 → ASCII 이름으로 복사: `cp "<2.png 경로>" docs/avatar-art/seoyeon/ref-image2.png`.
|
||||
2. **base 재생성**(검증용 1장):
|
||||
`codex_imagegen.sh --out base-v2.png --size 1024x1280 --quality high -i ref-image2.png "Use the supplied reference as the exact character. Same character, head-and-shoulders bust, perfectly front-facing, symmetrical, neutral calm expression, flat solid light gray #C9CDD2 background, no gradient/shadow/scenery, semi-realistic soft anime. No text/watermark/hands. Bangs above eyes."`
|
||||
|
|
@ -52,9 +48,9 @@
|
|||
## 4. 환경 메모
|
||||
- **dev 서버**: 5173 단일 인스턴스로 띄움(백그라운드). 끊기면 `cd apps/web && npm run dev -- --port 5173 --strictPort`. (5173/5174 중복 인스턴스가 "깨진 화면" 원인이었음 — 항상 5173 단일로.)
|
||||
- codex CLI 0.142.2, auth_mode=chatgpt(구독). object-separation venv: `~/.venvs/object-separation` (CPU-only BiRefNet).
|
||||
- 서연=P1은 API 시드(`apps/api/app/services/persona.py`). 웹엔 P1이 `PERSONA_AVATAR_LOOKS`에 있고 현재 실제 세션 기본값은 rasterArtSet="seoyeon-live2d-psd-v2"다.
|
||||
- 서연=P1은 API 시드(`apps/api/app/services/persona.py`). 웹의 P1 외형/기본 정서값은 유지하지만 래스터 아트셋은 전달하지 않는다.
|
||||
|
||||
## 5. 미해결/결정 필요
|
||||
- Image #2 캐릭터의 **의상 색/추가 디테일** (비전 도구 불안정 → 사용자 확인 필요).
|
||||
- 파츠 분리 리깅 vs 통째 변주 크로스페이드 — 사용자가 "파츠 분리"를 원했으므로 현행(파츠 분리) 유지하되, 결과가 어색하면 통째 변주 방식(`RasterBust` 이전 버전, git 히스토리 참조)으로 롤백 검토.
|
||||
- 래스터 아바타 재도입 여부와 방식은 현재 보류다. 현행 제품 기준은 SVG 도형 기반 파라미터 리그다.
|
||||
- 실제 Live2D Cubism(.moc3) 도입 여부는 소유자 결정(편집기 저작 필요).
|
||||
|
|
|
|||
70
docs/ops/hanshin-meeting-actions-2026-07-13.md
Normal file
70
docs/ops/hanshin-meeting-actions-2026-07-13.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# 2026-07-13 한신대 회의 → 구현 액션 스펙
|
||||
|
||||
> 출처: 2026-07-13(월) 15:30 한신대 회사방문 회의 (경과보고·시연·파일럿 협의).
|
||||
> 회의록 원본·전사록: vault `Projects/수업/2026/한신대 SW중심대학 산학협력 2026/회의록/2026-07-13 AI상담실습플랫폼 회의록 (경과보고·파일럿 협의).md`
|
||||
> 이 문서는 그 회의의 **트웬티온스(개발) 몫 결정사항을 에이전트가 착수 가능한 스펙으로 변환**한 것.
|
||||
> 진행 상태 체크는 `docs/TODO.md` §H가 소유한다. 여기는 요구사항 상세(불변 스펙)만 둔다.
|
||||
|
||||
## 데드라인 컨텍스트
|
||||
|
||||
- **7월 내**: 연구팀 페르소나·프로토콜 입력 + 테스트 완료 → 그 전에 아래 P1이 배포돼 있어야 함.
|
||||
- **7월 말~8월 초**: 효과성 검증 파일럿 (대학원생 약 20명, 집중 사용).
|
||||
- **9~10월**: 개발사→연구팀 점진 이관 (저장소 공유, 추가 개발 인력 합류).
|
||||
|
||||
---
|
||||
|
||||
## P1. 회기 종료 — 시간 기반 전환 (최우선, 파일럿 전 필수)
|
||||
|
||||
**임상 근거(회의 합의)**: 실제 상담은 한 회기에 라포·탐색·개입·정리 4단계가 모두 이뤄지지 않는다. 라포만 여러 회기도 정상. 현행 "4단계 완수 시 종료"(게임 퀘스트식)는 비현실적.
|
||||
|
||||
**요구사항**:
|
||||
1. 세션 종료 조건을 4단계 완수 → **시간 기반(1시간)**으로 전환. 목표 미달이어도 시간 종료 시 종료. (기본 30분이던 것을 1시간으로 — LLM 응답 지연 감안 실대화 40~50분)
|
||||
2. **종료 10분 전 알람** (학습자 화면).
|
||||
3. **회기 시작 전 준비 페이지**: 이번 회기 목표 체크 — 4개 전부가 아니라 **2개 수준** 선택 후 시작.
|
||||
4. 상태머신(라포→탐색→개입→정리)은 **채점·진행 표시용으로 유지** — 종료 조건에서만 분리. 정리 발화 시 자동 종료(기존 동작)는 시간 내 조기 종료 경로로 유지.
|
||||
|
||||
**완료 기준**: 1시간 경과 시 세션 종료 처리(정리 유도 포함) / 10분 전 알람 UI 노출 / 시작 전 목표 체크 페이지 동작 / 백엔드 pytest + web e2e 기존 스위트 pass / SSOT 대시보드 갱신.
|
||||
|
||||
## P2. 회기 계획·달성도 게이지 (설계 → 구현)
|
||||
|
||||
**요구사항(회의 방향)**:
|
||||
1. 단계별 **누적 게이지**: 예) 라포 100 중 이번 회기 30 달성 — 회기가 이어질수록 누적. (초심 상담자는 라포를 1회기에 못 만드는 게 정상이라는 학습 신호)
|
||||
2. 페르소나 수치값(신뢰/개방도)은 **전 회기 공유** — 장기 메모리 누적(기존 구현)과 정합.
|
||||
3. 별도 "치료 계획 탭"은 만들지 않는다(복잡도 우려) — **계획서·프로토콜 파일 첨부 시 자동 반영**이 절충안. 치료계획은 페르소나에 장기/단기 사례 단서로 흡수.
|
||||
4. 회기 간 망각 기능: **불필요로 결론** (교육 목적) — 작업 없음.
|
||||
|
||||
**진행 방식**: 설계 문서(1p) 먼저 → 소유자 확인 → 구현. P1과 독립적으로 진행 가능하나 P1 이후 권장(종료 조건 변경이 게이지 표시에 영향).
|
||||
|
||||
## P3. 파일럿 인프라 대비 (7월 말 전)
|
||||
|
||||
1. **20명 동시 사용 부하 검증** — 세션 동시 생성 시 RAG warm 동시성 이슈 이력 있음. 20 동시 세션 시나리오 부하 테스트 + 병목 기록.
|
||||
2. **사용 지침·규정 문서** — 파일럿 기간 집중 트래픽 대비 사용 가이드(비교 점수 설계는 연구팀과 협의).
|
||||
3. **서버 이관 대비** — 현 윤찬 자택 개인 서버 → 학교 정보처 호스팅/도메인 이관 협의 예정(협의 주체는 연구팀). 개발 몫: Docker Compose 스택의 호스트 이식성 점검(하드코딩 경로·도메인·시크릿 주입 방식), 이관 절차 문서 초안.
|
||||
4. 비용 참고: 실사용 지난달 ~$10, 20명 액티브 시 최대 $100 이내 추정(연구팀이 서버·자문 비용 지원).
|
||||
|
||||
## P4. 데이터 업로드 흐름 검증 (즉시)
|
||||
|
||||
연구팀이 단톡방 자료를 교수자 페이지에 **자유 양식 엑셀(파란 라벨 방식 그대로)** 업로드 시작함("즉시" 합의). 개발 몫:
|
||||
- 자유 양식 엑셀 업로드 → DB 적재 → AI용 변환 → **원본 파기** 흐름이 실데이터로 정상 동작하는지 검증.
|
||||
- 실패 케이스(양식 변형·인코딩) 방어 확인. 원본 추고록은 학교 재산 — DB 원본 미적재·암호화 원칙 유지.
|
||||
|
||||
## P5. 협업 체계 (연구팀 직접 참여 개방)
|
||||
|
||||
1. **저장소 초대** — 소유자(윤찬) 직접 실행. 연구팀 디자인 담당 학생 포함.
|
||||
2. **PR/브랜치 협업 가이드** 문서 작성 — 비개발자(연구팀·학생) 눈높이: 브랜치 생성→수정→PR→리뷰 흐름, 직접 main 덮어쓰기 금지 이유. `docs/guides/`에 배치.
|
||||
3. **UI 문구 수정 프로세스** — 연구팀이 문구 수정 목록을 단톡방으로 전달 → 즉시 반영. 반영 작업은 단순 텍스트 치환 수준으로 유지.
|
||||
|
||||
## 외부 대기 (연구팀 몫 — 구현 착수 금지, 수신 시 반영)
|
||||
|
||||
| 항목 | 내용 |
|
||||
|---|---|
|
||||
| '실패(게임 오버)' 정의 | 어떤 발화가 실패이고 무엇이 표시돼야 하는지 — 연구팀 정의 후 구현 |
|
||||
| 검수 표준화 항목 | 페르소나·AI 피드백 검수 항목(임상=연구팀 설계, 기술=개발 지원). 논문·상품화 validation 근거 |
|
||||
| AI 평가 노출 정책 | 세션 종료 후에만 학생 노출 중 — 노출 범위·시점 정책 확정 대기 |
|
||||
| 페르소나 5~6개 추가 | 연구팀 제작(추고록+프로토콜 쌍, 다음 주까지) — 개발 몫 없음 |
|
||||
|
||||
## 명시적 비(非)작업
|
||||
|
||||
- 비언어(표정·제스처) 레이블링·채점 — **차기 사업으로 분리** (이번 범위 아님).
|
||||
- 회기 간 망각 기능 — 불필요 결론.
|
||||
- 치료 계획 별도 탭 — 만들지 않음 (P2 절충안으로 흡수).
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
## 판단
|
||||
|
||||
페르소나 저작은 교수 콘솔 안의 보조 카드가 아니라 별도 작업면이어야 한다. 이 기능의 본질은 JSON 입력이 아니라, 실제 기록·교재·가이드 자료를 SSOT로 등록하고, RAG가 추출한 근거를 따라 AI 초안을 만들고, 교수자가 임상·회기·말투·안전·프롬프트 계약을 검수하는 저작 워크플로다.
|
||||
페르소나 영역은 교수 콘솔 안의 보조 카드가 아니라 별도 운영·저작 작업면이어야 한다. 이 기능의 본질은 JSON 입력이 아니라, 운영 중인 페르소나의 학습·검수 상태를 파악하고 실제 기록·교재·가이드 자료를 SSOT로 등록한 뒤, RAG가 추출한 근거를 따라 AI 초안을 만들고 교수자가 임상·회기·말투·안전·프롬프트 계약을 검수하는 전체 생명주기다.
|
||||
|
||||
현재 캡처에서 문제였던 지점:
|
||||
|
||||
|
|
@ -33,10 +33,12 @@
|
|||
1. 홈/대시보드는 역할별 작업대다. 학습자는 "다음 연습", 교수자는 "검토할 회기와 위험 신호", 관리자는 "운영 이상과 접근 권한"을 첫 화면에서 바로 처리해야 한다.
|
||||
2. 한 화면에는 하나의 1차 CTA만 둔다. 나머지는 행 액션, 세컨더리 버튼, 필터로 낮춘다.
|
||||
3. 카드 남발을 피한다. 반복 비교가 필요한 정보는 큐/테이블로, 개별 상세만 카드로 둔다.
|
||||
4. PersonaStudio는 3열을 유지하되 의미를 고정한다.
|
||||
- 좌측: 자료 큐, 저작 단계, 검수/공개 카탈로그
|
||||
- 중앙: RAG 자료 등록, 탭별 구조화 편집, 저장/검수 요청
|
||||
- 우측: 검증, 생성 근거, 마스킹, 승인/반려
|
||||
4. PersonaStudio의 현행 정보구조는 상단 horizontal tab을 단일 진입점으로 삼는다.
|
||||
- 대시보드: 공개/활성 페르소나, 학습 인원·세션, 실제 평가·라포 진척, 검수 요약
|
||||
- 카탈로그: 카탈로그 정의, 승인본 공개 규칙, 이론 분포, 버전·출처
|
||||
- 페르소나: 전체 table, 시스템/사용자 생성 구분, 공개/검수/숨김 상태, 상세·수정 drilldown
|
||||
- 검수 현황: 페르소나 탭 내부 2차 horizontal tab으로 두고 승인/반려/수정 작업을 모은다.
|
||||
- 신규/수정: 자료→초안→설정→검토 4단계 독립 작업면. 중앙 편집과 우측 검증/근거의 2열을 사용한다.
|
||||
5. RAG 첨부 파일은 SSOT다. 원문 파일, 마스킹 결과, source/document/chunk hash, 초안 생성 evidence를 한 작업면에서 추적해야 한다.
|
||||
6. 임상·회기·안전 탭은 빈 입력란만 두지 않는다. 각 탭 상단에 작성 목적, 포함할 정보, 피해야 할 입력을 짧게 제시한다.
|
||||
|
||||
|
|
|
|||
69
docs/ops/pilot-usage-guide-draft.md
Normal file
69
docs/ops/pilot-usage-guide-draft.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# 파일럿 사용 지침 (초안) — 대학원생 참가자용
|
||||
|
||||
> **초안 — 연구팀 확정 전.** 접속 방법·연락 채널·세부 문구는 연구팀 확정 후 갱신한다.
|
||||
> 대상: 효과성 검증 파일럿 참가자(대학원생 약 20명, 집중 사용).
|
||||
> 근거: [`hanshin-meeting-actions-2026-07-13.md`](./hanshin-meeting-actions-2026-07-13.md) P3-2, 이번에 반영된 시간 기반 회기 흐름(P1).
|
||||
|
||||
이 도구는 **AI 내담자와 상담 회기를 연습**하고, 회기 중·후에 피드백을 받는 훈련 플랫폼입니다. 실제 내담자가 아니며, 연습 기록은 연구·교육 목적으로 사용됩니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 접속과 로그인
|
||||
|
||||
1. 안내받은 주소로 접속합니다(파일럿 도메인 — 연구팀 공지).
|
||||
2. **Google 계정으로 로그인**합니다. 참가자 명단에 등록된 이메일(학교/승인 도메인)로 로그인하세요.
|
||||
3. 처음 로그인하면 **"승인 대기"** 화면이 나올 수 있습니다.
|
||||
- 운영자가 수업·연구 참여 범위를 확인하는 동안 화면이 열리지 않습니다.
|
||||
- 승인되면 화면의 **"승인 상태 새로고침"** 버튼을 누르거나 다시 접속하면 온보딩으로 넘어갑니다.
|
||||
- 반나절 이상 승인이 안 되면 아래 §6 연락 채널로 이메일을 알려주세요.
|
||||
|
||||
## 2. 온보딩과 동의
|
||||
|
||||
- 첫 진입 시 **온보딩·동의 화면**이 뜹니다. 도구의 목적, 데이터 사용 범위, 유의사항을 읽고 동의해야 회기를 시작할 수 있습니다.
|
||||
- 동의는 언제든 철회 가능합니다(철회 절차는 연구팀 안내를 따름).
|
||||
|
||||
## 3. 회기 진행 방법 (새 흐름)
|
||||
|
||||
이번 파일럿부터 회기는 **"단계 완수"가 아니라 "시간"으로 끝납니다.** 실제 상담처럼 한 회기에 모든 걸 끝내지 않아도 됩니다.
|
||||
|
||||
1. **회기 준비 페이지 — 목표 선택**
|
||||
- 회기를 시작하기 전, 이번 회기에서 집중할 **목표를 1~4개 선택**합니다(라포/탐색/개입/정리 중 — 2개 수준 권장).
|
||||
- 4개를 다 하려 하지 마세요. 초심 상담자가 한 회기에 라포만 다지는 것도 정상입니다.
|
||||
2. **회기 진행 — 약 60분(시간 기반)**
|
||||
- 목표 달성 여부와 관계없이 **시간이 되면 회기가 종료**됩니다.
|
||||
- AI 응답에 약간의 지연이 있어 실제 대화 시간은 40~50분 정도입니다.
|
||||
- 목표를 일찍 이뤄도 시간 안에서는 계속 대화를 이어갈 수 있습니다.
|
||||
3. **종료 10분 전 알람**
|
||||
- 종료 10분 전에 화면에 알람이 뜹니다. 이때부터 **대화를 정리(마무리)** 하는 방향으로 이끌어 주세요.
|
||||
- 마무리 인사를 나눈 뒤에는 시간 전이라도 자연스럽게 회기가 조기 종료될 수 있습니다.
|
||||
4. **회기 종료 후 — 리뷰 확인**
|
||||
- 회기가 끝나면 **리뷰(회기 요약·피드백)** 를 확인할 수 있습니다.
|
||||
- 단계별 진행은 **누적**됩니다. 이번 회기에 라포를 조금 쌓았다면 다음 회기에 이어집니다(회기 간 페르소나 신뢰/개방도도 이어짐).
|
||||
|
||||
## 4. 라이브 코칭 사용법
|
||||
|
||||
회기 **중에** 짧은 슈퍼비전 힌트를 받을 수 있는 기능입니다.
|
||||
|
||||
- 화면의 **코칭 탭**에서 확인합니다. 상담 흐름을 끊지 않도록, 턴을 주고받은 뒤 다음 한 문장을 더 낫게 만드는 짧은 조언(다음 발화 제안 포함)이 나옵니다.
|
||||
- **기회는 회기당 3개**로 시작합니다. 코칭을 한 번 받을 때마다 1개씩 소모됩니다.
|
||||
- **충전(다시 채워지는) 규칙**:
|
||||
- **좋은 발화**로 내담자에게 긍정적 변화 신호(신뢰·개방도 상승)가 확인되면 1개 충전, 또는
|
||||
- **6턴마다** 자동으로 1개 충전(최대 3개까지).
|
||||
- 그래서 잘 진행할수록, 또 오래 진행할수록 코칭 기회가 다시 생깁니다. 아껴 쓰되 막히는 순간에 활용하세요.
|
||||
- 참고: 코칭은 **점수·정답·내담자 내부 설정을 알려주지 않습니다.** 관찰 가능한 상담 행동과 다음 발화 방향만 제시합니다. AI 엔진이 일시적으로 느릴 때는 규칙 기반의 간단한 코칭으로 대체되어 계속 동작합니다.
|
||||
|
||||
## 5. 유의사항 (꼭 읽어주세요)
|
||||
|
||||
- **개인정보를 입력하지 마세요.** 외부 AI로 보내기 전 이름·연락처 등은 **자동 마스킹**되지만, 실명·전화번호·주소·기관명 등은 **처음부터 입력을 자제**하세요. 연습에는 필요하지 않습니다.
|
||||
- **실제 사례의 식별 정보를 넣지 마세요.** 연습용 가상의 상황으로 진행하세요.
|
||||
- **위기 문구 관련 안전 게이트**: 대화 중 자·타해 등 위기 단서가 감지되면, 시스템은 방법을 캐묻지 않고 **안전 확인·보호요인 점검·기관 연결** 방향으로 코칭을 우선합니다. 이는 정상 동작이며 훈련의 일부입니다. 실제 위기 상황이라면 이 도구가 아니라 정식 상담·응급 기관(예: 자살예방상담 109)에 연락하세요.
|
||||
- 이 도구는 **훈련용 시뮬레이션**입니다. AI 내담자의 반응은 학습 목적으로 설계된 것이며 실제 임상 판단의 근거로 삼지 마세요.
|
||||
|
||||
## 6. 문제 발생 시 연락
|
||||
|
||||
- 로그인/승인 지연, 화면 오류, 회기가 뜨지 않음, 코칭이 계속 안 나옴 등의 문제는 **연구팀 지정 채널(단톡방/이메일 — 연구팀 확정)** 로 알려주세요.
|
||||
- 신고 시 도움이 되는 정보: 로그인 이메일, 발생 시각, 회기 ID(리뷰 화면에서 확인 가능), 화면 캡처.
|
||||
|
||||
---
|
||||
|
||||
> 이 문서는 초안입니다. 접속 주소, 연락 채널, 동의 문구, 비교 점수 노출 정책 등은 연구팀 확정 후 갱신합니다.
|
||||
95
docs/ops/refactor-governance-2026-07-15.md
Normal file
95
docs/ops/refactor-governance-2026-07-15.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# 전 저장소 리팩터 거버넌스 실행 기록 — 2026-07-15
|
||||
|
||||
> 상태: **DONE (증거 기반 구조 개선 패스)**
|
||||
> 방법: `refactor-governance` Edit Pass(P1→P8), 동작 보존 우선
|
||||
> 권위 상태: `docs/dev_dashboard.html`과 동기화. 기능 로드맵이나 외부 실증 게이트를 대신하지 않는다.
|
||||
|
||||
## 1. 목표와 불변 조건
|
||||
|
||||
목표는 파일 길이를 기계적으로 줄이는 것이 아니라, 변경 시 서로 어긋날 수 있는 계약·권한·캐시·표시 규칙을 한 소유자로 모으고 반복 IO와 초기 번들 비용을 줄이는 것이었다.
|
||||
|
||||
불변 조건:
|
||||
|
||||
- 인증/RBAC/RLS, 감사 로그, PII 마스킹, durable/degraded 판정은 바꾸지 않는다.
|
||||
- API 응답과 생성 OpenAPI 타입, 엔진 packet, DB 스키마의 의미를 바꾸지 않는다.
|
||||
- 세션 턴 순서, 평가 재시도, 알림 수신자, 화면의 오류 문구와 빈 상태를 보존한다.
|
||||
- 기존 사용자 작업이 섞인 dirty worktree를 되돌리거나 일괄 포맷하지 않는다.
|
||||
- 공통화는 오류 순서·캐시 fallback·side effect가 같은 경우에만 한다.
|
||||
|
||||
## 2. 감사 범위와 최초 증거
|
||||
|
||||
아카이브를 제외한 생산 코드, 테스트, 인프라, 활성 문서를 전수 스캔했다.
|
||||
|
||||
| 영역 | 파일/줄 기준 | 중점 검사 |
|
||||
|---|---:|---|
|
||||
| `apps/api/app` | 100 files / 45,112 lines | 장기 함수·인자 묶음·SQL 반복·캐시·LLM 감사·세션 IO |
|
||||
| `apps/web/src` | 74 files / 46,935 lines | 대형 라우트·중복 view model·API 타입·초기 번들·디자인 소유권 |
|
||||
| `apps/web/e2e` | 25 files / 12,381 lines | 동작/레이아웃/DB 증거 게이트 |
|
||||
| `scripts`, `infra` | 37 files / 7,430 lines | 런타임 스키마와 운영 경로 중복 |
|
||||
| 활성 `docs`, `data` | 111 files / 42,955 lines | SSOT drift·검증 숫자·현재/아카이브 경계 |
|
||||
|
||||
최초 정적 결과는 Ruff 16건, 생산 코드 중복 15 clones / 327 lines(0.60%), 초기 JS 678.88 kB(gzip 195.75 kB), 초기 CSS 328.49 kB(gzip 51.46 kB)였다. Python 함수 1,903개 중 60줄 이상 135개·인자 7개 이상 26개, TS/TSX 함수 1,426개 중 60줄 이상 61개가 후보였다. 이 후보는 길이만으로 수정하지 않고 호출·중복·소유권 증거를 다시 확인했다.
|
||||
|
||||
## 3. 적용한 패치 그룹
|
||||
|
||||
| 그룹 | 변경 | 단일 소유자/효과 |
|
||||
|---|---|---|
|
||||
| P1 삭제·정적 정리 | Ruff 16건 제거, 미사용 컴포넌트·의존성·죽은 export 정리, Knip 게이트 도입 | 참조 0인 코드와 선언 drift 제거 |
|
||||
| P2 shape 안정화 | dataset manifest, voice context/prosody/turn/audio를 명시적 input object로 전환 | 긴 positional/keyword 묶음의 의미를 타입 이름으로 고정 |
|
||||
| P3 계약 SSOT | 평가 write 계약, OpenAPI 생성 타입, `runtime_schema.py`, format/runtime diagnostics를 단일화 | API/DB/runtime/UI 미러 선언 drift 차단 |
|
||||
| P4 캐시 | KB process cache의 key·수명·invalidate 경로를 한 모듈에 고정 | admin sync 뒤 stale source pack 방지 |
|
||||
| P5 workflow | KB/persona source workflow, LLM generate+audit, 알림 수신자, managed session sync, admin normalization 공통화 | 같은 side effect·오류 의미를 한 구현으로 통합 |
|
||||
| P6 성능 | `App.tsx` 전 라우트 lazy loading, 공통 Suspense, 세션 상태/턴 batch read | 초기 payload 감소, missing-evaluation 복구 1+2N query를 3 query로 축소 |
|
||||
| P7 인프라 | dev runtime schema와 `infra/db/init` 정의 정합, prod DDL fail-closed, E2E fixture/single-run 2단계 실행 | 개발 자동보강과 운영 migration 역할 분리, 단일 DB/engine 포화 방지 |
|
||||
| P8 UI/디자인 | Surface/AppShell/theme/페르소나 시각 view model SSOT, 대형 화면의 순수 모델·음성 캡처 분리, raw-color 예산 게이트 | 공통 primitive와 화면 예외의 소유권을 테스트 가능한 규칙으로 고정 |
|
||||
|
||||
추출된 주요 경계:
|
||||
|
||||
- 백엔드: `services/llm_audit.py`, `runtime_schema.py`, 평가/세션/알림/KB의 named contract와 batch loader.
|
||||
- 프론트: `lib/personaViewModel.ts`, `lib/runtimeDiagnostics.ts`, `pages/admin/dataNormalization.ts`, `pages/persona-studio/model.ts`, `pages/learner-home/model.ts`, `pages/session-review/model.ts`, `pages/session/voiceCapture.ts`.
|
||||
- 거버넌스: `check:dead-code`, `check:duplication`, `check:design-ssot`, `check:api-types`.
|
||||
|
||||
## 4. 전후 측정
|
||||
|
||||
| 지표 | 이전 | 현재 | 판정 |
|
||||
|---|---:|---:|---|
|
||||
| 생산 코드 중복 | 15 clones / 327 lines / 0.60% | 1 clone / 15 lines / 0.03% | 95.4% duplicated-line 감소, threshold 0.05% 게이트 |
|
||||
| 초기 JS | 678.88 kB / gzip 195.75 kB | 241.49 kB / gzip 77.13 kB | gzip 60.6% 감소 |
|
||||
| 초기 CSS | 328.49 kB / gzip 51.46 kB | 28.59 kB / gzip 6.45 kB | gzip 87.5% 감소 |
|
||||
| `Session.tsx` | 4,002 lines | 3,491 lines + `voiceCapture.ts` | 음성 브라우저 경계 분리 |
|
||||
| `LearnerHome.tsx` | 2,053 lines | 1,857 lines + 222-line model | 표시 계산을 순수 모델로 분리 |
|
||||
| API 단위 테스트 | 393 | 400 passed | 신규 구조 회귀 포함 |
|
||||
| Gateway 단위 테스트 | 27 | 29 passed | packet/model 계약 포함 |
|
||||
| Playwright 수집 | 214 / 20 files | 215 / 20 files | 현행 목록 동기화 |
|
||||
|
||||
## 5. 의도적 비추출과 통제된 예외
|
||||
|
||||
1. `session_persistence.py`의 15-line clone 한 건은 유지한다. 두 principal-aware acquire 경로는 표면 구조만 같고 예외 시 cache fallback과 durable 판정이 다르다. 합치면 실패 의미가 숨겨지므로 `jscpd` 전체 threshold 안의 근거 있는 제외다.
|
||||
2. `Session`, `Admin`, `PersonaStudio`, `Professor`의 라우트 컨테이너는 여전히 크지만 현재 생산 TS/TSX 중복은 0이다. 화면별 상태 전이까지 무리하게 generic hook/component로 만들면 읽기 비용과 prop surface가 늘어난다. 새 기능이 독립 상태·독립 E2E를 가질 때 해당 slice를 추출한다.
|
||||
3. 세션 dark stage와 아바타/인증 아트의 국소 raw color는 전역 토큰으로 승격하지 않는다. 공통 `ui.css`/`shell.css`는 raw color 0개를 강제하고, 예외 파일은 2026-07-15 기준 수치 이상 증가하지 못하도록 고정 예산을 둔다.
|
||||
4. 전체 215개 Playwright에는 실제 DB·엔진·provider가 필요한 single-run 시나리오가 섞인다. 기존 direct runner는 로컬에서 12 workers로 세 프로젝트를 동시에 실행해 188 passed/27 resource-timeout을 만들었고, single-run을 뺀 12-worker fixture 단계도 145/166 뒤 21 request timeout을 재현했다. `npm run e2e`를 fixture desktop/mobile `workers=4` 단계 뒤 DB/engine `workers=1` 단계가 시작되는 구조로 바꿨다.
|
||||
5. 관리자 티켓 큐는 최대 120개 복합 카드를 한 번에 다시 그리며 액션 pending 상태까지 페이지 루트가 소유했다. 카드 렌더 상한을 24개로 두고 나머지는 서버 검색/필터로 탐색하게 했으며, 중복 요청 잠금과 시각 pending은 `TicketActions`가 카드 단위로 소유한다. 네이티브 입력 dispatch와 React 상태 렌더를 분리해 해결/중복 연결 실제 클릭 4건이 desktop/mobile에서 통과한다.
|
||||
6. Settings 초기 로드는 React 개발 모드 effect 재실행으로 같은 GET 두 개가 경합할 수 있었고, 늦게 끝난 응답이 사용자가 방금 바꾼 엔진 모드·모델을 원래 값으로 덮어썼다. 요청 세대 번호를 추가해 최신 로드만 상태를 반영하고 unmount된 요청은 무효화했다. 기존 engine settings E2E가 실제 PATCH body와 복구까지 고정한다.
|
||||
7. AI 튜터 DB 영속화는 정상 provider 응답이 독립 실행에서도 138.5초 걸려 기존 150초 테스트 상한과 여유가 11초뿐이었다. 기능 timeout을 숨기지 않고 동일 파일의 장시간 실엔진 기준인 240초로 테스트 예산을 조정했으며, 최종 전체 직렬 실행에서는 2.1분에 ready 응답·source pack·DB history·reload 증거를 모두 통과했다.
|
||||
|
||||
## 6. 검증 증거
|
||||
|
||||
- `ruff check app` — passed.
|
||||
- `pytest -q app` — **400 passed**, Starlette `python_multipart` PendingDeprecationWarning 1건.
|
||||
- `pytest -q engine_gateway` — **29 passed**, 같은 외부 의존 warning 1건.
|
||||
- `npm run typecheck` / `check:api-types` / `check:design-ssot` / `check:dead-code` / `check:duplication` — passed.
|
||||
- `npm run build` — 109 modules, initial JS 241.49 kB(gzip 77.13 kB), initial CSS 28.59 kB(gzip 6.45 kB).
|
||||
- `npm audit --audit-level=high` — 0 vulnerabilities.
|
||||
- `npx playwright test --list` — **215 tests / 20 files**.
|
||||
- `npm run e2e:parallel` — **166 passed** (desktop 83 + mobile 83, workers=4).
|
||||
- `npm run e2e:single-run` — **49 passed** (DB/engine/provider 직렬, 23.2분).
|
||||
- 전체 Playwright 최종 판정 — **215 / 215 passed**.
|
||||
- `git diff --check` — passed; checkout 정책에 따른 LF→CRLF 경고만 존재.
|
||||
|
||||
## 7. 앞으로의 변경 규칙
|
||||
|
||||
- 새 DTO/packet은 OpenAPI·schema·named adapter 중 한 곳만 원본으로 둔다.
|
||||
- 새 캐시는 key, SoR, invalidate, TTL, 실패 시 durable/degraded 의미를 함께 정의한다.
|
||||
- 새 공통화는 `check:duplication`의 실제 clone 또는 둘 이상의 동일 side effect가 증거일 때만 한다.
|
||||
- 새 페이지 색은 먼저 의미 토큰을 사용하고, 국소 예외면 이유와 raw-color 예산을 함께 갱신한다.
|
||||
- 라우트 컨테이너 추출은 독립 입력/출력, 독립 상태, 독립 테스트가 생긴 뒤 한다. 파일 길이만으로 분리하지 않는다.
|
||||
191
docs/ops/server-migration-runbook-draft.md
Normal file
191
docs/ops/server-migration-runbook-draft.md
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
# 서버 이관 런북 (초안) — 자택 개인 서버 → 학교 정보처 호스팅
|
||||
|
||||
> **초안 — 연구팀·정보처 협의 후 확정.** 이 문서는 개발(트웬티온스) 몫인 *기술 이관 절차* 초안이다.
|
||||
> 도메인·서버 사양·비용·보안 정책의 최종 결정 주체는 연구팀·한신대 정보처이며, 아래 값과 절차는 협의 결과에 맞춰 갱신한다.
|
||||
> 스펙 근거: [`hanshin-meeting-actions-2026-07-13.md`](./hanshin-meeting-actions-2026-07-13.md) P3.
|
||||
|
||||
## 0. 요약 (한 장)
|
||||
|
||||
- **현재 운영 형태**: 윤찬 자택 Windows PC에서 `scripts/start-public-runtime.ps1`로 **베어 프로세스**(uvicorn + `vite preview` + 로컬 `claude -p` 게이트웨이)를 띄우고, **Cloudflare 터널**로 공개하며, **Windows 작업 스케줄러 워치독**이 살아있게 유지한다. → 이 경로 전체가 Windows 종속이라 리눅스로 그대로 못 옮긴다.
|
||||
- **이관 목표 형태**: 학교 리눅스 서버에서 **`infra/docker-compose.yml` 스택**(web·api·db·proxy)을 그대로 기동. Docker 스택은 이식성을 전제로 설계돼 있어 Windows 런타임 스크립트 계층을 대부분 대체한다.
|
||||
- **가장 큰 이관 블로커 2개**: ① 엔진 모드 `claude_cli`(개인 구독 CLI 인증) → `claude_api`(`ANTHROPIC_API_KEY`) 전환 필수. ② 하드코딩된 도메인/리다이렉트 URI/허용 호스트를 학교 도메인으로 교체(일부는 **빌드 시점**에 굽혀서 재빌드 필요).
|
||||
|
||||
---
|
||||
|
||||
## 1. 사전 요구사항 (이관 시작 전 확보)
|
||||
|
||||
### 1.1 정보처가 준비할 것
|
||||
- **리눅스 호스트**: Docker Engine + Docker Compose v2 설치 가능한 서버(Ubuntu 22.04 LTS 등). 20명 파일럿 기준 최소 4 vCPU / 8GB RAM / 40GB SSD 권장(부하 검증 결과에 맞춰 조정).
|
||||
- **공개 도메인 2개**: 웹용 1개 + API용 1개(예: `vignette.hs.ac.kr`, `api-vignette.hs.ac.kr`). **웹과 API는 별도 호스트네임**이어야 한다(OAuth 콜백·CORS 설계 전제).
|
||||
- **DNS A/AAAA 레코드**: 위 두 도메인이 서버 공인 IP로 향하도록 설정.
|
||||
- **TLS**: 다음 중 하나.
|
||||
- (a) 서버에 공인 IP가 있으면 **Caddy 자동 TLS**(Let's Encrypt) 사용 — `.env`의 `SITE_ADDRESS`, `ACME_EMAIL`만 채우면 됨.
|
||||
- (b) 정보처가 기관 인증서를 강제하면 Caddy 앞단에 리버스 프록시를 두거나 인증서를 Caddy에 주입(협의 필요).
|
||||
- **아웃바운드 네트워크 허용**: `api.openai.com`, `api.anthropic.com`(엔진 claude_api 모드), Google OAuth 엔드포인트로의 HTTPS egress.
|
||||
|
||||
### 1.2 연구팀·개발이 준비할 것
|
||||
- **Google OAuth 클라이언트**: Google Cloud Console에서 **Authorized redirect URI에 `https://api-<학교도메인>/auth/callback` 추가**(기존 chanpaca/18ka URI는 유지 또는 정리). Client ID/Secret 확보.
|
||||
- **API 키**: `OPENAI_API_KEY`(음성 STT/TTS), `ANTHROPIC_API_KEY`(엔진 claude_api 모드 — 아래 §2 참조).
|
||||
- **관리자·교수자 이메일 목록**: `AUTH_SUPER_ADMIN_EMAILS`, `AUTH_ADMIN_EMAILS`, `AUTH_TEACHER_EMAILS`, 허용 도메인 `AUTH_ALLOWED_EMAIL_DOMAINS`(파일럿 참가자 도메인 포함).
|
||||
- **현재 운영 데이터 스냅샷**: DB 덤프 + uploads 디렉터리(아래 §4).
|
||||
|
||||
---
|
||||
|
||||
## 2. 엔진 모드 주의 — claude_cli → claude_api (필수)
|
||||
|
||||
현재 운영은 `ENGINE_MODE=claude_cli`다. 이는 **호스트에 상주하는 개인 `claude` CLI(개인 구독 OAuth 인증)** 를 게이트웨이(`http://host.docker.internal:9099`)로 호출하는 방식이라 **학교 서버에는 옮길 수 없다**(개인 계정 인증에 묶임).
|
||||
|
||||
학교 서버에서는 반드시 다음으로 전환한다.
|
||||
|
||||
```dotenv
|
||||
ENGINE_MODE=claude_api
|
||||
ANTHROPIC_API_KEY=sk-ant-... # 연구팀/기관 발급 키
|
||||
# ENGINE_URL 은 claude_api 모드에서 사용하지 않음 (host.docker.internal 라인 무시됨)
|
||||
```
|
||||
|
||||
- 코드는 `claude_api`를 **기본값으로 지원**한다(`apps/api/app/config.py`의 `engine_mode` 기본이 `claude_api`). 별도 개발 없이 env 전환으로 동작한다.
|
||||
- 비용: 회의 기록상 실사용 지난달 ~$10, 20명 액티브 시 최대 $100 이내 추정. 과금은 `ANTHROPIC_API_KEY` 계정으로 발생 → 연구팀/기관 계정 사용 권장. 관리자 UI에서 `ADMIN_USAGE_BUDGET_USD`로 예산 표시 가능.
|
||||
- `claude_cli` 게이트웨이(`apps/api/engine_gateway/`), 로컬 `claude -p` 상주풀, `ENGINE_READY_TTL_SECONDS` 튜닝은 **모두 불필요**해진다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 이관 단계
|
||||
|
||||
### 3.1 코드 배치 + 환경파일 작성
|
||||
```bash
|
||||
git clone <저장소> vignette && cd vignette/infra
|
||||
cp .env.example .env
|
||||
# .env 편집 — 아래 §5 체크리스트의 "설정만 바꾸면 됨" 값을 학교 값으로 교체
|
||||
```
|
||||
배포 전 프리플라이트(읽기 전용 검증):
|
||||
```bash
|
||||
# 저장소 루트에서
|
||||
python scripts/check-deploy-preflight.py --env-file infra/.env
|
||||
# DB 앱롤까지 검증하려면 app-role DATABASE_URL 을 넘기고 --require-app-role
|
||||
```
|
||||
이 스크립트는 필수 env 누락, `change-me` 류 플레이스홀더, prod에서 켜지면 안 되는 플래그(`AUTH_DEV_LOGIN_ENABLED` 등), 잘못된 `ENGINE_MODE`를 잡아준다.
|
||||
|
||||
### 3.2 DB dump / restore
|
||||
|
||||
현재 DB(자택)에서 덤프를 뜬다. 접속 방식에 맞춰 하나 선택.
|
||||
|
||||
```bash
|
||||
# (A) DB가 docker compose db 컨테이너인 경우 — 자택 서버에서
|
||||
docker compose exec -T db pg_dump -U vignette_owner -Fc vignette > vignette-$(date +%F).dump
|
||||
|
||||
# (B) DB가 외부/NAS PostgreSQL인 경우 — 접속 문자열로 직접
|
||||
pg_dump --format=custom --no-owner --no-privileges \
|
||||
--dbname="postgresql://vignette_owner:<PW>@<OLD_HOST>:5432/vignette" \
|
||||
--file=vignette-$(date +%F).dump
|
||||
```
|
||||
|
||||
학교 서버에서 복원. **권장: 스키마 init 스크립트가 만든 빈 DB가 아니라, 완전히 빈 DB에 풀 덤프를 복원**한다(스키마·데이터·RLS 정책을 덤프가 통째로 재현하도록).
|
||||
|
||||
```bash
|
||||
# 새 호스트: db 컨테이너만 먼저 띄우되 init 스크립트와 충돌을 피하려면
|
||||
# - 신규 빈 볼륨 + init 스크립트로 스키마 생성 후 --data-only 복원, 또는
|
||||
# - init 없이 빈 DB 생성 후 풀 덤프 복원(권장, 아래)
|
||||
docker compose up -d db
|
||||
docker compose exec -T db psql -U vignette_owner -c "CREATE DATABASE vignette_new;"
|
||||
cat vignette-YYYY-MM-DD.dump | docker compose exec -T db \
|
||||
pg_restore --no-owner --no-privileges --clean --if-exists \
|
||||
-U vignette_owner -d vignette_new
|
||||
# 검증 후 vignette_new 를 정식 DB로 승격(또는 처음부터 vignette 로 복원)
|
||||
```
|
||||
|
||||
> ⚠️ **RLS/앱롤 주의**: 스키마는 `infra/db/init/`의 `01_extensions.sql … 06_session_evaluation.sql`이 **빈 볼륨 최초 기동 시 1회만** 실행한다. 여기엔 RLS 정책(`04_audit_eval_rls.sql`)과 앱 전용 롤 생성(`99_app_role.sh`)이 포함된다. 풀 덤프 복원 시 롤/권한이 덤프에 없으면(=`--no-owner --no-privileges` 사용) **복원 후 `99_app_role.sh` 상당의 앱롤 GRANT를 다시 적용**해야 API app-role(`vignette_app`)이 붙는다. 프리플라이트 `--require-app-role`로 확인.
|
||||
|
||||
### 3.3 persona / KB 데이터 이동
|
||||
페르소나·평가·라이브코칭 KB는 **저장소가 원본을 소유**하고 스크립트로 DB에 동기화한다. DB 덤프에 이미 승인된 `persona_card` 행이 포함되지만, KB 소스팩은 코드 기준으로 재동기화한다.
|
||||
```bash
|
||||
# dry-run(기본): repo 소스팩 해시 vs 활성 kb.document 비교
|
||||
python scripts/sync-persona-sources.py
|
||||
# 실제 반영
|
||||
python scripts/sync-persona-sources.py --apply
|
||||
```
|
||||
- 페르소나 시드 재생성이 필요하면 `python scripts/materialize-persona-seeds.py`.
|
||||
- 원칙: prod에서는 `AUTO_SEED_PERSONAS=false`, `ALLOW_SEED_PERSONA_FALLBACK=false` 유지(승인된 DB 행만 사용). 추고록 원본 등 학교 재산 데이터는 repo·이미지에 넣지 않는다.
|
||||
|
||||
### 3.4 uploads 디렉터리 이동
|
||||
사용자 업로드(교수자 자료 등)는 `apiuploads` 도커 볼륨(`/app/uploads`, `USER_UPLOAD_DIR`)에 있다.
|
||||
```bash
|
||||
# 자택: 볼륨 내용 tar 로 추출
|
||||
docker run --rm -v vignette_apiuploads:/data -v "$PWD":/backup alpine \
|
||||
tar czf /backup/uploads-$(date +%F).tgz -C /data .
|
||||
# 학교: 새 볼륨에 복원
|
||||
docker run --rm -v vignette_apiuploads:/data -v "$PWD":/backup alpine \
|
||||
tar xzf /backup/uploads-YYYY-MM-DD.tgz -C /data
|
||||
```
|
||||
|
||||
### 3.5 기동
|
||||
```bash
|
||||
cd infra
|
||||
docker compose build # 웹은 도메인이 빌드에 굽히므로 §5 빌드 인자 확인 후 빌드
|
||||
docker compose up -d
|
||||
docker compose ps # web/api/db/proxy healthy 확인
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 이식성 이슈 체크리스트
|
||||
|
||||
### 4.1 설정만 바꾸면 되는 것 (env / DNS / 콘솔 — 재빌드·코드수정 불필요)
|
||||
| 항목 | 현재 하드코딩/기본값 | 이관 시 조치 |
|
||||
|---|---|---|
|
||||
| API 리다이렉트 URI | `OAUTH_REDIRECT_URI=https://api-vignette.chanpaca.net/auth/callback` | 학교 API 도메인으로 교체 + Google Console에 등록 |
|
||||
| 프론트 기준 URL | `FRONTEND_BASE_URL=https://vignette.chanpaca.net` | 학교 웹 도메인 |
|
||||
| 콜백→프론트 매핑 | `FRONTEND_ORIGIN_MAP={api-vignette.chanpaca.net:…, api-vnet.18ka.net:…}` | 학교 `api-호스트: https://웹-호스트` |
|
||||
| CORS 허용 | `CORS_ORIGINS=[chanpaca.net, 18ka.net, pages.dev, localhost…]` | 학교 웹 도메인만(로컬 항목 제거) |
|
||||
| 슈퍼관리자 | `AUTH_SUPER_ADMIN_EMAILS=[yunchan@twentyoz.kr, hoonjungkoo@hs.ac.kr]` | 운영 담당자 이메일 |
|
||||
| 허용 도메인 | `AUTH_ALLOWED_EMAIL_DOMAINS=[hs.ac.kr, twentyoz.kr]` | 파일럿 참가 도메인 |
|
||||
| 시크릿 | `POSTGRES_PASSWORD/APP_DB_PASSWORD/SESSION_SECRET`(=`change-me…`) | 랜덤 강값으로 교체 |
|
||||
| TLS/노출 | `SITE_ADDRESS=:80`, `ACME_EMAIL=`(빈값) | 학교 웹 도메인 + 관리자 메일 |
|
||||
| 포트 | `HTTP_PORT=8080`, `HTTPS_PORT=8443` | 정보처 방화벽 정책에 맞춰 |
|
||||
| 엔진 | `ENGINE_MODE=claude_cli` | `claude_api` + `ANTHROPIC_API_KEY`(§2) |
|
||||
|
||||
### 4.2 고쳐야(재빌드/치환해야) 넘어가는 것
|
||||
| 항목 | 위치 | 왜 블로커인가 | 조치 |
|
||||
|---|---|---|---|
|
||||
| **웹 허용 호스트** | `apps/web/vite.config.ts` `defaultAllowedHosts`(chanpaca/18ka/ts.net 배열) | `vite preview`가 Host 헤더를 검증 → 학교 도메인 누락 시 전체 화면이 403 블랭크. **빌드 시점 값**이라 재빌드 필요 | 빌드 전 `VITE_ALLOWED_HOSTS=<학교 웹 도메인>` 환경변수로 주입(코드 수정 없이 가능) |
|
||||
| **웹 API 베이스** | compose `web` 빌드 인자 `VITE_API_BASE=${PUBLIC_API_BASE:-/api}` | 프론트가 붙는 API 경로가 **빌드에 굽힘** | Caddy 프록시로 `/api` 동일 오리진 유지 시 기본값 그대로 OK. 별도 API 도메인 직결이면 `PUBLIC_API_BASE` 지정 후 재빌드 |
|
||||
| **엔진 인증 방식** | `ENGINE_MODE=claude_cli` 런타임 경로 | 개인 구독 CLI 인증 → 학교서버 이식 불가 | §2대로 `claude_api` 전환(코드 지원됨, env 전환) |
|
||||
| **Windows 런타임 계층 전체** | `scripts/*.ps1`, `.vbs`, Windows 작업 스케줄러(`install-public-runtime-task.ps1`, `register-boot-task.ps1`), `Get-CimInstance`, winget 경로 | Windows 전용. `$Workspace="D:\workspace\vignette"`, `$Python=%LOCALAPPDATA%\…`, `$Cloudflared`, `$USERPROFILE\.cloudflared` 등 하드코딩 | 리눅스에서는 **docker compose 스택 + systemd/cron 헬스체크**로 대체(PowerShell 스크립트 미사용). Cloudflare 터널은 공인 IP+Caddy 자동TLS로 대체 권장 |
|
||||
| **config.py 하드코딩 기본값** | `apps/api/app/config.py`의 `oauth_redirect_uri/frontend_origin_map/cors_origins/auth_super_admin_emails` 기본값에 chanpaca·18ka·특정 이메일 | env로 override되지만, env 누락 시 자택 값으로 조용히 폴백될 위험 | env를 반드시 채우고(§4.1), 이관 확정 후 기본값도 중립화 검토(별도 PR) |
|
||||
|
||||
> **비밀 주입 방식**: 현재는 `infra/.env` **평문 파일** 한 장(요구변수는 compose `${VAR:?}` 문법으로 미설정 시 기동 실패 → 누락 방어는 됨). 학교 서버에서는 `.env` 파일 권한 `chmod 600` + repo 미포함 확인, 가능하면 정보처 시크릿 관리 수단으로 승격 협의. 시크릿 매니저/볼트는 현재 미사용.
|
||||
|
||||
---
|
||||
|
||||
## 5. 이관 후 검증 체크리스트
|
||||
|
||||
```bash
|
||||
# 1) 헬스 — environment=prod, db=true, engine=true, engine_mode=claude_api 확인
|
||||
curl -s https://api-<학교도메인>/health | jq
|
||||
# 기대: {"status":"ok","environment":"prod","db":true,"engine":true,"engine_mode":"claude_api", ...}
|
||||
|
||||
# 2) dev-login 꺼짐 확인 — prod에서 AUTH_DEV_LOGIN_ENABLED=false 여야 함(preflight도 검증)
|
||||
# /auth/dev-login 류가 비활성인지, 로그인은 Google OAuth로만 되는지 확인
|
||||
|
||||
# 3) OAuth 왕복 — Google 로그인 → 콜백 → pending 승인 대기 → 슈퍼관리자 승인 → 온보딩 진입
|
||||
|
||||
# 4) /turn 스모크 — 세션 1개 생성 후 발화 1턴 왕복(내담자 응답 + 라이브 코칭 카드 수신)
|
||||
|
||||
# 5) 백엔드 pytest
|
||||
cd apps/api && python -m pytest -q
|
||||
|
||||
# 6) (선택) 20 동시 세션 부하 — scripts/load-test-sessions.py 로 병목 재확인
|
||||
```
|
||||
|
||||
추가 확인:
|
||||
- 음성 SSE/WSS 경로가 Caddy `flush_interval -1`로 버퍼링 없이 흐르는지(자막·실시간 피드백).
|
||||
- persona 목록이 승인 DB 행 기준으로 뜨는지(시드 폴백 아님).
|
||||
- uploads 업로드→변환→원본 파기 흐름(P4)이 실데이터로 동작하는지.
|
||||
|
||||
---
|
||||
|
||||
## 6. 롤백 / 주의
|
||||
|
||||
- 이관은 **자택 운영을 내리기 전에** 학교 서버에서 병렬로 세워 검증하고, 검증 통과 후 DNS를 전환한다(다운타임 최소화).
|
||||
- DB 덤프는 **이관 시점 스냅샷**이다. 병렬 운영 중 자택에서 발생한 신규 데이터는 최종 컷오버 시점에 재덤프·재복원(또는 컷오버 동안 자택 쓰기 중지).
|
||||
- OAuth redirect URI를 학교 도메인으로 바꾸는 순간 기존 도메인 로그인은 깨질 수 있으니, 컷오버 타이밍을 연구팀과 합의.
|
||||
- 확정 후 이 문서의 "초안" 표기를 제거하고 [`DEPLOYMENT.md`](../DEPLOYMENT.md)·SSOT 대시보드에 반영한다.
|
||||
48
docs/ops/session-progress-gauge-design-2026-07-14.md
Normal file
48
docs/ops/session-progress-gauge-design-2026-07-14.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# P2 회기 계획·달성도 게이지 설계 (1p)
|
||||
|
||||
> 2026-07-13 한신대 회의 P2 결정의 구현 설계. 소유자 확인용 1페이지.
|
||||
> 원칙: 게이지는 **백엔드 결정론 상태머신 수치에서 파생**하며, LLM이 만들지 않는다.
|
||||
> CCD·정답 라벨·ideation 등 내담자 내부 설정은 계속 비노출(M6). 게이지는 "훈련 진행 신호"만 보여준다.
|
||||
|
||||
## 1. 무엇을 보여주나
|
||||
|
||||
| 표면 | 내용 |
|
||||
|---|---|
|
||||
| 세션 화면(좌측 트랙) | 단계별 누적 게이지 4개 — "라포 100 중 65" 식. 이번 회기 목표 단계에 목표 배지(P1 구현됨) |
|
||||
| 세션 화면(관찰 패널) | **상세 수치**: 유효 개방도(%), 방어(저항) 수준(%), 라포 누적(%) + 이번 회기 증가분(+N%p) |
|
||||
| 학습자 홈(페르소나 카드) | 페르소나별 라포 누적 게이지 — 회기가 이어질수록 차오르는 학습 신호 |
|
||||
|
||||
**학습 신호 설계 의도(회의 합의)**: 초심 상담자는 라포를 1회기에 못 만드는 게 정상이다.
|
||||
게이지가 회기를 건너 누적되는 모습 자체가 "여러 회기에 걸쳐 쌓는 것"임을 가르친다.
|
||||
|
||||
## 2. 게이지 공식 (결정론)
|
||||
|
||||
상태머신(`state_machine.py`)의 기존 수치만 사용한다. 새 상태 없음.
|
||||
|
||||
- 단계 전이 조건은 `turns_in_stage >= STAGE_MIN_TURNS[stage]` AND `rapport_credit >= STAGE_ADVANCE_RAPPORT[stage]`.
|
||||
- **단계 게이지**(stage s):
|
||||
- 지나온 단계: `100`
|
||||
- 미래 단계: `0`
|
||||
- 현재 단계: `min(99, rapport_credit / STAGE_ADVANCE_RAPPORT[s] × 99)` — 라포 누적이 게이지의 본질.
|
||||
(두 게이트를 모두 충족해 전이 대기 상태면 99에서 대기, 전이 순간 100)
|
||||
- `정리`(CLOSE)는 진입 자체가 100 (전이 임계 없음)
|
||||
- **누적성**: `rapport_credit`은 회기 종료 시 ×0.7로 이월(`init_state(carry)`, 기존 구현)되므로
|
||||
다음 회기 게이지는 0이 아니라 이월분에서 시작한다 → "누적 게이지"가 자동 성립.
|
||||
- **이번 회기 증가분**: `rapport_credit − prev_rapport_credit`(세션 생성 시 저장된 이월 기준값, 기존 컬럼).
|
||||
- **방어(저항) 수준**: `resistance`(0~1)를 %로. 학습자 표기는 "방어 신호(저항)" — 학술 용어화.
|
||||
- **유효 개방도**: `effective_openness`(이미 학습자 노출 중인 값)를 %로.
|
||||
|
||||
## 3. 계약 변경
|
||||
|
||||
- `session_read_model.py`에 `SessionProgress` DTO 신설:
|
||||
`stages[] {stage, percent, achieved, is_goal}` + `rapport_percent, rapport_delta_percent, resistance_percent, openness_percent`.
|
||||
- 파생 함수는 순수함수 `build_session_progress(state, prev_rapport_credit, goal_stages)` — 상태머신 상수를 단일 원천으로 읽는다.
|
||||
- 노출 지점: `SessionDetailResponse.progress`(새로고침 복원), `TurnResponse.progress` + 스트림 `done` payload(턴마다 갱신),
|
||||
대시보드 `persona_progress[].rapport_percent`(홈 카드).
|
||||
- **비노출 유지**: ideation_stage, CCD, 정답 라벨, raw resistance 원값 명칭("저항 엔진" 등 내부 용어).
|
||||
|
||||
## 4. 비(非)작업 (회의 결정 준수)
|
||||
|
||||
- 별도 "치료 계획 탭" 없음 — 계획서·프로토콜은 페르소나 RAG 첨부(`/personas/sources`, 기존 경로)로 흡수.
|
||||
- 회기 간 망각 기능 없음.
|
||||
- 페르소나 수치의 전 회기 공유는 기존 carry-over(라포 ×0.7, resistance drift)가 이미 소유 — 변경 없음.
|
||||
51
docs/ops/tagged-data-utilization-2026-07-15.md
Normal file
51
docs/ops/tagged-data-utilization-2026-07-15.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# 연구팀 태깅 축어록 4종 활용 — 실행 기록 + 후속 계획 (2026-07-15)
|
||||
|
||||
> 원천: 2026-07-14 연구팀 제공 태깅 엑셀 4종(카카오톡 단톡방).
|
||||
> 회의 근거: `ops/hanshin-meeting-actions-2026-07-13.md` P4(데이터 업로드)·페르소나 확충,
|
||||
> 2026-07-14 정식 회의록("데이터가 많을수록 성능·비용·응답속도 개선 — 추가 데이터 지속 요청").
|
||||
> 원본 파일은 저장소·DB에 적재하지 않는다(원본 파기 원칙). 실행 내역과 후속 계획만 이 문서가 소유한다.
|
||||
|
||||
## 1. 자료 개요
|
||||
|
||||
| 자료 | 내용 | 태그 규모 | 페르소나 산출 |
|
||||
|---|---|---|---|
|
||||
| 첫회기 축어록(2장, 0711tagged) | 45세 어머니, 자녀 등교거부발 우울·분노·신체화, BPS 통합 + 사례개념화 워크시트 | 상호작용 분석 98건 | **P8 미영** (부모 측 페르소나) |
|
||||
| 게슈탈트 3회기(3장, TAG) | 40대 한부모, 아들 학업갈등 분노/죄책감, 침묵 70회(2~58초)·울음·웃음 정량 | 주석 188건 | **P10 선혜** |
|
||||
| 자해 DBT(42, tagged) | 성인 직장인 반복 자해, DBT 강의+역할극(타당화 6수준·서약·체인분석·AVIS-R) | 강의 40행+주석 11건 | **P9 유진** (게이티드) |
|
||||
| 우울 CBT(영상 40, 0712tagged) | 40대 남성 우울·부부갈등, Beck 모델 다회기 발췌 | 상담자 태그 261쌍(최다) | **P11 정호** |
|
||||
|
||||
## 2. 이번 세션에서 실행 완료
|
||||
|
||||
1. **KB 등록 (P4 업로드 경로 실사용)** — 실명 사전 치환본(은진/이현정/Ashley/Shelly/Carmen Wiebe/Edward/Sharon → 역할명) 4종을
|
||||
`POST /personas/sources/upload`로 등록. 서버 마스킹(NAME/ORG) 추가 적용, 총 **101 청크 색인+임베딩**.
|
||||
source_ids: `persona_authoring_9c383fdb7f254d61`(ch2) · `persona_authoring_fa77f213b3664422`(ch3) ·
|
||||
`persona_authoring_77c1cf9a505b4d77`(dbt) · `persona_authoring_25feeb93ab384e18`(cbt). 원본 바이트 미저장(hash-only 증거).
|
||||
2. **taxonomy v1.1.0** (`apps/api/app/taxonomy.py`, append-only) — 연구팀 태그 정합을 위해
|
||||
Technique 9종(명료화·수치화·체인분석·인지재구성·대안행동·전념전략·지금-여기·재진술·기술코칭),
|
||||
ClientState 9종(표면순응·외재화·지시요청·겉보기유능함·적극적수동성·자해보고·신체증상·감정가림·초점이탈/융합) 추가.
|
||||
evaluator fast-loop 후보는 `TECHNIQUE_KO` 전체를 쓰므로 자동 반영. 조작적 정의는 `docs/taxonomy.md` 갱신.
|
||||
3. **가드레일 자해 수단 확장** (`services/guardrail.py`) — DBT 자해 페르소나 대비 `_MEANS_TERMS`에
|
||||
커터/면도날/손목 긋/깊게 긋기/라이터 지지기 추가. "선을 긋다"(경계 설정) 오차단 방지 테스트 포함.
|
||||
4. **라이브 코칭 소스팩 3종 신설** (`data/kb/live_coaching_sources/`) — DBT 위기·전념 / 게슈탈트 회기 무브 /
|
||||
부모 첫회기 무브. 요약 재구성(원문 미적재), 기존 0615 스키마 준수.
|
||||
5. **신규 페르소나 초안 4종 생성** — 등록한 RAG 근거로 `POST /personas/drafts/generate` → **draft 상태 저장**.
|
||||
승인·활성화는 연구팀/교수 검수 몫(월권 방지). 특히 **P9(자해·DBT)는 안전장치 실측·교수 검수 전 승인 금지**.
|
||||
|
||||
## 3. 후속 계획 (임상팀·소유자 확인 필요 — 착수 전 게이트)
|
||||
|
||||
| 항목 | 내용 | 게이트 |
|
||||
|---|---|---|
|
||||
| 평가 golden set 확장 | P8~P11.jsonl — 태그를 신규 taxonomy 라벨로 매핑, 발화는 합성 재작성(출판물 원문 금지). cbt 261쌍이 최대 소스 | 임상팀 라벨 검수 |
|
||||
| 다회기 스키마 격상 | `Annotation`에 session_no/segment 추가, nonverbal을 {type,raw,duration_s} 구조화(ch3 침묵 분포 정량 활용) | 골든셋 재현성 검토 |
|
||||
| 이론모드 확장 | `persona.py THEORY_MODE_GUIDANCE`에 gestalt/dbt 모드 신설 (현재 humanistic/cbt/integrative) | 소유자 결정 |
|
||||
| P1~P7 보강 | P1 침묵 연출·위험사정 골든, P2 신체화 언어, P3 triggers(한부모 낙인), P4 수치화 골든, P6 표면순응 단서, P7 비자발 오프닝, P4~P7 triggers 일괄 | 임상팀 콘텐츠 |
|
||||
| 사례개념화 루브릭 | ch2 워크시트(BPS 강점·취약, 보호/방해요인 매트릭스)를 `data/rubrics/` draft로 | 임상팀 승인 후 활성 |
|
||||
| evaluator 이론모드 가중 | 라벨 수 증가에 따른 fast-loop 정밀도 방어(dbt→전념/체인분석 우선 등) | 정확도 벤치 후 |
|
||||
| 부모-자녀 교차 훈련 세트 | P8(부모)↔P4~P7(자녀) 같은 사건 양측 상담 구성 | 연구팀 교육설계 |
|
||||
|
||||
## 4. 저작권·PII 원칙 (재확인)
|
||||
|
||||
- ch3(출판 도서)·dbt/cbt(교육 영상 번역물)는 **패턴 추출·합성 재작성만** 허용, 원문 대량 재현 금지.
|
||||
- 재사용 전 치환 실명 목록: 은진·이현정(ch2) / Ashley·Shelly·Shelley·Carmen Wiebe·Sharon(dbt) / Edward Crane·Sharon(cbt).
|
||||
Marsha Linehan 등 이론 창시자 학술 인용은 유지 가능.
|
||||
- 업로드 파이프라인이 서버측 마스킹을 추가 적용하지만, 사전 치환을 표준 절차로 유지한다.
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
# 발화 라벨 Taxonomy v1.0 — 0615 파란색 라벨 정의서
|
||||
# 발화 라벨 Taxonomy v1.1 — 0615 파란색 라벨 정의서
|
||||
|
||||
> SoT(라벨 정의). 코드 구현: `apps/api/app/taxonomy.py`. 예시 데이터: `docs/golden_schema.jsonl`.
|
||||
> 근거: `데이터/README.md`(파랑 #3057B9 = 3종 혼재), `MASTERPLAN.md` §3.1~§3.2(89개·77종, 3축 분리),
|
||||
> `MEMORY_KNOWLEDGE_PERSONA_DESIGN.md` §3.6(kb.chunk.label_id FK).
|
||||
> **버전 = 1.0.0** (`TAXONOMY_VERSION`). 라벨 코드는 불변, 추가는 append-only.
|
||||
> **버전 = 1.1.0** (`TAXONOMY_VERSION`). 라벨 코드는 불변, 추가는 append-only.
|
||||
> v1.1.0(2026-07-15): 연구팀 태깅 축어록 4종(ch2_bps·ch3_gestalt·dbt_video·cbt_video) 정합을 위해
|
||||
> Technique 9종·ClientState 9종을 append-only 추가했다(§3.6, §4.1, §8 버전 이력).
|
||||
|
||||
## 0. 설계 원칙
|
||||
|
||||
|
|
@ -85,6 +87,24 @@
|
|||
| `psychoeducation` | 심리교육 | 보편적 교범·정보를 주관적 해석과 구별해 전달 | "보편적 교범 전달, 주관적 해석과 구별" |
|
||||
| `homework` | 과제 부여 | 회기 간 실천 과제를 이해 가능하게 제시·확인 | "다음 회기까지 해 와야 하는 과제 설명" |
|
||||
|
||||
### 3.6 v1.1.0 추가 기법 (연구팀 태깅 자료 정합)
|
||||
|
||||
> 연구팀 태깅 축어록 4종에서 관찰됐으나 v1.0 enum에 없던 기법. **군집**은 기존 5개 위계에 편입한다.
|
||||
> 예시 발화는 모두 **합성**이며 원문 인용이 아니다. **출처**는 어느 태깅 파일 계열에서 유래했는지를 표기한다
|
||||
> (ch2_bps=첫 회기 BPS 워크북, ch3_gestalt=게슈탈트 3회기 도서, dbt_video=자해 DBT 영상, cbt_video=우울 CBT 영상).
|
||||
|
||||
| code | 한글 | 군집 | 조작적 정의 | 예시 발화(합성, 상담자) | 출처 |
|
||||
|---|---|---|---|---|---|
|
||||
| `clarification` | 명료화 | EXPLORATORY | 내담자 말의 뜻이 모호할 때 이해를 정확히 맞추기 위해 되묻거나 확인한다. 반영과 달리 되돌려 주기보다 '무엇을 뜻하는지'를 좁혀 묻는 데 목적이 있다. | "방금 '견디기 힘들다'고 하셨는데, 그게 어떤 느낌에 더 가까운지 조금만 더 말해 줄 수 있을까요?" | cbt_video, ch3_gestalt |
|
||||
| `scaling` | 수치화 질문 | EXPLORATORY | 감정·증상의 강도를 숫자 척도로 표현하게 해 상태와 변화를 가늠한다. 막연한 정서를 비교 가능한 수치로 옮겨 개입 전후를 확인하는 CBT 기법이다. | "지금 그 불안이 0에서 10 중에 어느 정도인지 숫자로 말해 볼 수 있을까요?" | cbt_video |
|
||||
| `chain_analysis` | 체인분석 | EXPLORATORY | 문제행동에 이르기까지의 촉발사건·감정·생각·행동을 사슬로 잘게 되짚어 개입 지점을 찾는다. 행동을 평가하지 않고 순서를 구체적으로 복원하는 것이 핵심이다. | "그 충동이 올라오기 직전에 어떤 일이 있었고, 그때 무슨 감정이 먼저 지나갔는지 순서대로 같이 짚어 볼까요?" | dbt_video |
|
||||
| `cognitive_restructuring` | 인지재구성 | INTERVENTION | 이분법·파국화·독심술 같은 인지 왜곡을 확인하고, 연속선 사고·핵심신념 검토를 통해 사고를 재구성한다. cbt 파일 55건의 최대 공백이던 핵심 개입이다. | "'완벽하지 않으면 실패'라고 느껴진다고 하셨는데, 그 사이에 여러 단계가 있다면 오늘 일은 어디쯤 놓일까요?" | cbt_video |
|
||||
| `behavioral_alternative` | 대안행동 탐색 | INTERVENTION | 부적응 행동 대신 시도해 볼 대안행동을 함께 탐색하고 실행을 계획한다. 지적이 아니라 선택지를 넓히는 방향으로 진행한다. | "그 상황이 또 오면, 참는 것 말고 해 볼 수 있는 다른 행동으로 뭐가 있을지 같이 찾아볼까요?" | cbt_video |
|
||||
| `commitment_strategy` | 전념 전략 | INTERVENTION | 변화 목표에 스스로 전념하도록 이끌고(악마의 옹호자 등), 실행을 막을 방해물을 미리 예상해 대처를 정한다. | "이걸 해 보기로 마음먹었다면, 한 주 안에 그걸 가로막을 만한 게 뭘지 미리 같이 생각해 둘까요?" | dbt_video |
|
||||
| `here_and_now_focus` | 지금-여기 초점화 | INTERVENTION | 과거 사실 캐내기보다 지금-여기에서 일어나는 알아차림·정서·신체 감각에 초점을 맞춘다(게슈탈트 현전·트래킹). | "지금 그 말을 하면서 몸에서 뭐가 느껴지는지, 이 순간에 잠시 머물러 볼까요?" | ch3_gestalt |
|
||||
| `restatement` | 재진술 | RELATIONAL | 내담자 발화의 내용을 압축해 되돌려 주고 이해가 맞는지 확인을 기다린다. 정서 중심의 반영과 구분해 '내용 확인'으로 태깅한다. | "그러니까 학회 준비로 집을 자주 비우게 되면서, 혼자 감당한다는 느낌이 커졌다는 말씀이시죠?" | cbt_video |
|
||||
| `skills_coaching` | 기술 코칭 | STABILIZING | 위기·고통 순간에 지금 쓸 수 있는 대처 기술을 하나 골라 단계적으로 실행하도록 짧게 안내한다(고통감내·위기전화 코칭). | "지금 그 파도가 지나갈 때까지, 우선 찬물에 손을 담그는 것부터 같이 해 볼까요?" | dbt_video |
|
||||
|
||||
## 4. (B) 내담자 상태 태그 (ClientState) — 내담자 발화/비언어
|
||||
|
||||
> 페르소나 저항 엔진의 상태전이 정답 + 평가 AI '반응 읽기' 채점 근거.
|
||||
|
|
@ -103,6 +123,23 @@
|
|||
| `expresses_plan` | 계획/욕구 표현 | 자기 계획·욕구·바람을 표현 | "자신의 계획을 표현함" |
|
||||
| `defense_loosening` | 방어 완화 | 누적된 반영/탐색으로 방어가 서서히 풀림(진전 신호) | "방어가 서서히 풀어지고 있음" |
|
||||
|
||||
### 4.1 v1.1.0 추가 상태 (연구팀 태깅 자료 정합)
|
||||
|
||||
> 예시 발화는 모두 **합성**이며 원문 인용이 아니다. 출처 표기는 §3.6과 동일하다
|
||||
> (ch2_bps / ch3_gestalt / dbt_video / cbt_video).
|
||||
|
||||
| code | 한글 | 조작적 정의 | 예시 발화(합성, 내담자) | 출처 |
|
||||
|---|---|---|---|---|
|
||||
| `compliant_surface` | 표면 순응 | 겉으로는 상담자 말에 동의·수긍하지만 내면의 변화나 진짜 이해가 따르지 않는 상태. 빠른 동의를 진전으로 오해하지 않도록 구분한다. | "네, 맞아요. 선생님 말씀이 다 맞죠. 그렇게 해야죠." (별다른 정서 없이) | ch2_bps |
|
||||
| `externalizing` | 문제 외재화 | 문제의 원인·책임을 자신이 아닌 타인·상황에 돌려 자기 몫을 보지 않는 상태. | "저는 문제없어요. 애가 학교만 제대로 가면 다 괜찮아질 거예요." | ch2_bps, cbt_video |
|
||||
| `seeks_guidance` | 지시 요청 | 스스로 탐색하기보다 상담자의 판단·지시·정답을 직접 요구하는 상태. | "그래서 제가 어떻게 하면 되는 거예요? 그냥 답을 좀 알려 주세요." | cbt_video |
|
||||
| `apparent_competence` | 겉보기 유능함 | 회기 안에서는 유능하고 안정돼 보이지만 실제 위기 상황에서는 대처가 무너지는 괴리 상태. | "저 괜찮아요. 회사에서도 다들 일 잘한다고 하고, 잘 지내고 있어요." (위기 직후임에도) | dbt_video |
|
||||
| `active_passivity` | 적극적 수동성 | 스스로 문제를 해결하지 못하고 그 해결을 적극적으로 타인에게 넘기거나 무력하게 기다리는 상태. | "저는 못 해요. 선생님이 대신 좀 해 주시면 안 돼요? 저 혼자서는 아무것도 안 돼요." | dbt_video |
|
||||
| `self_harm_disclosure` | 자해 보고 | 자해 행동·충동을 보고하는 상태로, 자살사고 인정(`suicidal_ideation_admit`)과 구분해 사정한다. 방법·도구 묘사는 담지 않는다(★안전 핵심). | "요즘 너무 힘들면… 저도 모르게 제 몸을 상하게 하게 돼요." | dbt_video |
|
||||
| `somatic_complaint` | 신체증상 호소 | 감정을 직접 말하기 어려워 답답함·숨막힘·두근거림 같은 신체 감각으로 고통을 호소하는 상태. | "그냥 가슴이 답답하고 숨이 안 쉬어져요. 자다가도 심장이 두근거려서 깨요." | ch2_bps |
|
||||
| `affect_masking` | 감정 가림 | 무거운 내용을 웃음·무덤덤한 태도로 덮어 정서 표현을 가리는 상태. | "뭐 별거 아니에요. (웃으며) 그냥 늘 있는 일인데요, 괜찮아요." | ch3_gestalt |
|
||||
| `focus_drift_fusion` | 초점 이탈/융합 | 자기 이야기를 하다 자녀·타인 이야기로 초점이 흩어지며 자기와 타인의 경계가 흐려지는(융합) 상태. | "제 얘기요? 아… 그게, 우리 애가요, 걔가 요즘 학교를 안 가서…" | ch2_bps, ch3_gestalt |
|
||||
|
||||
## 5. (C) 슈퍼바이저 논평 (SupervisorComment)
|
||||
|
||||
2종으로 분리한다. critique 는 `intent_deviation`(의도 vs 실제)을 구조화한다(윤찬: 1급 시민).
|
||||
|
|
@ -145,3 +182,10 @@
|
|||
| `SupervisorComment` | `supervisor_comment(turn_id, kind, text, intent_deviation JSONB)` | (1:N) | — |
|
||||
|
||||
> 라벨 추가 시: enum 에 append → `technique_label_def` seed → `TAXONOMY_VERSION` minor++. 기존 code 변경 금지(골든셋 재현성).
|
||||
|
||||
## 8. 버전 이력
|
||||
|
||||
| 버전 | 날짜 | 변경 |
|
||||
|---|---|---|
|
||||
| 1.0.0 | 초기 | 0615 파란색 라벨 정규화 — Technique 21종·ClientState 11종·Stage 4·CommentKind 2. |
|
||||
| 1.1.0 | 2026-07-15 | 연구팀 태깅 축어록 4종(ch2_bps·ch3_gestalt·dbt_video·cbt_video) 정합. Technique 9종(`clarification`, `scaling`, `chain_analysis`, `cognitive_restructuring`, `behavioral_alternative`, `commitment_strategy`, `here_and_now_focus`, `restatement`, `skills_coaching`)·ClientState 9종(`compliant_surface`, `externalizing`, `seeks_guidance`, `apparent_competence`, `active_passivity`, `self_harm_disclosure`, `somatic_complaint`, `affect_masking`, `focus_drift_fusion`) append-only 추가(§3.6·§4.1). 기존 code 불변. |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue