chore: Vignette 모노레포 초기 스캐폴드 + 설계 문서 이관
- apps/web(React) · apps/api(FastAPI) · infra(Docker) · docs(설계 SoT) - 마스터플랜·적대검증·메모리설계·디자인컨셉 docs 이관 - 미성년 사례데이터는 .gitignore로 제외(개인정보 보호)
This commit is contained in:
commit
709baadd01
12 changed files with 3087 additions and 0 deletions
942
docs/DESIGN_CONCEPT.md
Normal file
942
docs/DESIGN_CONCEPT.md
Normal file
|
|
@ -0,0 +1,942 @@
|
|||
---
|
||||
date: 2026-06-25
|
||||
type: design-concept
|
||||
project: 한신대학교 AI 심리상담 시뮬레이션 훈련 플랫폼
|
||||
tags:
|
||||
- 디자인컨셉
|
||||
- 디자인시스템
|
||||
- UX
|
||||
- 아바타
|
||||
- 한신대
|
||||
status: 통합본 v1
|
||||
---
|
||||
|
||||
# 한신대 AI 심리상담 시뮬레이션 플랫폼 — 통합 디자인 컨셉 문서
|
||||
|
||||
> **이 문서 하나로 사이트를 만들 수 있다.** 디자인 철학 → 디자인 시스템 토큰(실측값) → 가상 내담자 아바타(상세) → 상담 세션 화면 → 3역할 대시보드 → 컴포넌트·모션 → React 구현 노트 → 안티패턴 체크리스트까지, 추상적 형용사 없이 실제 hex·px·컴포넌트·인터랙션으로 적었다.
|
||||
>
|
||||
> 5개 영역 설계([design-language] / [design-system] / [avatar] / [session-ux] / [role-dashboards])를 단일 기준 문서로 종합한 것이다. 토큰 충돌 지점은 본 문서가 **단일 진실원본(SSOT)**으로 일원화했다.
|
||||
>
|
||||
> **재사용 자산 연계**: 윤찬 기존 디자인 시스템(`Resources/50_Tech_Dev/디자인/slide-base.css`)과 동일한 토큰 철학(무채 골격 + accent 교체). slide-base 표의 "한신대/의료" 빈 행을 본 문서의 `--accent`로 채운다.
|
||||
|
||||
---
|
||||
|
||||
## 0. 한 문장 컨셉과 SSOT 결정
|
||||
|
||||
### 0.1 컨셉
|
||||
|
||||
**"불안한 사람을 다루는 사람을 위한, 차분한 임상 도구."**
|
||||
|
||||
상담 수련생(학습자)이 가상 내담자 AI와 음성으로 대면하고, 뒤에서 평가 AI가 조용히 관찰한다. 이 화면은 **두 종류의 신뢰**를 동시에 줘야 한다.
|
||||
|
||||
1. **임상적 신뢰감** — 학습자가 "이건 진지한 임상 훈련 장비다"라고 느낀다.
|
||||
2. **교육적 따뜻함** — 연습 중 실수해도 안전하다고 느낀다.
|
||||
|
||||
자극적·게임적이면 안 되고, 차갑고 행정적이어도 안 된다. 정답은 그 사이의 **"조용한 따뜻함(quiet warmth)"** — 스펙트럼 위 **65% 임상 : 35% 따뜻함** 지점이다.
|
||||
|
||||
> 화면은 "상담실"이지 "대시보드"가 아니다. 평가 정보는 상담실 벽에 걸린 은은한 시계처럼 곁에 있되, 내담자의 얼굴에서 시선을 빼앗지 않는다.
|
||||
|
||||
### 0.2 토큰 SSOT 결정 (5개 설계 영역의 색 통일)
|
||||
|
||||
5개 영역이 각각 미세하게 다른 그린(`#5B7A6B` / `#3E7A6E` / `#4F6B5D` / `#0E7C7B` / `#0F766E`)을 제안했다. 본 문서는 아래로 **확정 일원화**한다.
|
||||
|
||||
| 용도 | 확정값 | 근거 |
|
||||
|---|---|---|
|
||||
| **메인 accent (전역 기본)** | `--accent: #3E7A6E` (세이지-틸) | 저채도·차분, 흰 배경 대비 약 5.0:1 (WCAG AA 통과). 흔한 메디컬 블루/형광 틸 회피. slide-base의 `#0F766E`보다 한 톤 더 낮춤. |
|
||||
| **역할별 보조 accent** | 학습자 `#3E7A6E` / 교수자 `#3A5BA0` / 관리자 `#5B5F6B` | 같은 골격, accent 한 토큰만 교체. 모두 저채도. |
|
||||
| **보조 온기 accent** | `--clay: #B0735C` (테라코타) | 양산형 메디컬 블루를 깨는 "사람이 만든 교육 도구" 온기. 저채도로만, 화면당 1곳. |
|
||||
|
||||
이유: **세이지-틸**은 임상적 차분함(자연·회복·안정)과 따뜻함(흙·식물)을 동시에 품으면서, 한국 상담/심리 분야에서 과용되지 않아 차별화된다. 의료 블루는 흔하고 차갑다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 핵심 디자인 원칙 (신성불가침 5개)
|
||||
|
||||
### 원칙 1 — One Screen, One Intent (한 화면 한 의도)
|
||||
상담 시뮬레이션은 본질적으로 집중을 요구한다. 학습자가 가상 내담자의 말에 귀 기울이는 순간 화면에 버튼 12개·카드 9개가 깔려 있으면 안 된다. 모든 화면은 그 순간의 **단 하나의 주된 행동**(말하기 / 듣기 / 피드백 읽기 / 세션 시작)을 중심으로 설계한다. 부차 정보는 위계 아래로.
|
||||
- *적용*: 세션 진행 화면 = 중앙에 내담자 발화 + 음성 비주얼 하나. 평가 피드백은 화면 가장자리 차분한 사이드 패널로, 절대 모달로 튀지 않는다.
|
||||
|
||||
### 원칙 2 — 위계는 색 블록이 아니라 타이포 weight + 여백으로
|
||||
Stripe·Linear의 공통점: 테두리·색 박스로 구역을 나누지 않고 **여백과 글자 굵기로** 나눈다. 강조는 ①font-weight(400→600) ②accent 텍스트/언더라인 ③상단 kicker(소제목) ④accent dot 으로만. **border-left 강조선·좌측 사이드바 강조줄·색 박스 카드 격자 절대 금지.**
|
||||
- *적용*: 평가 피드백 "공감 반영: 우수"를 초록 배지로 칠하지 않는다. kicker("공감 반영") + 본문 한 줄 + 미세한 accent dot 한 점으로 위계를 만든다.
|
||||
|
||||
### 원칙 3 — 임상적 차분함 = 채도를 죽인 중성 베이스 + 절제된 단일 악센트
|
||||
Calm/Headspace에서 배울 건 파스텔 그라데이션이 아니라 **고채도·고대비를 의도적으로 피하는 절제**다. 베이스는 따뜻한 회백(off-white)·차분한 슬레이트. 악센트는 **세이지-틸 단 하나**를 화면당 1군데만 배급(Linear의 "accent rationing"). 빨강·주황 경고색은 진짜 오류/위험에만.
|
||||
- *근거*: 불안한 내담자를 다루는 훈련 도구이므로 자극적 색은 학습자 정서·임상 톤 양쪽에 해롭다. 녹색은 우울·불안·스트레스 점수를 가장 많이 낮춘 색이며 "go/긍정" 신호로도 조용히 작동한다.
|
||||
|
||||
### 원칙 4 — 따뜻함은 "둥근 카드"가 아니라 "휴머니스트 타이포 + 따뜻한 중성톤 + 넉넉한 여백"
|
||||
따뜻함을 둥근 모서리·말랑한 일러스트로 내면 임상 신뢰가 깨진다(애들 앱처럼 보임). 대신 **헤드라인에 휴머니스트 세리프 한 톤 옵션**(therapy 웹의 정설: serif=신뢰·인간미), 본문은 깔끔한 산세리프. 순백(#FFF) 대신 따뜻한 종이색, 순흑 대신 잉크 네이비-그린. 이 미세한 온기가 "행정 시스템"과 "사람을 위한 도구"를 가른다.
|
||||
- *주의*: 한글 본문은 Pretendard 단일 패밀리를 기본으로 한다(국문 상담 도메인 가독·중립). 세리프 헤드라인은 랜딩·표지 같은 희소 지점에서만 선택 적용(본명조). UI 전반은 산세리프 weight 위계로 통일.
|
||||
|
||||
### 원칙 5 — 평가 AI는 판사가 아니라 조용한 멘토 (Non-judgmental surfacing)
|
||||
실시간 피드백이 빨간 X·점수 폭격·"틀렸습니다"로 뜨면 학습자는 위축된다. 피드백은 **관찰("이 지점에서 내담자가 방어적으로 반응했어요") → 제안("열린 질문으로 바꿔볼까요?")** 순서의 **서술형·저채도·비모달**로. 점수는 세션이 끝난 뒤 회고 화면에서만, 절대값이 아니라 성장 추세로.
|
||||
|
||||
---
|
||||
|
||||
## 2. 무드 좌표 (임상 ↔ 따뜻함 균형)
|
||||
|
||||
양 극단(차가운 EHR / 말랑한 명상앱) 사이, **65% 임상 : 35% 따뜻함**.
|
||||
|
||||
| 축 | 임상 신뢰(차가움) ← | **→ 우리 좌표 ←** | → 따뜻함(부드러움) |
|
||||
|---|---|---|---|
|
||||
| 베이스색 | 순백·차가운 그레이 | **따뜻한 off-white `#FBFAF8` + 차분한 슬레이트** | 크림·파스텔 |
|
||||
| 악센트 | 임상 블루 | **세이지-틸 `#3E7A6E`** | 코랄·더스티 로즈 |
|
||||
| 헤드라인 | 기하 산세리프 | **산세리프 weight 위계 (희소 지점만 본명조 옵션)** | 손글씨·라운드 |
|
||||
| 모서리 | 직각 | **6~8px (거의 직각, 미세 라운드)** | 16px+ 둥근 |
|
||||
| 그림자 | 없음(헤어라인) | **1px 헤어라인 + 아주 약한 그림자** | 부드러운 큰 blur |
|
||||
| 모션 | 즉각·없음 | **200~320ms ease-out, 절제** | 느린 호흡 애니메이션 |
|
||||
|
||||
> 단 하나의 예외 — 음성 듣기 중 마이크/오브의 **느린 호흡 펄스**. 이건 "지금 듣고 있다"는 정서적 안심 신호라 의도적으로 명상앱 호흡 모션을 차용한다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 디자인 시스템 토큰 (그대로 `:root`에 붙여 쓰는 실측값)
|
||||
|
||||
CSS 변수는 primitive(원시값) → semantic(의미 별칭) 2계층. 컴포넌트는 semantic만 참조 → 다크모드 1곳에서 스왑.
|
||||
|
||||
### 3.1 중성 베이스 (Neutral) — 골격
|
||||
|
||||
순백/순흑 금지. 따뜻한 종이색 + 잉크 네이비-그린.
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* ── Primitive: Neutral (따뜻한 종이 + 쿨 슬레이트) ── */
|
||||
--paper: #FBFAF8; /* 본문 배경: 따뜻한 종이색 (순백 아님) */
|
||||
--paper-2: #F4F2EE; /* 카드/패널 미세 단차 (테두리 대신 톤차) */
|
||||
--surface: #FFFFFF; /* 카드 표면 (캔버스가 종이색이라 분리됨) */
|
||||
--neutral-100: #ECEFF1; /* 표면 hover / subtle fill */
|
||||
--neutral-150: #E5E1DA; /* 카드 보더 baseline = hairline */
|
||||
--neutral-200: #D5DBDE; /* divider 진함 */
|
||||
--neutral-400: #98A4A9; /* placeholder / disabled */
|
||||
|
||||
--ink: #1C2A2A; /* 본문 텍스트: 잉크 네이비-그린 (순흑 아님) */
|
||||
--ink-2: #5A6663; /* 보조 텍스트: 슬레이트 */
|
||||
--ink-3: #93A09C; /* 캡션/메타 */
|
||||
--hair: #E5E1DA; /* 1px 헤어라인 (구역 분리는 이걸로) */
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 악센트 — 1주 + 1보조 (저채도)
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* ── Sage-Teal (주 악센트 = 상담/신뢰), 화면당 1곳 배급 ── */
|
||||
--accent: #3E7A6E; /* ★ 메인 악센트 (버튼/링크/포커스). 흰 배경 대비 5.0:1 AA */
|
||||
--accent-deep: #316257; /* hover / 진한 강조 텍스트 */
|
||||
--accent-tint: #EEF4F2; /* 악센트 배경 틴트 (콜아웃·선택·active) */
|
||||
--accent-bright:#5F968B; /* dot·진행바 fill (화면 전용) */
|
||||
|
||||
/* ── Terracotta (보조 악센트 = 학습/온기), 저채도, 화면당 1곳 ── */
|
||||
--clay: #B0735C; /* ★ 보조 악센트 (학습자 온기 포인트, 내담자 측 신호) */
|
||||
--clay-deep: #94604C;
|
||||
--clay-tint: #F6F0EC;
|
||||
}
|
||||
```
|
||||
|
||||
`--accent #3E7A6E`는 흰 배경 명도대비 약 5.0:1 → 본문 링크/버튼 텍스트 WCAG AA 통과. **2색을 동시에 큰 면적으로 쓰지 않는다** — sage가 전역 기본, clay는 학습자 모드/온기 포인트에서만 점·언더라인·소형 fill로.
|
||||
|
||||
### 3.3 의미색 (Semantic feedback) — solid + tint 페어
|
||||
|
||||
각 의미색은 아이콘/텍스트용 solid + 배경용 tint. tint 위 text는 모두 AA(4.5:1↑).
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* 긍정 = 녹색 (상담 진전·라포 형성·가드레일 통과·잘함) */
|
||||
--pos-text: #2C6E49; --pos-solid: #3B8C5E; --pos-tint: #E8F3EC;
|
||||
|
||||
/* 주의 = 앰버 (라포 약화·과도한 침묵·페이싱) — 빨강 대신 머스터드 */
|
||||
--warn-text: #8A5A00; --warn-solid:#C28A2C; --warn-tint: #FAF1E0;
|
||||
|
||||
/* 위험 = 적색 (가드레일 위반·세션 강제중단·임상적 위급) — 오직 진짜 위급만 */
|
||||
--crit-text: #A8352C; --crit-solid:#C4453A; --crit-tint: #FAEAE8;
|
||||
|
||||
/* 정보 = 슬레이트블루 (평가 AI 피드백·시스템 안내, 중립) */
|
||||
--info-text: #2E5A7A; --info-solid:#3C7299; --info-tint: #EAF1F6;
|
||||
|
||||
--neutral-sig:#8895A2; /* 데이터 없음·미평가 */
|
||||
}
|
||||
```
|
||||
|
||||
**핵심 원칙: 평가 AI의 실시간 피드백 기본 톤은 `info`(슬레이트블루) 또는 `pos`(녹색)** 으로 — 학습자가 비난받는 느낌을 받지 않게. 빨강은 코칭 피드백에 절대 쓰지 않고, 가드레일/위급에만.
|
||||
|
||||
### 3.4 시맨틱 별칭 (라이트 모드)
|
||||
|
||||
컴포넌트는 primitive를 직접 부르지 않고 이 별칭만 참조.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--bg-app: var(--paper); /* 앱 캔버스 */
|
||||
--bg-surface: var(--surface); /* 카드/패널 */
|
||||
--bg-surface-2: var(--paper-2); /* 중첩 표면/입력 */
|
||||
--bg-tint: var(--accent-tint); /* 선택·강조 배경 */
|
||||
--bg-stage: #1E2724; /* 내담자 아바타 영역 (깊은 차콜그린, 시선 집중) */
|
||||
|
||||
--text-strong: var(--ink); /* 제목 */
|
||||
--text-body: var(--ink-2); /* 본문 보조 */
|
||||
--text-muted: var(--ink-3); /* 캡션 */
|
||||
--text-on-accent:#FBFAF8; /* accent 버튼 위 텍스트 */
|
||||
|
||||
--border-subtle: var(--neutral-150);
|
||||
--border-strong: var(--neutral-200);
|
||||
--border-focus: var(--accent);
|
||||
--focus-ring: color-mix(in srgb, var(--accent) 45%, transparent);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 다크 모드 (gloom 회피 — 충분한 밝은 면 + 절제된 악센트)
|
||||
|
||||
배경은 한색 슬레이트(순흑 아님). 악센트는 명도를 올려 가독 확보.
|
||||
|
||||
```css
|
||||
[data-theme="dark"] {
|
||||
--bg-app: #131A1E; --bg-surface: #1A2429; --bg-surface-2: #222E34;
|
||||
--bg-tint: #1E2E2A; --bg-stage: #0E1614;
|
||||
--text-strong: #EAF0F1; --text-body: #C4CFD3; --text-muted: #88969C;
|
||||
--text-on-accent:#0F1A17;
|
||||
--border-subtle: #2A363C; --border-strong: #38454C; --border-focus: #6FB3A4;
|
||||
--accent: #6FB3A4; --accent-deep: #84C2B4; --accent-tint: #1E2E2A;
|
||||
--clay: #CC8F77;
|
||||
|
||||
--pos-text: #7FD0A0; --pos-solid: #5FB682; --pos-tint: #16271D;
|
||||
--warn-text: #E2B566; --warn-solid:#D4A24A; --warn-tint: #2A2415;
|
||||
--crit-text: #E89086; --crit-solid:#D86459; --crit-tint: #2A1715;
|
||||
--info-text: #79B0D4; --info-solid:#5C97BE; --info-tint: #15222C;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 타이포그래피
|
||||
|
||||
| 역할 | 폰트 | 이유 |
|
||||
|---|---|---|
|
||||
| **본문/UI 기본** | **Pretendard Variable** | 한글 UI 사실상 표준. 9 weight 가변, 숫자 정렬 우수. 임상 데이터·평가 점수 표기에 깔끔. |
|
||||
| **숫자·타임코드** | Pretendard + `tabular-nums` | 세션 타이머·점수는 정렬 필요 → `font-variant-numeric: tabular-nums`. |
|
||||
| (옵션) 랜딩 헤드라인 | Noto Serif KR (본명조) | 신뢰·인간미. **희소 지점만**(표지·히어로). UI 전반은 산세리프. |
|
||||
|
||||
```css
|
||||
:root {
|
||||
--font-sans: "Pretendard Variable", Pretendard, -apple-system,
|
||||
"Apple SD Gothic Neo", "Noto Sans KR", system-ui, sans-serif;
|
||||
--font-serif:"Noto Serif KR", "Source Serif 4", serif; /* 랜딩 헤드만 */
|
||||
--font-num: "Pretendard Variable", "JetBrains Mono", ui-monospace, monospace;
|
||||
}
|
||||
body { font-family: var(--font-sans); -webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility; }
|
||||
.tabular { font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
|
||||
```
|
||||
|
||||
CDN: `https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/variable/pretendardvariable.min.css`
|
||||
|
||||
**타입 스케일** (기준 본문 16px / 1.6, 한글 자간 -0.01~-0.02em. 위계는 크기보다 weight로):
|
||||
|
||||
| 토큰 | px / rem | line-height | weight | letter-spacing | 용도 |
|
||||
|---|---|---|---|---|---|
|
||||
| `--fs-display` | 40 / 2.5rem | 1.2 | 700 | -0.02em | 표지·랜딩 대제목 (희소) |
|
||||
| `--fs-h1` | 30 / 1.875rem | 1.3 | 700 | -0.02em | 페이지 제목 |
|
||||
| `--fs-h2` | 24 / 1.5rem | 1.35 | 600 | -0.015em | 섹션 제목 |
|
||||
| `--fs-h3` | 20 / 1.25rem | 1.4 | 600 | -0.01em | 카드/패널 제목 |
|
||||
| `--fs-lead` | 18 / 1.125rem | 1.6 | 400 | -0.01em | 리드 문장 |
|
||||
| `--fs-body` | 16 / 1rem | 1.6 | 400 | -0.01em | 본문 기본 |
|
||||
| `--fs-sm` | 14 / 0.875rem | 1.55 | 400 | 0 | 보조·폼 라벨 |
|
||||
| `--fs-xs` | 13 / 0.8125rem | 1.5 | 500 | 0.01em | 캡션·메타·배지 |
|
||||
| `--fs-kicker` | 12 / 0.75rem | 1.4 | 600 | 0.08em | 상단 kicker 소제목 |
|
||||
|
||||
**강조 방식(철칙):** 색 블록·border-left 금지. ① weight 400→600 ② accent 텍스트 컬러 ③ 은은한 `--accent-tint` 배경 틴트 ④ 상단 kicker(작은 도트 동반). kicker가 양산형 좌측바를 대체하는 위계 신호다.
|
||||
|
||||
### 3.7 스페이싱 (8pt 기반, 넉넉하게)
|
||||
|
||||
```css
|
||||
:root {
|
||||
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
|
||||
--sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px; --sp-9: 96px;
|
||||
}
|
||||
```
|
||||
|
||||
원칙: **컴포넌트 내부 패딩은 8의 배수, 섹션 간 간격은 48~96px 넉넉히.** 불안 완화의 핵심은 여백. 카드 내부 패딩 기본 `--sp-5`(24px), 패널 `--sp-6`(32px), 읽기 영역 max-width 640~680px.
|
||||
|
||||
### 3.8 그리드·형태
|
||||
|
||||
```css
|
||||
:root {
|
||||
--maxw: 1200px; /* 대시보드 콘텐츠 최대폭 */
|
||||
--maxw-read: 680px; /* 읽기 영역 */
|
||||
--nav-w: 240px; /* 좌측 네비 펼침 */
|
||||
--nav-w-collapsed: 72px; /* 좌측 네비 아이콘 only */
|
||||
--radius: 8px; /* 카드/버튼/입력 (거의 직각) */
|
||||
--radius-sm: 6px; /* 입력·칩 */
|
||||
--radius-lg: 12px; /* 패널 */
|
||||
--gutter: 24px;
|
||||
}
|
||||
```
|
||||
|
||||
- `border-radius`: 카드 8px, 버튼 8px, 입력 6px (거의 직각, 미세 라운드만). **원형은 음성 오브·아바타·dot만 예외.**
|
||||
- **깊이는 1px 헤어라인(`--hair`) + 화면용 아주 약한 그림자.** glow·gradient-text·큰 blur 금지.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--shadow-sm: 0 1px 2px rgba(28,42,42,.05);
|
||||
--shadow-md: 0 4px 14px rgba(28,42,42,.07); /* 모달·팝오버만 */
|
||||
}
|
||||
```
|
||||
|
||||
### 3.9 모션
|
||||
|
||||
```css
|
||||
:root {
|
||||
--ease-out: cubic-bezier(.22,.61,.36,1);
|
||||
--ease-in-out: cubic-bezier(.45,.05,.55,.95);
|
||||
--dur-fast: 120ms; /* hover·포커스 */
|
||||
--dur-base: 200ms; /* 버튼·토글·드롭다운 */
|
||||
--dur-slow: 320ms; /* 패널 전환·모달 */
|
||||
--dur-breathe: 3200ms; /* 음성 호흡 펄스 */
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,*::before,*::after { animation-duration:.01ms!important; transition-duration:.01ms!important; }
|
||||
}
|
||||
```
|
||||
|
||||
원칙: **차분·짧게·자연 이징.** 불안 도구이므로 튀는 스프링/바운스 금지. 화면 전환은 페이드+미세 슬라이드(translateY 8px 이하). 실시간 피드백 칩은 **자리 미리 확보 후 fade**(밀어내기/점프 금지 — 레이아웃 시프트가 불안 유발). `prefers-reduced-motion` 필수 대응.
|
||||
|
||||
---
|
||||
|
||||
## 4. 가상 내담자 아바타 (이 플랫폼의 심장)
|
||||
|
||||
학습자의 시선이 30~50분간 머무는 단 하나의 오브젝트. 산만하면 라포 연습을 방해하고, 과하면 실제 환자로 오인되며, 빈약하면 몰입이 깨진다.
|
||||
|
||||
### 4.1 추천: "반(半)구상 페르소나 카드 (Semi-figurative Persona Card)"
|
||||
|
||||
사실적 얼굴도, 추상 오브도 아닌 그 사이. **사실성 35~45%의 양식화 반구상 흉상 + 호흡하는 광배(aura)**.
|
||||
|
||||
#### 표현 방식 비교 (왜 이걸 골랐나)
|
||||
|
||||
| 방식 | 몰입 | 제작비용(20주) | 윤리/오인 위험 | 불쾌한 골짜기 | 결론 |
|
||||
|---|---|---|---|---|---|
|
||||
| A. 추상 오브 / 음성 비주얼라이저 | 낮음 | 매우 낮음(1주) | 없음 | 없음 | **폴백으로만**(미선택/로딩/에러) |
|
||||
| **B. 2D 양식화 일러스트(반구상)** | 높음 | 중간(3~6주) | 낮음 | 회피 가능 | **★ 1차 채택** |
|
||||
| C. 실사 / AI생성 얼굴 | 매우 높음 | 높음 | **높음** | **직격(최악)** | 탈락 |
|
||||
| D. Live2D / 3D | 매우 높음 | 매우 높음(6~10주+) | 중간 | 리깅 품질 의존 | 2차/장기 후보 |
|
||||
|
||||
- **B를 고른 이유**: 사실성을 의도적으로 35~45%에 고정 → 불쾌한 골짜기 회피. 벡터라 페르소나별 파라미터(피부톤·헤어·눈 형태·연령감)를 **데이터로 찍어내 시스템화 가능**(페르소나 무한 확장). 눈·눈썹·입꼬리·어깨선 4채널만으로 저항/라포/정서를 은은히 표현. 윤찬 미니멀 톤과 정합.
|
||||
- **C 탈락**: 80% 사실성에서 미세 립싱크 오차가 더 크게 불쾌. 미성년·취약 내담자를 실사로 그리면 "이 아이 실존?" 윤리 문제 + 데이터 편향.
|
||||
- **A를 폴백으로 흡수**: ElevenLabs Orb 같은 성숙한 React 자산을 페르소나 미선택/로딩/에러 화면에 영구 잔존.
|
||||
|
||||
#### 카드 구조
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ [상단 kicker] 교육용 가상 내담자 │ ← 상시 라벨(오인 차단)
|
||||
│ │
|
||||
│ ╭───────────────╮ │
|
||||
│ │ 양식화 흉상 │ │ ← 어깨~머리, 평면 벡터
|
||||
│ │ (얼굴 35% 사실) │ │ 배경에 호흡하는 광배
|
||||
│ ╰───────────────╯ │
|
||||
│ │
|
||||
│ 서연 · 17세 · 고2 │ ← 페르소나 메타 1줄
|
||||
│ ────────── │
|
||||
│ 지금: 머뭇거리며 듣는 중 │ ← 상태 텍스트 1줄
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
핵심: **얼굴은 양식화하되 "사람"으로 읽히고, 절대 "실존 인물"로는 안 읽히게.** 코·주름·모공 없음. 눈·눈썹·입꼬리·어깨 각도가 정서를 나르는 4개 채널.
|
||||
|
||||
### 4.2 음성 발화 중 반응 — 상태 모델 (4-state)
|
||||
|
||||
음성 UI 표준 idle/listening/thinking/speaking을 내담자 맥락으로 재정의.
|
||||
|
||||
| 상태 | 트리거 | 입 | 눈/눈썹 | 광배(aura) | 어깨 호흡 |
|
||||
|---|---|---|---|---|---|
|
||||
| **listening (학습자 발화 중)** | 마이크 입력 감지 | 닫힘, 미세 입꼬리 | **학습자 쪽 응시**, 깜빡임 4~6s | 차분 펄스 0.06 opacity, 5.5s | 진폭 1.5px, 4s |
|
||||
| **thinking (응답 생성 대기)** | STT 종료~TTS 시작 | 살짝 다묾 | 시선 살짝 아래/옆, 깜빡임 느려짐 | 안쪽 수축, 채도 -8% | 정지에 가깝게 |
|
||||
| **speaking (내담자 발화)** | TTS 오디오 재생 | **립싱크 진폭** | 정면 복귀, 눈썹 정서 반영 | 발화 진폭에 약동기(0.04~0.12) | 발화 리듬따라 미세 |
|
||||
| **idle (시작 전/일시정지)** | 무입력 | 닫힘 | 천천히 깜빡, 가끔 시선 이동 | 최소 펄스 | 느린 호흡 3.5s |
|
||||
|
||||
### 4.3 립싱크 — "정밀 음소 매칭 안 함"이 정답
|
||||
|
||||
35% 사실성 얼굴에 정밀 비짐(viseme) 매핑은 오히려 불쾌. **오디오 진폭(RMS) → 입 열림 높이** 1채널 매핑.
|
||||
|
||||
```ts
|
||||
// Web Audio AnalyserNode → 매 프레임 RMS → 입 SVG path 세로 스케일
|
||||
const rms = getRMS(analyser); // 0~1
|
||||
const mouthOpen = smooth(rms, 0.18); // 지수평활(튐 방지), τ≈180ms
|
||||
// <path class="mouth" style={{ transform:`scaleY(${0.35 + mouthOpen*0.65})` }}/>
|
||||
```
|
||||
|
||||
- 입 모양: 닫힘=얇은 호, 열림=타원. `scaleY`만 변형. **이빨·혀 절대 안 그림.**
|
||||
- **데드존**: rms < 0.04는 닫힘 고정 → 무음 구간 입 떨림 제거.
|
||||
- 평활 필수: raw RMS 직결하면 입이 경련함. τ=150~200ms.
|
||||
|
||||
### 4.4 모션 진폭 가이드 (산만함 차단의 핵심)
|
||||
|
||||
| 모션 | 진폭 | 주기/속도 | 비고 |
|
||||
|---|---|---|---|
|
||||
| 어깨 호흡(idle) | translateY 1.5~2.5px | 3.5~5.5s ease-in-out 무한 | "있는 줄 모르게" |
|
||||
| 깜빡임 | scaleY 1→0.08→1 | 120ms, 간격 4~7s 랜덤 | 생체감의 80%가 여기서 |
|
||||
| 광배 펄스 | opacity ±0.04~0.08 | 5~6s | speaking 때만 오디오 동기 |
|
||||
| 시선 이동(saccade) | ±3px | 0.3s, 간격 6~10s | thinking에서 빈도↑ |
|
||||
| 립싱크 | scaleY 0.35~1.0 | 오디오 실시간 | 데드존+평활 |
|
||||
|
||||
> 원칙: **동시에 움직이는 채널 ≤ 2개.** speaking이면 입+광배만. listening이면 깜빡임+호흡만.
|
||||
|
||||
### 4.5 정서 시각화 — 6파라미터 상태머신
|
||||
|
||||
valence/arousal 축을 아바타 파라미터로 인코딩. **과장 절대 금지**(만화적 눈물·홍조·땀방울 전부 금지). 상담 도구의 정서는 "감지되되 단언되지 않는" 수준.
|
||||
|
||||
| 내담자 정서 | 눈/눈썹 | 입꼬리 | 자세(어깨선) | 광배 색조 | 호흡 |
|
||||
|---|---|---|---|---|---|
|
||||
| **중립/라포** | 눈썹 수평, 눈맞춤 | 미세 상향 | 정면, 약간 열림 | 세이지 `#3E7A6E` 4% | 안정 4s |
|
||||
| **우울(저각성·부정)** | 눈꺼풀 1~2px 하강, 시선 아래 | 수평~미세 하향 | 어깨 안으로·아래로 | 청회색 `#5B6B73` 5% | 느리고 얕게 5.5s |
|
||||
| **불안(고각성·부정)** | 깜빡임↑(2~4s), saccade↑ | 다묾, 긴장 | 약간 들썩 | 차가운 청 `#3B6E8F` 5%, 빠른 펄스 | 빠르고 얕게 3s |
|
||||
| **저항/방어** | 시선 회피(옆/아래 고정) | 굳게 닫힘 | 반쯤 돌아선 각도(turn 8°) | 중립 회색, 무펄스 | 정지에 가깝게 |
|
||||
|
||||
상태머신이 `(eyelidDrop, gazeAvert, shoulderTurn, breathRate, auraHue, blinkInterval)` 6개 숫자만 바꾸면 정서 전환. **라포가 쌓이면(세션 진행) 저항 페르소나가 shoulderTurn 8°→0°, gazeAvert→눈맞춤으로 서서히 완화** → 학습자가 "내가 라포를 만들었다"를 시각적으로 체감. **이게 이 도구의 교육적 핵심.** 정서 전환은 0.8~1.2s ease로 천천히, 동시 변하는 파라미터 ≤3개.
|
||||
|
||||
### 4.6 페르소나 파라미터 시스템 (새 페르소나 = 새 숫자 묶음)
|
||||
|
||||
```jsonc
|
||||
// persona/seoyeon.json — 청소년 페르소나 예시
|
||||
{
|
||||
"id": "seoyeon",
|
||||
"label": "서연 · 17세 · 고2",
|
||||
"appearance": {
|
||||
"ageBand": "teen", // teen | youngAdult | adult | senior
|
||||
"skinTone": "#E8C9A8",
|
||||
"hair": { "style": "long-straight", "color": "#2B2723" },
|
||||
"faceRatio": { "eyeSize": 1.15, "jawWidth": 0.92 }, // teen=큰 눈·좁은 턱
|
||||
"outfit": "hoodie", // 실루엣만, 디테일 X
|
||||
"realism": 0.40 // 0.35~0.45 고정(불쾌한 골짜기 회피)
|
||||
},
|
||||
"voice": { "provider": "openai", "preset": "soft-young-fem", "rate": 0.96 },
|
||||
"baseAffect": "anxious", // 시작 정서
|
||||
"resistance": 0.6, // 0~1, 라포로 감소
|
||||
"auraHueShift": -6
|
||||
}
|
||||
```
|
||||
|
||||
#### 연령대 외형 규칙
|
||||
|
||||
| ageBand | 눈 크기 | 턱/얼굴 | 실루엣 | 광배 채도 | 음성 톤 |
|
||||
|---|---|---|---|---|---|
|
||||
| **teen** | 1.1~1.2× | 좁은 턱·둥근 | 후디·체크 | 약간 높음(생기) | 부드럽고 높은 |
|
||||
| **youngAdult** | 1.0× | 표준 | 캐주얼 | 중간 | 표준 |
|
||||
| **adult** | 0.95× | 각진 턱 | 셔츠·니트 | 낮음(차분) | 낮고 안정 |
|
||||
| **senior** | 0.92× | 부드러운 윤곽 | 단정 | 낮고 따뜻 | 느린 |
|
||||
|
||||
- **미성년 페르소나는 realism을 0.35로 더 낮추고** `교육용 가상 내담자(미성년 시뮬레이션)` 라벨 강화 → 실존 미성년 오인 원천 차단.
|
||||
- 외형은 인종·특정 외모 stereotype 회피를 위해 **중립 톤 팔레트 3~4종**만 제공(편향 최소화).
|
||||
|
||||
### 4.7 아바타 구현 기술 — SVG 1차, Rive 2차
|
||||
|
||||
| 기술 | 적합 | 근거 |
|
||||
|---|---|---|
|
||||
| **SVG + CSS/Web Animations + Web Audio** | ★ 1차 | 페르소나=벡터 파라미터, 립싱크=RMS 1채널, 의존성 0, React 친화, 미니멀 정합 |
|
||||
| **Rive (상태머신)** | ★ 2차 고도화 | 상태머신이 .riv에 내장, React는 input만 wiring. Lottie 대비 파일 10~15× 작음 |
|
||||
| Lottie | 보조 | 정형 애니(로딩·전환)엔 OK, 실시간 오디오 반응엔 부적합 |
|
||||
| Live2D/3D | 장기 | 표현력 최고지만 20주·시스템화 불리 |
|
||||
|
||||
#### 1차안 컴포넌트 구조 (SVG)
|
||||
|
||||
```
|
||||
<ClientAvatar persona state affect analyser/>
|
||||
├─ <AuraLayer hue affect state/> // 배경 광배: radial-gradient, opacity 펄스
|
||||
├─ <BodySilhouette persona/> // 어깨선, shoulderTurn 각도
|
||||
├─ <FaceGroup persona affect>
|
||||
│ ├─ <Eyes blink gaze eyelidDrop/>
|
||||
│ ├─ <Brows affect/> // 정서별 각도 2~3px
|
||||
│ └─ <Mouth analyser state/> // RMS→scaleY 립싱크
|
||||
└─ <PersonaLabel/> // "교육용 가상 내담자" 상시
|
||||
```
|
||||
|
||||
```tsx
|
||||
// 단일 rAF 루프, 의존성 0
|
||||
function useAvatarMotion(state, affect, analyser) {
|
||||
const [frame, setFrame] = useState(initFrame(affect));
|
||||
useEffect(() => {
|
||||
let raf, t0 = performance.now();
|
||||
const loop = (t) => {
|
||||
const dt = (t - t0) / 1000;
|
||||
const breath = Math.sin(dt * (2*Math.PI/affect.breathPeriod)) * affect.breathAmp;
|
||||
const mouth = state==='speaking' ? smoothRMS(analyser) : 0;
|
||||
const blink = blinkSchedule(t, affect.blinkInterval);
|
||||
setFrame({ breath, mouth, blink, gaze: gazeFor(state) });
|
||||
raf = requestAnimationFrame(loop);
|
||||
};
|
||||
raf = requestAnimationFrame(loop);
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}, [state, affect, analyser]);
|
||||
return frame;
|
||||
}
|
||||
```
|
||||
|
||||
- **퍼포먼스**: SVG 변형은 `transform`(GPU 합성)만, layout 트리거 속성(width/top) 금지. 단일 rAF, 60fps 여유.
|
||||
- **RN 이전 대비**: SVG는 `react-native-svg`, Web Audio는 `react-native-audio-api`로 대체 → 로직 재사용. Rive도 RN 런타임 있음.
|
||||
- **접근성**: `prefers-reduced-motion` 켜지면 호흡·광배 정지, 상태는 텍스트로만.
|
||||
|
||||
---
|
||||
|
||||
## 5. 상담 세션 화면 (메인 화면)
|
||||
|
||||
핵심 긴장: **몰입 vs 평가 가시성.** 실시간 피드백이 라포 형성을 방해하면 안 되고, 평가가 사후로만 묶이면 학습 효과가 떨어진다. 해법 = **2단(two-tier) 피드백 모델** — 실시간은 "신호(signal)" 수준, 정밀 평가는 "회기말 리뷰"로.
|
||||
|
||||
### 5.1 3-column 골격 (데스크탑 ≥1280px)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ 상단 바 (56px) 좌:세션 제목/시나리오 중앙:회기 단계 우:경과시간·종료 │
|
||||
├──────────────────┬──────────────────────────────────┬──────────────────────┤
|
||||
│ LEFT (320px) │ CENTER (flexible) │ RIGHT (300px) │
|
||||
│ ───────────── │ ───────────────── │ ───────────── │
|
||||
│ 회기 단계 트랙커 │ 내담자 아바타 / 음성 STAGE │ 피드백 신호 패널 │
|
||||
│ (라포→탐색→ │ (어두운 배경, 중앙 집중) │ (은은한, 평시 접힘) │
|
||||
│ 개입→정리) │ │ │
|
||||
│ 내담자 컨텍스트 │ [ 아바타 + 음성 오브 ] │ ── 내담자 상태 ── │
|
||||
│ 카드 │ │ ── 라이브 신호 ── │
|
||||
│ │ 실시간 자막 (아래→위 흐름) │ (최대 1개, 페이드) │
|
||||
│ │ │ ── 셀프 노트 ── │
|
||||
├──────────────────┴──────────────────────────────────┴──────────────────────┤
|
||||
│ 하단 컨트롤 바 (80px) 마이크 · 일시정지 · [몰입│은은│코칭] · (밀어서)종료 │
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**비율 의도: 시각적 무게 = 주의 배분.** 내담자 stage 60%, 평가 패널 15%(평시 대부분 접힘). 화면은 대시보드가 아니라 상담실이다.
|
||||
|
||||
**반응형:**
|
||||
- 태블릿(768~1279px): RIGHT 패널이 오버레이 drawer(평시 숨김, 우상단 신호 도트만). LEFT 280px.
|
||||
- 모바일/RN(<768px): 단일 컬럼. stage 상단 고정(40vh), 자막 스크롤, 단계는 상단 가는 진행선, 평가는 하단 시트(bottom sheet). 컨트롤 바 하단 고정.
|
||||
|
||||
### 5.2 중앙 STAGE — 음성 오브 (Voice Orb, 4상태)
|
||||
|
||||
자극적 막대 파형 대신 **차분한 호흡(breathing) 애니메이션**. 아바타(원형 220px) 둘레 오브 링.
|
||||
|
||||
| 상태 | 시각 | 색 | 모션 |
|
||||
|---|---|---|---|
|
||||
| **내담자 말하는 중** | 따뜻한 링, 음량 따라 미세 진동 | `--clay` glow, 두께 4px | 음량 연동 amplitude(0.5~3px), 0% 형광 |
|
||||
| **학습자 말하는 중** | 세이지 링, 호흡 펄스 | `--accent` glow | 1.4s ease breathing scale 1.0→1.04 |
|
||||
| **대기/침묵(thinking)** | 가는 점선 매우 느리게 회전 | `--ink-3` | 8s/회전, 거의 안 보일 정도 |
|
||||
| **일시정지** | 반투명 오버레이 + "일시정지" | `--bg-stage` 60% | 정지 |
|
||||
|
||||
```css
|
||||
.orb {
|
||||
position: absolute; inset: -14px; border-radius: 50%;
|
||||
box-shadow: 0 0 0 3px var(--ring-color),
|
||||
0 0 28px 4px color-mix(in srgb, var(--ring-color) 35%, transparent);
|
||||
transition: box-shadow 320ms ease;
|
||||
}
|
||||
.orb[data-state="learner"] { --ring-color: var(--accent); animation: breathe 1.4s ease-in-out infinite; }
|
||||
.orb[data-state="client"] { --ring-color: var(--clay); /* JS가 RMS 음량을 --amp로 주입 */ }
|
||||
@keyframes breathe { 0%,100%{ transform: scale(1) } 50%{ transform: scale(1.035) } }
|
||||
```
|
||||
|
||||
음량 연동은 JS `onVolumeChange`/RMS → CSS 변수 `--amp`로 box-shadow blur 미세 모듈레이션. **막대 파형(equalizer) 금지** — 너무 "녹음 앱"스럽고 자극적. 호흡하는 빛이 임상적이다.
|
||||
|
||||
### 5.3 실시간 자막 (Transcript) — 대본 스타일
|
||||
|
||||
음성이 주, 자막은 보조. stage 하단, **아래→위 흐름**. 채팅 버블 양산형 회피, 대본(script) 스타일:
|
||||
|
||||
```
|
||||
─────────────────────────────────────
|
||||
내담자 14:32
|
||||
요즘은 그냥… 아침에 일어나는 게
|
||||
제일 힘들어요.
|
||||
나(학습자) 14:33
|
||||
아침에 일어나는 것조차 버겁게
|
||||
느껴지시는군요. 그 마음을 조금
|
||||
더 들려주실 수 있을까요?
|
||||
─────────────────────────────────────
|
||||
```
|
||||
|
||||
- 화자 라벨 12px 600 메타톤. 내담자 발화는 `--clay-tint` 옅은 배경 틴트 블록, 학습자 발화는 `--accent-tint` 틴트. **버블 외곽선·꼬리·border-left 없음.** 틴트 블록만으로 화자 구분.
|
||||
- 진행 중 발화(partial)는 `--ink-3` 흐릿하게 타이핑되다 확정되면 `--ink` 또렷하게.
|
||||
- 자동 스크롤, 위로 스크롤하면 자동스크롤 해제 + "최신으로" 작은 칩.
|
||||
|
||||
### 5.4 회기 단계 표시 (라포 → 탐색 → 개입 → 정리)
|
||||
|
||||
LEFT 패널 세로 트랙커 + 상단 바 가로 미니. **현재 단계만 또렷, 나머지는 은은히** (단계 압박 방지).
|
||||
|
||||
```
|
||||
회기 진행 ← kicker
|
||||
● 라포 형성 09:12 ← 완료: 채워진 sage 도트 + 소요시간
|
||||
│
|
||||
◉ 탐색 진행중 ← 현재: 링+채움, accent, 굵게
|
||||
│
|
||||
○ 개입 ← 예정: 빈 도트, ink-3
|
||||
│
|
||||
○ 정리
|
||||
```
|
||||
|
||||
- 연결선 1px `--hair`, 완료 구간만 `--accent-bright`.
|
||||
- **자동 추적 기본, 수동 보정 가능.** 백그라운드 평가 AI가 대화로 현재 단계 추정해 `◉` 이동. 학습자가 직접 "다음 단계로" 가능(자각 촉진). 추정과 인식 어긋나면 회기말 리뷰에서 짚음.
|
||||
- **자극 배제:** 단계 전환 시 화면 전환·효과음·축하 애니 없음. 도트가 조용히 채워질 뿐(400ms ease).
|
||||
|
||||
### 5.5 실시간 피드백 — "거슬리지 않게" (이 설계의 심장)
|
||||
|
||||
**3단계 강도 모델**을 학습자가 토글로 고른다 (하단 컨트롤 바 segmented control, 기본=은은한 신호):
|
||||
|
||||
| 모드 | 실시간 표시 | 대상 |
|
||||
|---|---|---|
|
||||
| **몰입(Immersive)** | 라이브 신호 전혀 없음. 백그라운드 기록만. 모든 피드백 회기말. | 숙련·실전 압박 연습 |
|
||||
| **은은한 신호(Ambient)** ★기본 | 우측 패널에 **앰비언트 도트 1개만**, 색으로 톤만. 텍스트 없음(호버 시) | 일반 연습 |
|
||||
| **코칭(Coached)** | 발화 직후 짧은 텍스트 힌트 페이드인 | 초보·특정 기법 집중 |
|
||||
|
||||
#### 앰비언트 신호 (기본 모드 핵심)
|
||||
|
||||
우측 패널에 **단 하나의 부드러운 도트**가 직전 발화 평가 톤만 색으로 전한다. 점수·숫자·순위 **없음.**
|
||||
|
||||
```
|
||||
라이브 신호 ← kicker
|
||||
● 공감적 반영 ← pos 도트 + 한 단어. 6초 후 페이드.
|
||||
최근 흐름 · · ● · ◦ · ● ← 최근 6개 발화 톤 미니 시퀀스(클릭 가능)
|
||||
```
|
||||
|
||||
- 도트 색: `--pos-solid`(잘함) / `--warn-solid`(살펴볼 점) / `--ink-3`(중립). **빨강·점수 없음.**
|
||||
- 등장: 발화 종료+평가 완료 시 opacity 0→1, 350ms. **6초 후 자동 페이드** — 누적되면 대시보드가 됨.
|
||||
- 거슬림 최소화 3원칙: ①한 번에 1개 ②텍스트 기본 숨김(색만) ③시간 제한 페이드. 진동/소리/번쩍임 전무. 주변시로 "느낄" 수준.
|
||||
|
||||
#### 코칭 모드 텍스트 힌트
|
||||
|
||||
```
|
||||
코칭 힌트 ← kicker
|
||||
방금 질문은 '예/아니오'로 닫혀 있었어요.
|
||||
내담자가 더 말하도록 열어보면 어떨까요?
|
||||
예: "그때 어떤 마음이 드셨는지 들려주세요."
|
||||
[ 알겠어요 ] ← 가볍게 dismiss
|
||||
```
|
||||
- `--accent-tint` 배경 틴트 블록(border 없음), 13~14px. 예시 문장 `--ink-2` 이탤릭. 한 번에 1개, 새 힌트 오면 이전 것 교체(stack 안 함).
|
||||
- **위험 신호만 예외적 즉시 표시(모드 무관):** 자해·위기 언급을 학습자가 놓치면 모드 무관하게 우측에 차분한(빨강 아닌 `--warn-solid`) 알림 — "안전 점검: 방금 위기 신호가 있었어요." 교육적으로 놓치면 안 되는 지점이라 항상 노출.
|
||||
|
||||
#### 내담자 상태 미터 (우측 상단) — "반응 읽기 연습"의 거울
|
||||
|
||||
```
|
||||
내담자 상태
|
||||
감정 톤 불안 ▓▓▓▓░░ ← 가로 게이지, clay 계열, 라벨 텍스트
|
||||
마음 열림 ▓▓░░░░ ← 라포 진척 대리지표
|
||||
```
|
||||
게이지는 매우 천천히(2s ease) — 실시간 흔들림이 산만하지 않게. 막대 라운드 없는 사각, 4px 높이.
|
||||
|
||||
### 5.6 하단 컨트롤 바
|
||||
|
||||
- **마이크 버튼(주 컨트롤):** 56px 원형. 학습자 말하는 중 = `--accent` 채움 + 호흡. 음소거 = 아웃라인. push-to-talk 또는 토글. 상태 텍스트 한 줄("듣고 있어요" / "마이크 꺼짐").
|
||||
- **일시정지:** 타이머·평가·녹음 모두 정지. 정지 중 stage 차분한 오버레이.
|
||||
- **종료:** 오발동 방지 — **밀어서 종료(slide-to-confirm)** 또는 0.8s 길게. 종료가 유일하게 진한 경고색(`--crit-solid`) 허용 영역.
|
||||
- 접근성: 키보드(Space=마이크, P=일시정지), `--focus-ring` 2px. 음성 상태는 색만이 아니라 텍스트 병기(색각 이상 대응).
|
||||
|
||||
### 5.7 회기 종료 후 리뷰 화면 (진짜 학습이 일어나는 곳)
|
||||
|
||||
실시간에선 일부러 참았던 정밀 평가를 여기서 푼다. 구조: **타임라인 + 기법 라벨 + 슈퍼바이저 코멘트 + 개선점.**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ 리뷰 · 시나리오:우울 호소 20대/회기1 오디오 다시듣기▷ PDF 내보내기 │
|
||||
├──────────────────────────────────────────────────────────────────────────┤
|
||||
│ 한 줄 요약 (큰 문장 하나, 24px/600 — 에디토리얼 미니멀의 핵심) │
|
||||
│ "라포는 안정적으로 형성됐지만, 탐색 단계에서 닫힌 질문이 반복돼 │
|
||||
│ 내담자의 자기개방이 일찍 멈췄습니다." │
|
||||
├───────────────────────────────────────┬──────────────────────────────────┤
|
||||
│ LEFT (62%) 타임라인 + 자막 │ RIGHT (38%) 분석 사이드 │
|
||||
│ 회기 타임라인(가로 막대) │ ── 기법 사용 분포 ── │
|
||||
│ 라포▏▏탐색▏▏▏▏▏개입▏▏정리▏ │ 반영적 경청 ▓▓▓▓▓▓ │
|
||||
│ ▲ 마커(코멘트·기법·하이라이트) │ 개방형 질문 ▓▓ │
|
||||
│ ─── 스크롤 자막(마커 연동) ─── │ 닫힌 질문 ⚠ ▓▓▓▓▓ (살펴볼 점) │
|
||||
│ 나 14:33 ◆[감정 반영] … │ ── 잘한 순간 ── │
|
||||
│ └ 슈퍼바이저: "좋은 반영…" │ ── 다음 회기 개선점(최대 3개) ── │
|
||||
└───────────────────────────────────────┴──────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **타임라인(가로):** 4단계 너비 비례 구간으로 색 농담 구분. 마커 = 기법 라벨·살펴볼 순간·잘한 순간·슈퍼바이저 코멘트(이모지 아님, SVG 글리프/도트). 클릭하면 좌측 자막 점프 + 오디오 그 시점으로.
|
||||
- **자막 + 인라인 분석:** 학습자 발화마다 기법 라벨 칩(`[반영적 경청]` `[개방형 질문]` `[닫힌 질문]`), `--accent-tint`/`--warn-tint` 배경, border 없음 11px. 라벨 아래 들여쓰기로 슈퍼바이저 코멘트(AI 자동 + 교수자 직접). AI코멘트 `--ink-2`, 사람 코멘트 `--accent` + "교수자" 태그.
|
||||
- **분석 사이드:** 기법 사용 분포(가로 게이지, 점수 아님 빈도+적절성, 과다·과소만 `--warn` 라벨). 잘한 것 먼저, 개선점 **최대 3개**(실행 가능 구체적, 각 항목 타임라인 마커 연결). 다음 회기 개선점은 "다음에 시도할 한 문장" 형태.
|
||||
- **톤:** 점수·등급·합불 **없음.** 상담 역량은 성장 곡선이지 시험이 아니다. 추세는 별도 "학습자 성장" 화면에서만, 단일 리뷰엔 절대점수 안 보임. PDF 내보내기(슈퍼바이저 제출), 오디오 다시듣기, 클립 공유.
|
||||
|
||||
---
|
||||
|
||||
## 6. 3역할 대시보드 (학습자 / 교수자 / 관리자)
|
||||
|
||||
### 6.1 4가지 설계 결정 (근거)
|
||||
|
||||
| 결정 | 근거 |
|
||||
|---|---|
|
||||
| **레이더차트 금지, small-multiples 라인 채택** | 레이더는 항목 순서로 모양 왜곡 + 범주선 "증가/감소" 착시. Tufte small-multiples + 공통 축이 정확. 윤찬 "카드덤프 금지"와도 정합. |
|
||||
| **신뢰·차분 = 단일 accent + 넉넉한 여백 + 위계** | 의료 UI: blue=신뢰, green=정상범위, **red는 진짜 임상 긴급에만 예약**. whitespace·typography로 불안 완화. |
|
||||
| **교수자 화면은 "인지"가 아니라 "개입"까지** | 교사용 LA 대시보드 대부분이 awareness만 주고 actionable intervention이 없다는 비판 → 첫 화면을 "개입 필요 학생" 액션 큐로. |
|
||||
| **역할 전환 = 컨텍스트 라벨 + accent 토큰** | 과한 색·뱃지 남용은 양산형. 좌상단 역할 라벨 + accent 한 토큰 교체로 "지금 누구의 화면인지"만 조용히. |
|
||||
|
||||
### 6.2 역할별 accent 오버라이드
|
||||
|
||||
```css
|
||||
body[data-role="learner"] { --accent:#3E7A6E; --accent-bright:#5F968B; --accent-tint:#EEF4F2; } /* 세이지-틸: 성장 */
|
||||
body[data-role="instructor"] { --accent:#3A5BA0; --accent-bright:#5478C4; --accent-tint:#EAEFF7; } /* 인디고-블루: 감독 */
|
||||
body[data-role="admin"] { --accent:#5B5F6B; --accent-bright:#7D818E; --accent-tint:#EEEFF1; } /* 슬레이트: 운영 */
|
||||
```
|
||||
|
||||
세 역할 모두 채도를 낮춰 자극 없게. red(`--crit-solid`)는 어느 역할에서도 장식으로 안 쓴다 — 오직 "지금 개입해야 하는 학생/세션" 단 한 곳.
|
||||
|
||||
### 6.3 공통 앱 셸 (Shell)
|
||||
|
||||
같은 골격 공유, 좌측 네비 항목 + 좌상단 컨텍스트 라벨만 차이. 역할 전환 시 화면 리셋이 아니라 메뉴·accent만 바뀜.
|
||||
|
||||
- 톱바 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 관용).
|
||||
- **역할 컨텍스트 라벨**: 톱바 좌측, `font-family:var(--font-num); font-size:13px; letter-spacing:0.04em; color:var(--accent);` — "학습 대시보드"/"교수 콘솔"/"운영 콘솔". "지금 누구로 보고 있나"의 유일하고 조용한 신호.
|
||||
- **권한 표현 원칙: 권한 없으면 DOM에서 제거.** disabled 회색처리도 안 함 — 존재를 숨김. 화면이 깨끗해지고 "내가 못 하는 것" 노이즈 제거. 임상 데이터 프라이버시(관리자는 학생 개별 성장곡선·transcript 기본 접근 불가, 메뉴 자체 렌더 안 함).
|
||||
|
||||
### 6.4 학습자 대시보드 (홈 = "당신은 나아지고 있다")
|
||||
|
||||
```
|
||||
COUNSELING PRACTICE · 4주차 ← kicker
|
||||
수련생님, 지금까지 12회 연습했어요. ← h1 28px/700
|
||||
최근 라포 형성이 눈에 띄게 안정됐습니다. ← lead, accent .em
|
||||
|
||||
┌─────────────────────────────┐ ┌───────────────────────────┐ ← cols 7:5 비대칭
|
||||
│ 종합 상담 역량 │ │ 다음 연습 │
|
||||
│ ╭───────╮ │ │ ▸ 불안 호소 내담자·중급 │
|
||||
│ │ 72 │ +6 ▲ │ │ 이전 약점: 침묵 다루기 │
|
||||
│ ╰───────╯ 지난주 대비 │ │ [ 연습 시작 → ] │
|
||||
│ (단일 게이지, 둘레 progress)│ │ 추천 이유: 라포는 올랐지만 │
|
||||
└─────────────────────────────┘ │ 위기개입 정확도 정체 │
|
||||
└───────────────────────────┘
|
||||
역량별 추이 (최근 8회) ← kicker
|
||||
┌────────┐┌────────┐┌────────┐┌────────┐ ← small multiples 4열, 공통 y축 0-100
|
||||
│라포형성 ││기법사용 ││개입정확도││공감반영 │
|
||||
│ ╱╲__╱ ││ __╱╲╱ ││ ╲__╱╲ ││ __╱╱ │
|
||||
│ 82 ▲ ││ 68 → ││ 61 ▼ ││ 75 ▲ │
|
||||
└────────┘└────────┘└────────┘└────────┘
|
||||
최근 피드백 모아보기 (테이블, 카드 아님) ← kicker
|
||||
6/24 불안 내담자 │ "침묵 견디지 못하고 성급히 해결책 제시…" — 개입 ▸
|
||||
6/22 분노 내담자 │ "반영 정확. 다만 감정 명명이 늦음." — 공감 ▸
|
||||
```
|
||||
|
||||
- **종합 게이지(radar 대체):** 단일 원형. SVG `<circle>` 트랙 `stroke:var(--neutral-150)`, fill arc `stroke:var(--accent); stroke-linecap:round`. 중앙 숫자 48px/700. 변화량 `+6 ▲` = `--pos-solid`, 하락이면 `--warn-solid`(앰버, 빨강 아님). 아래 한 문장 자연어 요약.
|
||||
- **역량별 추이 = small multiples:** 4개 동일 크기 미니 라인. **공통 y축 0-100**(첫 카드만 0/50/100 옅게 표기). 라인 `stroke:var(--ink-2); stroke-width:1.5`. 마지막 점만 `r:3; fill:var(--accent)`. 카드 `background:var(--surface); border:1px solid var(--hair); border-radius:8px; padding:16px`. **상단 강조선·좌측바 금지.** 우하단 최신값+추세 화살표(▲`--pos`, →`--ink-3`, ▼`--warn`). 호버 툴팁, 클릭 시 세션 상세.
|
||||
- **피드백 모아보기 = 테이블, 카드 아님:** 날짜/내담자유형/인용 코멘트/태그/`▸`. zebra 없이 행 `border-bottom:1px solid` `--paper-2`. 행 hover `background:var(--accent-tint)`. 카드 격자로 흩뿌리지 않고 시간순 한 컬럼 위계.
|
||||
|
||||
### 6.5 교수자 콘솔 (홈 = "누구를 먼저 도와야 하는가")
|
||||
|
||||
핵심: **awareness가 아니라 triage.** 첫 화면 최상단이 "개입 필요 학생" 액션 큐.
|
||||
|
||||
```
|
||||
상담실습 II · 28명 담당 ← kicker
|
||||
이번 주, 3명에게 개입이 필요합니다. ← h1, accent .em
|
||||
|
||||
┌──────────────────────────────────────────────────────────────┐ ← 개입 큐(최우선, 풀폭)
|
||||
│ ● 박서연 개입정확도 3주 연속 하락(61→52→44) 미연습 5일 ▸ │ 좌측 dot = --crit-solid
|
||||
│ ● 이도현 위기상담 시뮬 중도이탈 2회 마지막 6/20 ▸ │
|
||||
│ ● 최민준 라포 점수 급락, 피드백 미열람 7건 ▸ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
학생별 현황 [정렬: 위험도▾] [영역: 전체▾]
|
||||
학생 세션 라포 기법 개입 공감 추세 마지막 상태
|
||||
김수련 12 82↗ 68→ 61↘ 75↗ ▁▃▅▆▇ 6/24 양호
|
||||
박서연 8 55↘ 49↘ 44↘ 58↘ ▇▅▃▂▁ 6/19 ● 개입
|
||||
이도현 14 88↗ 79↗ 71→ 84↗ ▅▆▆▇▇ 6/23 양호
|
||||
```
|
||||
|
||||
- **개입 큐(triage):** 풀폭 패널. 각 행 좌측 dot `8px; background:var(--crit-solid)`(red의 유일한 정당한 사용처). 사유 = 수치 변화 + 행동 신호(미연습일·미열람·중도이탈)를 자연어로. 알고리즘이 뽑되 교수가 최종 판단하도록 "왜"를 노출.
|
||||
- **반 평균 역량 분포 = 가로 막대**(레이더 아님): fill `var(--accent)`, 트랙 `var(--paper-2)`. **최저 항목만** 우측 `← 취약` `color:var(--warn-solid)`. 나머지 차분한 무채.
|
||||
- **학생별 테이블:** sparkline(▁▃▅▆▇, 셀 안 7px SVG, 최근 8회 종합점수). 역량 셀 `82↗`(화살표 색만 시맨틱). 정렬 기본 위험도 순. 상태 "양호"는 `--ink-3` 무채, "● 개입"만 `--crit-solid` dot. red를 한 곳에 집중. 행 클릭 → 학생 드릴다운(학습자 화면 + 세션별 utterance-level 평가 타임라인).
|
||||
|
||||
### 6.6 관리자 콘솔 (홈 = "시스템이 건강한가 · 누가 무엇을 했는가")
|
||||
|
||||
핵심: **운영 신뢰성 + 감사.** 학습 내용엔 개입 안 하고 시스템·사용·교수활동 감사에 집중. 무채 슬레이트로 가장 차분.
|
||||
|
||||
- **운영 KPI 5:** 사용자/강좌/세션주/가동%/API오류. mono 32px. 오류 0 초과 시 해당 KPI만 `--warn-solid`, 임계 초과 `--crit-solid`. 정상 땐 전부 무채 — "조용한 게 정상".
|
||||
- **세션 추이 = 단일 라인차트**(막대 아님). 피크 1점만 라벨.
|
||||
- **음성·AI 사용 + 비용:** OpenAI 음성 분/평가 토큰/추정 비용/예산 대비 게이지(80% 넘으면 `--warn-solid`). 운영비 가시화(윤찬 graphify 비용 통제 문화).
|
||||
- **교수 활동 감사 테이블:** 교수별 피드백 열람률(품질 신호)·시나리오 발행·마지막 활동. 저조 지표만 `← 저조` `--warn`. "⚠ 점검" = 관리자 리마인드 대상.
|
||||
- **Audit log:** 시간순 한 컬럼. 행위자·행위·대상. 색 없음(감사는 중립). 클릭 시 상세.
|
||||
|
||||
### 6.7 데이터 시각화 하드룰 (전 역할 공통)
|
||||
|
||||
| 상황 | 채택 | 금지 |
|
||||
|---|---|---|
|
||||
| 한 사람 여러 역량 **추이** | small multiples 라인(공통 y축) | 레이더/스파이더 |
|
||||
| 여러 역량 **현재값 비교** | 가로 막대(공통 0-100), 최저값만 강조 | 레이더, 전항목 색칠 |
|
||||
| 한 지표 **시간 추이** | 단일 라인(피크/최신 1점만 라벨) | 3D·면적 그라데이션 |
|
||||
| 테이블 안 **추세 압축** | sparkline(7px) + 화살표 | 셀마다 미니 도넛 |
|
||||
| **종합 한 숫자** | 단일 원형 게이지 + 변화량 | 여러 도넛 격자 |
|
||||
|
||||
**차트 공통 스타일:** 격자선 최소(y축 0/50/100 옅은 점선만, 세로 격자 없음). 라인 `stroke-width:1.5`, 점은 최신/이상치만. 색은 데이터 의미에만(추세 ↗`--pos`/↘`--warn`/→무채, 평상시 라인 `--ink-2` 무채 → 색 인플레이션 방지). 진입 라인 draw 280ms, 게이지 arc 400ms ease-out 1회만. 깜빡임·펄스·glow 없음. 모든 수치 `tabular-nums`.
|
||||
|
||||
---
|
||||
|
||||
## 7. 컴포넌트 원칙 (요약 카탈로그)
|
||||
|
||||
공통: **border-radius 절제(6~12px, 알약 금지), 그림자 절제(헤어라인 우선), border는 `--hair`.** 카드는 쓰되 균등 4-4-4 격자 남용 금지 → 비대칭·표·도식.
|
||||
|
||||
### 7.1 버튼 (위계 3단, 알약형 금지 radius 8px)
|
||||
|
||||
| 종류 | 배경 | 텍스트 | 보더 | 용도 |
|
||||
|---|---|---|---|---|
|
||||
| Primary | `--accent` | `--text-on-accent` | none | 세션 시작·저장 (화면당 1~2개) |
|
||||
| Secondary | `--bg-surface` | `--accent` | 1px `--accent` | 보조 액션 |
|
||||
| Ghost | transparent | `--text-body` | none | 취소·텍스트 액션 |
|
||||
| Danger | `--crit-solid` | #fff | none | 세션 강제 종료 등 위급만 |
|
||||
|
||||
```css
|
||||
.btn { font: 600 var(--fs-sm)/1 var(--font-sans); padding: 10px 18px;
|
||||
border-radius: var(--radius); border:1px solid transparent;
|
||||
transition: background .16s ease, transform .12s ease; cursor:pointer; }
|
||||
.btn-primary { background: var(--accent); color: var(--text-on-accent); }
|
||||
.btn-primary:hover { background: var(--accent-deep); }
|
||||
.btn:active { transform: translateY(.5px); } /* 미세 누름, 과한 모션 X */
|
||||
.btn:focus-visible { outline:none; box-shadow: 0 0 0 3px var(--focus-ring); }
|
||||
```
|
||||
|
||||
### 7.2 입력 (radius 6px, 포커스 시 보더색 + 얇은 ring, 좌측바 X)
|
||||
|
||||
```css
|
||||
.input { width:100%; background: var(--bg-surface-2); color: var(--text-body);
|
||||
border:1px solid var(--border-strong); border-radius: var(--radius-sm);
|
||||
padding:10px 12px; font:var(--fs-body)/1.5 var(--font-sans);
|
||||
transition:border-color .15s, box-shadow .15s; }
|
||||
.input:focus { border-color: var(--border-focus); outline:none; box-shadow: 0 0 0 3px var(--focus-ring); }
|
||||
.input[aria-invalid="true"] { border-color: var(--crit-solid); }
|
||||
```
|
||||
|
||||
### 7.3 카드 / 패널 (헤어라인 + 미세 그림자, 상단 강조선 금지)
|
||||
|
||||
```css
|
||||
.card { background: var(--bg-surface); border:1px solid var(--border-subtle);
|
||||
border-radius: var(--radius); padding: var(--sp-5); box-shadow: var(--shadow-sm); }
|
||||
.panel { background: var(--bg-surface); border:1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg); padding: var(--sp-6); }
|
||||
.panel-tint { background: var(--bg-tint); border-color: transparent; } /* 강조 패널: 틴트 배경 */
|
||||
```
|
||||
|
||||
### 7.4 평가 피드백 콜아웃 (이 플랫폼 핵심, 좌측 컬러바 금지 → 배경 틴트 + 도트 + kicker)
|
||||
|
||||
```css
|
||||
.callout { border-radius: var(--radius); padding: var(--sp-4) var(--sp-5);
|
||||
font-size: var(--fs-sm); line-height:1.6; }
|
||||
.callout .clh { font: 600 var(--fs-xs)/1.4; letter-spacing:.04em;
|
||||
display:flex; align-items:center; gap:8px; margin-bottom:6px; }
|
||||
.callout .clh::before { content:""; width:7px; height:7px; border-radius:50%; } /* dot, 바 아님 */
|
||||
.callout.pos { background:var(--pos-tint); color:var(--pos-text); }
|
||||
.callout.pos .clh::before { background:var(--pos-solid); }
|
||||
.callout.info { background:var(--info-tint); color:var(--info-text); }
|
||||
.callout.info .clh::before { background:var(--info-solid); }
|
||||
.callout.warn { background:var(--warn-tint); color:var(--warn-text); }
|
||||
.callout.warn .clh::before { background:var(--warn-solid); }
|
||||
.callout.crit { background:var(--crit-tint); color:var(--crit-text); } /* 가드레일 위반만 */
|
||||
.callout.crit .clh::before { background:var(--crit-solid); }
|
||||
```
|
||||
|
||||
피드백 강도 매핑: **잘함→`pos`(녹색), 일반 코칭/관찰→`info`(슬레이트), 페이싱·라포 주의→`warn`(앰버), 가드레일/위급→`crit`(적색).** 학습자 화면 기본 피드백은 info/pos가 다수가 되도록.
|
||||
|
||||
### 7.5 라이브 음성 오브 (§5.2 참조)
|
||||
|
||||
내담자 발화=sage, 학습자 발화=clay로 화자 구분(채도 낮게). 깜빡이는 빨강 금지 → 호흡 펄스.
|
||||
|
||||
---
|
||||
|
||||
## 8. React 구현 노트
|
||||
|
||||
### 8.1 컴포넌트 트리
|
||||
|
||||
```
|
||||
<AppShell role={role}> // body[data-role] 설정, 네비·톱바
|
||||
<RoleNav items={navByRole}/> // 권한 없는 항목 필터링 후 렌더(DOM 제거)
|
||||
<ContextLabel/> // mono accent 라벨
|
||||
<Outlet/>
|
||||
├ <LearnerHome> Hero1줄 + cols(Gauge, NextPractice) + MiniMultiples×4 + FeedbackTable
|
||||
├ <InstructorHome> InterventionQueue + KPIs + ClassDistribution + StudentTable(sparkline)
|
||||
└ <AdminHome> OpsKPIs + SessionTrend + UsageCost + FacultyAudit + AuditLog
|
||||
|
||||
<SessionScreen>
|
||||
├ <SessionTopBar scenario stage elapsed onEnd/>
|
||||
├ <SessionLayout> // CSS grid 320/1fr/300, RN은 Flex
|
||||
│ ├ <StagePanel> <StageTracker/> <ClientContextCard/> // LEFT
|
||||
│ ├ <ClientStage> // CENTER
|
||||
│ │ ├ <ClientAvatar persona state affect analyser/> // §4 아바타
|
||||
│ │ ├ <VoiceOrb state amplitude/>
|
||||
│ │ └ <LiveTranscript turns partial/>
|
||||
│ └ <FeedbackRail mode> // RIGHT
|
||||
│ ├ <ClientStateMeter tone openness/>
|
||||
│ ├ <AmbientSignal latest recentSequence/> // 은은한 신호
|
||||
│ ├ <CoachHint hint onDismiss/> // 코칭 모드만
|
||||
│ └ <SelfNote/>
|
||||
└ <ControlBar mic paused mode onMic onPause onModeChange onEnd/>
|
||||
|
||||
<ReviewScreen>
|
||||
├ <ReviewHeader scenario onExportPdf onReplay/>
|
||||
├ <ReviewHeadline summary/> // 큰 한 문장
|
||||
├ <SessionTimeline phases markers onSeek/>
|
||||
├ <AnnotatedTranscript turns techniqueLabels supervisorComments audioSync/>
|
||||
└ <AnalysisSide techniqueDistribution highlights improvements/>
|
||||
```
|
||||
|
||||
### 8.2 상태 관리
|
||||
|
||||
- 음성 이벤트(`onModeChange`/`onVolumeChange`/partial transcript)는 OpenAI Realtime 스트림 → 단일 `SessionStore`(Zustand 권장)로 모아 `VoiceOrb`·`LiveTranscript`·`FeedbackRail`·`ClientAvatar` 구독.
|
||||
- 평가 AI는 **별도 채널(백그라운드)**로 발화 단위 신호를 throttle해서 push. `FeedbackRail`이 6초 페이드 큐로 소비. 평가와 음성 상태 표시는 분리(평가가 음성 UX를 막지 않게).
|
||||
- 차트 프리미티브 4종 먼저 구축: `<Gauge>` `<SparkLine>` `<MiniMultiple>` `<HBar>` (§6.7 룰 내장, props=data+role토큰). 학습자 컴포넌트가 교수 드릴다운에서 재사용됨.
|
||||
|
||||
### 8.3 구현 순서
|
||||
|
||||
1. `tokens.css`(§3) + 앱 셸(§6.3) — 셸은 3역할 공통이므로 1회만.
|
||||
2. 차트 프리미티브 4종.
|
||||
3. 학습자 홈 → 교수 홈 → 관리자 홈 순(재사용 극대화).
|
||||
4. 세션 화면: 폴백 추상 오브 먼저(음성 파이프라인·4-state 배선 검증) → 반구상 SVG 아바타 → 정서 6파라미터 → 페르소나 JSON.
|
||||
5. 리뷰 화면 + utterance-level 평가 타임라인.
|
||||
6. 역할 가드: 라우터에서 `role` 기반 메뉴·라우트 **렌더 제외**(disable 아님).
|
||||
7. (여유) Rive 아바타 고도화. 안 되면 SVG 1차안으로 출시(폴백 안전).
|
||||
|
||||
### 8.4 RN 전환 대비
|
||||
|
||||
레이아웃 토큰은 절대 px 의존 최소화(rem·flex 우선). SVG→`react-native-svg`, Web Audio→`react-native-audio-api`, Rive→RN 런타임. 브레이크포인트 sm:640/md:768/lg:1024/xl:1280.
|
||||
|
||||
---
|
||||
|
||||
## 9. 안티패턴 체크리스트 (HARD NO — 출시 전 자가검증)
|
||||
|
||||
1. **border-left 강조선 / 좌측 사이드바 형태 강조줄** — 피드백·콜아웃·카드·활성표시에 색 막대 금지. 위계는 kicker+weight+여백+dot+배경틴트.
|
||||
2. **이모지** — UI·일러스트·SVG 내부 장식까지 전부 금지. 아이콘은 Lucide(stroke 2, 직각). 평가 결과에 체크/엑스 이모지 금지. 추세는 ▲▼→ 기하 화살표.
|
||||
3. **카드 덤프 / 균등 그리드 무한반복(4-4-4)** — 비대칭(7:5) 레이아웃·데이터 테이블·도식으로. 학습자 홈은 게이지+small-multiples+테이블, 교수 홈은 triage 큐+KPI+테이블.
|
||||
4. **양산형 SaaS 랜딩** — "그라데이션 히어로 + 떠다니는 mockup + 3컬럼 feature 카드 + 보라 그라데이션 CTA" 공식 금지.
|
||||
5. **과한 그라데이션·glow·gradient-text·큰 blur** — 깊이는 1px 헤어라인.
|
||||
6. **둥근 모서리 과다(16px+)·말랑 일러스트** — 거의 직각(6~12px). 원형은 오브·아바타·dot만.
|
||||
7. **게임화 폭격** — 점수 팡파레, 빨간 X, 레벨업 배지, 별점 5개 즉시. 평가는 서술형·비모달·회고 시점.
|
||||
8. **순흑(#000)·순백(#FFF)** — `--ink:#1C2A2A` / `--paper:#FBFAF8`.
|
||||
9. **고채도 메디컬 블루 단독 의존** — 세이지-틸로 차별화 + 온기.
|
||||
10. **실시간 피드백 모달** — 상담 흐름 중 팝업 금지. 차분한 사이드 패널로.
|
||||
11. **레이더/스파이더 차트** — small-multiples 라인 + 단일 게이지로.
|
||||
12. **권한 없는 메뉴 disabled 회색 나열** — DOM에서 제거.
|
||||
13. **빨강 장식 사용** — `--crit-solid`는 (교수)개입 큐·"개입" 상태·(관리자)임계 오류에만.
|
||||
14. **One screen, one message 위반** — 각 화면 최상단 h1이 그 화면 단일 메시지("나아지고 있다"/"3명 개입 필요"/"정상 운영"/"듣고 있어요").
|
||||
|
||||
---
|
||||
|
||||
## 10. 핵심 의사결정 5줄 (윤찬이 기억할 것)
|
||||
|
||||
1. **컬러: 세이지-틸 `#3E7A6E` 단일 accent + 테라코타 `#B0735C` 온기 보조.** 의료 블루 회피, 저채도로 임상+따뜻함 동시. 역할별 accent 한 토큰만 교체.
|
||||
2. **아바타: 사실적 얼굴(오인·불쾌한 골짜기·편향)도 추상 오브(비언어 연습 불가)도 답 아님 → 사실성 35~45% 양식화 반구상 흉상 + 호흡 광배.** 눈·눈썹·입·어깨 4채널 × 정서 6파라미터. SVG 1차 → Rive 2차. 미성년은 realism 더 낮추고 `교육용 가상 내담자` 라벨 상시.
|
||||
3. **세션 화면: 실시간 피드백은 "신호(색 도트, 6초 페이드, 1개)"까지만, 정밀 평가는 회기말 리뷰로.** 학습자가 모드(몰입/은은/코칭)를 고른다. 음성 상태는 막대 파형이 아니라 빛의 호흡(sage=학습자, clay=내담자).
|
||||
4. **시각적 무게 = 주의 배분.** 내담자 stage 60%, 평가 패널 15%·평시 접힘. 화면은 대시보드가 아니라 상담실.
|
||||
5. **대시보드: 레이더 금지 → small-multiples 라인 + 단일 게이지.** 점수·합불 없음(성장 추세만). 교수자 첫 화면은 triage 큐. 권한 없으면 DOM 제거.
|
||||
|
||||
---
|
||||
|
||||
## 참고 (실조사 출처)
|
||||
|
||||
**디자인 언어·시스템**
|
||||
- Linear Brand Guidelines / Stripe Design / Calm·Headspace 색 (accent rationing, 저채도, 호흡 모션)
|
||||
- Fuzzy Math — Color Palettes of Mental Healthcare UI / Villa Healing Center — Calming Colors
|
||||
- Halo Lab — Digital Healthcare UX Trends 2026 / Progress — Color Psychology in Healthcare
|
||||
- Pretendard (orioncactus/pretendard) / az-loc — Best Fonts for CJK Websites 2026
|
||||
- Aufait UX — Accessible Color Tokens / WCAG Contrast Guide 2026
|
||||
|
||||
**아바타**
|
||||
- Behavioral Sciences 2026 — Human-like/Animal-like/Object-like LLM Virtual Doctor Avatar
|
||||
- arXiv 2506.11890 — Graduated Realism (AI Avatars in VR Teacher Training)
|
||||
- PMC12692777 — AI Physician Avatars (윤리·편향·오인 위험)
|
||||
- NYU Steinhardt — AI Avatar for Social Work Counseling Practice
|
||||
- ElevenLabs UI Orb / orb-ui (4-state) / Rive vs Lottie 2026 / arXiv 2502.16038 (Valence-Arousal-Dominance)
|
||||
|
||||
**세션·대시보드**
|
||||
- arXiv 2502.18673 — Scaffolding Empathy (utterance-level 피드백 학습효과 vs 인지부하) ← 2단 피드백 모델 근거
|
||||
- arXiv 2505.02428 — LLM-Simulated Practice/Feedback for Counselors
|
||||
- assistant-ui / Vocalis / Agora (음성 상태 시각화)
|
||||
- Scott Logic·Observable — Radar Charts 비판 (small-multiples 대체 근거)
|
||||
- GoReact — Counseling Skills Rubric / Springer Open — LA Dashboard actionable intervention 비판
|
||||
- Eleken·Fuselab — Healthcare UI/UX 2026
|
||||
|
||||
**vault 연계 자산**
|
||||
- `Resources/50_Tech_Dev/디자인/slide-base.css` (무채 골격·강조 위계 계승)
|
||||
- `Resources/50_Tech_Dev/디자인/[디자인 시스템] HTML 슬라이드·보고서 베이스 (2026-06).md` (한신대/의료 행에 `--accent:#3E7A6E` 채움)
|
||||
324
docs/MASTERPLAN.md
Normal file
324
docs/MASTERPLAN.md
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
# 한신대학교 SW중심대학 산학협력 — 생성형 AI 심리상담 시뮬레이션 플랫폼 마스터플랜
|
||||
|
||||
> 통합 기준 문서 (SoT, Single Source of Truth) · 작성 2026-06-25 · 트웬티온스 산학협력
|
||||
> 제약: **20주 / 매칭금 500만 / 기존 풀스택팀**
|
||||
>
|
||||
> **🔄 2026-06-25 업데이트 (윤찬 확인):** **IRB 정식 심의 불필요.** 수련생 대상 *교육 활용*이라 인간 대상 임상시험이 아니므로 IRB 면제. → 아래 "IRB 승인"이 걸려있던 의존성/리드타임은 모두 해소(Phase 3 게이트 제거, 즉시 착수 가능). 단 **미성년 원본 사례데이터(0615 등) 활용동의 + 개인정보 처리방침**은 별도로 한신대 공문 확인 유지(IRB와 무관한 데이터 거버넌스 사안).
|
||||
> 본 문서는 9개 도메인 전문 설계서를 단일 마스터플랜으로 종합한다. 각 도메인 상세는 §부록 A의 설계서 원본 참조.
|
||||
|
||||
상담 수련생 훈련용 — **가상내담자 시뮬레이션 + AI 실시간 피드백 + 안전 가드레일**. 이 플랫폼은 진단·치료가 아니라 **수련생 연습용**(FDA General Wellness / 식약처 비의료기기 포지셔닝)이라는 점을 전 계층에서 고정한다.
|
||||
|
||||
---
|
||||
|
||||
## 0. 한 페이지 요약 (의사결정 고정값)
|
||||
|
||||
| 영역 | 확정 결정 | 핵심 근거 |
|
||||
|---|---|---|
|
||||
| **AI 엔진** | 기본=Anthropic Messages API(Opus 4.8/Sonnet 4.6) **엔진 어댑터** 뒤에 배치. `claude -p`(Max OAuth)는 옵션 플래그·개발 시연용 | `claude -p`+OAuth 과금 누수(이틀 $1,800 사례)·동시성 1프로세스 한계·rate limit 마비 리스크. 어댑터 한 줄로 전환 가능하게 흡수 |
|
||||
| **3-AI 구조** | 상담사 AI(선택) / 내담자 AI / 백그라운드 평가 AI. **정보 비대칭을 DB `visible_to[]` 컬럼이 강제**(프롬프트 신뢰 X) | 상담사AI가 내담자 CCD를 보면 시뮬레이션 가치 0 |
|
||||
| **상태머신** | 라포→탐색→개입→정리 전이는 **FastAPI 백엔드가 결정론적으로 소유**(LLM 아님) | 단계 흐릿하면 수련생 채점 신뢰도 붕괴 |
|
||||
| **저항 엔진** | 내담자 저항을 프롬프트 형용사 아닌 **상태머신 수치**로 단계 주입 (핵심 차별기술) | CARE-Bench가 LLM positivity bias(과순응) 정량 확인 |
|
||||
| **피드백** | 피드백 패널 = 1급 시민. **자유연습(피드백 OFF) 기본 차단** | Stanford CARE RCT: 연습만 d=-0.52 하락, 연습+피드백 집단격차 d=0.72 |
|
||||
| **프론트** | React 19 + SSE, pnpm/Turborepo 모노레포(`packages/core` 100% 공유, 추후 RN) | 웹→네이티브 전환을 구조로 증명, 20주 과대약속 회피 |
|
||||
| **백엔드** | FastAPI + SSE 스트리밍, 상태머신·가드레일·엔진 어댑터 | |
|
||||
| **DB** | NAS PostgreSQL 16(+pgvector) 단일 SoR. Timescale은 확장 시 hypertable만 선택 | 20명 규모에 Timescale 과잉 |
|
||||
| **배포** | Docker Compose 단일 호스트 모놀리식(5~6 컨테이너), Caddy 리버스프록시. K8s 금지 | 20주·인력 제약에 모놀리식이 최적 |
|
||||
| **외부노출** | chanpaca.net = Cloudflare named tunnel(일반) **+ SSE 경로는 버퍼링 우회 분리**(Tailscale Funnel) | Cloudflare가 SSE를 ~100KB 버퍼링 + 100초 timeout → 50분 상담 스트림 충돌 |
|
||||
| **음성** | 1차 캐스케이드(Deepgram STT → 로컬 엔진 → OpenAI gpt-4o-mini-tts). s2s 1차 탈락 | transcript가 평가·로깅·재귀학습·가드레일의 1급 자산. s2s는 transcript 손실 |
|
||||
| **인증** | BFF + OAuth 2.1 Auth Code + PKCE(S256). 토큰은 서버(Redis)에만, 브라우저엔 HttpOnly 쿠키 | 미성년 사례데이터+상담 민감정보 → XSS 토큰탈취 원천 차단 |
|
||||
| **안전** | MIND-SAFE식 다층 분리 + 한국어 자살콘텐츠 분류(Llama Guard 한국어 미지원 공백 보강) | 재현율=법적 책임 최우선 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 시스템 아키텍처 개요
|
||||
|
||||
### 1.1 물리 토폴로지 (인바운드 포트 0)
|
||||
|
||||
```
|
||||
[교수님 브라우저] https://chanpaca.net
|
||||
│
|
||||
┌─────────┴──────────┐
|
||||
│ Cloudflare Edge │ ← named tunnel(영구 URL, TLS/DDoS)
|
||||
│ (정적·일반 API) │ ⚠ SSE는 버퍼링 → 분리 경로
|
||||
└─────────┬──────────┘
|
||||
│ (outbound-only, 개방포트 0)
|
||||
┌───────────────────────────┼────────────────────────────────────────┐
|
||||
│ 로컬 PC / NAS (한국 내 · 데이터 주권) Docker Compose: counsel-net │
|
||||
│ ┌────────────┐ │
|
||||
│ │ cloudflared│──┐ ┌──────────┐ ┌──────────────────────────┐ │
|
||||
│ │(named tun.)│ │ │ frontend │ │ backend (FastAPI) │ │
|
||||
│ └────────────┘ │ │ React19 │ │ ┌────────────────────┐ │ │
|
||||
│ ┌────────────┐ ├──►│ 정적빌드 │◄──►│ │ 상태머신(결정론) │ │ │
|
||||
│ │ Caddy │◄─┘ └──────────┘ │ │ 가드레일/안전레일 │ │ │
|
||||
│ │ SSE flush-1 │◄──── [SSE 직결경로] │ │ SSE 스트리머 │ │ │
|
||||
│ └─────┬──────┘ (Tailscale Funnel) │ │ 엔진 어댑터(라우터)│ │ │
|
||||
│ │ │ └────────┬───────────┘ │ │
|
||||
│ ▼ └──────┬────┼──────────────┘ │
|
||||
│ stream.chanpaca.net (SSE 전용) │ │ │
|
||||
│ ┌─────▼─┐ ┌▼─────────┐ │
|
||||
│ ┌────────────────┐ ┌──────────────┐ │engine │ │ voice-gw │ │
|
||||
│ │ Postgres 16 │ │ pgvector(RAG)│ │adapter│ │ OpenAI │ │
|
||||
│ │ (NAS 영구볼륨) │ │ 임베딩 1024d │ │ ①claude_api(기본) │ │
|
||||
│ │ 학습로그·관계형 │ └──────────────┘ │ ②claude_cli(옵션) │ │
|
||||
│ │ RLS 이중강제 │ │ ③openai/solar │ │
|
||||
│ └────────────────┘ [Presidio PII 마스킹] ──► 외부 LLM(마스킹 후만) │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**원칙**: ① 인바운드 포트 0 (cloudflared outbound 터널) ② 데이터는 로컬(NAS)을 떠나지 않음, PII 마스킹 후에만 외부 LLM 호출 ③ 전송 계층 추상화(`VITE_SSE_BASE_URL`)로 SSE 경로 무중단 전환.
|
||||
|
||||
### 1.2 논리 레이어 (프롬프트 캐싱 친화)
|
||||
|
||||
```
|
||||
[L0 역할+안전가드레일+도식노출금지] ┐
|
||||
[L1 페르소나 카드(정적)] ├─ cache_control (입력비 90%↓, 실측 59~70% 절감)
|
||||
[L2 RAG 임상청크] ┘
|
||||
[L3 상태머신 주입(stage, openness)]
|
||||
[L4 메모리 버퍼(사실 hard-pin)]
|
||||
[L5 수련생 발화]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. AI 3종 상호작용 모델
|
||||
|
||||
### 2.1 정보 비대칭 (DB가 강제)
|
||||
|
||||
| AI | system 프롬프트 | 볼 수 있는 것 | 절대 못 보는 것 |
|
||||
|---|---|---|---|
|
||||
| **① 상담사 AI** (선택/보조) | 마이크로스킬 시연용 | 대화 표면 | 내담자 CCD·DSM 차원·평가결과 |
|
||||
| **② 가상내담자 AI** | 페르소나 카드+CCD+저항 | 자기 페르소나·대화 | 평가 점수·정답 라벨 |
|
||||
| **③ 백그라운드 평가 AI** | 루브릭+골든 taxonomy | 전체 회기·CCD·정답 | (전부 봄, 학습자에 노출 X) |
|
||||
|
||||
3-AI는 서로 다른 system 프롬프트의 **독립 `claude -p`/API 호출**. "누가 무엇을 보는가"는 **DB `visible_to[]` 컬럼이 강제**하며 프롬프트를 신뢰하지 않는다.
|
||||
|
||||
### 2.2 턴 사이클 (결정론 상태머신 중심)
|
||||
|
||||
```
|
||||
수련생 발화 U_t
|
||||
→ [입력 가드레일] PII 마스킹 + 위기 분류(수련생 실제위기 vs 페르소나 연기 구분)
|
||||
→ [상태머신] stage·effective_openness(t) 계산
|
||||
effective_openness = clamp(stage.openness
|
||||
+ rapport_credit(t)*unlock_rate
|
||||
- resistance*decay(t), 0, 1)
|
||||
→ [내담자 AI] 페르소나 응답 생성 (Structured Outputs, CCD 직접노출 금지)
|
||||
→ [출력 가드레일] 자살수단 정보 차단, ideation_stage ≤ 3 상한
|
||||
→ [평가 AI 2-tier]
|
||||
· fast-loop(경량): U_t만 보고 4차원 태깅 (실시간 신호)
|
||||
· deep-loop(Opus): 단계 전환·회기말, 0-5 채점 + 대안발화 (대시보드)
|
||||
→ [로깅] TurnLog → Postgres (= 재귀학습 JSONL 원천)
|
||||
```
|
||||
|
||||
**rapport_credit**: 수련생이 공감·반영·타당화·홀딩을 쓰면 +, 조언점프·평가적언어·유도질문이면 0/−. → **좋은 상담을 하면 내담자가 열리고, 나쁜 상담을 하면 닫힌다.** 0615 실데이터의 "방어가 서서히 풀어지고 있음" 궤적 재현.
|
||||
|
||||
### 2.3 평가 AI 2-tier 루프 (핵심 레버)
|
||||
|
||||
- **fast-loop**: Haiku/Solar 경량, 발화별 4차원(공감·개방질문·검증·이론부합) 태깅 → 콜드스타트 지연·비용 통제
|
||||
- **deep-loop**: Opus 4.8, 전체 회기 + 골든라벨 RAG로 0-5 채점(CTRS는 0-6 분리) + 대안발화 제시. **실시간 팝업보다 종료 후 대시보드가 SUS↑·인지부하↓**
|
||||
- **LLM-judge 보정**: raw 직접사용 금지, 길이정규화, 이중코딩 vs LLM weighted-κ≥0.6 / ICC≥0.75 게이트 통과분만 골든셋 승격
|
||||
|
||||
### 2.4 가상내담자 페르소나 (DSM-5 = 내부 파라미터, 비노출)
|
||||
|
||||
- **차원적 프로파일**(RDoC 정신): negative_affect·hopelessness·suicide_risk{ideation_stage 1~5}·sleep_disturbance를 0~1 연속값 (단일 DSM 라벨 X)
|
||||
- **CCD 8요소**(Patient-Ψ): core_belief(worthless 등)·automatic_thought·coping_strategy — **직접 발화 금지**, 행동으로만 드러내 상담자가 추론하게
|
||||
- **시드 페르소나 3종(교수 검수 필수 게이트)**: P1=0615 청소년 우울·자퇴·자살사고(hard) / P2=성인 범불안·신체화(moderate, 자살0) / P3=인간중심 미혼모(PCT 훈련). 2차 백로그: ADHD품행·경계선특질·적응장애·위기4~5단계 전용
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터 모델 + Postgres 스키마 요약
|
||||
|
||||
### 3.1 원천 — 0615 파란색 라벨의 3종 혼재 (실측 확인)
|
||||
|
||||
`축어록_파란색라벨_0615.hwpx`를 직접 파싱(파랑 #3057B9, 라벨 89개·77종) → 한 셀에 **3종이 혼재**함을 확인. 학습 신호 오염 방지를 위해 **3개 분리 필드로 격상**이 스키마 설계의 출발점:
|
||||
- **(A) 기법 태그**(복수): 공감/반영/탐색/홀딩/타당화/직면/해석/자기개방/안정화/희망고취/위험사정…
|
||||
- **(B) 내담자 상태 태그**(복수): 비자발/방어/자살사고인정/부정적자기인식/갈등상태…
|
||||
- **(C) 슈퍼바이저 논평** 2종: `rationale`(근거설명) vs `critique`(과도한 자기개방·비언어반영 부족 등)
|
||||
|
||||
### 3.2 스키마 요약 (PostgreSQL 16, 3 스키마: app / audit / ds)
|
||||
|
||||
```sql
|
||||
-- ── app: 운영 ──────────────────────────────────────────────
|
||||
sessions(id, learner_id, persona_id, theory_mode, started_at, ended_at, stage_path JSONB)
|
||||
turns/utterances( -- 발화 = 최소 원자단위, 모든 라벨/평가/PII가 FK로 매달림
|
||||
id, session_id FK, seq, speaker('counselor'|'client'),
|
||||
stage('라포'|'탐색'|'개입'|'정리'),
|
||||
text, -- 저장 전 Presidio PII 마스킹
|
||||
text_masked, -- 외부 LLM 전송용
|
||||
actor_kind, -- 3 AI + 인간(수련생/슈퍼바이저) 통합 출처
|
||||
llm_provider, model, tokens_in, tokens_out, cost_usd, visible_to TEXT[])
|
||||
-- 라벨은 버전드 코드테이블 + 다대다(복수 라벨)
|
||||
technique_label_def / client_state_def / stage_def / scale_def(0-5, CTRS 0-6)
|
||||
turn_technique(turn_id, label_id) / turn_client_state(turn_id, label_id)
|
||||
feedback_scores(turn_id, dimension, score, rationale) -- 0-5, D3
|
||||
supervisor_comment(turn_id, kind('rationale'|'critique'),
|
||||
text, intent_deviation JSONB{expected, actual, severity, dimension}) -- 윤찬 "의도와 다른 부분" 1급 시민
|
||||
safety_events(session_id, trigger_type, ko_risk_level, escalated, latency_ms, created_at)
|
||||
rag_chunks(id, source, embedding vector(1024), text)
|
||||
|
||||
-- ── audit: append-only 법적 증거 ──────────────────────────
|
||||
event_log / llm_call_log(cost·드리프트·inference_geo) / audit_log(열람추적)
|
||||
|
||||
-- ── ds: 재귀 학습 파이프라인 ──────────────────────────────
|
||||
dataset / dataset_item / annotation_round / annotation / export_manifest
|
||||
-- AI자동(1R) → 인간검수(2R) → IAA 게이트(κ≥0.6, ICC≥0.75) → 골든셋 → JSONL
|
||||
```
|
||||
|
||||
### 3.3 거버넌스 (인프라 직결)
|
||||
- **저장 전 PII 마스킹 하드 게이트**: backend→외부 LLM 경로에 Presidio(MedicalNERRecognizer). 마스킹 후 텍스트만 적재·전송
|
||||
- **RBAC + Postgres RLS 이중강제**: 학습자=본인 / 교수자=담당 코호트 / 관리자=전부+교수활동(열람을 audit_log에 기록)
|
||||
- **데이터 국내처리**: DB=NAS(국내), 민감구간은 국내 모델(Solar/HyperCLOVA) 라우팅 또는 `inference_geo:us` 명시
|
||||
- **미성년 원본 4단계 격리**: 원본 축어록은 플랫폼 DB 절대 미적재, 사람이 가명처리·전문가 검수한 CCD 스펙만 진입 (Character.AI 판례 대응)
|
||||
|
||||
---
|
||||
|
||||
## 3.6 RAG 아키텍처 (2026-06 최신 기준 · 윤찬 요청 보강)
|
||||
|
||||
> RAG는 부가기능이 아니라 본 시스템 3개 축(페르소나 일관성 / 피드백 근거 / 지식)을 떠받치는 핵심. 2026.6 production RAG 합의 = **failure mode 기준으로 사다리 최소한만 오른다**(GraphRAG·Agentic 풀스택 처음부터 X).
|
||||
|
||||
### 용도별 매핑 (서로 다른 RAG)
|
||||
1. **페르소나 메모리** (내담자 일관성) — 순수 RAG 아님 → **하이브리드 메모리**: 구조화 상태(상태머신 수치) + rolling summary + episodic 벡터 recall. 임상 비네트는 소규모 고정셋이라 검색난도 낮음.
|
||||
2. **피드백 근거** (0615 발화-라벨 정답셋 + 슈퍼바이저 논평) — 오답비용 큼 → 하이브리드 + 리랭킹 (+확장 시 CRAG 그레이딩).
|
||||
3. **지식** (DSM-5 진단기준 · 상담이론 인간중심/CBT) — 정적 KB → 하이브리드 + Contextual Retrieval.
|
||||
|
||||
### 확정 스택 (MVP = Phase 1)
|
||||
- **임베딩 = BGE-M3**: 다국어(한국어 강함) + dense·sparse·ColBERT 멀티벡터를 **단일 모델**로 제공 → 하이브리드와 late-interaction을 한 모델로 흡수. *우리 rag-memory가 이미 BGE-M3 사용 중 → 재활용.*
|
||||
- **저장/검색 = pgvector(HNSW)**: 기존 NAS Postgres에 인프라 추가 0, 수백만 벡터 sub-100ms.
|
||||
- **하이브리드 검색**: dense(pgvector) + sparse BM25(PG full-text 또는 BGE-M3 sparse), top-50 회수.
|
||||
- **Contextual Retrieval(Anthropic)**: 청크 임베딩 전 1문장 맥락 주입 → 검색실패 **49%↓**(+리랭킹 시 **67%↓**).
|
||||
- **리랭커 = cross-encoder(BGE-reranker-v2-m3, 한국어)**: top-50 → top-5. 답변품질 **15~30%↑**.
|
||||
|
||||
### 확장 (Phase 2+)
|
||||
- **Adaptive RAG 라우터**(2026 SOTA): 질의 복잡도 분류 → 단순=하이브리드 / 복잡=다단계 라우팅.
|
||||
- **CRAG(Corrective RAG)**: 피드백 근거처럼 오답비용 큰 경로에 관련성 그레이딩 + 재질의.
|
||||
- (선택) **GraphRAG**: 상담이론 교차개념(인간중심↔CBT 연결) 맵이 커질 때만 2차 — 단순 lookup엔 과잉이라 1차 제외.
|
||||
- (선택) ColBERT 독립 late-interaction: BGE-M3 멀티벡터로 충분, 품질 부족 시에만.
|
||||
|
||||
> 근거 출처: [Anthropic Contextual Retrieval](https://www.anthropic.com/news/contextual-retrieval), [RAG Production Patterns 2026](https://ailearningguides.com/rag-production-patterns-2026/), [Jina ColBERT v2 다국어](https://jina.ai/news/jina-colbert-v2-multilingual-late-interaction-retriever-for-embedding-and-reranking/). Agentic+KG 환각 62%↓(2026.5 MLOps 벤치).
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase별 구현 로드맵 (단일 로드맵, 산발 task 금지)
|
||||
|
||||
> 각 Phase에 **우선순위·의존성·완료기준(DoD)** 명시. Phase는 순차, 내부 트랙은 병렬(소유 파일 분리).
|
||||
|
||||
### Phase 0 — 기반 정렬 (1~2주차) · 우선순위 P0
|
||||
- **목표**: 인프라 스캐폴드 + taxonomy 확정 + IRB 착수
|
||||
- **작업**:
|
||||
- docker-compose 6서비스 스캐폴드 + `secrets/` git-ignore + Caddyfile(`flush_interval -1`)
|
||||
- Postgres 스키마 §3.2 적용 + RLS 정책 + pgvector
|
||||
- 라벨 taxonomy v1.0 확정(3축 분리) → 0615 축어록 골든셋 라벨링 1차
|
||||
- IRB 프로토콜 초안(Stanford CARE 차용: minimal risk·자살시나리오 1차 제외·전문가 검증 내담자)
|
||||
- 엔진 어댑터 인터페이스 정의(provider 플래그: claude_api/claude_cli/openai/solar)
|
||||
- **의존성**: 없음 (시작점). taxonomy ↔ DB 코드테이블 동기화 필요
|
||||
- **완료기준**: `docker compose up`으로 6서비스 기동 + Postgres 스키마 마이그레이션 통과 + 골든셋 50발화 라벨링 + IRB 신청서 제출
|
||||
|
||||
### Phase 1 — MVP 코어 루프 (3~8주차) · 우선순위 P0
|
||||
- **목표**: 텍스트 기반 1-페르소나 상담→피드백 1회 완주 (chanpaca.net 시연 가능)
|
||||
- **트랙 A (백엔드/AI)**: 엔진 어댑터 Messages API 직결 + 캐싱 레이어 + 상태머신(라포→탐색→개입→정리) + 내담자 AI(P1 페르소나) + 평가 AI deep-loop + 입출력 가드레일
|
||||
- **트랙 B (프론트)**: `/learn` 셸 + 상담 시뮬레이션 UI 3존(대화60%·피드백패널40%·숨김안전배너) + SSE 스트리밍(React19 `useActionState`/`use`) + 단계바
|
||||
- **트랙 C (인프라)**: Cloudflare named tunnel → chanpaca.net + SSE 실측(50분 세션 끊김 테스트) → 끊기면 stream.chanpaca.net Funnel 분리
|
||||
- **의존성**: Phase 0 전부. 트랙 A·B는 SSE 스키마·점수 Structured Output 스키마 합의 후 병렬
|
||||
- **완료기준**: 교수님이 chanpaca.net에서 P1 내담자와 텍스트 상담 → 회기말 0-5 4차원 피드백+대안발화 수신 → 전 turn이 Postgres에 cost 텔레메트리 포함 적재. **저항 엔진 작동 검증**(공감 시 개방도↑, 조언점프 시↓)
|
||||
|
||||
### Phase 2 — 음성 + 3역할 + 페르소나 확장 (9~15주차) · 우선순위 P1
|
||||
- **목표**: 멀티모달 + 3역할 페이지 + 페르소나 3종 + 안전 가드레일 완성
|
||||
- **트랙 A (음성)**: 캐스케이드(Deepgram STT → 엔진 → OpenAI TTS), persona→voice 매핑 테이블, 턴테이킹(semantic EOT·침묵 임계 1.2~2.0s), barge-in 로깅
|
||||
- **트랙 B (3역할)**: `/teach`(담당 학습자 전체 열람+검수 오버라이드) `/admin`(전부+교수활동 감사) + BFF OAuth 2.1 + RBAC×소유권 이중게이트
|
||||
- **트랙 C (AI)**: 페르소나 P2·P3 추가(교수 검수), fast-loop 실시간 태깅, 한국어 자살콘텐츠 분류기(few-shot 5단계)
|
||||
- **트랙 D (데이터)**: 재귀 학습 파이프라인(annotation_round + IAA 게이트), 교수자 검수 루프(AI vs 교수자 weighted-κ 누적)
|
||||
- **의존성**: Phase 1 코어 루프. 음성은 transcript 입력레일 위기분류와 결합 필수
|
||||
- **완료기준**: 음성 왕복 상담 round-trip ~1.1~2.0s + 3역할 권한 격리 실동작 검증(학습자가 타인 기록 접근 차단) + safety_events 트리거→교수자 자동알림 + 교수 검수 데이터 누적
|
||||
|
||||
### Phase 3 — IRB 파일럿 + 효과성 검증 (16~20주차) · 우선순위 P1
|
||||
- **목표**: 수련생 20명 IRB 파일럿 + 사전사후 효과 측정 + 데이터셋 골든화
|
||||
- **작업**: IRB 승인 하 20명 pre-post(자기효능감, 자살/자해 시나리오 1차 제외) + SUS·KPI 측정 + 골든셋 export_manifest → JSONL + 결과 리포트
|
||||
- **의존성**: Phase 2 전부 + IRB 승인(Phase 0 신청)
|
||||
- **완료기준**: 20명 세션 완주 + KPI 충족(임베딩 일관성≥0.79, 환각률≤5%, SUS≥80, κ≥0.6/ICC≥0.75) + 자기효능감 사전사후 유의 + 재귀학습 데이터셋 1차 산출
|
||||
|
||||
### 1차 명시 제외(2차 백로그)
|
||||
6-에이전트 풀시스템 · 임상KG 자체구축 · 멀티벤더 A/B · 위기 4~5단계 자유시뮬 · RN 네이티브 앱(1차는 `apps/mobile` 스캐폴드+core import 검증만) · OpenAI Realtime s2s · K8s · Timescale 전면 도입 · 한신대 학내 SSO(1차 Google OIDC 단독)
|
||||
|
||||
---
|
||||
|
||||
## 5. 기술스택 확정
|
||||
|
||||
| 계층 | 확정 | 비고 |
|
||||
|---|---|---|
|
||||
| 프론트 | **React 19** + Vite 정적빌드, SSE(`useActionState`/`useOptimistic`/`use`), pnpm+Turborepo 모노레포 | `packages/core` 로직/타입/스키마 100% 공유, 추후 RN/Expo |
|
||||
| 백엔드 | **FastAPI** + uvicorn(`--workers`, SSE 롱리브드 연결 산정) | 상태머신·가드레일·엔진 어댑터·SSE 스트리머 소유 |
|
||||
| DB | **PostgreSQL 16** + pgvector(RAG 통합), NAS 영구볼륨, RLS | Timescale은 hypertable 선택 확장 |
|
||||
| 컨테이너 | **Docker Compose** 단일 호스트 모놀리식(6 컨테이너), Caddy 리버스프록시(`flush_interval -1`) | 이식=compose+secrets, 클라우드 VM(국내 리전) `docker compose up` |
|
||||
| AI 엔진 | **엔진 어댑터** → 기본 Anthropic Messages API(Opus 4.8/Sonnet 4.6), `claude -p`(Max OAuth)는 옵션 플래그 | 과금 누수·동시성·rate limit 흡수. tier: client=Sonnet/Solar, feedback=Opus, cheap=Haiku |
|
||||
| 음성 | **OpenAI gpt-4o-mini-tts**(13 voice + instructions prosody) + Deepgram STT, voice_id 추상화(Higgs/GPT-SoVITS 로컬 폴백) | s2s 1차 탈락(transcript 보존) |
|
||||
| 인증 | **OAuth 2.1 Auth Code + PKCE(S256)** BFF, Google OIDC 1차, Authlib | 토큰 Redis만, HttpOnly `__Host-` 쿠키, stateful 세션(IRB 철회 즉시 무효화) |
|
||||
| 외부노출 | **Cloudflare named tunnel** + SSE는 Tailscale Funnel 분리 | 인바운드 포트 0 |
|
||||
| 안전 | Presidio PII 마스킹 + NeMo rail + 한국어 자살분류 + 결정론 게이트 | Llama Guard 한국어 미지원 보강 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 핵심 리스크 + 완화책
|
||||
|
||||
| # | 리스크 | 영향 | 완화책 |
|
||||
|---|---|---|---|
|
||||
| R1 | **`claude -p` OAuth 과금 누수** (이틀 $1,800 사례) | 매칭금 증발 | 기본=Messages API 직결, claude_cli는 옵션 플래그+비용캡 모니터. 어댑터로 무중단 전환 |
|
||||
| R2 | **Cloudflare SSE 버퍼링·100초 timeout** | 시연 중 스트림 끊김 | named tunnel(일반)+Tailscale Funnel(SSE) 분리, 30초 heartbeat, 전송계층 추상화 |
|
||||
| R3 | **positivity bias**(저항 내담자 재현 실패) | 시뮬레이션 가치 저하 | 저항을 백엔드 결정론 수치로, Big5 저항 파라미터, CARE-Bench 회귀 테스트 |
|
||||
| R4 | **CCD 메타노출**("제 핵심신념은…") | 추론 훈련 무력화 | 직접노출 금지 + QA 체크리스트 + Structured Outputs |
|
||||
| R5 | **자살수단 정보 누출** | 법적 책임·안전 | ideation_stage ≤ 3 상한, 출력 가드레일, 4~5단계는 안전종료 시나리오만 |
|
||||
| R6 | **LLM-judge 편향**(길이·성별) | 채점 타당도 | raw 직접사용 금지, 길이정규화, 이중코딩 κ≥0.6/ICC≥0.75, 성별 균형 |
|
||||
| R7 | **PII 외부 LLM 유출** | IRB·법적 책임 | Presidio 마스킹 미들웨어 강제, 마스킹 후만 적재·전송, safety_events 로깅 |
|
||||
| R8 | **한국어 위기 분류 공백**(Llama Guard 미지원) | 위기 미탐지 | JMIR 2026 한국어 자살콘텐츠 벤치(few-shot 5단계), 재현율 우선 |
|
||||
| R9 | **한국어 토큰 팽창(최대 35%)** | 비용 과소추정 | 실샘플 token counting, cost_usd 실측 적재 후 재추산, 캐싱 |
|
||||
| R10 | **NAS 단일 호스트 SPOF** | 시연 중 다운 | 스냅샷 백업, pgdata 정기 덤프, 클라우드 VM 페일오버 리허설 |
|
||||
| R11 | **한신대 SSO 미확인** | 2단계 일정 불확실 | 1차 Google OIDC 단독, SSO는 Authlib provider 추상화 뒤 2차, 공문 질의 |
|
||||
| R12 | **20주 일정 압박** | 과대약속 실패 | 1차 범위 통제(2차 백로그 명시), MVP 코어 루프 우선, RN은 스캐폴드만 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 학술 / IRB 체크리스트
|
||||
|
||||
**학술 엄밀성**
|
||||
- [ ] DSM-5 차원 프로파일(RDoC) 반영, 진단명 외부 비노출 가드레일
|
||||
- [ ] 가상내담자 충실도: Patient-Ψ(CCD 8요소) + Client101(비네트+메모리)
|
||||
- [ ] 피드백 타당도: 검증 척도 이식(0-5, CTRS 0-6 분리), 4차원 채점, Structured Outputs
|
||||
- [ ] 효과성 인과: Stanford CARE RCT(연습+피드백 d=0.72)로 피드백 필수성 입증 → 자유연습 비활성화
|
||||
- [ ] LLM-judge 보정 프로토콜(이중코딩 vs LLM κ≥0.6/ICC≥0.75)
|
||||
- [ ] 인용 논문 마스터 목록: PSI-Bench, PsychEval, TherapyGym/THERAPYJUDGE, Roleplay-doh, CARE-Bench, MIND-SAFE
|
||||
|
||||
**IRB / 윤리**
|
||||
- [ ] "교육용·비치료용" intended use 고정(FDA General Wellness / 식약처 비의료기기)
|
||||
- [ ] minimal risk 프로토콜, **자살 시나리오 1차 제외**, 전문가 검증 내담자
|
||||
- [ ] 수련생 20명 사전 동의(로그인 직후 하드 게이트, 미동의=시뮬레이션 라우트 서버 차단)
|
||||
- [ ] 미성년 원본 데이터 4단계 격리, 법정대리인 활용동의 범위 확인
|
||||
- [ ] PIPC(2024.12) + 생명윤리법 + 가명처리, 데이터 국내처리/해외전송 명시
|
||||
- [ ] D6 컴플라이언스 매트릭스(APA/PIPC/WHO/Character.AI 판례/FDA)
|
||||
- [ ] **한신대 공문 질의 필요**: SSO 프로토콜/클레임, IRB 주관, 미성년 원본 활용동의, NAS 국내처리 보장
|
||||
|
||||
**KPI(M5 수용기준)**: 임베딩 일관성≥0.79 · 환각률≤5%(목표≤1%) · Top-1≥0.80 · SUS≥80 · κ≥0.6/ICC≥0.75 · 저항 재현 · 자기효능감 사전사후
|
||||
|
||||
---
|
||||
|
||||
## 8. 즉시 착수할 다음 액션 Top 5
|
||||
|
||||
1. **한신대 공문 질의서 발송** — SSO 프로토콜/클레임, IRB 주관 기관, 미성년 원본 사례데이터 법정대리인 활용동의 범위, NAS Postgres 국내처리 보장. (트웬티온스→한신대, 가장 긴 리드타임·블로킹 의존성)
|
||||
2. **IRB 프로토콜 초안 작성·제출** — Stanford CARE 차용(minimal risk, 자살시나리오 1차 제외, 전문가 검증 내담자, 수련생 20명 사전동의). 승인 리드타임이 Phase 3 게이트.
|
||||
3. **docker-compose 6서비스 스캐폴드 + Postgres 스키마 §3.2 + Caddyfile(`flush_interval -1`)** — 기존 풀스택팀에 인프라 베이스 인계. 이후 트랙 병렬화의 기반.
|
||||
4. **0615 축어록 라벨 taxonomy v1.0 확정 + 골든셋 1차 라벨링** — 3축 분리(기법/내담자상태/슈퍼바이저논평) 코드테이블화. 평가 AI 정답셋 + 페르소나 상태전이 정답으로 이중 활용.
|
||||
5. **엔진 어댑터 PoC(Messages API 기본 + 캐싱 레이어 + Presidio 마스킹 선행)** — `claude -p` 과금 누수 회피 검증 + P1 페르소나 1턴 응답 + 비용 텔레메트리 실측. claude_cli는 옵션 플래그로 병존.
|
||||
|
||||
---
|
||||
|
||||
## 부록 A. 도메인 설계서 원본 (절대경로)
|
||||
|
||||
- 학술근거·평가: `Projects/영업/2026/한신대학교 산학협력 프로젝트/설계서/01_학술근거_평가프레임워크_설계서.md`
|
||||
- DSM-5 페르소나(M1): `…/scratchpad/M1_persona_engine_design.md`
|
||||
- taxonomy·annotation: `…/_analysis/상담기법-슈퍼비전-taxonomy-annotation-스키마-설계서.md`
|
||||
- 3-AI 오케스트레이션: `…/설계/3AI_오케스트레이션_아키텍처_설계서.md`
|
||||
- 데이터모델·NAS Postgres: `Projects/수업/2026/한신대 SW중심대학 산학협력 2026/설계/데이터모델_NAS_Postgres_스키마_설계서.md`
|
||||
- 인증·RBAC·IRB: `…/설계/auth-rbac-설계서.md`
|
||||
- 실시간 음성 채팅: `…/설계/[설계서] 실시간 음성 채팅 아키텍처.md`
|
||||
- 프론트엔드·UX: `…/설계/프론트엔드-UX-설계서.md`
|
||||
- 배포·인프라: 본 마스터플랜 §1·§4·§5에 통합 (별도 파일 미생성)
|
||||
|
||||
## 부록 B. 디자인 철칙 (윤찬, 전 화면 강제)
|
||||
border-left 강조선 · 이모지(SVG 내부 포함) · AI 양산형 카드덤프 · 무지개 점수색 **전부 금지**. 에디토리얼 미니멀, 한 페이지 한 메시지, 강조는 weight/배경틴트/상단 kicker. 채도 낮은 그린-그레이 신뢰 톤. (린트 강제 권장)
|
||||
602
docs/MEMORY_KNOWLEDGE_PERSONA_DESIGN.md
Normal file
602
docs/MEMORY_KNOWLEDGE_PERSONA_DESIGN.md
Normal file
|
|
@ -0,0 +1,602 @@
|
|||
# 메모리·지식·페르소나 통합 아키텍처 설계서
|
||||
|
||||
> 한신대 AI 심리상담 시뮬레이션 훈련 플랫폼 · **단일 종합 설계서**
|
||||
> 작성 2026-06-25 · 트웬티온스 산학협력
|
||||
> 종합 대상 4개 영역: ① 회기 간 연속성 메모리(최우선 갭) · ② 지식 데이터베이스(KB) · ③ 페르소나 일관성 · ④ 3-AI 메모리 뷰/정보비대칭/종단평가
|
||||
> 상위 SoT: `HANSHIN_AI_PLATFORM_MASTERPLAN.md` + `MASTERPLAN_REVISIONS.md`(BLUE TEAM 패치). **본 문서는 두 상위 문서의 확정 전제를 깨지 않는 하위 통합 설계다.**
|
||||
|
||||
---
|
||||
|
||||
## 0. 확정 전제 (바꾸지 않음) + 설계 철학
|
||||
|
||||
### 0.1 변경 불가 기술 전제
|
||||
|
||||
| 항목 | 확정값 |
|
||||
|---|---|
|
||||
| 엔진 | 로컬 `claude -p` 상주 멀티턴 풀(`--input-format stream-json`), Messages API는 가용성 폴백 |
|
||||
| RAG | BGE-M3(dense+sparse+ColBERT 단일모델) + pgvector(HNSW) + 하이브리드 + Anthropic Contextual Retrieval + BGE-reranker-v2-m3 |
|
||||
| DB | NAS PostgreSQL 16 + pgvector, RLS 이중강제 |
|
||||
| 백엔드 | FastAPI(상태머신·가드레일·엔진 어댑터 소유) |
|
||||
| 3-AI | 상담사AI(보조/선택) · 내담자AI · 백그라운드 평가AI |
|
||||
| 정보비대칭 | DB `visible_to[]` 컬럼이 강제(프롬프트 신뢰 X), 인간 RBAC×cohort와 2-레이어 AND |
|
||||
|
||||
### 0.2 통합 설계 철학 — 람다 자신의 메모리 시스템에서 차용한 3대 원리
|
||||
|
||||
이 플랫폼의 메모리·지식·페르소나는 **람다(이 AI)의 실제 운영 메모리 구조**를 1차 레퍼런스로 삼는다. 검증된 운영 패턴이라 추측이 아니다.
|
||||
|
||||
| 람다 시스템 원리 | 통합 설계 적용 |
|
||||
|---|---|
|
||||
| **계층 압축** (daily 원본 → weekly 요약 → monthly 요약, 원본 archive 절대보존, 숫자 무손실) | 메모리 4계층(working/episodic/summary/semantic) + KB 3계층(청크/Contextual prefix/community 요약). 원본 append-only, 압축은 파생 테이블에만 |
|
||||
| **역할 분리** (rag-memory=BGE-M3 sqlite 시맨틱 ↔ graphify=GraphRAG 구조, "둘 다 유지, 충돌 X, 질문 유형에 맞게 선택") | 에피소드 메모리(내담자 일관성) ↔ 정적 지식 KB를 **물리적으로 다른 스키마·다른 검색정책**으로 분리. 충돌 시 우선순위 룰로 결정 |
|
||||
| **부트의 큰그림→세부 읽기** (monthly→weekly→daily, "본문 인용 필요할 때만 직접 Read") | 회기 시작 회상도 case_digest(큰그림)→직전 summary(중간)→episodic recall(세부) 순으로 토큰 예산 배분. 매 발화 전체 덤프 금지 |
|
||||
| **IDENTITY/SOUL 불변 + daily 가변** (정체성이 일지 압축에 오염 안 됨) | 페르소나 = 불변 카드(persona_card) + 가변 메모리(persona_session_memory)를 다른 테이블·다른 프롬프트 레이어·다른 권한으로 물리 분리 → drift 구조적 차단 |
|
||||
|
||||
### 0.3 4대 대원칙 (전 영역 관통)
|
||||
|
||||
- **P1. 원본은 절대 삭제 안 한다** — `turns`는 append-only(람다 archive). "망각"은 물리삭제가 아니라 회상 우선순위 배제(salience 하위 → 검색 랭킹 하위 → system 미주입).
|
||||
- **P2. 숫자(상태 수치)는 무손실로 넘긴다** — working state(저항·라포·openness)는 LLM 요약이 아니라 **결정론 상태머신이 DB 컬럼으로** 직접 carry-over. LLM에 수치를 맡기면 hallucination.
|
||||
- **P3. 큰 그림 → 세부 순서로 회상** — 토큰 예산을 case→summary→episodic 순으로 배분.
|
||||
- **P4. 압축은 LLM, carry-over는 결정론** — 서사(narrative)는 LLM 압축(손실 허용), 상태 수치·pinned fact는 코드가 직접 복사(무손실).
|
||||
|
||||
---
|
||||
|
||||
## 1. 통합 메모리 계층도 (working/episodic/semantic/summary + 지식KB)
|
||||
|
||||
```
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
통합 메모리·지식 아키텍처 — 5개 저장 계층 (메모리 4 + 지식 1), 물리 분리
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
계층 수명 저장소(스키마.테이블) 채우는 주체 람다 대응물
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
┌──── 에피소드 메모리 (내담자 연속성) ── app 스키마 ────────────────────────────┐
|
||||
│① WORKING 회기 내 app.session_state 상태머신(결정론) L3+L4 │
|
||||
│ 회기내상태 (체크포인트) +단기버퍼(JSONB) 매 턴 UPSERT 상태머신 │
|
||||
│ │
|
||||
│② EPISODIC 영구 app.turns + 발화 로깅(원자) daily │
|
||||
│ 회기별사건 (append-only) app.turn_embedding(HNSW) 회기 중 임베딩 (원본) │
|
||||
│ │
|
||||
│③ SUMMARY 영구 app.session_summary 회기종료 압축(LLM) weekly │
|
||||
│ 회기종료요약 (+digest 임베딩) 회기당 1행 │
|
||||
│ │
|
||||
│④ SEMANTIC 영구(evolving) app.case_profile 케이스누적 압축 monthly │
|
||||
│ 누적임상프로파일 + app.pinned_fact 회기마다 갱신/병합 +MEMORY │
|
||||
└───────────────────────────────────────────────────────────────────────────────┘
|
||||
┌──── 정적 지식 KB (DSM/이론/taxonomy) ── kb 스키마 (독립, FK 없이 read-only) ──┐
|
||||
│⑤ KNOWLEDGE 영구(버전드) kb.chunk(멀티벡터 HNSW) 오프라인 인덱싱 graphify │
|
||||
│ 정적임상지식 +kb.document +kb.source (배치, 런타임 아님) references│
|
||||
│ 3계층: 원본청크 → Contextual prefix → community 요약(예산초과 fallback) │
|
||||
└───────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
데이터 흐름:
|
||||
회기종료: ②turns ──압축(LLM)──► ③session_summary ──병합(LLM+rule)──► ④case_profile
|
||||
+ pinned_fact(rule)
|
||||
회기시작: ④case_profile+pinned ──┐
|
||||
③직전 summary ──┼─로드(큰그림→세부)──► 내담자AI system L2-EP
|
||||
②episodic recall(top-k)─┘ (BGE-M3 하이브리드+reranker, ~1.5~2K토큰)
|
||||
⑤KB 청크 ───────────────────로드(정책별 필터)──► system L2-KB (캐시친화)
|
||||
|
||||
모순 시 우선순위: pinned(L4) > 에피소드(이미 한 말) > 상태머신수치 > KB > LLM사전지식
|
||||
("KB는 사전이지 판사가 아니다" — 내담자는 에피소드를 따르고 KB는 보정용)
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
```
|
||||
|
||||
### 1.1 계층별 역할 한 줄 정의
|
||||
|
||||
- **① WORKING**: 상태머신 결정론 수치(stage·effective_openness·rapport_credit·resistance·ideation_stage) + 최근 K턴 버퍼. 매 턴 체크포인트 UPSERT(프로세스 죽어도 복원).
|
||||
- **② EPISODIC**: 발화=최소 원자단위, 저장 시 BGE-M3 임베딩 → 회기 시작 회상의 검색 대상. append-only(재귀학습·audit 원천).
|
||||
- **③ SUMMARY**: 회기당 1행. 구조화 수치 snapshot(무손실) + narrative(LLM 압축) 분리. 다음 회기 carry-over 1차 단위.
|
||||
- **④ SEMANTIC**: 케이스 전체 궤적 누적 압축(CCD 추정·호소문제·라포궤적·치료동맹). pinned_fact=절대 흘리면 안 되는 핵심(모순 처리 기준점).
|
||||
- **⑤ KNOWLEDGE**: DSM/이론/taxonomy/논평. 버전드, 거의 불변. 3-AI가 같은 물리 테이블을 다른 정책으로 검색.
|
||||
|
||||
---
|
||||
|
||||
## 2. 회기 라이프사이클 시퀀스
|
||||
|
||||
```
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
회기 라이프사이클: 회기시작 회상 → 진행(턴 사이클) → 회기종료 압축 → 다음 회기
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
【A. 회기 시작 — 회상 + 상태 복원】 (람다 부트 monthly→weekly→daily 차용)
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
학습자가 P1 내담자와 N번째 회기 시작
|
||||
│
|
||||
├─1. case_profile + pinned_fact 로드 (큰 그림, ~400+300토큰) [P3]
|
||||
│ load_pinned_facts(case_id, status IN stable/evolving/locked)
|
||||
│
|
||||
├─2. 직전 session_summary 로드 (중간, ~500토큰)
|
||||
│ {digest, open_threads, homework, end_state}
|
||||
│
|
||||
├─3. episodic recall (세부, top-5, ~600토큰)
|
||||
│ query = open_threads + homework (+학습자 첫 발화)
|
||||
│ → hybrid_search(case_id 스코프, dense+sparse RRF, top-50)
|
||||
│ → BGE-reranker-v2-m3 → top-5
|
||||
│
|
||||
├─4. 상태 복원 (carry-over, 결정론) [P2]
|
||||
│ init_session_state_from_history():
|
||||
│ stage = '라포' (매 회기 라포부터 재시작 — 상담 구조)
|
||||
│ rapport_credit = prev.rapport_credit × 0.7 (주1회 간격 일부 이월)
|
||||
│ resistance = inter_session_drift(prev, alliance)
|
||||
│ ideation_stage = prev (보수적 유지 — 안전 R5)
|
||||
│
|
||||
├─5. KB 정적 청크 로드 (페르소나 증상 단서, 정책 필터)
|
||||
│ visible_to='client' AND sensitivity<=1, 본문 비노출(행동단서만)
|
||||
│
|
||||
└─6. 상주 claude -p 프로세스 점유 + system 주입 (불변, cache_control)
|
||||
L0 가드레일 + L1 페르소나카드(불변) + L2-EP 회상 + L2-KB 지식
|
||||
→ session_state.recall_context에 캐시 (회기 내 재검색 0)
|
||||
|
||||
【B. 회기 진행 — 턴 사이클】 (마스터플랜 §2.2, 매 발화)
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
학습자 발화 U_t
|
||||
│
|
||||
├─ [입력 가드레일] Presidio PII 마스킹 + 위기분류(실제위기 vs 연기 분기)
|
||||
├─ [라우터] route_memory_vs_knowledge(turn, ai_role) [람다 "필요할때만 Read"]
|
||||
│ needs_episodic = 지시어("아까/저번에") OR role=client OR 단계전이
|
||||
│ needs_knowledge = role=evaluator OR 임상용어 OR role=counselor
|
||||
├─ [상태머신] effective_openness(t) 계산 (결정론, LLM 아님) [P2]
|
||||
│ = clamp(stage.openness + rapport_credit×unlock_rate - resistance×decay)
|
||||
├─ [모순 검사] handle_potential_contradiction(new_turn, pinned)
|
||||
│ locked 사실모순 → 차단·재생성 / clinical 변화 → 수용·갱신+이력
|
||||
├─ [내담자 AI] stdin JSON 주입 → 페르소나 응답 (CCD 직접노출 금지)
|
||||
│ L3 상태수치 + L4 fact hard-pin + L5 episodic recall + L6 최근N턴
|
||||
├─ [출력 가드레일] 자살수단 차단, ideation≤3, drift 감지(임베딩 일관성≥0.79)
|
||||
├─ [평가 AI 2-tier]
|
||||
│ fast-loop(로컬/Haiku): U_t만 4차원 태깅 (실시간)
|
||||
│ deep-loop(Opus 상주): 단계전환·회기말 0-5 채점 + 대안발화
|
||||
├─ [working 갱신] session_state UPSERT (체크포인트, 단기버퍼 append)
|
||||
└─ [로깅] turns → Postgres + 임베딩 (재귀학습 JSONL 원천)
|
||||
↑ K턴 반복 (recall_context는 회기 내 재사용, 재검색 안 함)
|
||||
|
||||
【C. 회기 종료 — 압축】 (람다 daily→weekly 컴프레션, 비동기 비블로킹)
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
stage='정리' 완료 OR 명시 종료
|
||||
│
|
||||
├─(A) 무손실 carry-over: end_state = working 종료 snapshot (코드 복사, LLM 미경유) [P4]
|
||||
├─(B) salience 산출 → 망각/유지 결정
|
||||
│ compute_salience(t): pinned/위기/최초개방/정서급변 ↑, 잡담 ↓
|
||||
│ salience ≥ 0.5 → episodic 영구유지(검색 우선), 미만 → digest 흡수
|
||||
├─(C) narrative 압축(LLM, 상주 claude -p 재사용): digest 6~10문장 + open_threads
|
||||
│ pinned 사실은 프롬프트 hard-pin → digest 보존 강제
|
||||
│ 입력 = turns.text_masked만 (F-03 마스킹 강제)
|
||||
├─(D) digest 임베딩 + session_summary UPSERT + episodic 임베딩 백필
|
||||
├─(E) case_profile 병합: rapport_trajectory append, CCD 추정 갱신,
|
||||
│ case_digest 재압축(N회기마다 or major shift), alliance EWMA
|
||||
├─(F) pinned_fact 모순/갱신 처리 + pinned_fact_history 이력화
|
||||
└─(G) RAG 동기화 (람다 storeDocument→embed): 다음 회기 회상 대비
|
||||
│
|
||||
├─ 상주 프로세스 회수 (누적 컨텍스트 리셋 → 말투표류 D-c 회기경계서 차단)
|
||||
└─ 종단 평가: learner_profile EWMA(현재수준)+slope(성장속도) 갱신
|
||||
|
||||
【D. 다음 회기】 → 【A】로 복귀 (case_profile·summary·pinned 누적된 상태로)
|
||||
토큰 ~4K 고정 (100회기여도 선형증가 X)
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 통합 Postgres 스키마 (메모리·지식·페르소나·평가, DDL 수준)
|
||||
|
||||
> 스키마 4분할: `app`(운영·메모리·페르소나) / `kb`(정적 지식, 독립) / `audit`(append-only 감사) / `ds`(재귀학습). **Phase 0에 전부 선반영(nullable), cross-session·KB 채우기는 Phase 2a**(MASTERPLAN_REVISIONS §3.5 재마이그레이션 폭탄 방지 원칙).
|
||||
|
||||
### 3.1 페르소나 — 불변 카드 (app.persona_card)
|
||||
|
||||
```sql
|
||||
-- ════ 불변 정체성 (람다 IDENTITY/SOUL, 버전드, 교수만 편집) ════
|
||||
CREATE TABLE app.persona_card (
|
||||
persona_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code TEXT NOT NULL, -- 'P1','P2','P3'
|
||||
version INT NOT NULL DEFAULT 1, -- 편집 시 +1 (진행 세션은 옛 version 핀)
|
||||
status TEXT NOT NULL DEFAULT 'draft'
|
||||
CHECK (status IN ('draft','review','approved','archived')),
|
||||
display_name TEXT NOT NULL,
|
||||
difficulty TEXT NOT NULL CHECK (difficulty IN ('easy','moderate','hard')),
|
||||
theory_target TEXT[], -- {'humanistic','cbt'}
|
||||
demographics JSONB NOT NULL, -- 범주화(재식별 방지 F-25): age_band,sex,grade...
|
||||
presenting JSONB NOT NULL, -- 표층 호소(내담자가 입으로 말함)
|
||||
history JSONB NOT NULL, -- 과거사
|
||||
big5 JSONB NOT NULL, -- {O,C,E,A,N} 0~1 (말투·반응 수학적 앵커)
|
||||
resistance JSONB NOT NULL, -- {base_resistance,unlock_rate,decay_floor,silence_prob,deflection_prob}
|
||||
speech_style JSONB NOT NULL, -- {register,avg_sentence_len,fillers,honorific,verbal_tics}
|
||||
affect_baseline JSONB NOT NULL, -- {negative_affect,hopelessness,anhedonia,sleep,anxiety,suicide_ideation_stage} hard상한=3
|
||||
ccd JSONB NOT NULL, -- Patient-Ψ 8요소 (★직접발화 금지 R4, 행동으로만)
|
||||
dsm5_dimensional JSONB NOT NULL, -- criteria_behavior_matrix (진단명 비노출, 내부 엄밀성 F-22)
|
||||
source_provenance TEXT NOT NULL, -- '0615 합성변형' (원문 미적재 F-05)
|
||||
is_synthetic BOOL NOT NULL DEFAULT TRUE,
|
||||
created_by UUID, approved_by UUID, approved_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (code, version)
|
||||
);
|
||||
CREATE INDEX idx_persona_card_status ON app.persona_card(status) WHERE status='approved';
|
||||
|
||||
-- 음성 매핑 (provider-agnostic, 카드 버전 정합)
|
||||
CREATE TABLE app.persona_voice_map (
|
||||
persona_id UUID NOT NULL REFERENCES app.persona_card(persona_id) ON DELETE CASCADE,
|
||||
version INT NOT NULL,
|
||||
voice_id TEXT NOT NULL, -- 'voice_p1_teen_m'
|
||||
provider TEXT NOT NULL CHECK (provider IN ('openai','higgs','melotts')),
|
||||
base_params JSONB NOT NULL,
|
||||
prosody_map JSONB NOT NULL, -- 상태(stage/affect/resistance)→prosody 함수
|
||||
PRIMARY KEY (persona_id, version)
|
||||
);
|
||||
|
||||
-- 상담사 AI 페르소나 (데모/self-play용, 이론 충실도 일관)
|
||||
CREATE TABLE app.counselor_profile (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code TEXT,
|
||||
theory TEXT CHECK (theory IN ('humanistic','cbt','integrative')),
|
||||
skill_level TEXT, reasoning_chain JSONB, -- CoE: PCT/CBT 추론체인
|
||||
allowed_microskills TEXT[], speech_style JSONB
|
||||
);
|
||||
```
|
||||
|
||||
### 3.2 메모리 ① WORKING (app.session_state, 체크포인트)
|
||||
|
||||
```sql
|
||||
CREATE TABLE app.session_state (
|
||||
session_id UUID PRIMARY KEY REFERENCES app.sessions(id) ON DELETE CASCADE,
|
||||
stage TEXT NOT NULL DEFAULT '라포' CHECK (stage IN ('라포','탐색','개입','정리')),
|
||||
turn_seq INT NOT NULL DEFAULT 0,
|
||||
effective_openness REAL NOT NULL DEFAULT 0.15 CHECK (effective_openness BETWEEN 0 AND 1),
|
||||
rapport_credit REAL NOT NULL DEFAULT 0.0, -- 회기말 carry 대상 (0.7 이월)
|
||||
resistance REAL NOT NULL DEFAULT 0.65 CHECK (resistance BETWEEN 0 AND 1),
|
||||
ideation_stage SMALLINT NOT NULL DEFAULT 1 CHECK (ideation_stage BETWEEN 1 AND 5),
|
||||
affect_state JSONB NOT NULL DEFAULT '{}',
|
||||
short_buffer JSONB NOT NULL DEFAULT '[]', -- 최근 K턴 rolling window
|
||||
recall_context JSONB, -- 회기시작 1회 로드, 회기내 재사용 캐시
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
### 3.3 메모리 ② EPISODIC (app.turns 확장 + app.turn_embedding)
|
||||
|
||||
```sql
|
||||
-- 마스터플랜 §3.2 turns에 연속성 컬럼 추가
|
||||
ALTER TABLE app.turns
|
||||
ADD COLUMN IF NOT EXISTS salience REAL DEFAULT 0.0, -- 망각/우선순위
|
||||
ADD COLUMN IF NOT EXISTS is_pinned BOOL DEFAULT FALSE, -- pinned_fact 승격 발화
|
||||
ADD COLUMN IF NOT EXISTS contradicts UUID[] DEFAULT '{}', -- 모순추적: 뒤집은 과거 turn_id[]
|
||||
-- 음성 paralinguistic (MASTERPLAN_REVISIONS §3.5 선반영)
|
||||
ADD COLUMN IF NOT EXISTS audio_ref TEXT,
|
||||
ADD COLUMN IF NOT EXISTS silence_ms INT,
|
||||
ADD COLUMN IF NOT EXISTS speech_rate REAL,
|
||||
ADD COLUMN IF NOT EXISTS barge_in BOOL;
|
||||
|
||||
CREATE TABLE app.turn_embedding (
|
||||
turn_id UUID PRIMARY KEY REFERENCES app.turns(id) ON DELETE CASCADE,
|
||||
case_id UUID NOT NULL, -- denormalized: 케이스 스코프 회상
|
||||
session_id UUID NOT NULL, seq INT NOT NULL,
|
||||
dense vector(1024) NOT NULL, -- BGE-M3 dense
|
||||
sparse JSONB, -- BGE-M3 sparse {token_id:weight}
|
||||
context_prefix TEXT, -- Contextual Retrieval 1문장
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX idx_turn_emb_hnsw ON app.turn_embedding
|
||||
USING hnsw (dense vector_cosine_ops) WITH (m=16, ef_construction=64);
|
||||
CREATE INDEX idx_turn_emb_case ON app.turn_embedding (case_id, session_id);
|
||||
```
|
||||
|
||||
### 3.4 메모리 ③ SUMMARY (app.session_summary)
|
||||
|
||||
```sql
|
||||
CREATE TABLE app.session_summary (
|
||||
session_id UUID PRIMARY KEY REFERENCES app.sessions(id) ON DELETE CASCADE,
|
||||
case_id UUID NOT NULL, session_no INT NOT NULL,
|
||||
-- (A) 무손실 carry-over (상태 수치 snapshot, LLM 미경유) [P2]
|
||||
end_state JSONB NOT NULL, -- {stage,openness,rapport_credit,resistance,ideation_stage,affect}
|
||||
rapport_delta REAL,
|
||||
-- (B) narrative 압축 (LLM, 손실 허용)
|
||||
digest TEXT NOT NULL, -- 6~10문장, 다음 회기 system 주입
|
||||
open_threads JSONB NOT NULL DEFAULT '[]', -- 미해결 [{topic,last_stance,raised_session_no}]
|
||||
homework JSONB, emotional_arc TEXT,
|
||||
-- (C) 검색용
|
||||
digest_embedding vector(1024),
|
||||
token_count INT, compressed_by TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (case_id, session_no)
|
||||
);
|
||||
CREATE INDEX idx_sess_sum_case ON app.session_summary (case_id, session_no DESC);
|
||||
CREATE INDEX idx_sess_sum_emb ON app.session_summary
|
||||
USING hnsw (digest_embedding vector_cosine_ops) WITH (m=16, ef_construction=64);
|
||||
```
|
||||
|
||||
### 3.5 메모리 ④ SEMANTIC (app.case_profile + app.pinned_fact)
|
||||
|
||||
```sql
|
||||
-- 케이스 = 한 가상내담자의 다회기 전체 (persona_id=템플릿, case=학습자별 인스턴스)
|
||||
CREATE TABLE app.case_profile (
|
||||
case_id UUID PRIMARY KEY,
|
||||
persona_id TEXT NOT NULL, -- 시드 페르소나
|
||||
learner_id UUID NOT NULL, -- ★ 학습자별 독립 연속체
|
||||
last_session_no INT NOT NULL DEFAULT 0,
|
||||
ccd_estimate JSONB NOT NULL DEFAULT '{}', -- CCD 8요소 현재추정 +confidence
|
||||
presenting_arc JSONB NOT NULL DEFAULT '[]', -- 호소문제 궤적
|
||||
rapport_trajectory JSONB NOT NULL DEFAULT '[]', -- [{session_no,end_rapport,end_openness}]
|
||||
alliance_level REAL DEFAULT 0.2, -- 치료동맹 누적
|
||||
case_digest TEXT NOT NULL DEFAULT '', -- 전체 궤적 8~12문장 (큰그림, 람다 monthly)
|
||||
digest_embedding vector(1024),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- pinned facts (람다 MEMORY.md, 절대 흘리면 안 되는 핵심, 모순 기준점)
|
||||
CREATE TABLE app.pinned_fact (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
case_id UUID NOT NULL REFERENCES app.case_profile(case_id) ON DELETE CASCADE,
|
||||
key TEXT NOT NULL, -- 'name','family','suicide_attempt_history'
|
||||
value TEXT NOT NULL, -- 가명처리/마스킹된
|
||||
fact_type TEXT NOT NULL CHECK (fact_type IN ('identity','history','relationship','clinical','agreement')),
|
||||
status TEXT NOT NULL DEFAULT 'stable' CHECK (status IN ('stable','evolving','contradicted','locked')),
|
||||
source_turn UUID, confidence REAL NOT NULL DEFAULT 1.0,
|
||||
version INT NOT NULL DEFAULT 1, updated_session_no INT,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (case_id, key)
|
||||
);
|
||||
CREATE INDEX idx_pinned_case ON app.pinned_fact (case_id, status);
|
||||
|
||||
-- pinned 변경 이력 (append-only, 모순/갱신 audit + 평가AI 정답신호)
|
||||
CREATE TABLE app.pinned_fact_history (
|
||||
id BIGSERIAL PRIMARY KEY, fact_id UUID NOT NULL, case_id UUID NOT NULL,
|
||||
old_value TEXT, new_value TEXT,
|
||||
reason TEXT, -- 'contradiction','clarification','progression'
|
||||
session_no INT, turn_id UUID, created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
### 3.6 지식 ⑤ KB (kb 스키마, 독립)
|
||||
|
||||
```sql
|
||||
CREATE SCHEMA kb;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
-- 출처 + 라이선스 (DSM-5 저작권 1차 통제)
|
||||
CREATE TABLE kb.source (
|
||||
source_id TEXT PRIMARY KEY, -- 'dsm5','theory','taxonomy_0615'
|
||||
title TEXT NOT NULL,
|
||||
kb_kind TEXT NOT NULL CHECK (kb_kind IN
|
||||
('diagnostic','theory','technique','taxonomy','supervisor_pattern','template','ko_context')),
|
||||
license_class CHAR(1) NOT NULL CHECK (license_class IN ('A','B','C','D')), -- C=저작권민감 D=미성년파생
|
||||
origin_path TEXT, citation TEXT,
|
||||
external_llm_ok BOOLEAN NOT NULL DEFAULT false, -- C/D면 false → 국내 라우팅
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- 문서 + 버전 (graphify content_hash 증분 인덱싱 차용)
|
||||
CREATE TABLE kb.document (
|
||||
doc_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
source_id TEXT NOT NULL REFERENCES kb.source(source_id),
|
||||
doc_uri TEXT NOT NULL, version INT NOT NULL DEFAULT 1,
|
||||
content_hash TEXT NOT NULL, -- SHA256 → 변경감지(증분)
|
||||
superseded_by BIGINT REFERENCES kb.document(doc_id),
|
||||
is_active BOOLEAN NOT NULL DEFAULT true, indexed_at TIMESTAMPTZ,
|
||||
UNIQUE (source_id, doc_uri, version)
|
||||
);
|
||||
|
||||
-- 청크 + 멀티벡터 (검색 본체)
|
||||
CREATE TABLE kb.chunk (
|
||||
chunk_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
doc_id BIGINT NOT NULL REFERENCES kb.document(doc_id) ON DELETE CASCADE,
|
||||
source_id TEXT NOT NULL, kb_kind TEXT NOT NULL, -- 비정규화(필터 가속)
|
||||
seq INT NOT NULL, heading_path TEXT,
|
||||
chunk_text TEXT NOT NULL, -- 표시·LLM 주입(prefix 미포함)
|
||||
context_prefix TEXT, -- Contextual Retrieval 주입문
|
||||
embedding vector(1024), -- BGE-M3 dense
|
||||
sparse_vec JSONB, colbert_vecs JSONB, -- 멀티벡터(단일 호출 산출)
|
||||
visible_to TEXT[] NOT NULL DEFAULT '{client,counselor,evaluator}', -- 정보비대칭
|
||||
sensitivity SMALLINT NOT NULL DEFAULT 0, -- 0공개 1비공개내부 2평가전용 3원천격리
|
||||
label_id BIGINT, -- taxonomy면 technique_label_def FK
|
||||
meta JSONB DEFAULT '{}', -- {dsm_category,theory,bias_weight...}
|
||||
token_count INT, created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
CREATE INDEX idx_chunk_hnsw ON kb.chunk USING hnsw (embedding vector_cosine_ops) WITH (m=16, ef_construction=64);
|
||||
CREATE INDEX idx_chunk_fts ON kb.chunk USING gin (to_tsvector('simple', chunk_text));
|
||||
CREATE INDEX idx_chunk_trgm ON kb.chunk USING gin (chunk_text gin_trgm_ops);
|
||||
CREATE INDEX idx_chunk_visible ON kb.chunk USING gin (visible_to);
|
||||
CREATE INDEX idx_chunk_route ON kb.chunk (kb_kind, sensitivity);
|
||||
```
|
||||
|
||||
### 3.7 평가 + 종단 추적 + 드리프트 감사 (app + audit)
|
||||
|
||||
```sql
|
||||
-- 발화별 평가 (마스터플랜 §3.2, visible_to로 학습자 비노출)
|
||||
CREATE TABLE app.feedback_scores (
|
||||
turn_id UUID NOT NULL, dimension TEXT NOT NULL, -- 공감/개방질문/검증/이론부합
|
||||
score REAL, -- 0-5 (CTRS 0-6)
|
||||
rationale TEXT, top1_score REAL, -- CRAG 게이트(임계미달→관찰프레이밍 F-06)
|
||||
visible_to TEXT[] NOT NULL DEFAULT '{evaluator}',
|
||||
PRIMARY KEY (turn_id, dimension)
|
||||
);
|
||||
|
||||
-- 종단 학습자 프로파일 (회기 거듭하며 나아지는지 — 윤찬 직접 답)
|
||||
CREATE TABLE app.learner_profile (
|
||||
learner_id UUID PRIMARY KEY,
|
||||
-- 4차원 EWMA(현재수준) + slope(성장속도) 동시
|
||||
dim_ewma JSONB NOT NULL DEFAULT '{}', -- {empathy:0.6, open_q:0.5, validation:..., theory:...}
|
||||
dim_slope JSONB NOT NULL DEFAULT '{}', -- 성장속도 (잘하는데정체 vs 못하지만급성장 구분)
|
||||
persistent_gaps JSONB DEFAULT '[]', -- deep-loop만 맥락화 코칭 (fast-loop 절대 미주입)
|
||||
session_count INT DEFAULT 0, updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- 페르소나 드리프트 감사 (KPI 임베딩일관성≥0.79 측정)
|
||||
CREATE TABLE audit.persona_drift_log (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
session_id UUID NOT NULL, turn_id UUID, persona_id UUID NOT NULL,
|
||||
drift_type TEXT NOT NULL, -- 'big5_violation'|'ccd_leak'|'fact_contradiction'|'style_drift'
|
||||
embedding_sim REAL, detail JSONB,
|
||||
severity TEXT CHECK (severity IN ('warn','block')),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- KB 검색 로그 (환각측정·캐시검증, 람다 cost.json 차용)
|
||||
CREATE TABLE kb.retrieval_log (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
session_id UUID, turn_id UUID, ai_role TEXT NOT NULL,
|
||||
query_text TEXT, policy TEXT NOT NULL, hit_chunk_ids BIGINT[],
|
||||
rerank_scores REAL[], top1_score REAL, used_in_answer BOOLEAN,
|
||||
latency_ms INT, created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
> **핵심 설계 결정 — `(persona_id, learner_id)` 복합키**: 한 페르소나(P1)를 20명이 각자 연습한다. 같은 카드(불변) + 다른 연속체(가변). `case_profile.learner_id`가 "이 학습자에게 이 내담자가 겪은 회기 히스토리"를 정의 → 학습자 간 기억 오염 차단.
|
||||
|
||||
---
|
||||
|
||||
## 4. 3-AI 정보비대칭이 메모리·KB 레이어에서 강제되는 방식
|
||||
|
||||
### 4.1 핵심 원리 — 위험한 건 turn 표면이 아니라 파생 메모리
|
||||
|
||||
turn 자체는 대화 표면이라 덜 위험하다. **위험한 건 CCD·정답라벨·점수·종단요약 같은 파생 메모리.** 여기에만 `visible_to[]`를 달고, **컨텍스트 조립기가 역할별로 물리적으로 다른 쿼리 분기**를 탄다.
|
||||
|
||||
```
|
||||
2-레이어 강제 (MASTERPLAN_REVISIONS §5.1 F-30):
|
||||
┌─ 레이어1: AI 정보비대칭 (current_ai_view enum) ── visible_to[] WHERE 강제
|
||||
│ CLIENT_AI 분기엔 load_ccd()·load_evaluator_answer() 함수 자체가 없음
|
||||
│ → "필터 누락"이 아니라 "코드 경로 부재"가 1차 방어
|
||||
└─ 레이어2: 인간 RBAC×cohort (current_role) ── RLS DB레벨 방어선
|
||||
인간이 turn 읽을 때 두 게이트 AND (단일 미들웨어 + RLS 이중강제)
|
||||
```
|
||||
|
||||
### 4.2 메모리 레이어 — 3-AI 뷰 격리 매트릭스
|
||||
|
||||
| 메모리/KB 항목 | ② 내담자 AI | ① 상담사 AI(보조) | ③ 평가 AI |
|
||||
|---|---|---|---|
|
||||
| ① working(상태수치) | 자기 stage·openness만 (수치 주입, 메타노출 X) | 표면 대화만 | 전체 (저항엔진 검증) |
|
||||
| ② episodic(자기 발화) | 자기 케이스 recall만 (case 스코프 강제) | 회상 없음(표면만) | 전체 (정답 대조) |
|
||||
| ③ session_summary | digest·pinned는 "자기기억"으로만 표현 | 미노출 | 전체 + open_threads |
|
||||
| ④ case_profile.ccd_estimate | **절대 비노출** (R4 메타노출 금지) | **절대 비노출** | 전체 (정답 CCD) |
|
||||
| ④ pinned_fact(clinical) | 자기 사실로만 | 미노출 | 전체 (변화 정답신호) |
|
||||
| ⑤ KB diagnostic(DSM) | 내부 파라미터 (본문 비노출, 행동단서만) | **차단** | 전체 |
|
||||
| ⑤ KB taxonomy 정답라벨 | 차단 (sensitivity≥2) | 차단 | 전체 (채점 기준) |
|
||||
| learner_profile | 차단 | 차단 | deep-loop만 (fast 미주입, 사전편향 차단) |
|
||||
|
||||
### 4.3 KB 레이어 — 같은 물리 테이블, 다른 retrieval 정책 4-튜플
|
||||
|
||||
검색 정책 = **사전필터(WHERE) + 회수 가중치 + 리랭킹 목표 + 주입 방식**.
|
||||
|
||||
| 차원 | ② 내담자 AI | ① 상담사 AI | ③ 평가 AI |
|
||||
|---|---|---|---|
|
||||
| 사전필터 | `kb_kind IN (diagnostic,theory,technique)` `AND 'client'=ANY(visible_to)` `AND sensitivity<=1` | `kb_kind IN (theory,technique,microskill,ko_context)` `AND 'counselor'=ANY(visible_to)` `AND sensitivity=0` | `'evaluator'=ANY(visible_to)` `AND sensitivity<=2` (taxonomy·supervisor 정답 포함) |
|
||||
| 회수 가중치 | dense 0.7 / sparse 0.3 (증상 의미) | dense 0.5 / sparse 0.5 (기법명) | sparse 0.6 / dense 0.4 (라벨명·논평) |
|
||||
| 주입 방식 | 본문 비노출, "행동단서 요약"만 | 본문+예시 | 본문+label_id+bias_weight |
|
||||
| 절대 차단 | sensitivity≥2, 타 페르소나 CCD | DSM diagnostic, 평가정답 | (전부 봄, 학습자 비노출은 응답 가공단) |
|
||||
|
||||
```sql
|
||||
-- 하이브리드 검색 SQL 계약 (정책별 파라미터화, visible_to DB 강제)
|
||||
WITH dense AS (
|
||||
SELECT chunk_id, 1-(embedding <=> :q_dense) AS s_dense FROM kb.chunk
|
||||
WHERE kb_kind=ANY(:kinds) AND :role=ANY(visible_to) AND sensitivity<=:sens_max
|
||||
ORDER BY embedding <=> :q_dense LIMIT 50),
|
||||
sparse AS (
|
||||
SELECT chunk_id, ts_rank_cd(to_tsvector('simple',chunk_text),:q_terms) AS s_sparse FROM kb.chunk
|
||||
WHERE kb_kind=ANY(:kinds) AND :role=ANY(visible_to) AND sensitivity<=:sens_max
|
||||
AND to_tsvector('simple',chunk_text) @@ :q_terms LIMIT 50)
|
||||
SELECT c.chunk_id, c.chunk_text, c.context_prefix, c.label_id,
|
||||
COALESCE(d.s_dense,0)*:w_dense + COALESCE(s.s_sparse,0)*:w_sparse AS fused
|
||||
FROM kb.chunk c LEFT JOIN dense d USING(chunk_id) LEFT JOIN sparse s USING(chunk_id)
|
||||
WHERE d.chunk_id IS NOT NULL OR s.chunk_id IS NOT NULL
|
||||
ORDER BY fused DESC LIMIT :k; -- → BGE-reranker-v2-m3 top-5
|
||||
```
|
||||
|
||||
### 4.4 누수 방지 자동 테스트 (F-17, 람다 직접검증)
|
||||
|
||||
- **T1 RLS 부정테스트**: 타 AI뷰 SET → 0행 반환 확인
|
||||
- **T2 조립기 화이트리스트**: CLIENT_AI 분기에 ccd/정답 로드 함수 부재 정적검증
|
||||
- **T3 CCD-메타 누설**: "제 핵심신념은…" 정규식 게이트 + 분류기
|
||||
- **T4 cross-trainee 누수**: 학습자A의 case가 B에 새지 않는지 (case_id 스코프)
|
||||
- **람다 직접**: IDOR 403 스크린샷, ai_view 위조 요청 차단 검증
|
||||
|
||||
---
|
||||
|
||||
## 5. 기존 마스터플랜 §3.6과의 정합/대체점
|
||||
|
||||
| 마스터플랜 §3.6 항목 | 본 설계 | 정합 / 대체 |
|
||||
|---|---|---|
|
||||
| 페르소나 메모리=하이브리드(상태수치+rolling summary+episodic 벡터) | 4계층(working/episodic/summary/semantic)으로 **구체화** | **정합·확장** (§3.6 추상을 테이블·압축 알고리즘으로 실체화) |
|
||||
| 피드백 근거=하이브리드+리랭킹(+CRAG) | KB ③평가 경로 = 하이브리드+Contextual+reranker, top1_score 게이트 | **정합** (CRAG를 ③경로에만 우선 도입) |
|
||||
| 지식=정적 KB+Contextual Retrieval | kb 스키마 + 3계층(청크/prefix/community) + license_class | **정합·확장** (DSM 저작권 통제 추가) |
|
||||
| 임베딩=BGE-M3 단일모델 멀티벡터 | episodic·summary·KB 전부 BGE-M3 | **정합** (동일 모델 재사용) |
|
||||
| pgvector(HNSW) | 모든 벡터 테이블 HNSW | **정합** |
|
||||
| 하이브리드 dense+sparse top-50 | episodic·KB 동일 RRF | **정합** |
|
||||
| GraphRAG=2차(이론 교차개념) | 본 설계도 graphify 2차 (theory KB 커질 때만) | **정합** |
|
||||
| (§3.6 미언급) 회기 간 연속성 | **본 설계가 신규 추가** (summary/case_profile/recall/모순처리) | **대체점=신규 보강** (§3.6은 단일회기 메모리만, 본 설계가 cross-session 채움) |
|
||||
| (§3.6 미언급) rag_chunks vs episodic 분리 | 에피소드(app)↔정적KB(kb) **물리 스키마 분리** | **대체점=명확화** (§3.6 rag_chunks를 KB 전용으로 좁히고 메모리는 별도) |
|
||||
|
||||
> **핵심 대체점**: 마스터플랜 §3.6은 "페르소나 메모리=하이브리드 메모리"라고만 했지 **회기 간 연속성(cross-session)을 다루지 않았다.** 본 설계가 summary→case_profile 계층 압축 + 회기 시작 회상 + 모순 처리를 신규로 채운다. §3.6의 RAG 스택 자체는 하나도 바꾸지 않는다(BGE-M3/pgvector/하이브리드/Contextual/reranker 그대로).
|
||||
|
||||
---
|
||||
|
||||
## 6. 20주·n=1 제약 하 MVP 메모리 범위
|
||||
|
||||
### 6.1 Phase 매핑 (단일 로드맵 정합, 산발 task 금지)
|
||||
|
||||
| 항목 | Phase | 소유(REVISIONS §6.1) | DoD 검증(람다 직접) |
|
||||
|---|---|---|---|
|
||||
| **스키마 전체 선반영** (§3 전부, nullable) | **Phase 0** | 람다 직접(인프라) | 마이그레이션 통과 + nullable 확인 |
|
||||
| 페르소나 카드 4테이블 + 음성맵 | Phase 0 | 람다 직접 | DDL 적용 |
|
||||
| ① working + 체크포인트 | Phase 1 | A2(상태머신) | 프로세스 kill 후 상태 복원 재현 |
|
||||
| ② episodic + 임베딩 | Phase 1 | A1(엔진)+데이터 | 발화 저장 시 임베딩 적재 DB쿼리 |
|
||||
| ⑤ KB (하이브리드+Contextual+reranker) | Phase 1 | A1+데이터 | ②/③ 다른 청크 회수 SQL 대조 |
|
||||
| ③ 회기종료 압축 | **Phase 2a** | A2+데이터 | 회기 완주→summary 생성, 토큰예산 실측 |
|
||||
| ④ case_profile+pinned+모순처리 | **Phase 2a** | A2+데이터 | 2회기→carry-over+모순처리 재현 |
|
||||
| 회기시작 회상 | **Phase 2a** | A1+B(UI카드) | N회기째 "지난번~" 정확 회상 + 정답대조 |
|
||||
| 종단 learner_profile EWMA+slope | Phase 2a~3 | D(데이터) | 다회기 성장곡선 대시보드 |
|
||||
|
||||
### 6.2 MVP 메모리 범위 결정 (정직한 단계화)
|
||||
|
||||
- **Phase 1 = 단일 회기 메모리만**: working(상태머신 carry-over 체크포인트) + episodic(발화 임베딩) + KB(정적 지식). **cross-session 없음.** 0615를 1회기 episodic 시드로.
|
||||
- **Phase 2a = cross-session 추가**: summary 압축 + case_profile 누적 + 회기 시작 회상 + 모순 처리. 다회기 진화는 데이터가 아니라 **결정론 상태머신 + 합성 시나리오**로 보장.
|
||||
- **스키마는 Phase 0에 전부 선반영** (재마이그레이션 폭탄 방지).
|
||||
|
||||
### 6.3 n=1 정직 표기 (절대 "검증됨" 보류)
|
||||
|
||||
- **0615은 사실상 단일 회기 사례** → 다회기 연속성 학습 코퍼스 없음. 압축 품질(정보보존율)·회상 정확도는 **0615로 검증 불가.**
|
||||
- Phase 1엔 **룰 기반으로 시작**(salience 룰, inter_session_drift 룰, EWMA α=0.4·FAST_WINDOW=6 전부 가정값).
|
||||
- 정량검증은 **Phase 3 파일럿(20명×다회기)에서 first measure** — "압축 전후 사실 보존율 / 다음회기 회상 정확도 / 성장곡선 타당도". 그 전엔 "검증됨" 표기 금지(`feedback_measure_before_report`).
|
||||
- **추가 축어록 수급이 전제** — n=1 해소는 한신대 공문(블로킹 의존성, F-04)에 달림.
|
||||
|
||||
---
|
||||
|
||||
## 7. 핵심 리스크 (통합)
|
||||
|
||||
| # | 리스크 | 영향 | 완화 |
|
||||
|---|---|---|---|
|
||||
| **M1** | 압축 정보손실로 페르소나 일관성 균열 | 페르소나 붕괴 | pinned 무손실+episodic 검색 백업, digest는 서사용. Phase3 보존율 측정 |
|
||||
| **M2** | 상태수치 LLM hallucination | 저항엔진 오작동 | end_state는 코드 복사(P4), LLM 미경유 |
|
||||
| **M3** | n=1로 압축/회상/성장곡선 품질 검증 불가 | 정량근거 부재 | 정직표기, 룰 시작, Phase3 first measure. α·window·TTFT 전부 "실측 필요" |
|
||||
| **M4** | 모순처리 오판 (정당변화↔버그 혼동) | 잘못 차단/오염 | fact_type별 분기(locked만 차단), 전건 이력화 사후교정 |
|
||||
| **M5** | 회상 검색 오류 (엉뚱한 과거발화) | "지난번~" 환각 | reranker top-5, open_threads 우선쿼리, case 스코프 강제 |
|
||||
| **M6** | CCD 메타노출 (회상·KB가 정답 누설) | 추론훈련 무력화 | visible_to 분리, 내담자="자기기억"만, KB 본문 비노출(단서만), 출력 가드레일 |
|
||||
| **M7** | 회기 경계 상태 리셋 버그 | 진화 손실 | carry-over 명시(0.7 이월), rapport_trajectory DB 회귀테스트 |
|
||||
| **M8** | 페르소나 drift (가변이 불변 덮음) | 페르소나 깨짐 | 불변 카드 압축 대상 제외, COMPRESS_SYSTEM hard rule+금칙어 정규식 이중방어 |
|
||||
| **M9** | DSM-5 verbatim 저작권 (license C) | 법적 책임 | 원문 미적재 환언본만, verbatim 검출기 게이트, 국내 라우팅 |
|
||||
| **M10** | 한국어 sparse/BM25 토크나이징 (PG simple 형태소 미분리) | 검색 품질 | pg_trgm+BGE sparse 병용, 부족 시 mecab-ko 2차. 실측 전 "해결됨" 금지 |
|
||||
| **M11** | 정보비대칭 누수 (내담자가 정답청크 봄) | 시뮬레이션 가치 0 | visible_to+sensitivity DB WHERE 강제, 코드경로 부재 1차방어, T1~T4 자동테스트 |
|
||||
| **M12** | 마스킹 게이트 우회 (claude -p stdin 직송) | IRB·법적 | C/D 청크 external_llm_ok=false, Presidio 선통과(F-03), KB는 인덱싱 시점 환언 |
|
||||
| **M13** | 압축/HNSW 재인덱싱 비용 누적 | 예산 | 정액 한계비용 0, content_hash 증분, case_digest 재압축 N회기마다, salience 입력축소 |
|
||||
| **M14** | 임베딩 일관성 0.79 임계가 임의값 | KPI 무근거 | self-play 회귀로 분포 측정 후 확정, 파일럿 전 "검증됨" 금지 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 통합 요약 (한 장)
|
||||
|
||||
- **5계층**: ①working(상태머신 수치+버퍼, 코드 carry-over·체크포인트) ②episodic(turns+BGE-M3, append-only) ③summary(회기종료 LLM 압축 ~600토큰) ④semantic(case_profile evolving+pinned_fact) ⑤knowledge(kb 스키마, 3-AI 다른 정책). 메모리(app)↔지식(kb) **물리 스키마 분리**.
|
||||
- **회기 라이프사이클**: 시작(case_digest→summary→episodic recall, 큰그림→세부, ~4K 고정) → 진행(턴 사이클, 상태머신 결정론, 모순검사) → 종료(수치 코드복사+서사 LLM압축+salience 망각/유지+case 병합) → 다음 회기(누적 상태).
|
||||
- **회기 간 연속성(최우선 갭)**: 상태 수치 carry-over(rapport 0.7 이월, ideation 보수적 유지), pinned hard-pin 무손실, 모순 처리(locked=차단·재생성 / clinical=수용·갱신+이력), 람다 daily→weekly→monthly 압축 직이식.
|
||||
- **페르소나 일관성**: 불변 카드 + 가변 메모리 물리 분리(L1 캐시 vs L3~L6 턴주입), drift=가변이 불변 덮을 때 → 구조적 차단. 음성 prosody=텍스트 말투와 같은 상태머신 입력 동기화.
|
||||
- **정보비대칭**: 파생 메모리(CCD·정답·점수·종단)에만 visible_to, 코드경로 부재 1차방어+RLS 2차, 인간 RBAC×cohort AND. KB는 같은 테이블 다른 4-튜플 정책.
|
||||
- **§3.6 정합**: BGE-M3/pgvector/하이브리드/Contextual/reranker 스택 그대로, 회기 간 연속성만 신규 보강.
|
||||
- **MVP·n=1**: Phase 0 스키마 전체 선반영, Phase 1 단일회기, Phase 2a cross-session. 0615 검증 불가→룰 시작+Phase3 first measure, 추가 축어록 수급(공문 블로킹) 전제. "검증됨" 표기 보류.
|
||||
|
||||
---
|
||||
|
||||
## 부록. 종합 근거 자료 (절대경로)
|
||||
|
||||
- 마스터플랜: `C:/Users/encep/Dropbox/Obsidian/Yun Chan Repository/Projects/영업/2026/한신대학교 산학협력 프로젝트/HANSHIN_AI_PLATFORM_MASTERPLAN.md` (§2 3-AI, §3 데이터모델, §3.6 RAG)
|
||||
- BLUE TEAM 패치: `…/적대검증_2026-06-25/MASTERPLAN_REVISIONS.md` (§1 stream-json 상주풀, §3 마스킹·스키마, §5 visible_to/RBAC 레이어분리, §6 RACI·DoD검증)
|
||||
- 데이터 원천: `…/데이터/README.md` (0615 n=1 시드, 파란색=발화별 기법라벨+슈퍼바이저논평 taxonomy)
|
||||
- 람다 메모리 시스템(1차 레퍼런스): `…/Lambda/BOOT.md`(부트 큰그림→세부), `…/Lambda/procedures/memory-compression.md`(daily→weekly→monthly 압축 알고리즘), `…/CLAUDE.md`(graphify=GraphRAG / rag-memory=BGE-M3 sqlite 역할분리)
|
||||
- KB 자료원: `…/Lambda/references/counseling/`(00~07 DSM·이론·마이크로스킬·위기·한국맥락, 출처인용·가드레일 완비 환언본)
|
||||
- 자매 설계서: `…/설계/연속성메모리_정보비대칭_종단평가_설계서.md`(3-AI 메모리뷰 상세)
|
||||
|
||||
**설계 차별점 4줄**: ① 같은 물리 KB(`kb.chunk`) 하나를 visible_to+sensitivity+정책 매트릭스로 3-AI가 다르게 검색(DB 강제, 코드경로 부재 1차방어). ② 람다 daily→weekly→monthly 계층압축을 메모리 4계층(working/episodic/summary/semantic) + KB 3계층(청크/prefix/community)으로 이식. ③ 람다 rag-memory↔graphify 역할분리를 에피소드 메모리↔정적 KB 물리분리로, 모순 시 "KB는 사전이지 판사가 아니다"(에피소드>KB) 우선순위로 페르소나 일관성 보호. ④ 회기 간 연속성=수치는 결정론 carry-over(무손실)+서사는 LLM 압축(손실허용)+pinned hard-pin, ideation은 보수적 유지(안전).
|
||||
486
docs/mockups/session.html
Normal file
486
docs/mockups/session.html
Normal file
|
|
@ -0,0 +1,486 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>한신대 AI 심리상담 시뮬레이션 — 상담 세션 화면 시안</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/variable/pretendardvariable.min.css">
|
||||
<style>
|
||||
/* =====================================================================
|
||||
한신대 AI 심리상담 시뮬레이션 — 상담 세션 화면 시안 (단일 파일)
|
||||
토큰: DESIGN_CONCEPT.md §3 그대로 적용. 외부 의존 = Pretendard CDN 1개.
|
||||
철칙 준수: border-left 0 · 이모지 0 · 카드덤프 0 · 에디토리얼 미니멀 ·
|
||||
순흑/순백 금지 · 강조는 weight/tint/kicker/dot로.
|
||||
===================================================================== */
|
||||
:root{
|
||||
/* 중성 베이스 (따뜻한 종이 + 잉크 네이비-그린) */
|
||||
--paper:#FBFAF8; --paper-2:#F4F2EE; --surface:#FFFFFF;
|
||||
--neutral-150:#E5E1DA;
|
||||
--ink:#1C2A2A; --ink-2:#5A6663; --ink-3:#93A09C; --hair:#E5E1DA;
|
||||
/* 악센트 세이지-틸 (주) + 테라코타 (보조) */
|
||||
--accent:#3E7A6E; --accent-deep:#316257; --accent-tint:#EEF4F2; --accent-bright:#5F968B;
|
||||
--clay:#B0735C; --clay-deep:#94604C; --clay-tint:#F6F0EC;
|
||||
/* 의미색 */
|
||||
--pos-text:#2C6E49; --pos-solid:#3B8C5E; --pos-tint:#E8F3EC;
|
||||
--warn-text:#8A5A00; --warn-solid:#C28A2C; --warn-tint:#FAF1E0;
|
||||
--crit-text:#A8352C; --crit-solid:#C4453A; --crit-tint:#FAEAE8;
|
||||
--info-text:#2E5A7A; --info-solid:#3C7299; --info-tint:#EAF1F6;
|
||||
/* stage */
|
||||
--bg-stage:#1E2724;
|
||||
/* 별칭 */
|
||||
--text-on-accent:#FBFAF8;
|
||||
--focus-ring:rgba(62,122,110,.45);
|
||||
/* 폰트 */
|
||||
--font-sans:"Pretendard Variable",Pretendard,-apple-system,"Apple SD Gothic Neo","Noto Sans KR",system-ui,sans-serif;
|
||||
--font-num:"Pretendard Variable","JetBrains Mono",ui-monospace,monospace;
|
||||
/* spacing */
|
||||
--sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:24px; --sp-6:32px;
|
||||
--radius:8px; --radius-sm:6px; --radius-lg:12px;
|
||||
--shadow-sm:0 1px 2px rgba(28,42,42,.05);
|
||||
--shadow-md:0 4px 14px rgba(28,42,42,.07);
|
||||
--ease-out:cubic-bezier(.22,.61,.36,1);
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
html,body{height:100%;}
|
||||
body{
|
||||
font-family:var(--font-sans); background:var(--paper); color:var(--ink);
|
||||
-webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
|
||||
letter-spacing:-0.01em; line-height:1.6; overflow:hidden;
|
||||
}
|
||||
.tab-num{font-variant-numeric:tabular-nums;}
|
||||
|
||||
/* ============ 앱 셸 ============ */
|
||||
.app{display:flex; flex-direction:column; height:100vh; min-height:640px;}
|
||||
|
||||
/* 상단 바 */
|
||||
.topbar{
|
||||
height:56px; flex:none; background:var(--surface);
|
||||
border-bottom:1px solid var(--hair);
|
||||
display:flex; align-items:center; gap:var(--sp-5);
|
||||
padding:0 var(--sp-6);
|
||||
}
|
||||
.topbar .scn{display:flex; flex-direction:column; gap:1px;}
|
||||
.topbar .scn .k{
|
||||
font-family:var(--font-num); font-size:11px; font-weight:600;
|
||||
letter-spacing:0.1em; text-transform:uppercase; color:var(--accent);
|
||||
}
|
||||
.topbar .scn .t{font-size:14px; font-weight:600; color:var(--ink); letter-spacing:-0.01em;}
|
||||
.topbar .stagebar{
|
||||
margin:0 auto; display:flex; align-items:center; gap:var(--sp-4);
|
||||
}
|
||||
.topbar .stagebar .seg{display:flex; align-items:center; gap:7px;}
|
||||
.topbar .stagebar .seg .pt{width:7px; height:7px; border-radius:50%; background:var(--neutral-150);}
|
||||
.topbar .stagebar .seg.done .pt{background:var(--accent-bright);}
|
||||
.topbar .stagebar .seg.cur .pt{background:var(--accent); box-shadow:0 0 0 3px var(--accent-tint);}
|
||||
.topbar .stagebar .seg .lb{font-size:12px; color:var(--ink-3); font-weight:500;}
|
||||
.topbar .stagebar .seg.cur .lb{color:var(--ink); font-weight:600;}
|
||||
.topbar .stagebar .ln{width:18px; height:1px; background:var(--hair);}
|
||||
.topbar .right{display:flex; align-items:center; gap:var(--sp-5);}
|
||||
.topbar .elapsed{font-family:var(--font-num); font-size:14px; font-weight:600; color:var(--ink-2); letter-spacing:0.02em;}
|
||||
.topbar .elapsed .sub{font-size:11px; color:var(--ink-3); font-weight:500; margin-left:4px;}
|
||||
.topbar .obs{display:flex; align-items:center; gap:7px; font-size:12px; color:var(--ink-3);}
|
||||
.topbar .obs .d{width:6px; height:6px; border-radius:50%; background:var(--info-solid);}
|
||||
|
||||
/* 본문 3컬럼 */
|
||||
.main{flex:1; display:grid; grid-template-columns:320px 1fr 300px; min-height:0;}
|
||||
|
||||
/* ---- LEFT ---- */
|
||||
.left{
|
||||
background:var(--surface); border-right:1px solid var(--hair);
|
||||
padding:var(--sp-6) var(--sp-5); display:flex; flex-direction:column; gap:var(--sp-6);
|
||||
overflow:auto;
|
||||
}
|
||||
.kicker{
|
||||
font-family:var(--font-num); font-size:12px; font-weight:600;
|
||||
letter-spacing:0.08em; color:var(--accent);
|
||||
display:flex; align-items:center; gap:8px; margin-bottom:var(--sp-4);
|
||||
}
|
||||
.kicker .dot{width:6px; height:6px; border-radius:50%; background:var(--accent-bright);}
|
||||
|
||||
/* 회기 단계 세로 트랙커 */
|
||||
.tracker{display:flex; flex-direction:column;}
|
||||
.tk{display:grid; grid-template-columns:24px 1fr auto; align-items:center; column-gap:var(--sp-3); min-height:30px;}
|
||||
.tk .mk{position:relative; display:flex; justify-content:center;}
|
||||
.tk .mk .o{width:14px; height:14px; border-radius:50%; border:2px solid var(--neutral-150); background:var(--surface); z-index:1;}
|
||||
.tk.done .mk .o{background:var(--accent-bright); border-color:var(--accent-bright);}
|
||||
.tk.cur .mk .o{border-color:var(--accent); box-shadow:0 0 0 4px var(--accent-tint);}
|
||||
.tk.cur .mk .o::after{content:""; position:absolute; top:5px; left:50%; transform:translateX(-50%); width:6px; height:6px; border-radius:50%; background:var(--accent);}
|
||||
.tk .nm{font-size:15px; color:var(--ink-3); font-weight:500;}
|
||||
.tk.done .nm{color:var(--ink-2);}
|
||||
.tk.cur .nm{color:var(--ink); font-weight:700;}
|
||||
.tk .tm{font-family:var(--font-num); font-size:12px; color:var(--ink-3); letter-spacing:0.02em;}
|
||||
.tk.cur .tm{color:var(--accent); font-weight:600;}
|
||||
.tkline{grid-column:1; width:2px; height:14px; margin-left:11px; background:var(--hair);}
|
||||
.tkline.done{background:var(--accent-bright);}
|
||||
|
||||
/* 내담자 컨텍스트 카드 */
|
||||
.ctx .row{display:flex; flex-direction:column; gap:3px; padding:var(--sp-3) 0; border-bottom:1px solid var(--paper-2);}
|
||||
.ctx .row:last-child{border-bottom:none;}
|
||||
.ctx .row .l{font-size:12px; color:var(--ink-3); font-weight:500;}
|
||||
.ctx .row .v{font-size:14px; color:var(--ink); font-weight:500; line-height:1.5;}
|
||||
.ctx .row .v.em{font-weight:700;}
|
||||
|
||||
/* ---- CENTER STAGE ---- */
|
||||
.stage{
|
||||
background:var(--bg-stage); position:relative;
|
||||
display:flex; flex-direction:column; align-items:center;
|
||||
padding:var(--sp-6) var(--sp-6) 0;
|
||||
min-height:0;
|
||||
}
|
||||
.stage .label{
|
||||
position:absolute; top:18px; left:50%; transform:translateX(-50%);
|
||||
font-family:var(--font-num); font-size:11px; font-weight:600;
|
||||
letter-spacing:0.12em; text-transform:uppercase; color:rgba(232,238,242,.55);
|
||||
display:flex; align-items:center; gap:8px;
|
||||
}
|
||||
.stage .label .d{width:6px; height:6px; border-radius:50%; background:var(--clay);}
|
||||
|
||||
/* 아바타 + 오브 */
|
||||
.avatarwrap{
|
||||
margin-top:44px; position:relative; width:240px; height:240px;
|
||||
display:flex; align-items:center; justify-content:center; flex:none;
|
||||
}
|
||||
.orb{
|
||||
position:absolute; inset:6px; border-radius:50%;
|
||||
box-shadow:0 0 0 3px var(--ring), 0 0 30px 6px var(--ring-glow);
|
||||
--ring:rgba(176,115,92,.55); --ring-glow:rgba(176,115,92,.30);
|
||||
animation:orbpulse 4.2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes orbpulse{0%,100%{transform:scale(1); opacity:.9} 50%{transform:scale(1.03); opacity:1}}
|
||||
.avatar{width:200px; height:200px; border-radius:50%; overflow:visible; display:block;}
|
||||
/* 호흡: 어깨 미세 상하 */
|
||||
.av-body{animation:breathe 3.6s ease-in-out infinite; transform-origin:center bottom;}
|
||||
@keyframes breathe{0%,100%{transform:translateY(0)} 50%{transform:translateY(2px)}}
|
||||
/* 깜빡임 */
|
||||
.av-eyes{animation:blink 6s steps(1) infinite; transform-origin:center; transform-box:fill-box;}
|
||||
@keyframes blink{0%,95%,100%{transform:scaleY(1)} 97.5%{transform:scaleY(0.1)}}
|
||||
|
||||
/* 페르소나 메타 */
|
||||
.persona{margin-top:26px; text-align:center; color:#E8EEF2;}
|
||||
.persona .nm{font-size:19px; font-weight:600; letter-spacing:-0.01em;}
|
||||
.persona .meta{font-size:13px; color:rgba(232,238,242,.6); margin-top:4px;}
|
||||
.persona .div{width:36px; height:1px; background:rgba(232,238,242,.2); margin:12px auto;}
|
||||
.persona .state{font-size:14px; color:rgba(232,238,242,.78); display:flex; align-items:center; gap:8px; justify-content:center;}
|
||||
.persona .state .d{width:6px; height:6px; border-radius:50%; background:var(--clay); animation:orbpulse 4.2s ease-in-out infinite;}
|
||||
|
||||
/* 자막 (대본 스타일) */
|
||||
.transcript{
|
||||
width:100%; max-width:680px; flex:1; min-height:0; margin-top:22px;
|
||||
overflow:auto; display:flex; flex-direction:column; gap:var(--sp-4);
|
||||
padding-bottom:var(--sp-6); -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 28px);
|
||||
}
|
||||
.turn{padding:var(--sp-3) var(--sp-4); border-radius:var(--radius);}
|
||||
.turn.client{background:rgba(176,115,92,.14);}
|
||||
.turn.learner{background:rgba(95,150,139,.16);}
|
||||
.turn .head{display:flex; justify-content:space-between; align-items:baseline; margin-bottom:5px;}
|
||||
.turn .who{font-size:12px; font-weight:600; letter-spacing:0.02em;}
|
||||
.turn.client .who{color:#E2B79F;}
|
||||
.turn.learner .who{color:#A9D2C6;}
|
||||
.turn .ts{font-family:var(--font-num); font-size:11px; color:rgba(232,238,242,.45);}
|
||||
.turn .tx{font-size:16px; line-height:1.65; color:#DDE5E4;}
|
||||
.turn .tx.partial{color:rgba(221,229,228,.5);}
|
||||
.turn .tx .cursor{display:inline-block; width:2px; height:1.05em; background:#A9D2C6; vertical-align:-2px; margin-left:2px; animation:cur 1s steps(1) infinite;}
|
||||
@keyframes cur{0%,50%{opacity:1}50.01%,100%{opacity:0}}
|
||||
|
||||
/* ---- RIGHT (피드백 레일) ---- */
|
||||
.right{
|
||||
background:var(--surface); border-left:1px solid var(--hair);
|
||||
padding:var(--sp-6) var(--sp-5); display:flex; flex-direction:column; gap:var(--sp-6);
|
||||
overflow:auto;
|
||||
}
|
||||
/* 내담자 상태 미터 */
|
||||
.meter{display:flex; flex-direction:column; gap:var(--sp-4);}
|
||||
.meter .m .top{display:flex; justify-content:space-between; align-items:baseline; margin-bottom:7px;}
|
||||
.meter .m .nm{font-size:13px; color:var(--ink-2); font-weight:500;}
|
||||
.meter .m .val{font-size:13px; color:var(--ink); font-weight:600;}
|
||||
.meter .m .bar{height:4px; background:var(--paper-2); border-radius:2px; overflow:hidden;}
|
||||
.meter .m .bar .fill{height:100%; border-radius:2px; transition:width 2s var(--ease-out);}
|
||||
.meter .m .bar .fill.clay{background:var(--clay);}
|
||||
.meter .m .bar .fill.sage{background:var(--accent-bright);}
|
||||
|
||||
/* 라이브 신호 (앰비언트) */
|
||||
.signal{display:flex; flex-direction:column; gap:var(--sp-4);}
|
||||
.signal .live{
|
||||
display:flex; align-items:center; gap:10px;
|
||||
padding:var(--sp-3) 0;
|
||||
animation:fadein .4s var(--ease-out);
|
||||
}
|
||||
@keyframes fadein{from{opacity:0; transform:translateY(3px)}to{opacity:1; transform:none}}
|
||||
.signal .live .d{width:9px; height:9px; border-radius:50%; flex:none;}
|
||||
.signal .live .d.pos{background:var(--pos-solid);}
|
||||
.signal .live .d.warn{background:var(--warn-solid);}
|
||||
.signal .live .nm{font-size:14px; color:var(--ink); font-weight:500;}
|
||||
.signal .live .nm .sub{font-size:12px; color:var(--ink-3); font-weight:400; display:block; margin-top:1px;}
|
||||
.signal .seq{display:flex; align-items:center; gap:9px; padding-top:var(--sp-2); border-top:1px solid var(--paper-2);}
|
||||
.signal .seq .sl{font-size:11px; color:var(--ink-3); font-weight:500; letter-spacing:0.02em;}
|
||||
.signal .seq .dots{display:flex; gap:7px; align-items:center;}
|
||||
.signal .seq .dots span{width:7px; height:7px; border-radius:50%;}
|
||||
.signal .seq .dots .pos{background:var(--pos-solid);}
|
||||
.signal .seq .dots .warn{background:var(--warn-solid);}
|
||||
.signal .seq .dots .neu{background:var(--neutral-150);}
|
||||
|
||||
/* 안전 점검 콜아웃 (warn, 좌측바 없음 → dot+kicker+tint) */
|
||||
.callout{border-radius:var(--radius); padding:var(--sp-4); background:var(--warn-tint);}
|
||||
.callout .clh{font-size:12px; font-weight:600; letter-spacing:0.04em; color:var(--warn-text); display:flex; align-items:center; gap:8px; margin-bottom:6px;}
|
||||
.callout .clh::before{content:""; width:7px; height:7px; border-radius:50%; background:var(--warn-solid);}
|
||||
.callout .cl{font-size:13px; color:var(--warn-text); line-height:1.55;}
|
||||
|
||||
/* 셀프 노트 */
|
||||
.note textarea{
|
||||
width:100%; min-height:78px; resize:none;
|
||||
background:var(--paper-2); border:1px solid var(--hair); border-radius:var(--radius-sm);
|
||||
padding:10px 12px; font-family:var(--font-sans); font-size:13px; color:var(--ink); line-height:1.6;
|
||||
}
|
||||
.note textarea::placeholder{color:var(--ink-3);}
|
||||
.note textarea:focus{outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--focus-ring);}
|
||||
|
||||
/* ============ 하단 컨트롤 바 ============ */
|
||||
.controls{
|
||||
height:84px; flex:none; background:var(--surface); border-top:1px solid var(--hair);
|
||||
display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
|
||||
padding:0 var(--sp-6); gap:var(--sp-5);
|
||||
}
|
||||
.controls .mic{display:flex; align-items:center; gap:var(--sp-4);}
|
||||
.controls .mic .btn{
|
||||
width:52px; height:52px; border-radius:50%; flex:none; border:none; cursor:pointer;
|
||||
background:var(--accent); display:flex; align-items:center; justify-content:center;
|
||||
animation:micbreath 1.5s ease-in-out infinite;
|
||||
}
|
||||
@keyframes micbreath{0%,100%{box-shadow:0 0 0 0 rgba(62,122,110,.35)}50%{box-shadow:0 0 0 7px rgba(62,122,110,0)}}
|
||||
.controls .mic .btn svg{width:22px; height:22px; stroke:var(--text-on-accent);}
|
||||
.controls .mic .lbl{display:flex; flex-direction:column; gap:1px;}
|
||||
.controls .mic .lbl .s{font-size:14px; font-weight:600; color:var(--ink);}
|
||||
.controls .mic .lbl .h{font-size:12px; color:var(--ink-3);}
|
||||
|
||||
/* segmented control (피드백 모드) */
|
||||
.modeseg{display:flex; background:var(--paper-2); border-radius:var(--radius); padding:3px; gap:2px; justify-self:center;}
|
||||
.modeseg button{
|
||||
font-family:var(--font-sans); font-size:13px; font-weight:500; color:var(--ink-2);
|
||||
border:none; background:transparent; padding:8px 16px; border-radius:6px; cursor:pointer;
|
||||
transition:all .16s var(--ease-out);
|
||||
}
|
||||
.modeseg button.on{background:var(--surface); color:var(--ink); font-weight:600; box-shadow:var(--shadow-sm);}
|
||||
|
||||
.controls .rightc{display:flex; align-items:center; gap:var(--sp-4); justify-self:end;}
|
||||
.btn-ghost{
|
||||
font-family:var(--font-sans); font-size:14px; font-weight:500; color:var(--ink-2);
|
||||
border:1px solid var(--hair); background:var(--surface); padding:9px 16px; border-radius:var(--radius); cursor:pointer;
|
||||
display:flex; align-items:center; gap:8px;
|
||||
}
|
||||
.btn-ghost svg{width:16px; height:16px; stroke:var(--ink-2);}
|
||||
/* 밀어서 종료 */
|
||||
.slide-end{
|
||||
position:relative; width:148px; height:40px; border-radius:var(--radius);
|
||||
background:var(--crit-tint); overflow:hidden; cursor:pointer; user-select:none;
|
||||
}
|
||||
.slide-end .track{position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
|
||||
font-size:13px; font-weight:600; color:var(--crit-text); letter-spacing:0.01em;}
|
||||
.slide-end .knob{position:absolute; top:3px; left:3px; width:34px; height:34px; border-radius:6px;
|
||||
background:var(--crit-solid); display:flex; align-items:center; justify-content:center;}
|
||||
.slide-end .knob svg{width:16px; height:16px; stroke:#fff;}
|
||||
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
*,*::before,*::after{animation-duration:.01ms!important; transition-duration:.01ms!important;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
|
||||
<!-- ============ 상단 바 ============ -->
|
||||
<header class="topbar">
|
||||
<div class="scn">
|
||||
<span class="k">LIVE SESSION</span>
|
||||
<span class="t">우울 호소 청소년 · 회기 1</span>
|
||||
</div>
|
||||
<div class="stagebar">
|
||||
<div class="seg done"><span class="pt"></span><span class="lb">라포</span></div>
|
||||
<span class="ln"></span>
|
||||
<div class="seg cur"><span class="pt"></span><span class="lb">탐색</span></div>
|
||||
<span class="ln"></span>
|
||||
<div class="seg"><span class="pt"></span><span class="lb">개입</span></div>
|
||||
<span class="ln"></span>
|
||||
<div class="seg"><span class="pt"></span><span class="lb">정리</span></div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="obs"><span class="d"></span>교수자 관찰 중</div>
|
||||
<div class="elapsed tab-num">12:41<span class="sub">경과</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ============ 본문 3컬럼 ============ -->
|
||||
<div class="main">
|
||||
|
||||
<!-- LEFT: 회기 단계 + 내담자 컨텍스트 -->
|
||||
<aside class="left">
|
||||
<section>
|
||||
<div class="kicker"><span class="dot"></span>회기 진행</div>
|
||||
<div class="tracker">
|
||||
<div class="tk done"><div class="mk"><span class="o"></span></div><div class="nm">라포 형성</div><div class="tm tab-num">09:12</div></div>
|
||||
<div class="tkline done"></div>
|
||||
<div class="tk cur"><div class="mk"><span class="o"></span></div><div class="nm">탐색</div><div class="tm">진행중</div></div>
|
||||
<div class="tkline"></div>
|
||||
<div class="tk"><div class="mk"><span class="o"></span></div><div class="nm">개입</div><div class="tm"></div></div>
|
||||
<div class="tkline"></div>
|
||||
<div class="tk"><div class="mk"><span class="o"></span></div><div class="nm">정리</div><div class="tm"></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="ctx">
|
||||
<div class="kicker"><span class="dot"></span>내담자 정보</div>
|
||||
<div class="row"><span class="l">호소 문제</span><span class="v em">2주째 무기력 · 등교 거부</span></div>
|
||||
<div class="row"><span class="l">시작 정서</span><span class="v">위축 · 방어적, 자기개방 낮음</span></div>
|
||||
<div class="row"><span class="l">배경</span><span class="v">고2 · 최근 성적 하락 · 또래 관계 위축</span></div>
|
||||
<div class="row"><span class="l">연습 목표</span><span class="v em">개방형 질문으로 자기개방 끌어내기</span></div>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<!-- CENTER: 내담자 아바타 + 음성 오브 + 자막 -->
|
||||
<main class="stage">
|
||||
<div class="label"><span class="d"></span>교육용 가상 내담자</div>
|
||||
|
||||
<div class="avatarwrap">
|
||||
<div class="orb"></div>
|
||||
<!-- 반구상 양식화 흉상 (사실성 ~40%): 코·주름·모공 없음, 눈/눈썹/입/어깨 4채널 -->
|
||||
<svg class="avatar" viewBox="0 0 200 200" aria-label="가상 내담자 서연">
|
||||
<defs>
|
||||
<radialGradient id="aura" cx="50%" cy="42%" r="58%">
|
||||
<stop offset="0%" stop-color="#2E3A36"/>
|
||||
<stop offset="100%" stop-color="#1E2724"/>
|
||||
</radialGradient>
|
||||
<clipPath id="head"><circle cx="100" cy="100" r="96"/></clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#head)">
|
||||
<rect width="200" height="200" fill="url(#aura)"/>
|
||||
<g class="av-body">
|
||||
<!-- 어깨/상체 실루엣 (후디, 디테일 없음) -->
|
||||
<path d="M44 200 C44 168 66 150 100 150 C134 150 156 168 156 200 Z" fill="#3C4A45"/>
|
||||
<!-- 목 -->
|
||||
<rect x="88" y="120" width="24" height="26" rx="11" fill="#E8C9A8"/>
|
||||
<!-- 머리카락 뒤 -->
|
||||
<path d="M58 96 C58 64 76 50 100 50 C124 50 142 64 142 96 L142 124 C142 122 138 110 132 108 L132 92 C132 76 118 70 100 70 C82 70 68 76 68 92 L68 108 C62 110 58 122 58 124 Z" fill="#2B2723"/>
|
||||
<!-- 얼굴 -->
|
||||
<path d="M70 92 C70 74 83 64 100 64 C117 64 130 74 130 92 L130 108 C130 126 117 138 100 138 C83 138 70 126 70 108 Z" fill="#E8C9A8"/>
|
||||
<!-- 눈썹 (정서: 약간 안쪽 하강 = 위축/불안) -->
|
||||
<path d="M80 92 q9 -3 18 0" stroke="#3A322C" stroke-width="2.4" fill="none" stroke-linecap="round"/>
|
||||
<path d="M102 92 q9 -3 18 0" stroke="#3A322C" stroke-width="2.4" fill="none" stroke-linecap="round"/>
|
||||
<!-- 눈 (큰 눈 = teen, 시선 살짝 아래) -->
|
||||
<g class="av-eyes">
|
||||
<ellipse cx="89" cy="102" rx="6.2" ry="5" fill="#FBFAF8"/>
|
||||
<ellipse cx="111" cy="102" rx="6.2" ry="5" fill="#FBFAF8"/>
|
||||
<circle cx="88.5" cy="103.5" r="3.1" fill="#3A2E26"/>
|
||||
<circle cx="110.5" cy="103.5" r="3.1" fill="#3A2E26"/>
|
||||
</g>
|
||||
<!-- 입 (닫힘 호 = listening 상태, 미세 긴장) -->
|
||||
<path d="M91 120 q9 4 18 0" stroke="#9A6A56" stroke-width="2.2" fill="none" stroke-linecap="round"/>
|
||||
<!-- 앞머리 -->
|
||||
<path d="M68 92 L68 86 C68 72 82 66 100 66 C118 66 132 72 132 86 L132 92 C128 80 116 78 108 80 C104 81 100 84 96 84 C88 84 76 82 68 92 Z" fill="#2B2723"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="persona">
|
||||
<div class="nm">서연</div>
|
||||
<div class="meta">17세 · 고2</div>
|
||||
<div class="div"></div>
|
||||
<div class="state"><span class="d"></span>머뭇거리며 말하는 중</div>
|
||||
</div>
|
||||
|
||||
<!-- 자막 (대본 스타일, 아래→위 흐름) -->
|
||||
<div class="transcript">
|
||||
<div class="turn learner">
|
||||
<div class="head"><span class="who">나 (학습자)</span><span class="ts tab-num">12:33</span></div>
|
||||
<div class="tx">학교에 가는 게 많이 버겁게 느껴지는 것 같네요. 혹시 어떤 순간이 가장 힘든지 조금 더 들려줄 수 있을까요?</div>
|
||||
</div>
|
||||
<div class="turn client">
|
||||
<div class="head"><span class="who">서연 (내담자)</span><span class="ts tab-num">12:34</span></div>
|
||||
<div class="tx">아침에… 눈을 뜨면요. 그냥 천장만 보고 있어요. 일어나야 하는 건 아는데, 몸이 안 움직여요. 그게 제일… 무서워요.</div>
|
||||
</div>
|
||||
<div class="turn learner">
|
||||
<div class="head"><span class="who">나 (학습자)</span><span class="ts tab-num">12:35</span></div>
|
||||
<div class="tx">눈을 떠도 몸이 따라주지 않는 그 무력감이, 무섭게까지 느껴지는군요.</div>
|
||||
</div>
|
||||
<div class="turn client">
|
||||
<div class="head"><span class="who">서연 (내담자)</span><span class="ts tab-num">12:36</span></div>
|
||||
<div class="tx partial">네… 그리고 요즘은 친구들이랑 있어도 자꾸 혼자인 것 같고<span class="cursor"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- RIGHT: 피드백 레일 (은은한 신호 모드) -->
|
||||
<aside class="right">
|
||||
<section class="meter">
|
||||
<div class="kicker"><span class="dot"></span>내담자 상태</div>
|
||||
<div class="m">
|
||||
<div class="top"><span class="nm">감정 톤</span><span class="val">불안 · 위축</span></div>
|
||||
<div class="bar"><div class="fill clay" style="width:68%"></div></div>
|
||||
</div>
|
||||
<div class="m">
|
||||
<div class="top"><span class="nm">마음 열림</span><span class="val">조금씩 열림</span></div>
|
||||
<div class="bar"><div class="fill sage" style="width:34%"></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="signal">
|
||||
<div class="kicker"><span class="dot"></span>라이브 신호</div>
|
||||
<div class="live">
|
||||
<span class="d pos"></span>
|
||||
<span class="nm">공감적 반영<span class="sub">방금 반영이 자기개방을 열었어요</span></span>
|
||||
</div>
|
||||
<div class="seq">
|
||||
<span class="sl">최근 흐름</span>
|
||||
<span class="dots">
|
||||
<span class="neu"></span><span class="pos"></span><span class="warn"></span><span class="pos"></span><span class="pos"></span>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="callout">
|
||||
<span class="clh">안전 점검</span>
|
||||
<span class="cl">내담자가 무기력·무력감을 반복해 표현하고 있어요. 위험 신호 여부를 자연스럽게 탐색해 보세요.</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="note">
|
||||
<div class="kicker"><span class="dot"></span>이번 회기 메모</div>
|
||||
<textarea placeholder="떠오른 가설·다음에 시도할 질문을 적어두세요. 회기말 리뷰에 함께 보여집니다."></textarea>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- ============ 하단 컨트롤 바 ============ -->
|
||||
<footer class="controls">
|
||||
<div class="mic">
|
||||
<button class="btn" aria-label="마이크 켜짐">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0"/><line x1="12" y1="18" x2="12" y2="21"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="lbl"><span class="s">듣고 있어요</span><span class="h">말하면 자동으로 전달돼요</span></div>
|
||||
</div>
|
||||
|
||||
<div class="modeseg" role="tablist" aria-label="피드백 모드">
|
||||
<button role="tab">몰입</button>
|
||||
<button role="tab" class="on" aria-selected="true">은은한 신호</button>
|
||||
<button role="tab">코칭</button>
|
||||
</div>
|
||||
|
||||
<div class="rightc">
|
||||
<button class="btn-ghost">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="5" width="4" height="14"/><rect x="14" y="5" width="4" height="14"/></svg>
|
||||
일시정지
|
||||
</button>
|
||||
<div class="slide-end" title="밀어서 세션 종료">
|
||||
<div class="track">밀어서 종료</div>
|
||||
<div class="knob">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
335
docs/redteam/MASTERPLAN_REVISIONS.md
Normal file
335
docs/redteam/MASTERPLAN_REVISIONS.md
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
# MASTERPLAN REVISIONS — BLUE TEAM 재설계 (윤찬 원래요구 100% 충족판)
|
||||
|
||||
> 작성 2026-06-25 · BLUE TEAM(재설계자) · 대상 결함: `REDTEAM_FINDINGS.md` F-01~F-40
|
||||
> **원본 마스터플랜은 보존.** 이 문서는 **변경점(패치) 전용**이다. 적용 시 `HANSHIN_AI_PLATFORM_MASTERPLAN.md`의 해당 절을 본 문서로 덮어쓴다.
|
||||
> **재설계 원칙: 타협으로 결함을 덮지 않는다.** "claude -p 못 쓰니 API로" 같은 요구 깎기는 금지. claude -p를 **1급 엔진으로 실제 돌리는** 아키텍처를 설계했다.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 0. 핵심 결정 역전 요약 (원본 §0 표를 이걸로 교체)
|
||||
|
||||
RED TEAM이 BLOCKER로 지목한 "요구 깎기"를 전부 되돌린다. **검증 근거는 실제 CLI v2.1.191 직접 실행**(추측 아님, `feedback_measure_before_report` 준수).
|
||||
|
||||
| 영역 | 원본(깎인) 결정 | **BLUE TEAM 복원 결정** | 검증/근거 |
|
||||
|---|---|---|---|
|
||||
| **AI 엔진** | 기본=Messages API, claude -p=옵션 | **기본=로컬 `claude -p` 상주 풀(resident pool).** Messages API는 **가용성 폴백 전용**(claude -p 다운 시에만 자동 전환) | `--input-format stream-json`(="realtime streaming input") 실재 확인 → 1턴종료 모델 극복, 상주 멀티턴 가능 |
|
||||
| **음성** | P1·Phase 2로 격하, Phase 1 음성 0 | **Phase 1 DoD에 "1페르소나·1보이스 캐스케이드 음성 왕복 1턴" 포함.** "무조건"을 일정 첫 외부시연부터 증명 | OpenAI gpt-4o-mini-tts + Deepgram, WSS 경로 1급화 |
|
||||
| **음성 s2s** | "1차 탈락" 단독 확정 | **"캐스케이드 1차 / s2s 2차 PoC 옵션".** 탈락 근거를 transcript 보존 단일 사유로 정리, 결정권은 윤찬 | OpenAI `input_audio_transcription` 네이티브 존재 → "transcript 없음"은 허수아비였음 |
|
||||
| **자유연습** | 서버단 하드 차단 | **기본 ON + 피드백 권장 배지 + 교수자 토글.** 차단은 미동의/미성년만(개인정보 사안) | `feedback_risk_user_decides`: 위험은 알리되 통제권은 사용자 |
|
||||
| **IRB** | 헤더만 패치, 본문 잔존 | **본문 전수 정리.** "IRB 신청/승인" 전 게이트 삭제, "교육용 파일럿"으로 통일 | 요구16 |
|
||||
| **fast-loop 엔진** | 외부 Haiku/Solar API 기본 | **로컬 상주 분류기(NAS) 또는 상주 Haiku claude -p 기본.** 외부 API는 폴백 | 요구3 데이터주권 |
|
||||
| **마스킹 게이트** | claude -p 우회 가능 | **claude -p 경로도 Presidio 선통과 강제.** 어댑터가 마스킹 후에만 stdin 주입 | 요구5/16 |
|
||||
| **음성 전송** | 토폴로지에서 누락 | **`voice.chanpaca.net` WSS 경로 1급 추가** | 요구7+요구4 E2E |
|
||||
|
||||
> **메타 선언:** 9개 하위 설계서가 엔진·음성전송·스키마·권한모델에서 서로 모순(RED TEAM 메타결론)이었다. 본 문서가 **단일 SoT 재정렬**을 수행한다. 적용 후 모든 하위 설계서의 엔진 결정은 본 §1로, 권한 모델은 본 §5로, 스키마는 본 §3으로 통일한다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 엔진 재설계 — claude -p 로컬 상주 풀을 1급으로 실제 구동 [F-01·F-12·F-13·F-14·F-15·F-29·F-39 해소]
|
||||
|
||||
### 1.1 핵심 돌파구: `--input-format stream-json` 상주 프로세스 (F-13 자기반박 해소)
|
||||
|
||||
RED TEAM·하위설계서 §6.3이 막혔던 지점 = "claude -p는 1턴 후 종료라 warm pool 불가 → SDK/API로 가야 함". **이 전제가 틀렸다.** CLI v2.1.191 실측:
|
||||
|
||||
```
|
||||
--input-format <format> "text"(default) | "stream-json" (realtime streaming input)
|
||||
--replay-user-messages stream-json in+out 조합에서 user 메시지 ack 재방출
|
||||
```
|
||||
|
||||
즉 **단일 claude -p 프로세스를 띄워놓고 stdin으로 턴마다 JSON 메시지를 주입**하는 양방향 멀티턴 모드가 네이티브로 존재한다. 콜드스타트를 **회기당 1회**(프로세스 기동 시)로 압축한다. 매 발화 콜드스타트(수 초)가 TTFT에 얹히는 문제 소멸.
|
||||
|
||||
```
|
||||
[engine-pool 컨테이너] (FastAPI와 분리된 별도 프로세스 매니저)
|
||||
┌─ claude -p #1 (resident, --input-format stream-json --output-format stream-json)
|
||||
│ stdin ◄── {"type":"user","message":{...},"session_id":...} (턴마다 주입)
|
||||
│ stdout ──► stream-json NDJSON (assistant delta, result, total_cost_usd)
|
||||
├─ claude -p #2 (resident) ← 내담자 AI 전용 풀
|
||||
├─ claude -p #3 (resident) ← deep-loop 평가 전용 풀
|
||||
└─ ... (MAX_RESIDENT 만큼, 회기 수요에 따라 동적 스폰/회수)
|
||||
```
|
||||
|
||||
### 1.2 정확한 호출 형태 (실CLI 검증 — 깨진 플래그 전부 교체) [F-14 해소]
|
||||
|
||||
원본 3AI §2.2 템플릿은 **존재하지 않는 플래그**를 썼다. 교체:
|
||||
|
||||
| 원본(깨짐) | 실CLI v2.1.191 | 교체 |
|
||||
|---|---|---|
|
||||
| `--max-turns 1` (없음) | 존재 안 함 | **제거.** 도구 미사용 시 단일응답 종료가 기본. 폭주 통제는 `--max-budget-usd`(실재) |
|
||||
| `--append-system-prompt-file` (메인 help 부재) | `--append-system-prompt <prompt>`(문자열)만 확인 | **전체 프롬프트 문자열 주입** 또는 `--settings`로. file 변형은 윤찬 PC 실증 후에만 채택 |
|
||||
| `--bare` (정액 모드 기본) | `--bare`는 **OAuth/keychain 절대 미독**(API키 강제) | **정액(모드 A)에서 `--bare` 제거.** 재현성은 격리 `CLAUDE_CONFIG_DIR`+`--settings`(hooks off)+`--strict-mcp-config`로 |
|
||||
|
||||
**모드 A (정액·기본 — 윤찬 요구3의 심장):**
|
||||
```bash
|
||||
# 환경: claude setup-token OAuth 토큰 발급된 전용 HOME/CLAUDE_CONFIG_DIR
|
||||
CLAUDE_CONFIG_DIR=/srv/sim/_cfg_client \
|
||||
claude -p \
|
||||
--model claude-opus-4-8 \
|
||||
--append-system-prompt "$(cat ./prompts/client_persona.txt)" \
|
||||
--input-format stream-json --output-format stream-json \
|
||||
--include-partial-messages --verbose \
|
||||
--strict-mcp-config --mcp-config ./mcp/rag.json \
|
||||
--allowedTools "mcp__counseling_rag__search" \
|
||||
--settings '{"hooks":{},"disableAllHooks":true}' \
|
||||
--max-budget-usd 5 \
|
||||
--fallback-model "claude-sonnet-4-6"
|
||||
# stdin: 턴마다 {"type":"user",...} 주입 (상주)
|
||||
```
|
||||
- `--bare` **없음** → OAuth 정액 정상 작동. Max ×3 구독 한도 내 **한계비용 0**(종량제 위험 $1,800은 API키 경로 위험이지 정액 위험 아님 — F-01 논리오류 교정).
|
||||
- 격리 `CLAUDE_CONFIG_DIR` + `--strict-mcp-config` + `disableAllHooks` → 글로벌 CLAUDE.md/hooks/디코MCP 오염 차단(=`--bare`가 주던 재현성을 정액 유지하며 확보).
|
||||
- `--max-budget-usd 5` → 회기당 비용 상한(폭주 자동 중단). `--max-turns` 없이도 통제.
|
||||
- `--fallback-model` → overload/unavailable 시 CLI 자체가 Sonnet으로 폴백(가용성 1차 안전망).
|
||||
|
||||
**모드 B (가용성 폴백 — 1급 아님, F-19 안전망):** claude -p 프로세스 자체가 죽거나(OOM/rate-limit/토큰만료) 헬스체크 실패 시에만 어댑터가 **Anthropic Messages API**로 자동 전환. 평시엔 안 씀.
|
||||
|
||||
### 1.3 동시성·큐·과금통제 (F-13 부하 / F-29 비용) — 실측 게이트 신설
|
||||
|
||||
```
|
||||
회기 라우터 → 회기별 직렬 큐(같은 session_id 턴 순서보장, asyncio.Lock)
|
||||
→ 전역 Semaphore(MAX_RESIDENT, 초기 4~6, 부하테스트로 확정)
|
||||
→ 상주 풀에서 idle 프로세스 할당 (없으면 스폰 or 백프레셔 큐)
|
||||
```
|
||||
- **상주 풀 정책:** 회기 시작 시 ②내담자용 1 + ③deep용 1 프로세스 점유, 회기 종료 시 회수. ③fast는 로컬 분류기(별도 경량 풀, claude -p 아님).
|
||||
- **과금통제 3중:** ①정액이라 한계비용 0(평시) ②`--max-budget-usd` 회기 상한 ③`total_cost_usd`(stream-json result 이벤트)를 `turns.cost_usd`에 실적재 → 폴백(모드 B) 비율·실비용 실시간 모니터, 임계 초과 시 디코 알림.
|
||||
- **F-13 게이트 (Phase 1 DoD 신설):** "상주 claude -p TTFT p50/p95 **실측** + 동시 4회기 부하에서 열화곡선·피크 RSS·OOM 0건". **실측 전 400~900ms를 계획수치로 쓰지 않는다**(`feedback_measure_before_report`). 측정은 윤찬 PC에서 1회 실증 후 "검증됨" 표기.
|
||||
|
||||
### 1.4 fast-loop 데이터주권 복원 [F-12 해소]
|
||||
- 매 발화 도는 fast-loop 기본 = **로컬 상주 분류기(NAS, 1B급 또는 상주 Haiku claude -p)**. 외부 Haiku/Solar API는 폴백.
|
||||
- 어느 경로든 `turns.llm_provider`에 적재 → "claude/로컬 비중" 측정 가능. 외부 유지 시 윤찬 승인 + 마스킹 지연을 §6 예산표 반영.
|
||||
|
||||
### 1.5 어댑터 정규화 계약 [F-15 해소]
|
||||
- `LLMEngine` 인터페이스: 정규화 이벤트 스키마(`delta.text` / `result` / `cost`) + **정규화 cost 규약**(정액=토큰×참조단가 shadow cost, 종량제=실 total_cost_usd). 두 경로 cost 의미 통일 → 재귀학습·텔레메트리 일관성(요구5).
|
||||
- **contract test DoD화:** 동일 입력 → claude_cli 경로와 messages_api 경로가 동일 정규화 출력. Phase 0~1에 둘 다 구현.
|
||||
|
||||
### 1.6 역할별 인증/플래그 분기 [F-39 해소]
|
||||
| AI | 엔진 | 인증/모드 |
|
||||
|---|---|---|
|
||||
| ②내담자(품질 생명) | Opus 상주 claude -p | 모드 A 정액 |
|
||||
| ③deep-loop 평가(재현성) | Opus 상주 claude -p, 격리 cfg | 모드 A 정액 + `--settings` 고정(재현성) |
|
||||
| ③fast-loop | 로컬 분류기/상주 Haiku | 로컬 |
|
||||
| (전 경로 폴백) | Messages API | 모드 B (claude -p 다운 시만) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 음성 재설계 — "무조건 + 멀티보이스 + 페르소나" 일정 첫날부터 증명 [F-02·F-07·F-08 해소]
|
||||
|
||||
### 2.1 Phase 1에 음성 PoC 진입 (F-02 해소)
|
||||
- **Phase 1 완료기준에 추가:** "P1 1페르소나·1보이스(OpenAI gpt-4o-mini-tts) 캐스케이드 음성 왕복 **1턴 PoC**가 chanpaca.net에서 동작". 텍스트 완주와 **별도 독립 트랙**(소유파일 분리)으로 얇게라도 "무조건"을 일정에 박는다.
|
||||
- Phase 2에서 음성을 IRB·3역할과 **분리 독립 트랙**으로(원본은 한 Phase에 뭉쳐 슬립 시 음성 증발 — F-27과 함께 해소).
|
||||
|
||||
### 2.2 음성 전송계층 WSS 1급화 (F-07 해소) — 토폴로지에 명시 추가
|
||||
원본 §1.1 토폴로지에 음성이 통째 없었다. **추가:**
|
||||
```
|
||||
[교수 브라우저] ─ getUserMedia/AudioWorklet PCM16 24kHz
|
||||
├─ https://chanpaca.net (정적·일반 API) ← Cloudflare named tunnel
|
||||
├─ https://stream.chanpaca.net (SSE 텍스트) ← §5에서 단일터널 통합 재검토
|
||||
└─ wss://voice.chanpaca.net (음성 양방향 PCM) ← WSS 전용 경로 [신규 1급]
|
||||
│
|
||||
[Caddy] WS upgrade 통과 + 버퍼링 off + proxy_read_timeout 3600s
|
||||
│
|
||||
[voice-gw (FastAPI WebSocket)]
|
||||
├─ ① Deepgram Nova streaming STT → interim/final transcript(+화자,ts)
|
||||
├─ ② EOT(semantic, median<300ms, 상담튜닝 침묵임계 1.2~2.0s)
|
||||
├─ ③ 입력 가드레일(한국어 위기분류, 화자=수련생 실제위기 vs 페르소나 연기 분기)
|
||||
├─ ④ engine-pool(상주 claude -p) 추론 ← §1 엔진과 동일 풀 공유
|
||||
├─ ⑤ 출력 가드레일
|
||||
└─ ⑥ OpenAI gpt-4o-mini-tts(voice_id 추상화) → 오디오청크 WSS down
|
||||
```
|
||||
- **Phase 1 트랙 C DoD화:** "50분 WSS 양방향 스트림 끊김 실측"을 SSE 테스트와 함께. Cloudflare 터널이 WSS 바이너리를 못 받으면 → voice 전용 Tailscale Funnel 또는 cloudflared WS 지원 경로로 분리(전송 라우팅 표를 단일 계약으로 고정).
|
||||
|
||||
### 2.3 멀티보이스·페르소나 (요구7 완전충족)
|
||||
- **voice_id provider-agnostic 추상화**: `voice_id → {provider, params}`. OpenAI TTS 13보이스 + `instructions` prosody 스티어링이 1차. 한국어 자연성 미달 보이스는 Higgs(127.0.0.1:9881)/MeloTTS 로컬 폴백(이미 vault 보유 자산).
|
||||
- **보이스=상태머신 함수**: 매 턴 `{voice, instructions, speed, stage, resistance, emotion}` 주입 → 단계별 음성 변조(라포기 닫힘 → 개입기 미세개방). 페르소나별 다른 목소리 = 요구7 "여러 목소리+페르소나" 충족.
|
||||
- **음성 paralinguistic 컬럼**(F-21): 침묵길이/말속도/바지인을 수치 메타로 turn 로그 적재(§3 스키마 선반영).
|
||||
|
||||
### 2.4 s2s 결정권 복원 (F-08 해소)
|
||||
- "s2s 탈락"(단독확정) → **"s2s 2차 PoC 옵션 유지"**로 표현 격상. 탈락 근거를 **transcript 보존 단일 사유**로 재정리(OpenAI `input_audio_transcription` 네이티브 존재 → "transcript 없음" 허수아비 폐기).
|
||||
- **윤찬 결정사항으로 명시 보고**: "캐스케이드 1차 / s2s는 가상내담자 1캐릭터 몰입 2차 PoC". 람다 단독 탈락 확정 철회(월권 시정).
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터·스키마·학술 재설계 [F-03·F-04·F-05·F-06·F-21·F-22·F-23·F-24·F-25·F-26·F-31·F-35 해소]
|
||||
|
||||
### 3.1 마스킹 게이트 우회 차단 (F-03 — BLOCKER)
|
||||
- **엔진 어댑터가 claude_cli 경로일 때도 프롬프트를 Presidio 선통과 후 stdin 주입.** claude -p가 backend 미들웨어를 우회해 미성년 원문을 Anthropic에 직송하던 구멍 봉쇄.
|
||||
- §1.1 문구 정정: "데이터는 로컬 미이탈" → **"마스킹된(가명처리) 텍스트만 Anthropic 포함 외부로 전송"**. 공문/처리방침에 "가명처리본 해외(Anthropic) 전송" 명시. 민감구간은 Solar/HyperCLOVA 국내 라우팅 우선.
|
||||
|
||||
### 3.2 n=1 데이터 토대 정직화 + 수급을 블로킹 의존성 격상 (F-04 — BLOCKER)
|
||||
- "골든셋" → **"시드 1례(seed exemplar), 신뢰도 측정 불가"**로 정직 표기. n=1 빈약함을 **리스크 레지스터에 신규 등록**(원본 누락).
|
||||
- Phase 0 "골든셋 50발화"를 **"한신대 추가 축어록 N건 수급"을 블로킹 의존성으로 격상** → §8 공문 액션에 **추가**(원본 누락 항목).
|
||||
- **train/test 누수 차단:** 0615을 페르소나 시드(②용)와 평가 정답셋(③용)으로 **분리**. 같은 데이터 이중사용 금지(F-26 순환의존 동시 해소).
|
||||
|
||||
### 3.3 미성년·자살사례 재귀학습 동의 (F-05 — BLOCKER)
|
||||
- 법정대리인 동의서에 **별도 동의항목 분리**: "AI 학습데이터 2차 가공 · DB 영구축적 · 파생모델 배포". **동의 전 0615 `turns.text` 적재 금지를 Phase 0 hard gate**로.
|
||||
- 자살사고 미성년 발화는 **합성·환언된 CCD 스펙만** 학습원천. 원문은 audit 보존용 격리(plain DB 미적재). → §3.3 "원본 미적재" 주장과 taxonomy §8 "원문 125턴 적재"의 자기모순 해소.
|
||||
- "IRB 면제"는 **수련생 실험 면제**이지 **미성년 사례데이터 활용 면제 아님** 명문화.
|
||||
|
||||
### 3.4 평가 AI 타당도 선검증 (F-06 — BLOCKER, 오학습 순환 차단)
|
||||
- 평가 AI 타당도를 **수련생 노출 전** 별도 검증셋(F-04 수급 전제)에서 LLM-judge κ 확보. 미달 차원은 학습자에 **점수 비노출(코칭 텍스트만)**.
|
||||
- 초기 deep-loop는 단정 채점 금지 → **"관찰" 프레이밍**("이 발화는 폐쇄형이었고, 개방형으로 바꾸면…"). 거짓양성률 정량 임계 설정 + 파일럿 1차 지표로 승격.
|
||||
- **순환 차단 원리:** 타당도 보증이 사용 시점보다 늦지 않게 — 검증 → 노출 순서 강제.
|
||||
|
||||
### 3.5 스키마 단일 SoT 통합 (F-21·F-31)
|
||||
원본 §3.2 스키마에 음성·RAG 컬럼 누락 → **선반영**:
|
||||
```sql
|
||||
-- turns 확장 (음성 메타, nullable 선반영 → Phase 2 재마이그레이션 폭탄 방지)
|
||||
ALTER ... turns ADD audio_ref TEXT, silence_ms INT, speech_rate REAL, barge_in BOOL;
|
||||
-- 또는 turn_paralinguistic(turn_id PK FK, ...) 1:1 확장
|
||||
voice_persona_map(persona_id, voice_id, provider, params JSONB)
|
||||
-- rag_chunks 하이브리드(BGE-M3 멀티벡터) 선반영
|
||||
rag_chunks(id, source, index_kind, embedding vector(1024),
|
||||
sparse_vec JSONB, context_prefix TEXT, colbert_vecs ... , text)
|
||||
```
|
||||
- 하이브리드 검색 SQL 계약 + 리랭커(BGE-reranker-v2-m3) 경계를 §3.6에 명시. graphify-lambda vs pgvector는 **pgvector 1차 단일**(graphify는 상담이론 교차개념 맵 2차).
|
||||
|
||||
### 3.6 학술 엄밀성 보강 (F-22·F-23·F-24·F-25·F-35)
|
||||
- **F-22 DSM-5:** `ideation_stage 1~5 ↔ C-SSRS 정식축 매핑표` + P1~P3 `DSM-5 진단기준 ↔ 행동증거 매트릭스` 추가. "진단 비노출"과 "진단학적 엄밀"은 양립(내부 파라미터).
|
||||
- **F-23 슈퍼바이저 편향:** 1인·1회기 논평을 "모범 코퍼스" 일반화 철회 → **few-shot/검증 참조로만**. AI 피드백 앵커는 EPITOME/CTRS 루브릭. "(현장 특이)" 항목 학습신호 제외. 슈퍼바이저 다인 확보 전 1인 편향 가중치 다운.
|
||||
- **F-24 인용 검증:** 신청서·공문 제출 **전** 모든 arXiv ID 실재 확인, preprint vs peer-reviewed 구분, 핵심 수치(d=0.72/ICC 0.853/77.2%) 원문 캡처. 미확인은 "preprint, 미검증" 라벨.
|
||||
- **F-25 재식별:** 사례 메타 재식별 위험평가(motivated intruder test), 학년/나이 범주화, 호소문제 일반화. 학습원천=합성변형(F-05 연동).
|
||||
- **F-35 재귀학습 루프 닫기:** "축적 ≠ 학습" → **명시 루프 정의**: `골든셋 누적 → few-shot 풀 자동갱신 → 평가/페르소나 프롬프트 업데이트`. fine-tuning 여부는 윤찬 1차 범위 결정(§잔여결정).
|
||||
|
||||
---
|
||||
|
||||
## 4. IRB 본문 전수 정리 [F-11 해소]
|
||||
|
||||
원본이 헤더만 패치하고 본문에 IRB 의존성 잔존. **전수 삭제/치환:**
|
||||
- Phase 0 작업 "IRB 프로토콜 초안" / 완료기준 "IRB 신청서 제출" → **삭제**.
|
||||
- Phase 3 의존성 "+IRB 승인" → **삭제**. "20명 IRB 파일럿" → **"20명 교육용 파일럿"**.
|
||||
- §8 액션 #2 "IRB 프로토콜 제출 — 승인 리드타임이 Phase 3 게이트" → **삭제**.
|
||||
- §7 IRB 체크리스트 → **미성년 활용동의 + 개인정보 처리방침 + PIPC/생명윤리법 가명처리만 잔존**, IRB 심의 항목 전부 제거.
|
||||
- 단 §3.3/§9 안전 가드레일·minimal risk 설계는 **윤리 설계로 유지**(IRB 절차와 무관한 안전 사안).
|
||||
|
||||
---
|
||||
|
||||
## 5. 권한·보안 재설계 [F-19·F-20·F-28·F-30·F-37·F-38·F-40 해소]
|
||||
|
||||
### 5.1 visible_to[] vs RBAC×cohort 레이어 분리 통일 (F-30)
|
||||
- `visible_to[]`(**AI 정보비대칭**)와 RBAC×cohort(**인간 권한**)는 **다른 레이어**로 명시 분리. 인간이 turn 읽을 때 **두 게이트 AND**를 단일 enforcement 미들웨어로 통일.
|
||||
- RLS "기본/2차" 모순 → **RLS 기본강제로 확정**(앱 게이트가 1차, RLS가 DB레벨 방어선). Phase 0 스키마에 `cohort` / `account_linking` / `audit_log` 포함(원본 누락).
|
||||
- 요구8(3페이지: 관리자/학습자/교수자) 권한격리 강제 메커니즘 일관화.
|
||||
|
||||
### 5.2 SSE/WSS 무인증 노출 + 쿠키 붕괴 차단 (F-20 — IDOR 위험)
|
||||
- SSE도 **단일 도메인 + Cloudflare 단일 터널**에서 **먼저 실측**(heartbeat + `no-transform` + flush -1). 끊길 때만 분리.
|
||||
- 분리 불가피 시 stream/voice 경로도 **BFF 경유 + short-lived signed token**(쿠키 아닌 header/query, 세션당 1회·단명). `__Host-` 쿠키가 다른 호스트에 미전송되는 구조적 붕괴 회피.
|
||||
- WSS(음성)·SSE(텍스트)를 **같은 전송 추상화**로 통합 검토(노출면 최소화). `reference_cloudflared_external_share` 멀티tailnet 함정 주의.
|
||||
|
||||
### 5.3 가용성 SPOF 해소 (F-19) — 요구3+요구4 양립
|
||||
- (a) 엔진 PC ↔ NAS DB **다른 물리 장애도메인** 분리.
|
||||
- (b) **교수 테스트 예약 슬롯제** + 직전 헬스체크(PC alive · 상주풀 워밍 · 터널 up) 후 링크 발송.
|
||||
- (c) **watchdog**(claude/cloudflared/docker 자동 재기동) + 외부 모니터 → 디코 알림. `reference_codex_node_memory_leak` 전례(좀비 node OOM) 대응 1GB+ 프로세스 reaping.
|
||||
- (d) claude -p 다운 시에만 Messages API 자동 폴백(=§1.2 모드 B, 가용성 안전망). PC 절전/윈도우업데이트 방지(`.ps1` ASCII 함정 주의 — `reference_ps1_ascii_only`).
|
||||
|
||||
### 5.4 SSO 블로커 게이트화 (F-28)
|
||||
- 공문 질의를 **Phase 0 게이트 격상**(IRB 면제로 SSO가 최장 리드타임 블로커). Google OIDC 단독 1차 진행 + Authlib provider 뒤 **SAML SP 어댑터 스캐폴드 선반영**.
|
||||
- 용어 분리 명시: **(사용자 로그인 OAuth 2.1)** vs **(엔진 Max OAuth 토큰)** 혼용 금지.
|
||||
|
||||
### 5.5 잔여 보안 (F-37·F-38·F-40)
|
||||
- **F-37 키관리:** secrets git-ignore만으론 불충분 → 키는 OS 키체인/secrets manager, **가명↔원본 매핑 테이블은 암호화 DB + 2인 승인**. 운영계정류는 정해진 위치만(`feedback_obsidian_secrets_folder`), 새 폴더 임의생성 금지.
|
||||
- **F-38 위기분류 계약:** 입력계약 `{speaker, stage, text_masked, is_roleplay_context} → {risk_level, escalate}`. "본인+1인칭 현재시제+시뮬레이션 메타밖" 결정론 룰 우선(페르소나 연기 false-positive 남발 차단). Phase 2 DoD에 false-positive 케이스.
|
||||
- **F-40 스케일업:** K8s 금지(20주 합리). "스케일업 = 클라우드 VM 이전 + compose replicas"로 윤찬 정의 합의. K8s 2차 백로그.
|
||||
|
||||
---
|
||||
|
||||
## 6. 오케스트레이션·일정 재설계 [F-16·F-17·F-18·F-27 해소] — 람다 실구현 작업분해 + 검증게이트
|
||||
|
||||
### 6.1 RACI — 소유주체 매핑 (F-16: "기존 풀스택팀 5글자" 해소)
|
||||
원본은 트랙만 나누고 소유주체 0. **명시:**
|
||||
|
||||
| 트랙 | 소유 | 람다 역할 | 디렉토리 경계 | 인터페이스 계약 동결 |
|
||||
|---|---|---|---|---|
|
||||
| A1 엔진풀(상주 claude -p) | spawn dev세션 #1 | 계약정의+E2E검증 | `backend/engine/` | LLMEngine 정규화 스키마(Phase 0말) |
|
||||
| A2 상태머신·가드레일 | spawn dev세션 #2 | 계약정의+검증 | `backend/sm/`,`backend/safety/` | TurnLog/SessionState 스키마(Phase 0말) |
|
||||
| B 프론트(React19·SSE·음성UI) | spawn dev세션 #3 | 검증 | `apps/web/` | SSE/WSS 이벤트 스키마(Phase 0말) |
|
||||
| C 인프라(터널·Caddy·Docker·DB) | 람다 직접(인프라 영역) | 직접 | `infra/`,`docker-compose.yml` | — |
|
||||
| D 데이터(라벨링·검수·κ) | 크론+영속워크플로우 | 오케스트레이션 | `ds/` | annotation 스키마(Phase 0말) |
|
||||
|
||||
- 람다(메인)는 **인프라·계약정의·팀간 중계·완료기준 검증**에 집중(`feedback_team_lead_active_supervision`). 비대 트랙 A를 A1/A2로 분할 → 람다 직접제작 회귀 차단.
|
||||
- `lambda-spawn-dev-session` 스킬로 PowerShell + `claude --dangerously-skip-permissions` + windows-mcp 세션 부려 위임. 산발 task 금지, 본 문서가 단일 로드맵(`feedback_proper_backlog_planning`).
|
||||
|
||||
### 6.2 검증 프로토콜 — 모든 DoD에 "누가/어떻게" 명시 (F-17 — 완료보고 불신 원칙)
|
||||
원본 DoD는 전부 결과서술("교수님이 상담→피드백 수신"). **검증 외주 금지**(`feedback_verify_actual_interaction`). 각 DoD에 **람다 직접 실동작 검증** 부착:
|
||||
|
||||
| Phase | 결과 DoD | **람다 검증 프로토콜(신규)** |
|
||||
|---|---|---|
|
||||
| P1 텍스트루프 | 상담→피드백 완주 | 람다가 Playwriter/windows-mcp로 **실제 로그인 → P1 상담 완주 → 피드백 수신 재현**, turn DB쿼리로 cost 적재 확인 |
|
||||
| P1 저항엔진 | 공감↑/조언점프↓ | **공감 5턴 vs 조언 5턴 입력 → openness 곡선 DB쿼리 정량 대조** 스크린샷 |
|
||||
| P1 엔진풀 | 상주 claude -p 동작 | **TTFT p50/p95 실측 로그 + 동시4회기 부하 RSS/OOM 0** 캡처(§1.3 게이트) |
|
||||
| P1 음성PoC | 음성 왕복 1턴 | 람다가 **실제 마이크/오디오 왕복 1턴 + WSS 50분 끊김 실측** |
|
||||
| P2 권한격리 | 타인기록 차단 | **타인 session_id 직접입력 → 403 스크린샷**(IDOR 음성검증) |
|
||||
| P2 안전 | 위기→교수알림 | **위기발화 주입 → 에스컬레이션+알림 end-to-end 재현** |
|
||||
| 각 Phase말 | — | **통합게이트: 람다 E2E 1회 재현** 후에만 윤찬 보고("완료" 보고 신뢰 금지) |
|
||||
|
||||
- subagent 물리적 불가 작업(50분 SSE/WSS, 실시간 음성 레이턴시, OAuth 콜백)은 **백그라운드 Bash+Monitor / 실기기 / Playwriter**로 배정(원본 미배정 해소).
|
||||
|
||||
### 6.3 장기·상태유지 작업 오케스트레이션 (F-18)
|
||||
- 라벨링·검수·κ누적은 수일~수주 인간-루프 → **DB/파일 영속 작업으로 재정의**. 람다는 **CronCreate + 단계 재개 워크플로우**로 오케스트레이션. `ds` 스키마에 체크포인트 저장, 다음 세션 이어받기(stateless 한 호출로 끌고 가려던 설계 결함 해소).
|
||||
|
||||
### 6.4 일정 재분할 (F-27: 7주에 음성+3역할+페르소나+재귀 동시압축 해소)
|
||||
- **Phase 2 분할:** 2a(3역할+OAuth BFF+페르소나, 9~13주) / 2b(음성 캐스케이드 1페르소나, 13~17주).
|
||||
- 재귀학습 IAA·자살분류기 정밀화는 **Phase 3 이연**. 음성 M-단계(설계서 §8)와 Phase 주차를 **단일 간트로 통합**(음성 단독 20주를 7주에 욱여넣던 충돌 해소).
|
||||
- "기존 풀스택팀(JS 추정)"의 FastAPI/asyncio/Presidio/pgvector/Deepgram WS 학습곡선을 일정에 반영(2b를 별도 트랙·여유 배치).
|
||||
|
||||
---
|
||||
|
||||
## 7. 결함ID → 해소방안 매핑 (전수 추적표)
|
||||
|
||||
| ID | 심각도 | 해소 위치 | 한 줄 해소 |
|
||||
|---|---|---|---|
|
||||
| F-01 | BLOCKER | §0·§1.2 | 기본엔진 claude -p 상주풀 복원, Messages API는 폴백 강등 |
|
||||
| F-02 | BLOCKER | §2.1 | Phase 1 DoD에 음성 왕복 1턴 PoC 포함 |
|
||||
| F-03 | BLOCKER | §3.1 | claude -p 경로도 Presidio 선통과 강제, 우회 봉쇄 |
|
||||
| F-04 | BLOCKER | §3.2 | n=1 정직표기 + 축어록 수급 블로킹 의존성 격상 |
|
||||
| F-05 | BLOCKER | §3.3 | 미성년 재귀학습 별도 동의항목 + 동의 전 적재 금지 hard gate |
|
||||
| F-06 | BLOCKER | §3.4 | 평가 AI 타당도 수련생 노출 전 선검증, 미달 비노출 |
|
||||
| F-07 | BLOCKER | §2.2 | voice.chanpaca.net WSS 경로 1급 추가 + 50분 실측 DoD |
|
||||
| F-08 | MAJOR | §2.4 | s2s "탈락"→"2차 PoC 옵션", 결정권 윤찬 |
|
||||
| F-09 | MAJOR | §3.4 | "판단 지속(deep-loop 백그라운드), 제시만 회기말" 분리 |
|
||||
| F-10 | MAJOR | §0 | 자유연습 하드차단 제거 → 기본ON+권장배지+교수토글 |
|
||||
| F-11 | MAJOR | §4 | IRB 본문 전수 삭제/치환 |
|
||||
| F-12 | MAJOR | §1.4 | fast-loop 로컬 상주분류기 기본, 외부API 폴백 |
|
||||
| F-13 | MAJOR | §1.1·§1.3 | `--input-format stream-json` 상주, TTFT/부하 실측 게이트 |
|
||||
| F-14 | MAJOR | §1.2 | `--max-turns`/`--append-system-prompt-file`/`--bare` 깨진 플래그 교체 |
|
||||
| F-15 | MAJOR | §1.5 | 정규화 이벤트/cost 규약 + contract test |
|
||||
| F-16 | MAJOR | §6.1 | RACI 소유주체 매핑, 트랙 A 분할 |
|
||||
| F-17 | MAJOR | §6.2 | 모든 DoD에 람다 직접 검증 프로토콜 |
|
||||
| F-18 | MAJOR | §6.3 | 라벨링/검수 영속화 + 크론 재개 워크플로우 |
|
||||
| F-19 | MAJOR | §5.3 | 장애도메인 분리·예약슬롯·watchdog·폴백 |
|
||||
| F-20 | MAJOR | §5.2 | 단일터널 우선 실측, 분리 시 signed token, IDOR 차단 |
|
||||
| F-21 | MAJOR | §3.5 | 음성 paralinguistic 컬럼 Phase 0 선반영 |
|
||||
| F-22 | MAJOR | §3.6 | C-SSRS 매핑표 + DSM-5 행동증거 매트릭스 |
|
||||
| F-23 | MAJOR | §3.6 | 슈퍼바이저 논평 few-shot 참조로 격하, 1인편향 다운 |
|
||||
| F-24 | MAJOR | §3.6 | arXiv ID 실재확인+수치 원문캡처, 미확인 라벨 |
|
||||
| F-25 | MAJOR | §3.6 | motivated intruder test + 범주화, 합성변형 학습 |
|
||||
| F-26 | MAJOR | §3.2 | 0615 페르소나/평가 분리(누수차단), 재귀학습 톤다운 |
|
||||
| F-27 | MAJOR | §6.4 | Phase 2 분할(2a/2b) + 단일간트, 재귀 Phase3 이연 |
|
||||
| F-28 | MAJOR | §5.4 | 공문 Phase 0 게이트, SAML 스캐폴드 선반영 |
|
||||
| F-29 | MAJOR | §1.3 | 폴백 회기당 비용 실측+캐싱+batch, cost 적재 |
|
||||
| F-30 | MAJOR | §5.1 | visible_to/RBAC 레이어 분리 AND게이트, RLS 기본 확정 |
|
||||
| F-31 | MAJOR | §3.5 | rag_chunks 하이브리드 멀티벡터 컬럼 선반영 |
|
||||
| F-32~40 | MINOR | §3.6·§5.5 | κ/ICC 임계 통일, 환각측정 정의, 캐싱 히트검증, 키관리, 위기계약, 스케일업 정의 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 즉시 착수 액션 Top 5 (원본 §8 교체)
|
||||
|
||||
1. **엔진 PoC — 윤찬 PC에서 상주 claude -p 실증** (`--input-format stream-json` 상주 + 격리 cfg + TTFT p50/p95 실측 + 동시4회기 부하). **"검증됨" 표기는 이 실증 후에만.** [F-01·F-13·F-14]
|
||||
2. **한신대 공문 질의서** — SSO 프로토콜/클레임 + **추가 축어록 N건 수급(블로킹)** + 미성년 원본 활용동의(AI학습 2차가공·DB축적·파생배포 별도항목) + NAS 국내처리. (IRB 항목 삭제) [F-04·F-05·F-28]
|
||||
3. **docker-compose 스캐폴드 + Postgres 스키마(음성·RAG·cohort·audit 선반영) + Caddyfile(WS upgrade·flush -1)** — 인프라 베이스, 람다 직접. [F-21·F-30·F-31·F-07]
|
||||
4. **0615 taxonomy v1.0 + 페르소나/평가 데이터 분리(누수차단)** — 시드 1례 정직표기, 골든셋 표본목표 명시. [F-04·F-23·F-26]
|
||||
5. **마스킹 게이트 강제 어댑터 PoC** — Presidio 선통과 후에만 claude -p stdin 주입(우회 봉쇄) + P1 1턴 + cost 텔레메트리 + voice PoC 1턴. [F-03·F-02]
|
||||
|
||||
---
|
||||
|
||||
## 9. 잔여 — 윤찬 결정 필요 (CONDITIONAL 사유)
|
||||
|
||||
아래는 **람다가 단독 결정하면 월권**인 항목. 결정 후 GO 전환:
|
||||
1. **s2s 2차 PoC 채택 여부** (캐스케이드 1차는 확정. 2차 옵션 유지/폐기) [F-08]
|
||||
2. **fast-loop 외부 API 허용 여부** (로컬 상주 분류기 기본 vs 외부 Haiku/Solar 폴백 허용 — 데이터주권 트레이드오프) [F-12]
|
||||
3. **재귀학습 fine-tuning 범위** (1차=few-shot 자동갱신만 vs 파인튜닝 포함) [F-35]
|
||||
4. **자유연습 기본값** (기본 ON + 교수토글 제안 — 승인) [F-10]
|
||||
5. **추가 축어록 수급 규모/일정** (n=1 해소의 전제, 한신대 협의 결과 의존) [F-04]
|
||||
6. **스케일업 정의** ("클라우드 VM 이전 + compose replicas"로 합의 vs K8s 2차) [F-40]
|
||||
|
||||
---
|
||||
|
||||
*이 문서는 패치다. 원본 마스터플랜과 9개 하위 설계서의 엔진·음성전송·스키마·권한 결정은 본 문서로 통일한다. 모든 엔진 플래그는 CLI v2.1.191 직접 실행으로 검증(추측 아님).*
|
||||
296
docs/redteam/REDTEAM_FINDINGS.md
Normal file
296
docs/redteam/REDTEAM_FINDINGS.md
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
# RED TEAM 종합 결함 보고서 — 한신대 AI 심리상담 시뮬레이션 플랫폼 마스터플랜
|
||||
|
||||
> 적대적 검증 일자: 2026-06-25
|
||||
> 대상: `HANSHIN_AI_PLATFORM_MASTERPLAN.md` + 9개 하위 설계서
|
||||
> 검증 렌즈 7종: 요구충실도 / 엔진실현성 / 오케스트레이션·구현 / 음성 / 데이터·학술·윤리 / 통합·E2E / 보안·운영·비용
|
||||
> 종합 방식: 7개 렌즈 결함을 중복 병합, 심각도 정렬, 카테고리 분류, 고유 ID(F-NN) 부여
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 최우선 강조 — 윤찬 원래요구를 위반/타협/축소한 항목 (이것이 핵심)
|
||||
|
||||
마스터플랜이 **비용·동시성·안전·일정을 명분으로 윤찬의 신성불가침 요구를 깎은** 지점들이다. 윤찬 작업원칙상 "요구를 지키면서 문제를 푸는 것"이 정답이지, 요구를 깎는 것은 결함이다.
|
||||
|
||||
| ID | 위반한 요구 | 무엇을 깎았나 | 심각도 |
|
||||
|---|---|---|---|
|
||||
| **F-01** | **요구3 (엔진=로컬 claude -p)** | 기본 엔진을 `claude -p`에서 **Anthropic Messages API(종량제)로 바꿔치기**, claude -p를 "옵션 플래그·시연용"으로 격하. 같은 프로젝트 하위 설계서 2종(음성·3AI)은 정반대로 claude -p를 기본 엔진으로 못박아 **문서 간 정면 모순** | **BLOCKER** |
|
||||
| **F-02** | **요구7 (음성 무조건)** | "무조건"인 음성을 우선순위 P1·Phase 2(9~15주)로 격하, 첫 외부 시연(Phase 1)에서 음성 통째 누락. 일정 1주만 밀려도 음성이 잘릴 구조 | **BLOCKER** |
|
||||
| **F-03** | **요구3+요구5/16 충돌** | claude -p(요구3)가 backend Presidio 마스킹 게이트를 **우회**해 미성년 원문을 Anthropic에 직송 가능 → "데이터 로컬 미이탈" 주장 부분 거짓, 미성년 보호(요구5/16)와 구조적 충돌 | **BLOCKER** |
|
||||
| **F-08** | **요구7 (s2s 결정권)** | OpenAI Realtime s2s를 윤찬 동의 없이 "탈락" 단독 확정(월권). 탈락의 1번 근거가 "claude -p 고정"인데 F-01이 그걸 풀어버려 **근거 자기붕괴**. "transcript 없음" 근거도 OpenAI 실제 스펙(`input_audio_transcription`)과 불일치한 허수아비 | **MAJOR** |
|
||||
| **F-09** | **요구6 (지속적 판단)** | "지속적으로 잘못 판단"인데 정밀평가(deep-loop)를 "회기말"로 미뤄 *판단 자체가 지연*되는 것처럼 축소(판단은 지속·제시만 회기말로 분리했어야) | **MAJOR** |
|
||||
| **F-10** | **요구에 없는 제약 추가** | 윤찬이 요구한 적 없는 **"자유연습 서버단 하드 차단"**을 람다가 임의 도입(교수자 통제권 침해). CARE RCT 근거는 경고·기본값 추천으로 족함 | **MAJOR** |
|
||||
| **F-11** | **요구16 (IRB 불필요)** | 헤더만 "IRB 불필요"로 패치하고 **본문(Phase 0/3 게이트·액션Top5·체크리스트)에 IRB 신청·승인 의존성 잔존** → 부분 수정 누락, 팀이 불필요한 IRB 리드타임 소모 위험 | **MAJOR** |
|
||||
| **F-12** | **요구3 (엔진 일관성)** | 매 발화 도는 fast-loop 평가 AI를 로컬 claude -p가 아닌 **외부 Haiku/Solar API**로 기본 설정 → 요구3 취지(로컬·정액·데이터주권) 이탈, 윤찬 명시 승인 없는 타협 | **MAJOR** |
|
||||
|
||||
> **핵심 한 줄:** 마스터플랜은 *무엇을 만들지(WHAT)*는 정교하나, 윤찬의 두 기둥 **로컬 claude -p 엔진**과 **음성 무조건**을 비용·안전 명분으로 깎았고, 그 깎음이 자기 하위 설계서와 논리적으로 모순된다. 해법(프로세스 풀, 어댑터 default 교체, s2s 2차 옵션화, `--input-format stream-json` 상주)은 이미 하위 문서에 적혀 있는데도 요구를 깎는 쪽을 택한 것이 최대 결함이다.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 BLOCKER 등급
|
||||
|
||||
### F-01. [원래요구위반] 엔진을 claude -p → Messages API로 바꿔치기 (요구3 전복) + 문서 간 자기모순
|
||||
- **카테고리:** 원래요구위반 / 기술결함
|
||||
- **병합 출처:** requirements F1 · engine F-1 · voice V-3 · integration F2 · security B1
|
||||
- **근거:** 마스터플랜 §0 표 "기본=Anthropic Messages API … `claude -p`(Max OAuth)는 옵션 플래그·개발 시연용", §5/§4 트랙 A "Messages API 직결", 다이어그램 "①claude_api(기본) ②claude_cli(옵션)". **자기모순:** 음성설계서 §0/§1/§2.2와 3AI설계서 D-4는 claude -p를 **기본 엔진으로 고정**. 두 문서가 동시에 참일 수 없음.
|
||||
- **원래요구위반:** **명백.** 요구3은 "엔진 = 로컬 PC Opus 4.8 claude code -p" 명시. 격하 근거 3개 전부 약함:
|
||||
1. "과금 누수 $1,800" = 종량제 API 위험이지 `claude -p`(Max OAuth 정액) 위험 아님. claude -p는 Max rate limit 내 한계비용 0(음성설계서 §7.1 자인). **논리 오류.**
|
||||
2. "동시성 1프로세스 한계" = 음성설계서 §6이 이미 browser-pool식 프로세스 풀로 해결책 명시. 거짓 제약.
|
||||
3. "rate limit 마비" = 파일럿 20명 규모, Max ×3 구독에서 근거 빈약(측정 없이 단정, `feedback_measure_before_report` 위반).
|
||||
- **수정방향:** 어댑터 구조는 유지하되 **default provider를 `claude_cli`로 복원**, Messages API는 폴백 옵션으로 강등(1줄 교체). 동시성은 프로세스 풀+세션 캡, 비용은 정액이라 한계 0. Phase 1 DoD를 "P1 내담자 응답이 로컬 claude -p로 생성·스트리밍"으로 재작성. **엔진 기본값 변경은 윤찬 명시 승인 사안(`feedback_no_blanket_overwrite`)** — 되돌려 확인.
|
||||
|
||||
### F-02. [원래요구위반] "음성 무조건"을 P1·Phase 2로 격하, 첫 외부 시연에서 음성 누락
|
||||
- **카테고리:** 원래요구위반
|
||||
- **병합 출처:** requirements F2 · voice V-1
|
||||
- **근거:** Phase 1 완료기준 "P1 내담자와 **텍스트 상담**"(음성 0), Phase 2(9~15주) "음성 + 3역할…" 우선순위 **P1**. 음성설계서 M4(13~17주)에야 chanpaca.net 음성 시연.
|
||||
- **원래요구위반:** 요구7 "음성 채팅 **무조건**" = P0 언어. 외부 데모(요구4) 첫 인상에 음성 없음. R12(일정압박) 현실화 시 가장 먼저 잘릴 P1 블록 + IRB·3역할과 한 Phase에 뭉쳐 슬립 시 음성 통째 증발 구조.
|
||||
- **수정방향:** Phase 1 완료기준에 "P1 1페르소나·1보이스 캐스케이드 음성 왕복 1턴 PoC" 추가(얇게라도 "무조건"을 일정으로 증명). Phase 2에서 음성을 IRB·3역할과 **분리 독립 트랙**으로. (OpenAI gpt-4o-mini-tts 사용은 요구7 충족 인정.)
|
||||
|
||||
### F-03. [데이터·윤리/원래요구위반] claude -p가 Presidio 마스킹 게이트 우회 → 미성년 원문 Anthropic 직송 (요구3 vs 요구5/16 충돌)
|
||||
- **카테고리:** 데이터·윤리 / 원래요구위반
|
||||
- **병합 출처:** security M4
|
||||
- **근거:** §3.3 "저장 전 PII 마스킹 하드게이트: **backend→외부 LLM 경로**에 Presidio", §1.1 "데이터는 로컬을 떠나지 않음". 그러나 claude -p는 윤찬 PC에서 직접 Anthropic 클라우드(Opus 4.8) 호출 → backend 미들웨어 우회 가능.
|
||||
- **원래요구위반:** 요구3(claude -p)와 요구5/16(미성년 개인정보 보호)이 구조적 충돌. "로컬 미이탈"은 부분 거짓 — 추론 프롬프트는 해외 전송됨.
|
||||
- **수정방향:** 엔진 어댑터가 claude_cli일 때도 **프롬프트를 Presidio 통과 후** claude -p에 전달(우회 경로 차단). §1.1 문구를 "마스킹된 텍스트만 Anthropic 포함 외부로"로 정정. 공문/처리방침에 "가명처리본 해외(Anthropic) 전송" 명시. 민감구간은 Solar/HyperCLOVA 국내 라우팅 우선.
|
||||
|
||||
### F-04. [데이터·윤리] 전체 학습·평가 시스템이 단일 사례(n=1) 축어록 위에 구축 — "골든셋"이 통계 신뢰도를 위장
|
||||
- **카테고리:** 데이터·윤리
|
||||
- **병합 출처:** data B1 · integration F6(부분)
|
||||
- **근거:** 실측 — `데이터/2026-06-25_프로토콜_축어록/`에 축어록 단 1개(고2 남학생, 단회기). taxonomy 설계서 "파란 라벨 89개·77종"(라벨당 평균 1.15회), 0615 125턴이 (a)평가 정답셋 (b)페르소나 학습신호 (c)재귀학습 시드 (d)κ/ICC 모집단 역할을 **동시에** 수행. 설계서 스스로 "세부코드 κ 낮을 수밖에 없다" 자인.
|
||||
- **원래요구위반:** 요구13(학술 엄밀 "빡세게")·요구5(재귀축적)를 데이터 기반 부재로 형식 충족 주장. n=1 빈약함이 리스크 등록부에서 누락.
|
||||
- **수정방향:** "골든셋"→"시드 1례(seed exemplar), 신뢰도 측정 불가"로 정직 표기. Phase 0의 "골든셋 50발화"를 **"한신대 추가 축어록 N건 수급"을 블로킹 의존성으로 격상**(공문 액션에 누락됨). 페르소나 시드와 평가 정답 데이터를 분리(train/test 누수 차단).
|
||||
|
||||
### F-05. [데이터·윤리/원래요구위반] 미성년·자살사고 사례의 "재귀학습 축적"이 IRB 면제와 양립 불가 — 요구5와 요구16 충돌을 봉합만 함
|
||||
- **카테고리:** 데이터·윤리 / 원래요구위반
|
||||
- **병합 출처:** data B2
|
||||
- **근거:** §3.3 "원본 축어록 DB 절대 미적재"라면서, taxonomy §8은 0615 원문 발화 125턴을 `turns.text`에 적재해 재귀학습 원천화 → **자기모순**. 헤더는 "IRB 불필요 즉시 착수"의 안도감으로 데이터 거버넌스 폭탄을 "공문 확인 유지" 한 줄로 격하.
|
||||
- **원래요구위반:** 요구16 "미성년 원본 활용동의는 별도"를 **선행 게이트가 아닌 후행 확인**으로 처리 = 축소. "IRB 면제"는 *수련생 실험* 면제이지 *미성년 사례데이터 활용* 면제가 아님.
|
||||
- **수정방향:** 법정대리인 동의서에 "AI 학습데이터 2차 가공·DB 영구축적·파생모델 배포"를 별도 동의항목으로 분리, 동의 전 0615 적재 금지를 Phase 0 hard gate로. 자살사고 미성년 발화는 **합성·환언된 CCD 스펙만** 학습원천, 원문은 audit 보존용 격리.
|
||||
|
||||
### F-06. [데이터·윤리] 타당도 미검증 평가 AI가 수련생 20명을 먼저 가르친 뒤 사후에 신뢰도를 잰다 — 오학습 순환구조
|
||||
- **카테고리:** 데이터·윤리
|
||||
- **병합 출처:** data B3
|
||||
- **근거:** §2.2 deep-loop "0-5 채점+대안발화", §2.3 "κ≥0.6/ICC≥0.75 게이트 통과분만 골든셋 승격". 그런데 그 타당도는 §3.3/R3 기준 **20명 파일럿에서야 측정**. CARE 인용으로 자유연습 차단(피드백 필수)했으나 그 피드백 타당도는 미검증 → 저품질 피드백 = 본인 인용대로 "오학습 유발". 거짓양성이 옳은 인간중심 개입(의도적 침묵)을 "수동적"으로 오교정할 위험.
|
||||
- **원래요구위반:** 요구6 "지속 판단"·요구13을 형식 충족(타당도 보증이 사용 시점보다 늦음).
|
||||
- **수정방향:** 평가 AI 타당도를 **수련생 노출 전** 별도 검증셋(F-04 해결 전제)에서 LLM-judge κ 확보, 미달 차원은 학습자에 **점수 비노출(코칭만)**. 초기 deep-loop는 단정 채점 금지·"관찰" 프레이밍. 거짓양성률 정량 임계 + 파일럿 1차 지표 승격.
|
||||
|
||||
### F-07. [기술결함] 음성 전송계층(WSS)이 마스터플랜 인프라 토폴로지에서 통째 누락 — 음성이 외부에서 동작할 E2E 경로 없음
|
||||
- **카테고리:** 기술결함
|
||||
- **병합 출처:** integration F1 · voice V-5(부분) · security M1(부분)
|
||||
- **근거:** 마스터플랜 §1.1에 음성 등장 안 함, 외부노출은 전부 SSE(Tailscale Funnel)로만. 그러나 음성설계서 §4.1 "캐스케이드는 브라우저 mic PCM 업스트림 → SSE 불가 → **WSS 강제**". Cloudflare 터널이 WSS 양방향 바이너리 PCM을 어떻게 프록시할지, stream.chanpaca.net이 WSS도 받는지 **계약 미정의**. 전송 3종(정적/SSE/WSS)이 한 브라우저 세션에서 서로 다른 인프라.
|
||||
- **원래요구위반:** 요구7(음성)+요구4(외부공유) E2E를 동시 위협.
|
||||
- **수정방향:** §1.1에 WSS 음성 경로를 1급 추가(`voice.chanpaca.net` WSS 전용 + Caddy WS upgrade/버퍼링off/`proxy_read_timeout 3600s`). Phase 1 트랙 C에 "50분 WSS 양방향 스트림 끊김 실측"을 SSE 테스트와 함께 DoD화. 전송계층 라우팅 표를 단일 계약으로 고정.
|
||||
|
||||
---
|
||||
|
||||
## 🟠 MAJOR 등급
|
||||
|
||||
### F-08. [원래요구위반] s2s를 윤찬 동의 없이 "탈락" 단독 확정 + 근거 자기붕괴/허수아비
|
||||
- **카테고리:** 원래요구위반 / 기술결함
|
||||
- **병합 출처:** requirements F6 · voice V-2
|
||||
- **근거:** §0 "s2s 1차 탈락". 음성설계서 §2.2 탈락 1번 근거 = "엔진 claude -p 고정" — 그런데 F-01이 그 고정을 풀어 **근거 절반 자기붕괴**. "transcript 없음" 근거도 OpenAI Realtime의 `input_audio_transcription`(입력)·`audio.transcript`(출력) 네이티브 제공을 무시한 허수아비(별도 STT 사이드카 불필요).
|
||||
- **원래요구위반:** 요구7은 "OpenAI API로 음성 서비스"이지 "캐스케이드만"이 아님. "탈락" 단독 확정은 월권. (단 transcript 보존=평가·재귀학습 1급 자산 논리는 타당 → 캐스케이드 1차는 합리적.)
|
||||
- **수정방향:** "s2s 탈락"→"**s2s 2차 PoC 옵션 유지**"로 표현 격상(음성설계서 §8이 이미 2차 옵션). 탈락 근거를 transcript 보존 단일 사유로 재정리. 윤찬에 "캐스케이드 1차/s2s 2차" 명시 보고·승인.
|
||||
|
||||
### F-09. [원래요구위반] 백그라운드 "지속적 판단"을 회기말로 미뤄 축소
|
||||
- **카테고리:** 원래요구위반
|
||||
- **병합 출처:** requirements F4
|
||||
- **근거:** §2.3 "deep-loop(Opus): 단계 전환·회기말 0-5 채점… 종료 후 대시보드가 SUS↑·인지부하↓".
|
||||
- **원래요구위반:** 요구6 "지속적으로 판단". fast-loop가 있어 완전 위반은 아니나, 정밀 판단을 UX 명분으로 회기말 배치 = 미세 축소.
|
||||
- **수정방향:** **"판단은 지속(deep-loop 백그라운드 계속), 제시만 회기말"**로 명확 분리. 현 문구는 판단 자체를 미룬 것처럼 읽힘.
|
||||
|
||||
### F-10. [원래요구위반] 윤찬이 요구한 적 없는 "자유연습 서버단 하드 차단" 임의 추가
|
||||
- **카테고리:** 원래요구위반
|
||||
- **병합 출처:** requirements F5
|
||||
- **근거:** §0 "자유연습(피드백 OFF) 기본 차단", §7 "자유연습 비활성화".
|
||||
- **원래요구위반:** 16개 요구 어디에도 자유연습 차단 없음. Stanford CARE 근거는 있으나 람다가 교수자 통제권을 임의 침해. `feedback_risk_user_decides`(위험은 알리되 차단 강제 권한 없음) 원칙 위배.
|
||||
- **수정방향:** **기본 ON + 피드백 권장 배지** 또는 **교수자 토글**로. CARE 근거는 UI 경고·기본값 추천으로 제시, 서버 하드차단 제거. (미동의자·미성년 차단은 IRB·개인정보 사안이므로 별개 유지 — 정당.)
|
||||
|
||||
### F-11. [원래요구위반/오케스트레이션] IRB 본문 잔재 — 헤더만 패치, Phase·액션·체크리스트에 IRB 의존성 잔존
|
||||
- **카테고리:** 원래요구위반 / 오케스트레이션·구현능력결함
|
||||
- **병합 출처:** requirements F7 · (integration·orchestration 플래그)
|
||||
- **근거:** 헤더 "IRB 불필요" vs 본문 §4 Phase 0 "IRB 신청서 제출", Phase 3 의존성 "+IRB 승인", Phase 3 "20명 IRB 파일럿", §8 액션#2 "IRB 프로토콜 제출 — 승인 리드타임이 Phase 3 게이트", §7 IRB 체크리스트 전체 잔존.
|
||||
- **원래요구위반:** 요구16 반영 누락(부분 수정). 팀 인계 시 불필요 IRB 리드타임 소모, 제거한 게이트 부활.
|
||||
- **수정방향:** 본문 전수 정리 — Phase 0 "IRB 신청서 제출" 삭제, Phase 3 의존성에서 "IRB 승인" 제거, §8 액션 IRB 삭제, "IRB 파일럿"→"교육용 파일럿". §7은 **미성년 활용동의+개인정보 처리방침**만 남기고 IRB 심의 항목 전부 제거.
|
||||
|
||||
### F-12. [원래요구위반/기술결함] fast-loop 평가 AI를 외부 Haiku/Solar API로 기본 설정 — 요구3 취지 이탈
|
||||
- **카테고리:** 원래요구위반 / 기술결함
|
||||
- **병합 출처:** engine F-6 · integration F4
|
||||
- **근거:** 3AI §3.2 "fast-loop 엔진=Haiku 4.5 또는 Solar Pro 3 직접 API". 매 발화 도는 가장 빈번한 경로가 비-claude·외부 종량제. 매 발화 Presidio 마스킹 지연이 지연예산(<1.5s)에 미반영.
|
||||
- **원래요구위반:** 요구3 취지(로컬·정액·데이터주권) 이탈. 윤찬 명시 승인 없는 타협. (단 발화단위 분류를 Opus로 도는 건 과잉 — 약한 위반이라 MAJOR.)
|
||||
- **수정방향:** fast-loop 기본을 **로컬 1B 분류기(NAS 상주)** 또는 **상주 Haiku claude -p**로, 외부 API는 폴백. 어느 경로든 `turns.llm_provider`에 적재해 "claude 비중" 측정. 외부 유지 시 윤찬 승인 + 마스킹 지연을 예산표 반영.
|
||||
|
||||
### F-13. [기술결함] claude -p "1턴 후 종료" 모델 vs warm pool 가정 충돌 — 핵심 지연(TTFT 400~900ms) 미검증
|
||||
- **카테고리:** 기술결함
|
||||
- **병합 출처:** engine F-4 · voice V-3(부분) · integration F3
|
||||
- **근거:** 음성설계서 §6 "LLM TTFT ~400~900ms"는 측정값 아닌 희망값. "프로세스 풀 워밍으로 콜드스타트 회피" vs 3AI §6.3 "claude -p는 1턴 후 종료라 warm pool은 SDK/API로 가야 깔끔" — **자기반박**. 회기당 30~50턴이면 매 턴 Node 콜드스타트(수 초)가 TTFT에 얹힘.
|
||||
- **누락된 해법(엔진 렌즈 실측):** CLI v2.1.191에 **`--input-format stream-json`(realtime streaming input)** 실재 — 단일 상주 프로세스에 턴마다 JSON 주입하는 양방향 멀티턴 모드. 설계서가 이 플래그를 검토조차 안 해 "claude -p로 warm 불가→API로" 논리의 전제가 틀림.
|
||||
- **수정방향:** `--input-format stream-json` 상주 프로세스 + 인스턴스 풀로 **콜드스타트를 회기당 1회로 압축**. Phase 1 DoD에 "상주 claude -p TTFT p50/p95 실측 + 동시 4회기 부하에서 열화곡선·피크 RSS·OOM 0" 게이트 신설. 실측 전 400~900ms를 계획에 쓰지 말 것(`feedback_measure_before_report`).
|
||||
|
||||
### F-14. [기술결함] 1급 claude -p 호출 템플릿이 존재하지 않는 플래그 사용 — "공식 문서 검증됨"이 실바이너리와 불일치
|
||||
- **카테고리:** 기술결함
|
||||
- **병합 출처:** engine F-2 · F-3
|
||||
- **근거(CLI v2.1.191 직접 실행 검증):** 3AI §2.2 "공식 문서 검증됨" 템플릿이 **`--max-turns`(존재하지 않음)**를 1턴 강제 메커니즘으로 사용. `--append-system-prompt-file`도 메인 help에 없어 미검증. **모드 자기모순:** `--bare`는 OAuth/keychain 차단(API 키 강제) → 정액(모드A)에서 못 씀. 즉 1급 템플릿이 정액 모드와 양립 불가 → 윤찬이 피하려던 종량제가 기본이 됨.
|
||||
- **수정방향:** `--max-turns` 의존 제거(claude -p는 도구 미사용 시 단일응답 종료가 기본, 폭주는 `--max-budget-usd`로). system 주입은 `--system-prompt-file` 실측 후 확정 또는 stdin JSON 동봉. 정액+재현성은 `--bare` 없이 OAuth + `--settings`(hooks off)·`--strict-mcp-config`·격리 `HOME`/`CLAUDE_CONFIG_DIR`로 글로벌 CLAUDE.md 차단 → **윤찬 PC에서 1회 실증 후** "검증됨" 표기.
|
||||
|
||||
### F-15. [기술결함] 엔진 어댑터 "한 줄 전환" 주장 미검증 — stream-json/구조화출력/cost 정규화 레이어 부재
|
||||
- **카테고리:** 기술결함
|
||||
- **병합 출처:** engine F-5
|
||||
- **근거:** §0 "어댑터 한 줄로 전환". 그러나 `claude -p` stream-json 이벤트(`total_cost_usd`, `structured_output`)와 Messages API SSE(`content_block_delta`, `usage`)는 스키마 상이. `MessagesAPIEngine` 구현 부재. cost_usd 의미가 두 경로에서 달라 재귀학습·비용 텔레메트리(요구5) 일관성 깨짐.
|
||||
- **수정방향:** `LLMEngine`에 정규화 이벤트 스키마 + 정규화 cost 규약(정액은 토큰×참조단가 shadow cost) 정의. 양쪽 어댑터를 Phase 0~1에 둘 다 구현 + contract test(동일 입력→동일 정규화 출력) DoD화.
|
||||
|
||||
### F-16. [오케스트레이션·구현능력] 오케스트레이션 주체·구조 부재 — "기존 풀스택팀" 5글자가 인적자원 전부
|
||||
- **카테고리:** 오케스트레이션·구현능력결함
|
||||
- **병합 출처:** orchestration D-O1 · D-O7
|
||||
- **근거:** 마스터플랜 line 4 "기존 풀스택팀", line 305 "인계". 트랙 A/B/C/D를 나눴으나 **소유 주체(인간/람다 TeamCreate/spawn 세션) 매핑 0**. "SSE 스키마 합의 후 병렬"의 합의 주체·산출물·동결 시점 없음. 트랙 A에 어댑터+상태머신+내담자AI+평가AI+가드레일 전부 = 람다 직접제작 회귀 위험.
|
||||
- **원래요구위반:** "팀 병렬 오케스트레이션 우선" + "람다는 인프라·감시만, 직접 다 만들지 마라" 원칙 위배.
|
||||
- **수정방향:** 트랙별 RACI(소유주체·디렉토리 경계·람다 역할·인터페이스 계약 동결시점). 비대 트랙 A를 sub-트랙으로 분할, 람다는 계약 정의+합류 검증만.
|
||||
|
||||
### F-17. [오케스트레이션·구현능력/원래요구위반] 모든 완료기준(DoD)이 결과 서술 — 검증 주체·수단·테스트 하네스 0
|
||||
- **카테고리:** 오케스트레이션·구현능력결함 / 원래요구위반
|
||||
- **병합 출처:** orchestration D-O2 · D-O3 · D-O4
|
||||
- **근거:** Phase 1 DoD "교수님이 상담→피드백 수신", Phase 2 "3역할 권한격리 실동작 검증" — **누가/어떤 도구로/어떤 절차로** 검증하는지 전무. "교수님이 한다"에 검증 외주. subagent가 물리적으로 못 하는 작업(50분 SSE·실시간 음성 레이턴시·OAuth 콜백) 미배정. 트랙 합류(integration) 소유자·순서 없음.
|
||||
- **원래요구위반:** `feedback_verify_actual_interaction`(람다 직접 클릭→타이핑→삭제 검증) 정면 위배 — "기능 존재=완료"는 금지된 패턴.
|
||||
- **수정방향:** 각 DoD에 **검증 프로토콜** 명시 — 람다가 windows-mcp/Playwriter로 실제 로그인→상담 완주→피드백 수신 재현, 권한격리는 타인 session_id 직접입력→403 스크린샷, 저항엔진은 공감5턴vs조언5턴 openness 곡선 DB쿼리 정량확인. subagent 불가 작업은 백그라운드 Bash+Monitor·실기기·Playwriter로 배정. Phase 말 통합게이트(람다 E2E 1회 재현).
|
||||
|
||||
### F-18. [오케스트레이션·구현능력] 장시간·상태유지 작업(라벨링·검수·κ누적)을 stateless 오케스트레이션으로 끌고 갈 방법 없음
|
||||
- **카테고리:** 오케스트레이션·구현능력결함
|
||||
- **병합 출처:** orchestration D-O5
|
||||
- **근거:** 트랙 D "annotation_round+IAA 게이트, 교수자 검수 κ 누적", "골든셋 50발화 라벨링"은 수일~수주 인간-루프. subagent 한 호출로 불가.
|
||||
- **수정방향:** 라벨링·검수·κ를 DB/파일 영속 작업으로 재정의, 람다는 크론(CronCreate)+단계 재개 워크플로우로 오케스트레이션. ds 스키마에 체크포인트 저장, 다음 세션 이어받기.
|
||||
|
||||
### F-19. [기술결함] 윤찬 PC = 엔진+터널 SPOF, 외부 서비스 가용성 설계 공백
|
||||
- **카테고리:** 기술결함
|
||||
- **병합 출처:** security B2 · integration F8
|
||||
- **근거:** §1.1 로컬 PC/NAS 한 박스, R10 "NAS SPOF→스냅샷/덤프"(데이터 손실 대응이지 가용성 아님). 요구3(엔진=윤찬 PC)+요구4(교수 임의시각 접속) 합치면 PC 절전/재부팅/크래시/OAuth 만료/rate limit/codex node 누수(`reference_codex_node_memory_leak` 전례) 시 교수 화면 상담 사망. cloudflared·Caddy·Postgres·claude -p·voice-gw가 단일 물리 호스트 수렴.
|
||||
- **수정방향:** (a)엔진 PC와 NAS DB를 다른 물리 장애도메인 분리, (b)교수 테스트 **예약 슬롯제**+직전 헬스체크(PC alive·세션 워밍·터널 up) 후 링크 발송, (c)watchdog(claude/cloudflared/docker 자동 재기동)+외부 모니터→디코 알림, (d)claude -p 다운 시에만 Messages API 자동 폴백(가용성 안전망). PC 절전/윈도우 업데이트 방지(`.ps1` ASCII 함정 주의).
|
||||
|
||||
### F-20. [기술결함/데이터·윤리] SSE 분리 경로(Tailscale Funnel)가 무인증 노출 + `__Host-` 쿠키 인증 붕괴 — 미성년 상담 스트림 IDOR 위험
|
||||
- **카테고리:** 기술결함 / 데이터·윤리
|
||||
- **병합 출처:** security M1 · integration F10
|
||||
- **근거:** §0/§1.1 SSE를 별도 도메인 stream.chanpaca.net + Tailscale Funnel(무인증 공개)로 분리. auth-rbac은 `__Host-` 쿠키(Domain 속성 금지)로 인증 일원화 → **다른 호스트에 쿠키 미전송 → SSE 인증 구조적 붕괴**. Funnel에 RBAC/세션검증 강제 방법 미설계 → 타인 상담 스트림 무인증 구독(IDOR) 가능. `reference_cloudflared_external_share`의 멀티tailnet 함정 전례.
|
||||
- **수정방향:** SSE도 **단일 도메인+Cloudflare 단일 터널**에서 먼저 실측(heartbeat+`no-transform`+flush -1), 끊길 때만 분리. 분리 불가피 시 stream 경로도 BFF 경유 + short-lived signed token(쿠키 아닌 header/query, 세션당 1회·단명). WSS(음성)·SSE(텍스트)를 같은 전송 추상화로 통합 검토(노출면 최소화).
|
||||
|
||||
### F-21. [데이터·윤리/기술결함] 음성 turn 스키마 ↔ §3.2 DB 스키마 불일치 — paralinguistic 컬럼 부재로 재마이그레이션 폭탄
|
||||
- **카테고리:** 기술결함 / 데이터·윤리
|
||||
- **병합 출처:** integration F5
|
||||
- **근거:** 음성설계서 §10 turn 로그 필수 `{audio_ref, silence_ms, speech_rate, barge_in}` vs §3.2 turns 스키마에 해당 컬럼 없음. Phase 0에서 스키마 확정(DoD=마이그레이션 통과) 후 Phase 2 음성 진입 시 핵심 평가 feature(침묵→자살암시) 담을 곳 없어 재마이그레이션+평가 입력계약 재작업.
|
||||
- **수정방향:** Phase 0 스키마에 음성 메타 컬럼(nullable) 선반영 또는 `turn_paralinguistic` 1:1 확장 테이블 + `voice_persona_map` 추가. §10 계약을 마스터플랜 스키마에 머지(단일 SoT).
|
||||
|
||||
### F-22. [데이터·윤리] DSM-5 반영이 표면적 — 감별진단·C-SSRS 매핑 없이 단일라벨 회피만
|
||||
- **카테고리:** 데이터·윤리
|
||||
- **병합 출처:** data M2
|
||||
- **근거:** §2.4 RDoC 차원 4개(자의적 추출, 정식 6도메인 아님), §7 "DSM-5 차원 프로파일" 미체크. ideation_stage 1~5와 C-SSRS 정식축 매핑표 부재, P1 감별진단(지속성우울/적응장애/주요우울) 없음.
|
||||
- **원래요구위반:** 요구13 "DSM-5 빡세게 반영" 부분 축소.
|
||||
- **수정방향:** ideation_stage↔C-SSRS 매핑표, P1~P3 페르소나의 DSM-5 진단기준↔행동증거 매트릭스 추가. "진단 비노출"과 "진단학적 엄밀성"은 양립 가능(내부 파라미터).
|
||||
|
||||
### F-23. [데이터·윤리] 슈퍼바이저 논평(1인·1회기)을 "AI 피드백 모범 코퍼스"로 일반화 → 편향 주입
|
||||
- **카테고리:** 데이터·윤리
|
||||
- **병합 출처:** data M3
|
||||
- **근거:** taxonomy §3.3 "모범 코멘트 코퍼스". 0615 논평은 슈퍼바이저 1인의 1회기 사후평. "어미 번갈아 쓰면 안정감 저해"(SV.LANG, 현장특이·근거불명)까지 모범화하면 CBT 학습자에 인간중심 미시문체 오적용. F-04(n=1)와 결합해 편향 전 시스템 증폭.
|
||||
- **수정방향:** 슈퍼비전 논평은 학습 코퍼스 아닌 few-shot/검증 참조로만, AI 피드백은 EPITOME/CTRS 루브릭 앵커. "(현장 특이)" 항목 학습신호 제외. 슈퍼바이저 다인 확보 전 1인 편향으로 간주·가중치 다운.
|
||||
|
||||
### F-24. [데이터·윤리] 인용 논문 다수가 검증 불가 미래시점 arXiv ID — "적대적 검증 완료" 주장과 모순
|
||||
- **카테고리:** 데이터·윤리
|
||||
- **병합 출처:** data M4
|
||||
- **근거:** 설계서 "WebFetch 적대적 검증 완료" 주장 vs PSI-Bench arXiv 2604.25840, PsychEval 2601.01802, TherapyGym 2603.18008 등 미래시점 ID 세션 내 web 검증 불가. 핵심 수치(d=0.72, ICC 0.853, 77.2%)가 이 논문에 의존하는데 ICC 0.853은 출처가 "D3-4 인용"으로만 적혀 추적 불가.
|
||||
- **수정방향:** 신청서·공문 제출 전 모든 arXiv ID 실재 확인, preprint vs peer-reviewed 구분, 인용 수치 원문 캡처 증빙. 미확인은 "preprint, 미검증" 라벨. ICC 0.853을 1차 논문으로 명기.
|
||||
|
||||
### F-25. [데이터·윤리] Presidio 발화단위 마스킹이 사례단위 준식별자 조합을 못 가림 — 미성년 자살사례 재식별 위험
|
||||
- **카테고리:** 데이터·윤리
|
||||
- **병합 출처:** data M5
|
||||
- **근거:** §3.3 Presidio 마스킹은 직접식별자만. taxonomy CCD "고2 남학생+자퇴+등교거부+자살사고+형비교+이모양육+맞벌이" 조합 = 준식별자 묶음으로 소집단 재식별 가능. PIPC 가명처리의 재식별 위험평가 부재.
|
||||
- **수정방향:** 사례 메타 재식별 위험평가(motivated intruder test), 학년/나이 범주화, 호소문제 일반화. 학습원천은 원사례 아닌 합성변형(F-05 연동).
|
||||
|
||||
### F-26. [기술결함] 교수 검수 weighted-κ→골든셋 승격 루프가 20명 단회기에서 통계 성립 불가 (순환의존)
|
||||
- **카테고리:** 기술결함 / 데이터·윤리
|
||||
- **병합 출처:** integration F6
|
||||
- **근거:** §2.3/§3.2 "κ≥0.6/ICC≥0.75 게이트 통과분만 골든셋", 재귀학습이 이 골든셋을 코퍼스로 씀(순환). 50~수백 발화로 안정적 κ/ICC 산출 어렵고, 0615 89라벨을 페르소나+평가 정답 이중사용(§8 액션4) = train/test 누수.
|
||||
- **수정방향:** 재귀학습을 Phase 3 산출물 아닌 "파이프라인 구축+소량 1차 산출"로 톤다운. 0615를 페르소나 시드와 평가 정답으로 분리. 골든셋 목표 표본 명시, 미달 시 "인프라만 검증, 효과는 후속" 명문화. 1라운드는 교수 단독 라벨로 시드.
|
||||
|
||||
### F-27. [오케스트레이션·구현능력/기술결함] 20주에 음성+3역할+페르소나3종+RAG+재귀학습+안전분류기가 Phase 2(7주)에 동시 압축 — 자기 부록 일정과 충돌
|
||||
- **카테고리:** 오케스트레이션·구현능력결함 / 기술결함
|
||||
- **병합 출처:** integration F7
|
||||
- **근거:** Phase 2(9~15주, 7주)에 트랙 A(음성)·B(3역할+OAuth BFF)·C(페르소나+분류기)·D(재귀+IAA) 병렬. 그러나 음성설계서 §8은 음성만 20주 전체(M4 통합 13~17주), 3AI §7 프로토타입 4~6주. **음성 단독 로드맵 20주를 7주 한 트랙에 욱여넣음**. "기존 풀스택팀(JS 추정)"이 FastAPI/asyncio/Presidio/pgvector/Deepgram WS/AudioWorklet을 7주 동시 습득 가정.
|
||||
- **수정방향:** Phase 2 분할 — 2a(3역할+OAuth+페르소나, 9~13주), 2b(음성 캐스케이드 1페르소나, 13~17주). 재귀학습 IAA·자살분류기 정밀화는 Phase 3 이연. 음성 M-단계와 Phase 주차를 **단일 간트로 통합**(`feedback_proper_backlog_planning`).
|
||||
|
||||
### F-28. [원래요구위반/오케스트레이션] 한신대 SSO 미확인 블로커가 로드맵 게이트에 미반영
|
||||
- **카테고리:** 오케스트레이션·구현능력결함
|
||||
- **병합 출처:** security M2
|
||||
- **근거:** auth설계서 §2.2/R11 한신대 SSO 프로토콜(OIDC vs SAML)·클레임 미확인. §8 액션#1은 공문이나 Phase 2 "BFF OAuth 2.1"을 태연히 배치 → 납품 직전 SAML 어댑터 미예산 작업 폭발 위험. (사용자 OAuth 2.1+BFF+PKCE 설계 자체는 견고 — 요구2 충족.)
|
||||
- **수정방향:** 공문 질의를 Phase 0 게이트 격상(IRB 면제로 SSO가 최장 리드타임 블로커). Google OIDC 단독 진행하되 Authlib provider 뒤 SAML SP 어댑터 스캐폴드 선반영. "OAuth" 용어가 (사용자 로그인)과 (엔진 Max OAuth) 혼용되니 분리 명시.
|
||||
|
||||
### F-29. [기술결함/원래요구위반] 폴백 시 LLM 토큰 비용 추산 통째 누락 — "claude -p라 0원"이 폴백에서 붕괴
|
||||
- **카테고리:** 기술결함
|
||||
- **병합 출처:** security M3
|
||||
- **근거:** 음성 §7.1 "로컬 claude -p라 한계비용 0, 폴백 시 별도"로 폴백 비용 증발. R9 한국어 토큰 35% 팽창. Opus 4.8 입력$5/출력$25 per 1M, deep-loop이 전체 회기 채점 → 폴백 시 LLM 비용이 STT/TTS 압도 가능. 회기당 비용 한 번도 추산 안 됨.
|
||||
- **수정방향:** 폴백 회기당 LLM 비용 상한 실측(Anthropic count_tokens, tiktoken 금지). 프롬프트 캐싱 필수화(L0~L2 정적 → cache_control, Opus 4.8 최소 프리픽스 4096토큰 검증, 읽기 0.1x로 ~90% 절감). deep-loop는 폴백 시 batch API(50% 할인). cost_usd turn 적재 + 폴백 비율 모니터.
|
||||
|
||||
### F-30. [기술결함] visible_to[] / RBAC×cohort / RLS 강제 메커니즘이 3개 설계서에서 명명·모델 불일치
|
||||
- **카테고리:** 기술결함
|
||||
- **병합 출처:** integration F9
|
||||
- **근거:** §3.2 `visible_to[]`(AI 정보비대칭) vs auth-rbac RBAC role+cohort(인간 권한)가 같은 "누가 무엇을 보나"를 두 독립 메커니즘으로 구현, 교차점 미정의. auth-rbac은 RLS를 "2차 강화"로, 마스터플랜 §0은 "RLS 이중강제 기본"으로 — **또 모순**. §3.2 스키마에 cohort·account_linking 없음.
|
||||
- **원래요구위반:** 요구8(3페이지 권한격리) 강제 메커니즘 비일관.
|
||||
- **수정방향:** visible_to[](AI)와 RBAC×cohort(인간)를 다른 레이어로 명시 분리, 교차점(인간이 turn 읽을 때 두 게이트 AND)을 단일 enforcement 미들웨어로 통일. RLS 기본/2차를 하나로 확정. Phase 0 스키마에 cohort/account_linking/audit_log 포함.
|
||||
|
||||
### F-31. [데이터·윤리] RAG 4종 용도가 단일 pgvector 1024d로 뭉뚱그려짐 — BGE-M3 멀티벡터/sparse/맥락 컬럼 부재
|
||||
- **카테고리:** 기술결함
|
||||
- **병합 출처:** integration F12
|
||||
- **근거:** §3.6은 RAG 3종 "서로 다른 RAG" + BGE-M3 하이브리드(dense+sparse+ColBERT) 명시하나, §3.2는 `rag_chunks(embedding vector(1024))` 단일 dense 컬럼. sparse·ColBERT·Contextual 맥락문장·리랭커 입력 담을 곳 없음. 3AI §4.3은 3인덱스+graphify MCP로 또 다름.
|
||||
- **수정방향:** `rag_chunks`에 `index_kind`·`sparse_vec`·`context_prefix`·`colbert_vecs` 추가. 하이브리드 검색 SQL 계약+리랭커 경계 §3.6 명시. graphify-lambda vs pgvector 택일(둘 다면 동기화 계약).
|
||||
|
||||
---
|
||||
|
||||
## 🟡 MINOR 등급
|
||||
|
||||
### F-32. [데이터·윤리] KPI 임계치 문서 간 불일치 (κ/ICC ≥0.80 vs ≥0.6/0.75)
|
||||
- **출처:** data m1. 설계서 §1.2 "≥0.80(39편 메타리뷰)" vs §3.3/§7 "현실치 ≥0.6/0.75". **수정:** 0.6/0.75로 통일 또는 "외부벤치 목표 0.80 vs 파일럿 현실 0.6" 명시 구분.
|
||||
|
||||
### F-33. [데이터·윤리] "환각률 ≤5%(목표 ≤1%)" 측정 정의 부재
|
||||
- **출처:** data m2. 판정 주체가 LLM-judge면 F-06(judge 타당도 미검증)과 순환. **수정:** 환각 판정 인간 검수 고정 또는 judge 환각판정 정확도 별도 보고.
|
||||
|
||||
### F-34. [데이터·윤리] CCD core_belief 3유형 중 unlovable 누락 + P1에 2개 동시 부여
|
||||
- **출처:** data m3. Patient-Ψ 원설계는 단일 우세 신념. **수정:** 우세 1개+보조 구조화, 3유형 커버리지 페르소나 백로그 반영.
|
||||
|
||||
### F-35. [데이터·윤리/원래요구위반] "재귀적 학습" 메커니즘 미정의 — 축적 ≠ 학습
|
||||
- **출처:** data m4. JSONL export까지만, 그 데이터로 무엇을 fine-tune/few-shot 업데이트하는지 루프 닫힘점 없음. **원래요구위반:** 요구5 "재귀적으로 AI가 학습"의 구현체 부재. **수정:** "골든셋 누적→few-shot 풀 자동갱신→평가 프롬프트 업데이트" 명시 루프로 정의(fine-tuning 여부 1차 범위 결정).
|
||||
|
||||
### F-36. [기술결함] 프롬프트 캐싱 "59~70% 절감"이 Opus 4.8 최소 프리픽스 4096토큰 미달 시 silent miss
|
||||
- **출처:** security m1. **수정:** `usage.cache_read_input_tokens`로 히트 검증, 페르소나+RAG 프리픽스가 4096토큰 넘는지 확인, silent invalidator(타임스탬프) 추적. 측정 후 절감률 보고(`feedback_measure_before_report`).
|
||||
|
||||
### F-37. [기술결함/데이터·윤리] secrets git-ignore만으로 미성년 데이터 키관리 불충분 — 가명↔원본 매핑 격리 미명세
|
||||
- **출처:** security m2. **수정:** 키는 secrets manager/OS 키체인, 가명 매핑 테이블은 암호화 DB+2인 승인 명세화. 운영 계정류는 정해진 위치만(`feedback_obsidian_secrets_folder`), 새 폴더 임의 생성 금지.
|
||||
|
||||
### F-38. [기술결함] 위기분류 "수련생 실제위기 vs 페르소나 연기" 판정 계약 미정의 (음성 화자라벨 경계케이스)
|
||||
- **출처:** integration F11. 화자=counselor의 모든 위기발화를 실제위기로 에스컬레이션하면 false-positive로 시뮬레이션 중단 남발. **수정:** 입력계약 `{speaker, stage, text_masked, is_roleplay_context}→{risk_level, escalate}`, "본인+1인칭 현재시제+시뮬레이션 메타밖" 결정론 룰 우선. Phase 2 DoD에 false-positive 케이스 포함.
|
||||
|
||||
### F-39. [기술결함] `--bare` 재현성 vs 모드A 정액 트레이드오프가 평가루프 신뢰도에 미치는 영향 미정량
|
||||
- **출처:** engine F-8. 평가루프(③deep)에 `--bare` 필수라면서 정액 모드(비-bare)는 글로벌 CLAUDE.md/hooks 로드로 재현성 위협. **수정:** ③deep만 API키+`--bare`(또는 Messages API)로 재현성, ②내담자는 정액. 역할별 인증/플래그 분기 명시.
|
||||
|
||||
### F-40. [기술결함/원래요구위반] 단일호스트 모놀리식 vs 요구12 "스케일업" 정의 갭
|
||||
- **출처:** security m3. K8s 금지는 20주에 합리적. **수정:** 1차 현 설계 유지, "스케일업=클라우드 VM 이전+compose replicas"로 윤찬과 정의 합의. K8s는 2차 백로그.
|
||||
|
||||
---
|
||||
|
||||
## 부록. 자기반증으로 폐기한 공격 (투명성)
|
||||
|
||||
각 렌즈가 약하다고 판단해 버린 결함 — Timescale 미도입(PG16으로 충분), pgvector RAG(벡터DB 미지정), 모노레포 과설계(요구10 대비 정당), DSM 진단명 비노출(학술적으로 옳음), 0-5 vs CTRS 0-6(분리운영 방어됨), React 19 미성숙(2026 안정화), Presidio 한국어 NER(M5로 통합), s2s 탈락이 데이터 렌즈상 요구위반(transcript 보존이 재귀학습엔 정당 — 단 *결정권 월권*은 F-08로 살림), BGE-M3 rag-memory 충돌(역할분리 명시), Docker 6컨테이너 과다(20명 규모 합리적).
|
||||
|
||||
---
|
||||
|
||||
## 종합 판정
|
||||
|
||||
| 카테고리 | 결함 수 | 핵심 ID |
|
||||
|---|---|---|
|
||||
| **원래요구위반** | 10 | F-01·02·03·08·09·10·11·12·28·35·40 |
|
||||
| **기술결함** | 14 | F-07·13·14·15·19·20·21·29·30·31·36·38·39·40 |
|
||||
| **오케스트레이션·구현능력** | 5 | F-11·16·17·18·27·28 |
|
||||
| **데이터·윤리** | 12 | F-03·04·05·06·20·22·23·24·25·26·31·32~35·37 |
|
||||
|
||||
**BLOCKER 7건:** F-01(엔진 바꿔치기) · F-02(음성 격하) · F-03(마스킹 우회) · F-04(n=1 골든셋) · F-05(미성년 재귀학습 동의) · F-06(타당도 미검증 평가) · F-07(음성 WSS 토폴로지 누락)
|
||||
|
||||
**최우선 시정 3건:**
|
||||
1. **F-01 — 엔진 기본값을 claude -p로 복원.** 윤찬 요구의 심장. 비용/동시성/rate limit 변명 셋 다 약함(정액 한계비용 0, 프로세스 풀+`--input-format stream-json` 상주로 동시성 해결, 20명 규모 근거 빈약). 마스터플랜과 하위 설계서 2종의 엔진 결정을 **하나로 통일**.
|
||||
2. **F-04+F-05+F-06 결합 — 시스템 전체가 동의 미확보 미성년 자살사례 1건 위에 서 있고, 타당도 미검증 평가 AI가 수련생을 먼저 가르친 뒤 사후에 신뢰도를 잰다.** "골든셋·빡세게·적대적 검증 완료"가 빈약한 데이터 토대와 순환 검증을 학술 외관으로 덮음.
|
||||
3. **F-11 — IRB 본문 전수 정리.** 헤더만 고치고 본문(Phase 게이트·액션·체크리스트) IRB 의존성 방치 = 부분 수정 누락.
|
||||
|
||||
**메타 결론:** 4개 하위 설계서가 서로의 결론을 부정하는 상태(엔진·음성전송·스키마·권한모델 모두 모순)라 "단일 기준 문서(SoT)"라는 전제 자체가 아직 안 지켜졌다. `feedback_proper_backlog_planning` 원칙상 통합 SoT 재정렬이 선행돼야 한다.
|
||||
Loading…
Add table
Add a link
Reference in a new issue