세션 평가·라이브코치·교수자 분석 라운드 마감 + 문서 정리 + 코드품질 리팩터
- 누적 작업트리 커밋: 회기 평가 복구·durable 저장, 라이브 코치 이력/근거, 교수자 학생분석, 음성 비언어 메타, PII 마스킹, 운영 티켓/헬스 등 - 문서: 완료 기록 docs/archive/ 냉동 보관, docs/ 단일 인덱스(docs/README.md)+통합 TODO(docs/TODO.md)로 정리 - 리팩터(행위 보존): Stage enum SSOT(taxonomy 소유·state_machine re-export), store recent/masked_turns 중복 제거, speaker_ko_label 단일 헬퍼, _list_sessions N+1 제거(state/turns 배치 + 턴평가 하이드레이션 배치) - 검증: 백엔드 pytest 352 passed, _list_sessions E2E chromium-single-run 2 passed
This commit is contained in:
parent
7c41c3ce79
commit
778e8526d4
108 changed files with 6457 additions and 455 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import importlib.util
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
|
@ -12,11 +14,34 @@ from app.services.dataset_export import (
|
|||
intraclass_correlation,
|
||||
scan_for_pii,
|
||||
sha256_file,
|
||||
validate_manifest_gate,
|
||||
write_jsonl,
|
||||
)
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
EXPORT_SCRIPT_PATH = REPO_ROOT / "scripts" / "export-recursive-dataset.py"
|
||||
|
||||
|
||||
def load_export_script():
|
||||
spec = importlib.util.spec_from_file_location("export_recursive_dataset", EXPORT_SCRIPT_PATH)
|
||||
assert spec is not None and spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class DatasetExportTests(unittest.TestCase):
|
||||
def test_exporter_query_requires_consent_and_client_visible_turns(self) -> None:
|
||||
exporter = load_export_script()
|
||||
query = exporter.TURN_QUERY
|
||||
|
||||
self.assertIn("u.consent_at IS NOT NULL", query)
|
||||
self.assertIn("'client' = ANY(t.visible_to)", query)
|
||||
self.assertNotRegex(query, r"\bt\.text\s*(?:,|AS\b)")
|
||||
self.assertNotIn("sc.text", query)
|
||||
|
||||
def test_build_dataset_record_uses_masked_text_and_pseudonymous_keys(self) -> None:
|
||||
keys = ExportKeyMaps()
|
||||
record = build_dataset_record(
|
||||
|
|
@ -45,6 +70,32 @@ class DatasetExportTests(unittest.TestCase):
|
|||
self.assertNotIn("22222222-2222-2222-2222-222222222222", blob)
|
||||
self.assertNotIn("raw text should never be exported", blob)
|
||||
|
||||
def test_build_dataset_record_drops_supervisor_comment_text(self) -> None:
|
||||
record = build_dataset_record(
|
||||
{
|
||||
"session_id": "11111111-1111-1111-1111-111111111111",
|
||||
"learner_id": "22222222-2222-2222-2222-222222222222",
|
||||
"persona_code": "P1",
|
||||
"stage": "rapport",
|
||||
"speaker": "client",
|
||||
"text_masked": "마스킹된 발화입니다.",
|
||||
"supervisor_comments": [
|
||||
{
|
||||
"kind": "note",
|
||||
"text": "김서연 010-1234-5678 raw supervisor note",
|
||||
"intent_deviation": {"severity": "low"},
|
||||
}
|
||||
],
|
||||
},
|
||||
item_index=1,
|
||||
export_manifest_id="phase3-rl-seed-test",
|
||||
keys=ExportKeyMaps(),
|
||||
)
|
||||
|
||||
self.assertEqual(record["supervisor_comments"], [{"kind": "note", "intent_deviation": {"severity": "low"}}])
|
||||
self.assertNotIn("김서연", str(record))
|
||||
self.assertNotIn("010-1234-5678", str(record))
|
||||
|
||||
def test_pii_scan_detects_identifiers_without_raw_samples(self) -> None:
|
||||
findings = scan_for_pii(
|
||||
{
|
||||
|
|
@ -89,6 +140,27 @@ class DatasetExportTests(unittest.TestCase):
|
|||
agreement={"kappa": 0.59, "icc": 0.74, "gold_status": "not_gold"},
|
||||
)
|
||||
|
||||
def test_manifest_gate_requires_withdrawal_and_recursive_consent_scope(self) -> None:
|
||||
manifest = {
|
||||
"export_status": APPROVED_EXPORT_STATUS,
|
||||
"pii_scan": {"status": "pass"},
|
||||
"agreement": {"kappa": 0.70, "icc": 0.75},
|
||||
"selection_criteria": {"include_withdrawn": True},
|
||||
"consent_scope": {"allowed_uses": ["education_quality_review"]},
|
||||
"approvals": {
|
||||
"data_steward": "steward",
|
||||
"legal_or_privacy_reviewer": "privacy",
|
||||
"technical_operator": "operator",
|
||||
"approved_at": "2026-07-01T00:00:00Z",
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
ValueError,
|
||||
"include_withdrawn must be false; recursive_learning_seed consent scope is required",
|
||||
):
|
||||
validate_manifest_gate(manifest)
|
||||
|
||||
def test_jsonl_hash_manifest_dry_run(self) -> None:
|
||||
record = {
|
||||
"schema": "phase3_dataset_item_v1",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue