From d18e4b6b4bea5418371c6e0422c137221bf9ee11 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Mon, 29 Jun 2026 00:01:24 +0900 Subject: [PATCH] =?UTF-8?q?Phase=203=20KPI=20=EA=B3=84=EC=95=BD=20?= =?UTF-8?q?=EC=83=81=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/app/services/phase3_kpi_contract.py | 40 +++++++++++++++++ apps/api/app/test_phase3_artifact_checker.py | 2 +- apps/api/app/test_phase3_kpi_export.py | 15 +------ docs/dev_dashboard.html | 13 +++--- docs/guides/source-docs-and-gaps.md | 5 ++- docs/ops/backlog-2026-06-26.md | 2 +- scripts/check-phase3-artifacts.py | 45 +++++++------------- 7 files changed, 69 insertions(+), 53 deletions(-) create mode 100644 apps/api/app/services/phase3_kpi_contract.py diff --git a/apps/api/app/services/phase3_kpi_contract.py b/apps/api/app/services/phase3_kpi_contract.py new file mode 100644 index 0000000..b36ce83 --- /dev/null +++ b/apps/api/app/services/phase3_kpi_contract.py @@ -0,0 +1,40 @@ +"""Phase 3 KPI evidence report contract constants.""" + +from __future__ import annotations + +from typing import Literal + +PREPOST_MEASURE_NAMES = ( + "self_efficacy", + "skill_proficiency", + "training_satisfaction", +) +PREPOST_TIMEPOINTS = ("pre", "post") +PHASE3_KPI_METRICS = ( + "embedding_consistency", + "hallucination_rate", + "icc", + "inter_rater_kappa", + "pilot_completion", + "self_efficacy_prepost", + "session_completion", + "sus", + "top1", +) +KPI_METRIC_STATUSES = ( + "computed_prepost", + "design_pending", +) +KpiMetricStatus = Literal["computed_prepost", "design_pending"] +KPI_METRIC_REQUIRED_KEYS = { + "denominator", + "method", + "numerator", + "pass", + "source_files", + "status", + "threshold", + "value", +} +PREPOST_CSV_PATH = "02-measures/prepost_measures.csv" +KPI_REPORT_PATH = "02-measures/kpi_report.json" diff --git a/apps/api/app/test_phase3_artifact_checker.py b/apps/api/app/test_phase3_artifact_checker.py index 6ee4189..29aa252 100644 --- a/apps/api/app/test_phase3_artifact_checker.py +++ b/apps/api/app/test_phase3_artifact_checker.py @@ -79,7 +79,7 @@ class Phase3ArtifactCheckerTests(unittest.TestCase): "method": "fixture", "source_files": ["fixture"], } - for name in checker.KPI_METRICS + for name in checker.PHASE3_KPI_METRICS } write_text( root / "02-measures" / "kpi_report.json", diff --git a/apps/api/app/test_phase3_kpi_export.py b/apps/api/app/test_phase3_kpi_export.py index 757b766..3188cb5 100644 --- a/apps/api/app/test_phase3_kpi_export.py +++ b/apps/api/app/test_phase3_kpi_export.py @@ -15,18 +15,7 @@ from app.services.phase3_kpi_export import ( write_kpi_report, write_prepost_csv, ) - - -REQUIRED_METRIC_KEYS = { - "denominator", - "method", - "numerator", - "pass", - "source_files", - "status", - "threshold", - "value", -} +from app.services.phase3_kpi_contract import KPI_METRIC_REQUIRED_KEYS def fixture_rows(): @@ -134,7 +123,7 @@ class Phase3KpiExportTests(unittest.TestCase): self.assertEqual(report["cohort_size"], 2) self.assertTrue(set(PHASE3_KPI_METRICS).issubset(report["metrics"])) for metric in report["metrics"].values(): - self.assertTrue(REQUIRED_METRIC_KEYS.issubset(metric)) + self.assertTrue(KPI_METRIC_REQUIRED_KEYS.issubset(metric)) self_efficacy = report["metrics"]["self_efficacy_prepost"] self.assertFalse(self_efficacy["pass"]) self.assertEqual(self_efficacy["status"], "computed_prepost") diff --git a/docs/dev_dashboard.html b/docs/dev_dashboard.html index b5ad9c6..b9d8850 100644 --- a/docs/dev_dashboard.html +++ b/docs/dev_dashboard.html @@ -509,6 +509,7 @@

74차 적용(2026-06-28): refactor-governance P4로 H2 live-coach source pack 캐시 경계를 명시했다. live_coach의 로컬 source pack snapshot은 API 프로세스 생명주기 캐시로 유지하고, 관리자 POST /kb/live-coach/source-packs/syncscripts/sync-persona-sources.pyrefresh=True로 캐시를 비운 뒤 repo 파일을 다시 읽는다. live turn 경로에는 매 턴 파일 IO를 추가하지 않았고, RAG content_hash/version 비교와 evaluator-only payload 정책은 유지했다. 파일 변경 뒤 stale hash가 refresh로 새 hash/version bump에 반영되는 회귀를 추가했다. 검증: source pack focused 9 passed, H2 related 44 passed, sync CLI help, py_compile.

75차 계획(2026-06-28): 다크 UI v2 시안을 실제 코드 적용 라운드로 승격했다. 기준 이미지는 03-session-responsive-v2-dark-unified.png, 04-session-review-responsive-v2-dark-unified.png, 02-learner-home-responsive-v2-dark-unified.png이고, 실행 순서는 라이브 상담 세션 → 회기 리뷰 → 학습자 홈이다. 생성 PNG는 앱에 직접 넣지 않고 React/CSS 구현의 톤·밀도·반응형 기준으로만 사용한다. 추적 항목은 docs/ops/backlog-2026-06-26.md, 구현 체크리스트는 docs/ops/dark-ui-refresh-implementation-plan-2026-06-28.md에 둔다. DONE 표기는 실제 Playwright 캡처와 typecheck/build/focused E2E/layout gate 통과 뒤에만 한다.

76차 적용(2026-06-28): refactor-governance P2로 M2 digest 계약 경계를 고정했다. SessionDigestInput/SessionDigestResult/SessionSummaryWrite를 추가해 future LLM worker가 raw text, evaluator-only turn, 평가 payload, CCD, deterministic carry를 우회하지 못하게 했다. /sessions/{id}/enddigest_pending은 여전히 CompressionJob 생성 여부를 알리는 비동기 압축 필요 신호일 뿐이며, LLM digest worker 실행·압축 품질 검증 완료를 뜻하지 않는다. 검증: M2 focused 74 passed, npm run check:api-types, npm run typecheck, py_compile.

+

77차 적용(2026-06-28): H1 Phase 3 KPI report 계약을 phase3_kpi_contract.py로 모아 exporter/checker/test의 metric 이름·필수키·status 중복 선언을 줄였다. kpi_report.json metric은 computed_prepostdesign_pending status를 구분하므로, 3척도 pre/post로 계산 가능한 파일럿 evidence와 평가설계 전이라 계산하지 않는 KPI를 기계적으로 분리한다. 공식 효과성 판정·통계해석·20명 파일럿 evidence는 계속 외부/파일럿 gate다. 검증: H1 KPI focused 10 passed, H1 broad 46 passed, exporter/checker help, py_compile.

20차 적용(2026-06-28): 신규 Google/SAML 사용자는 account_status=pending으로 시작하고 승인 전에는 /pending 안내 화면만 본다. yunchan@twentyoz.kr는 슈퍼 관리자 allowlist로 admin+approved를 받으며, /admin/users는 가입 승인 탭에서 pending 계정을 승인 또는 보류 처리한다.

21차 적용(2026-06-28): 관리자 페이지 진입권을 기본 역할과 분리해 app_user.admin_access로 저장한다. AUTH_SUPER_ADMIN_EMAILS 기본값은 yunchan@twentyoz.kr, hoonjungkoo@hs.ac.kr이며, 슈퍼 관리자는 학습자·교수자·관리자 공간 전환과 관리자 권한 부여/회수를 할 수 있다. 학생·교수 계정도 admin_access=true면 우측 상단 관리자 진입이 노출된다. 구성 슈퍼 관리자의 권한 회수와 계정 비활성화는 차단한다.

25차 적용(2026-06-28): /admin/users에서 허용 도메인 밖 이메일도 정확한 계정 단위로 강제 등록할 수 있다. Google/SAML/dev-login은 미리 등록된 이메일만 도메인 게이트 예외로 통과시키고, provider 로그인 시 기존 관리 row의 역할·코호트·승인 상태를 이어받는다. 미등록 외부 도메인 로그인은 계속 차단한다.

@@ -668,9 +669,9 @@
현재

TurnContext+prepare_turn에 theory_mode, sessions/voice가 sess.theory_mode 전달, evaluator._theory_mode가 세션 이론(학습자 선택) 우선. build_turn_messagesTHEORY_MODE_GUIDANCE를 통해 인간중심·CBT·통합 이론 프레이밍을 엔진 메시지에 넣는다. 프론트 Session.tsx는 기본값을 persona.theory_target에서 잡되, 세션 시작 전 humanistic/cbt/integrative segmented control로 학습자가 명시 선택할 수 있다. 선택값은 POST /sessionstheory_mode로 전송된다. 검증: npm run typecheck, npm run build, C3 backend focused 31 passed, session-layout.spec.ts desktop/mobile 8 passed, session-mvp.spec.ts single-run 1 passed, layout-visual-gate.spec.ts 9 passed.

잔여(임상팀)

CBT 이론별 단계 프롬프트 체인 콘텐츠 + 이론부합 채점 루브릭은 후속이다. 이번 UI는 임상 문안·루브릭·백엔드 enum을 확장하지 않았다.

-
- -
현재

doc4가 요구한 3척도 pre-post에 대해 app.learner_prepost_measure 원장, 학습자 본인용 GET/PUT /users/me/prepost-measures, SessionReview의 파일럿 증거 원장 카드를 연결했다. 저장 shape는 measure_name(self_efficacy/skill_proficiency/training_satisfaction), timepoint(pre/post), raw/min/max score, instrument_version, item_count만 받으며, 문항 텍스트·효과크기·p-value·pass/fail은 만들지 않는다. 이번 패스에서 app.services.phase3_kpi_exportscripts/export-phase3-kpi.py를 추가해 원장 row를 Phase 3 evidence root의 02-measures/prepost_measures.csv02-measures/kpi_report.json으로 산출한다. participant id는 P3-###로 가명화하고, 3척도 paired normalized mean pre/post/delta, complete/missing pair를 report에 채운다. 화면과 report 모두 공식 효과성·성적·수료 판정이 아니라 파일럿 evidence 계산임을 유지한다.

검증

python -B -m py_compile app/services/phase3_kpi_export.py app/test_phase3_kpi_export.py scripts/export-phase3-kpi.py, python -B -m pytest -p no:cacheprovider app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q 9 passed, python -B -m pytest -p no:cacheprovider app/test_user_support_tickets.py app/test_runtime_policy.py app/test_admin_ops.py app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q 45 passed, python scripts/export-phase3-kpi.py --help passed. 기존 API/UI 검증은 backend 36 passed, API types/typecheck, SessionReview focused E2E 3 passed, visual gate 2 passed.

잔여

평가설계·공식 문항 확정, 실험/통제군 배정, 추이 시각화, 통계 검정 종류/alpha/결측 처리, 실제 20명 파일럿 evidence와 steward/legal/IAA 검수는 아직 남아 있다.

+
+ +
현재

doc4가 요구한 3척도 pre-post에 대해 app.learner_prepost_measure 원장, 학습자 본인용 GET/PUT /users/me/prepost-measures, SessionReview의 파일럿 증거 원장 카드를 연결했다. 저장 shape는 measure_name(self_efficacy/skill_proficiency/training_satisfaction), timepoint(pre/post), raw/min/max score, instrument_version, item_count만 받으며, 문항 텍스트·효과크기·p-value·pass/fail은 만들지 않는다. app.services.phase3_kpi_exportscripts/export-phase3-kpi.py는 원장 row를 Phase 3 evidence root의 02-measures/prepost_measures.csv02-measures/kpi_report.json으로 산출한다. participant id는 P3-###로 가명화하고, 3척도 paired normalized mean pre/post/delta, complete/missing pair를 report에 채운다. 77차에서는 phase3_kpi_contract.py가 metric 이름·필수키·status를 소유하게 해 exporter와 checker의 계약 drift를 줄였다. report metric status는 계산된 3척도 pre/post evidence를 computed_prepost, 공식 평가설계 전이라 계산하지 않는 지표를 design_pending으로 구분한다. 화면과 report 모두 공식 효과성·성적·수료 판정이 아니라 파일럿 evidence 계산임을 유지한다.

검증

py -3.11 -X utf8 -m py_compile apps\api\app\services\phase3_kpi_contract.py apps\api\app\services\phase3_kpi_export.py apps\api\app\test_phase3_kpi_export.py apps\api\app\test_phase3_artifact_checker.py scripts\check-phase3-artifacts.py scripts\export-phase3-kpi.py, py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q 10 passed, py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_user_support_tickets.py app/test_runtime_policy.py app/test_admin_ops.py app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q 46 passed, py -3.11 -X utf8 scripts\export-phase3-kpi.py --help, py -3.11 -X utf8 scripts\check-phase3-artifacts.py --help. 기존 API/UI 검증은 API types/typecheck, SessionReview focused E2E 3 passed, visual gate 2 passed.

잔여

평가설계·공식 문항 확정, 실험/통제군 배정, 추이 시각화, 통계 검정 종류/alpha/결측 처리, 실제 20명 파일럿 evidence와 steward/legal/IAA 검수는 아직 남아 있다.

@@ -846,7 +847,7 @@

Phase 3 교육용 파일럿 / 효과성 상세

IRB 승인 게이트가 아니라 교육 활용 파일럿과 데이터/개인정보 게이트로 관리한다.

주차: 18-20+표본: 20명first measure
GATE
20명 교육용 파일럿 운영

20명 파일럿을 실행하기 위한 roster, consent, session completion evidence gate를 문서화했다. checker는 CSV enum(withdrawal_state, completion_state, timepoint, withdrawal scope/status)을 검증한다. 실제 참여자 완주 증거는 아직 필요하다.

산출물

docs/phase3/pilot-runbook.md, evidence root layout, operator gates, scripts/check-phase3-artifacts.py

검증

python scripts/check-phase3-artifacts.py --help, pytest app/test_phase3_artifact_checker.py -q 5 passed. 실제 evidence root validation은 파일럿 시점에 실행.

-
GATE
효과성/KPI 측정

SUS, 자기효능감, κ/ICC, 환각률, 피드백 타당도 기준과 report schema를 고정했다. 3척도 pre/post aggregate score를 저장하는 app.learner_prepost_measure, /users/me/prepost-measures API, 학습자 SessionReview 입력 카드에 더해, scripts/export-phase3-kpi.py가 원장 row를 prepost_measures.csvkpi_report.json scaffold로 산출한다. checker는 KPI report top-level keys와 metric 필수 필드(value/threshold/pass/method/source_files 등)를 error로 검증한다. 실제 수치 충족 여부와 통계 해석은 파일럿 데이터 이후 판단한다.

산출물

docs/phase3/kpi-measurement-plan.md, KPI report schema, checker tests, UserPrepostMeasure* API contract, PrepostMeasureCard, app/services/phase3_kpi_export.py, scripts/export-phase3-kpi.py

검증

checker가 prepost_measures, sus_responses, kpi_report.json evidence와 metric shape를 요구. H1 KPI export/backend focused 45 passed, exporter --help passed, API types/typecheck passed, SessionReview focused E2E 3 passed, session review visual gate 2 passed.

+
GATE
효과성/KPI 측정

SUS, 자기효능감, κ/ICC, 환각률, 피드백 타당도 기준과 report schema를 고정했다. 3척도 pre/post aggregate score를 저장하는 app.learner_prepost_measure, /users/me/prepost-measures API, 학습자 SessionReview 입력 카드에 더해, scripts/export-phase3-kpi.py가 원장 row를 prepost_measures.csvkpi_report.json scaffold로 산출한다. checker는 KPI report top-level keys와 metric 필수 필드(value/threshold/pass/status/method/source_files 등)를 error로 검증한다. metric status는 computed_prepost/design_pending으로 제한해 계산 가능한 evidence와 평가설계 미확정 지표를 분리한다. 실제 수치 충족 여부와 통계 해석은 파일럿 데이터 이후 판단한다.

산출물

docs/phase3/kpi-measurement-plan.md, KPI report schema, checker tests, UserPrepostMeasure* API contract, PrepostMeasureCard, app/services/phase3_kpi_contract.py, app/services/phase3_kpi_export.py, scripts/export-phase3-kpi.py

검증

checker가 prepost_measures, sus_responses, kpi_report.json evidence와 metric shape/status를 요구. H1 KPI contract focused 10 passed, exporter/checker --help passed, 기존 API types/typecheck passed, SessionReview focused E2E 3 passed, session review visual gate 2 passed.

DONE
재귀학습 데이터셋 1차 산출

masked-text JSONL dry-run exporter, PII scan, κ/ICC 계산, approved export 차단 게이트를 구현했다. 이번 패스에서 artifact checker도 approved manifest false-positive를 막도록 강화했다. 실제 approved export와 데이터 steward 검수는 남아 있다.

산출물

scripts/export-recursive-dataset.py, app.services.dataset_export, docs/phase3/dataset-export-manifest.md, app/test_phase3_artifact_checker.py

검증

pytest app/test_dataset_export.py app/test_phase3_artifact_checker.py -q 10 passed, DB dry-run 5 rows → technical_dry_run manifest + sha256 생성, approved manifest는 PII pass·κ/ICC·withdrawn exclusion·consent scope·file sha256을 요구.

GATE
개인정보/동의 감사

동의, 철회, privacy audit, 해외 전송 고지 evidence checklist를 만들었다. 실제 법무/개인정보 검토 서명은 파일럿 전 gate로 남아 있다.

산출물

docs/phase3/privacy-consent-audit.md, withdrawal log contract

검증

동의 철회 후 session/export 제외 evidence를 checker 입력으로 요구

@@ -909,7 +910,7 @@ Postgres RLS/audit smoke checkerpython scripts\check-postgres-rls-audit.py --helppy_compile/help/safe default OK; live DSN run still required for proof OAuth/BFF cookie securitypython -B -m pytest -p no:cacheprovider app/test_auth_providers.py -q30 tests OK; PKCE/state, cookie-bound signed state restart recovery, state cookie mismatch reject, opaque HttpOnly/Secure cookie, no browser tokens, SAML AuthnRequest/dev-only fixture ACS, dev/Tailnet OAuth guard, public-origin dev-login closed H4 consent gatepytest app/test_auth_providers.py app/test_session_turn_persistence.py app/test_persona_review.py app/test_voice_ws.py -q64 passed; learner consent accept/withdraw, non-learner/unaccepted reject, no-consent session start 403 consent_required, voice dev persona consent guard, frontend generated API contract updated - H1 pre/post evidence API + KPI exportapp/test_user_support_tickets.py, app/test_runtime_policy.py, app/test_admin_ops.py, app/test_phase3_kpi_export.py, app/test_phase3_artifact_checker.py, npm run check:api-types, npm run typecheck, e2e/session-review.spec.ts45 backend passed; app.learner_prepost_measure stores learner-scoped aggregate pre/post scores for self-efficacy, skill proficiency, and training satisfaction. scripts/export-phase3-kpi.py emits pseudonymous prepost_measures.csv and KPI report scaffold with paired normalized deltas. Generated API contract exposes UserPrepostMeasure*. 학습자 SessionReview는 1~5 aggregate score 조회/저장 카드를 표시하고, focused E2E 3 passed + visual gate 2 passed. + H1 pre/post evidence API + KPI exportapp/test_user_support_tickets.py, app/test_runtime_policy.py, app/test_admin_ops.py, app/test_phase3_kpi_export.py, app/test_phase3_artifact_checker.py, npm run check:api-types, npm run typecheck, e2e/session-review.spec.tsCurrent H1 broad 46 passed; KPI contract focused 10 passed. app.learner_prepost_measure stores learner-scoped aggregate pre/post scores for self-efficacy, skill proficiency, and training satisfaction. scripts/export-phase3-kpi.py emits pseudonymous prepost_measures.csv and KPI report scaffold with paired normalized deltas. phase3_kpi_contract.py owns metric names, required keys, and computed_prepost/design_pending status values used by exporter/checker/tests. Generated API contract exposes UserPrepostMeasure*. 학습자 SessionReview는 1~5 aggregate score 조회/저장 카드를 표시하고, focused E2E 3 passed + visual gate 2 passed. Persona review workflowapp.test_persona_review31 tests OK; approved-only catalog, teacher/admin queue, draft authoring, approved persona revision, archive audit, learner 403 Voice WS contractapp.test_voice_ws8 tests OK; auth guard, audio_start/chunks/audio_end, text_turn, ping, max audio cap, DB persona voice map binding, explicit preset override Voice preset/EOTapp.test_voice_service14 tests OK; TTS payload, preset fallback, DB voice-map resolver, unsupported provider fallback, P1 sample TTS, EOT readiness @@ -921,7 +922,7 @@ Hanshin data/SSO gatedocs/ops/hanshin-data-governance-gate.mdartifact created; written external evidence still required Resistance engineapp.test_state_machine_resistance + scripts/smoke-resistance-openness-db.py9 focused tests OK; live DB smoke PASS. Empathy P1 curve 0→0→0.095→0.133→0.171 and DB final stage=탐색. Advice-jump curve stayed 0 and DB final stage=라포. Python compilepython -m compileall app engine_gatewayPassed - Phase 3 artifact gatespython scripts\check-phase3-artifacts.py --help + pytest app/test_phase3_artifact_checker.py -qPassed; checker now enforces CSV enums, KPI metric required fields, approved export PII/agreement/consent/withdrawal/file-hash gates. Actual pilot evidence still external. + Phase 3 artifact gatespython scripts\check-phase3-artifacts.py --help + pytest app/test_phase3_artifact_checker.py -qPassed; checker now enforces CSV enums, KPI metric required fields/status, approved export PII/agreement/consent/withdrawal/file-hash gates. Actual pilot evidence still external. Web buildnpm run buildPassed Pages production deploywrangler pages deploy dist --project-name vignette --branch main --commit-dirty=true24c90784, dirty local source deploy Custom domain assetshttps://vignette.chanpaca.net/loginindex-HjXyC0JT.js, index-C8JKXhNP.css diff --git a/docs/guides/source-docs-and-gaps.md b/docs/guides/source-docs-and-gaps.md index bada57c..8b37067 100644 --- a/docs/guides/source-docs-and-gaps.md +++ b/docs/guides/source-docs-and-gaps.md @@ -59,7 +59,7 @@ | ID | 갭 | 현재상태 | 권고 | 근거 | |---|---|---|---|---| -| **H1** | 계약 평가 KPI(자기효능감·기술숙련도·수련만족도 사전사후) 수집·입력·CSV/report 계산 1차 | `app.learner_prepost_measure`, 학습자 본인용 `GET/PUT /users/me/prepost-measures`, `SessionReview`의 파일럿 증거 원장 카드가 3척도 pre/post 1~5 aggregate evidence를 저장·조회한다. `app.services.phase3_kpi_export`와 `scripts/export-phase3-kpi.py`는 원장 row를 Phase 3 evidence root의 `02-measures/prepost_measures.csv`와 `02-measures/kpi_report.json` scaffold로 산출한다. participant id는 가명화하고, 3척도 paired normalized mean pre/post/delta, complete/missing pair를 계산한다. | 공식 문항 확정, 실험/통제군 배정, 추이 시각화, 통계검정 종류/alpha/결측 처리, 실제 20명 evidence와 steward/legal/IAA 검수. 현재 API/UI/export는 공식 효과성·성적·수료 판정이 아니라 파일럿 evidence 계산이다. (κ/ICC·환각률은 doc4 미명시 → 평가설계 확정.) | doc4(20명 실험/통제군·단회기 50분·3척도 pre-post) | +| **H1** | 계약 평가 KPI(자기효능감·기술숙련도·수련만족도 사전사후) 수집·입력·CSV/report 계산 2차 | `app.learner_prepost_measure`, 학습자 본인용 `GET/PUT /users/me/prepost-measures`, `SessionReview`의 파일럿 증거 원장 카드가 3척도 pre/post 1~5 aggregate evidence를 저장·조회한다. `app.services.phase3_kpi_export`와 `scripts/export-phase3-kpi.py`는 원장 row를 Phase 3 evidence root의 `02-measures/prepost_measures.csv`와 `02-measures/kpi_report.json` scaffold로 산출한다. participant id는 가명화하고, 3척도 paired normalized mean pre/post/delta, complete/missing pair를 계산한다. 2차에서는 `phase3_kpi_contract.py`가 KPI metric 이름·필수키·`computed_prepost`/`design_pending` status 값을 소유해 exporter/checker/test의 drift를 줄이고, 계산 가능한 pre/post evidence와 평가설계 전 미계산 KPI를 report 안에서 구분한다. | 공식 문항 확정, 실험/통제군 배정, 추이 시각화, 통계검정 종류/alpha/결측 처리, 실제 20명 evidence와 steward/legal/IAA 검수. 현재 API/UI/export는 공식 효과성·성적·수료 판정이 아니라 파일럿 evidence 계산이다. (κ/ICC·환각률은 doc4 미명시 → 평가설계 확정.) | doc4(20명 실험/통제군·단회기 50분·3척도 pre-post) | | **H2** | 턴별 fast-loop + 라이브 코칭 1차 가동·학습자 리뷰 2열 UI 1차 완료·골든셋 잔여 | `make_eval_hook`이 submit/voice 생성 경로에 주입되고, stream은 `_evaluate_stream_turn`으로 fast-loop 평가를 붙인다. 결과는 `feedback_scores`, `alternative_utterance` 등 정규화 테이블에 적재·hydrate된다. 추가로 `app/services/live_coach.py`, `POST/GET /sessions/{id}/live-coach`, `POST /kb/live-coach/source-packs/sync`, `app.live_coach_events`, `data/kb/live_coaching_workbook_0615.json`, `data/kb/live_coaching_sources/*.json`을 연결해 워크북·DSM·공식 지침 요약 기반 코칭 아바타 말풍선·근거 모달·발화별 이력 오버레이와 RAG 증분 색인을 제공한다. `app.services.source_pack_sync`가 repo source pack의 active `content_hash`를 비교하고 변경 시 document version을 최신+1로 올린다. live turn은 프로세스 로컬 source pack snapshot을 재사용하지만, 관리자 sync/CLI는 `refresh=True`로 캐시를 비운 뒤 repo 파일을 다시 읽어 stale `content_hash` 비교를 막는다. 학습자 `SessionReview` 데스크톱은 좌측 축어록 타임라인, 우측 요약·감정·흐름·루브릭·강점·개선점·pre/post·워크시트·피드백 작업열의 2열 구조로 재배치했다. | 원천 축어록 few-shot 골든셋 적재, 임상팀 확정 루브릭과 source pack 임상 검수 상태 운영정책 보강. | doc2·doc5(골드 포맷) | | **H3** | 임상팀 콘텐츠 입력 경로(페르소나 저작 CRUD) 2차 구현·원문 격리 정책 1차·항목형 목록 저작/프롬프트 검토 UI 완료·임상 검수 잔여 | draft 생성·조회·편집·검수요청 API와 교수 콘솔 JSON 초안 패널은 연결됐다. `persona_repository.py`는 in-code `SEED_PERSONAS`(P1~P3)와 `data/personas/P4.json`~`P7.json`을 `PersonaCard`로 합쳐 `materialize_seed_personas()`와 seed fallback catalog에 포함한다. `scripts/materialize-persona-seeds.py`는 같은 seed manifest를 dry-run 기본으로 보고하고, `--apply`에서만 DB pool을 초기화한 뒤 기존 idempotent DB materializer를 호출한다. `scripts/sync-persona-sources.py`는 DB-backed dry-run/apply runner로 repo-managed source pack의 `content_hash`/document version을 비교한다. RAG 기반 draft 생성은 source/chunk evidence와 함께 `persona-draft-rag@2026-06-28.1` prompt bundle id/version/hash를 engine metadata 및 draft `source_provenance`에 남긴다. `POST /personas/sources`는 raw 원문 hash-only 증거를 `kb.raw_source_artifact`에 따로 기록하고, sanitized 파생본만 evaluator-only RAG chunk로 색인한다. `rag.index_document()`는 `sensitivity=3` 또는 raw marker chunk를 DB 접근 전에 차단한다. `app/persona_read_model.py`는 catalog/review/draft/source/evidence DTO와 mapper를 route에서 분리해 OpenAPI schema 이름을 유지한다. PersonaStudio는 자동사고, 회기 시나리오, 말투 filler/verbal tic/nonverbal cue, 역린·금기 응답·금기어를 행 추가/삭제 UI로 편집하고, 저장 직전 빈 항목을 제거하되 기존 배열 schema를 유지한다. 프롬프트 탭은 raw JSON textarea 대신 L1 카드·인적 범주·임상 배경·말투·수치 파라미터·역린·회기 시나리오·추가 계약 섹션으로 같은 draft 데이터를 검토하게 한다. | 루브릭·이론 콘텐츠 외부화, P4~P7 포함 임상팀 최종 검수/서면 evidence 확보, 암호화 blob/vault 기반 원문 실저장. | doc3(R&R)·doc4(페르소나=전문가 산출물) | | **H4** | PII 마스킹 한국어 로컬 휴리스틱 + optional ko recognizer adapter + 15-case fixture/schema 평가 harness + 온보딩·동의 게이트 잔여 | Presidio `language='en'` 고정 한계를 보완하기 위해 정규식 폴백에 한국어 날짜·금액·행정구역 주소와 함께 이름/성명 라벨, 성씨+이름+조사/호칭, 대학교·학과·병원·센터 등 기관 suffix 기반 로컬 휴리스틱 마스킹을 추가했다. 66차에서는 `제 이름은 김서연입니다`, `보호자 이름은 박민수입니다`, `저는 최하늘입니다` 같은 자연 발화형 이름 라벨·자기소개 패턴을 추가하고 `이름은 중요하지 않다` negative control로 오탐을 막았다. Presidio가 설치돼도 한국어 누락을 막기 위해 fallback을 후단에 한 번 더 태운다. 70차에서는 `guardrail.mask_pii()` 내부에 선택형 한국어 PII recognizer adapter 경계를 추가했다. adapter는 import-time hard dependency가 아니며 명시 등록 전에는 비활성이고, 실패해도 기존 regex fallback이 마지막 안전망으로 유지된다. fake adapter 테스트는 regex가 못 잡는 별명/기관 span을 `[NAME]`/`[ORG]`로 마스킹하고 같은 문장의 전화번호는 후단 regex가 `[PHONE]`으로 처리하는지, adapter 실패 시에도 fallback이 유지되는지 검증한다. 상담 생성(generate/stream), fast evaluator prompt, client turn `text_masked`에서 한국어 NAME/ORG raw 값이 남지 않도록 회귀화했다. `app.services.pii_masking_eval`, `data/privacy/pii-masking-ko-fixtures.json`, `scripts/evaluate-pii-masking.py`로 합성 fixture 15케이스를 NAME/ORG/PHONE/EMAIL/RRN/NUMID/DATE/MONEY/ADDR/negative-control 범위에서 entity recall·forbidden substring removal·unexpected entity violation으로 평가한다. `data/privacy/pii-masking-eval-input.schema.json`과 `data/privacy/pii-masking-eval-report.schema.json`은 source/category/severity metadata와 summary-only `technical_dry_run` 리포트 계약을 고정하며, 기본 CLI JSON은 `masked_text`/`forbidden_remaining` 원문 증거를 제외한다. `소속`/`안내`/`이름` NAME 오탐도 stopword로 보정했다. 외부 LLM 호출은 상담 생성(generate/stream)·fast/deep 평가 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재하도록 연결했고, prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log` 3행 증가를 확인했다. `app_user`에 이름·소속·학과·학년/직위·연락처·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 버전 필드를 추가했고, 로그인 직후 `/onboarding` 완료 전에는 역할 홈과 learner 회기 시작을 막는다. 아바타 이미지는 `/users/me/avatar`에서 MIME/시그니처/3MB 제한 후 파일 저장소에 두고 URL만 보관한다. 온보딩 저장 시 learner `consent_at`도 함께 세팅하며 auth E2E에서 신규 계정 온보딩→아바타 업로드→학습자 홈 이동을 검증했다. | 실제 ko recognizer 모델/provider 선정, 운영 말뭉치 기반 오탐/미탐 평가, 미성년/guardian 및 법무 검토가 필요한 최종 서명 동의서·개인정보 처리방침·약관 evidence 확보. 공개 Google OAuth 실제 `/turn` proof는 별도 운영 게이트. | doc1/2/5(실명·날짜·미성년·자살시도 다수)·doc4(IRB·개인정보) | @@ -154,12 +154,13 @@ npm run e2e # Playwright — web+api+DB 스택 필요 - OCR 잔재·오탈자 존재(doc1·doc5). 의미는 시각 보정했으나 일부 표현 불확실. - **doc4 κ/ICC·환각률은 신청서 본문 미명시** — 계약 확정 지표로 단정 금지. - doc4/doc3 행정 불일치(참여교수 1명 vs 2명, 서식 연도 '2025' 오기, 연구책임자 표기 불일치, 트웬티온스 성명 공란). -- '현재상태'는 grep/코드 사실 기반 적대적 비평 인용에서 시작했으나, **C1·C2·C3·H1·H2·H3·M1 provider_events 보존/taxonomy/인증 리뷰 파생 칩·M2 case memory/pinned_fact/history/명시철회 contradiction/episodic embedding writer/다음턴 주입회귀/digest contract-only·M3, H4 로컬 마스킹/optional ko adapter/fixture 평가/감사/온보딩 경로, X1 dry-run export, X2 비용 관측·evaluator routing/cache/hit-rate 관측·모델별 비용 리포트, L1 engine gateway contract/golden/schema/Node conformance runner/session/persona read-model 분리는 구현 후 재검증 완료** 기준이다. M2는 fallback digest와 `digest_pending` 응답 계약까지이며, LLM digest worker 실행·품질평가·재압축은 완료로 보지 않는다. 다만 H4의 실제 ko recognizer 모델/provider 선정과 운영 corpus 평가, guardian/legal evidence, 공개 OAuth `/turn` proof, M1 실제 provider 기반 한숨·울음 감지는 여전히 게이트로 남아 있다. L1 외부 거버넌스는 FastAPI 유지 사유와 Node 전환 계획의 제출/등재 증거가 남아 있다. +- '현재상태'는 grep/코드 사실 기반 적대적 비평 인용에서 시작했으나, **C1·C2·C3·H1 pre/post 원장·KPI export·metric status contract·H2·H3·M1 provider_events 보존/taxonomy/인증 리뷰 파생 칩·M2 case memory/pinned_fact/history/명시철회 contradiction/episodic embedding writer/다음턴 주입회귀/digest contract-only·M3, H4 로컬 마스킹/optional ko adapter/fixture 평가/감사/온보딩 경로, X1 dry-run export, X2 비용 관측·evaluator routing/cache/hit-rate 관측·모델별 비용 리포트, L1 engine gateway contract/golden/schema/Node conformance runner/session/persona read-model 분리는 구현 후 재검증 완료** 기준이다. H1은 파일럿 evidence 계산과 report 계약까지이며, 공식 효과성 판정·통계해석·20명 evidence는 완료로 보지 않는다. M2는 fallback digest와 `digest_pending` 응답 계약까지이며, LLM digest worker 실행·품질평가·재압축은 완료로 보지 않는다. 다만 H4의 실제 ko recognizer 모델/provider 선정과 운영 corpus 평가, guardian/legal evidence, 공개 OAuth `/turn` proof, M1 실제 provider 기반 한숨·울음 감지는 여전히 게이트로 남아 있다. L1 외부 거버넌스는 FastAPI 유지 사유와 Node 전환 계획의 제출/등재 증거가 남아 있다. --- ### 변경 이력 - 2026-06-28: M2 8차 digest contract-only 경계와 focused 74 passed 검증 기준 반영. +- 2026-06-28: H1 2차 KPI metric status contract와 focused 10 passed 검증 기준 반영. - 2026-06-28: H4 optional ko recognizer adapter 경계와 focused 47 passed 검증 기준 반영. - 2026-06-26: 초판. `docs/ops/source-docs-gap-analysis-2026-06-26.md`와 SSOT 대시보드 "원천문서 갭 분석" 섹션을 요약·인덱스화. diff --git a/docs/ops/backlog-2026-06-26.md b/docs/ops/backlog-2026-06-26.md index cd7d5ab..6f0e5f3 100644 --- a/docs/ops/backlog-2026-06-26.md +++ b/docs/ops/backlog-2026-06-26.md @@ -71,7 +71,7 @@ ## Phase 3 파일럿 게이트 (실참여자 필요) -- [ ] 20명 교육용 파일럿 운영 / 효과성·KPI 측정(SUS·자기효능감·κ/ICC·환각률) / 재귀학습 데이터셋 approved 산출 / 개인정보·동의 감사. 문서/checker와 dry-run exporter는 준비됨(`docs/phase3/*`, `scripts/check-phase3-artifacts.py`, `scripts/export-recursive-dataset.py`). 2026-06-27 checker 강화로 CSV enum, KPI metric required fields, approved export의 PII pass·κ/ICC·withdrawn exclusion·consent scope·file sha256 false-positive 차단을 추가했다. 2026-06-28에는 3척도 pre/post aggregate evidence 원장 `app.learner_prepost_measure`, 학습자 본인용 `GET/PUT /users/me/prepost-measures`, `SessionReview` 입력 UI를 추가했고, `app.services.phase3_kpi_export`/`scripts/export-phase3-kpi.py`로 `prepost_measures.csv`와 `kpi_report.json` scaffold를 생성한다. participant id는 가명화하고 3척도 paired normalized delta, complete/missing pair를 계산한다. 공식 문항 확정, 통계 검정, 실험/통제군 배정, 실제 approved evidence는 파일럿·steward/legal 검수·IAA 통과 시점. **검증: `python -B -m py_compile app/services/phase3_kpi_export.py app/test_phase3_kpi_export.py scripts/export-phase3-kpi.py`, `python -B -m pytest -p no:cacheprovider app/test_user_support_tickets.py app/test_runtime_policy.py app/test_admin_ops.py app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q` 45 passed, `python scripts/export-phase3-kpi.py --help`.** +- [ ] 20명 교육용 파일럿 운영 / 효과성·KPI 측정(SUS·자기효능감·κ/ICC·환각률) / 재귀학습 데이터셋 approved 산출 / 개인정보·동의 감사. 문서/checker와 dry-run exporter는 준비됨(`docs/phase3/*`, `scripts/check-phase3-artifacts.py`, `scripts/export-recursive-dataset.py`). 2026-06-27 checker 강화로 CSV enum, KPI metric required fields, approved export의 PII pass·κ/ICC·withdrawn exclusion·consent scope·file sha256 false-positive 차단을 추가했다. 2026-06-28에는 3척도 pre/post aggregate evidence 원장 `app.learner_prepost_measure`, 학습자 본인용 `GET/PUT /users/me/prepost-measures`, `SessionReview` 입력 UI를 추가했고, `app.services.phase3_kpi_export`/`scripts/export-phase3-kpi.py`로 `prepost_measures.csv`와 `kpi_report.json` scaffold를 생성한다. participant id는 가명화하고 3척도 paired normalized delta, complete/missing pair를 계산한다. `phase3_kpi_contract.py`는 KPI metric 이름·필수키·`computed_prepost`/`design_pending` status 값을 공유해 exporter/checker/test drift를 줄이고, checker는 status unknown 값을 error로 처리한다. 공식 문항 확정, 통계 검정, 실험/통제군 배정, 실제 approved evidence는 파일럿·steward/legal 검수·IAA 통과 시점. **검증: `py -3.11 -X utf8 -m py_compile apps\api\app\services\phase3_kpi_contract.py apps\api\app\services\phase3_kpi_export.py apps\api\app\test_phase3_kpi_export.py apps\api\app\test_phase3_artifact_checker.py scripts\check-phase3-artifacts.py scripts\export-phase3-kpi.py`, `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q` 10 passed, `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_user_support_tickets.py app/test_runtime_policy.py app/test_admin_ops.py app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q` 46 passed, `py -3.11 -X utf8 scripts\export-phase3-kpi.py --help`, `py -3.11 -X utf8 scripts\check-phase3-artifacts.py --help`.** --- diff --git a/scripts/check-phase3-artifacts.py b/scripts/check-phase3-artifacts.py index e4ea084..70e151b 100644 --- a/scripts/check-phase3-artifacts.py +++ b/scripts/check-phase3-artifacts.py @@ -13,6 +13,18 @@ from dataclasses import dataclass from pathlib import Path from typing import Any +REPO_ROOT = Path(__file__).resolve().parents[1] +API_ROOT = REPO_ROOT / "apps" / "api" +sys.path.insert(0, str(API_ROOT)) + +from app.services.phase3_kpi_contract import ( # noqa: E402 + KPI_METRIC_REQUIRED_KEYS, + KPI_METRIC_STATUSES, + KPI_REPORT_PATH, + PHASE3_KPI_METRICS, + PREPOST_CSV_PATH, +) + FORBIDDEN_HEADER_TERMS = { "address", @@ -116,7 +128,7 @@ CSV_SPECS = ( ), ), CsvSpec( - "02-measures/prepost_measures.csv", + PREPOST_CSV_PATH, ("participant_id", "measure_name", "timepoint", "score", "collected_at"), ), CsvSpec( @@ -131,18 +143,6 @@ CSV_SPECS = ( REQUIRED_MARKDOWN = ("04-privacy/privacy_audit.md",) -KPI_METRICS = { - "embedding_consistency", - "hallucination_rate", - "icc", - "inter_rater_kappa", - "pilot_completion", - "self_efficacy_prepost", - "session_completion", - "sus", - "top1", -} - KPI_REPORT_KEYS = { "cohort_size", "exclusions", @@ -154,21 +154,6 @@ KPI_REPORT_KEYS = { "source_window", } -KPI_METRIC_REQUIRED_KEYS = { - "denominator", - "method", - "numerator", - "pass", - "source_files", - "status", - "threshold", - "value", -} -KPI_METRIC_STATUSES = { - "computed_prepost", - "design_pending", -} - MANIFEST_KEYS = { "agreement", "anonymization", @@ -317,7 +302,7 @@ def read_json(path: Path, report: Report) -> dict[str, Any] | None: def validate_kpi_report(root: Path, report: Report) -> None: - rel_path = "02-measures/kpi_report.json" + rel_path = KPI_REPORT_PATH path = root / rel_path if not path.exists(): report.error(f"missing required file: {rel_path}") @@ -334,7 +319,7 @@ def validate_kpi_report(root: Path, report: Report) -> None: if not isinstance(metrics, dict): report.error(f"{rel_path}: missing object key 'metrics'") return - missing = sorted(KPI_METRICS - set(metrics)) + missing = sorted(set(PHASE3_KPI_METRICS) - set(metrics)) if missing: report.error(f"{rel_path}: missing metric keys: {', '.join(missing)}")