C-001 임상 검토 게이트 보강
This commit is contained in:
parent
391fb9f4d0
commit
6988280b30
18 changed files with 1896 additions and 67 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import unittest
|
||||
|
||||
|
|
@ -9,33 +10,101 @@ from .paths import repo_path
|
|||
from .services import guardrail
|
||||
|
||||
|
||||
def _load_clinical_review_checker():
|
||||
checker_path = repo_path("scripts", "check-clinical-crisis-review.py")
|
||||
spec = importlib.util.spec_from_file_location("clinical_crisis_review_checker", checker_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"임상 검토 checker를 불러올 수 없다: {checker_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class ClientReplyQualityGateTest(unittest.TestCase):
|
||||
def test_crisis_protocol_manifest_preserves_external_clinical_approval_gate(self) -> None:
|
||||
def test_crisis_protocol_manifest_obeys_status_specific_external_review_contract(self) -> None:
|
||||
manifest = json.loads(
|
||||
repo_path("data", "clinical", "crisis-protocol-validation.json").read_text(
|
||||
encoding="utf-8",
|
||||
),
|
||||
)
|
||||
case_set = json.loads(
|
||||
repo_path("data", "clinical", "p1-crisis-review-cases.json").read_text(
|
||||
encoding="utf-8",
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(manifest["technical_status"], "verified")
|
||||
self.assertEqual(manifest["clinical_status"], "pending_external_review")
|
||||
status = manifest["clinical_status"]
|
||||
status_decisions = {
|
||||
"pending_external_review": None,
|
||||
"approved": "approved",
|
||||
"conditional": "conditional",
|
||||
"rejected": "rejected",
|
||||
}
|
||||
self.assertIn(status, status_decisions)
|
||||
required_approval_fields = {
|
||||
"reviewer",
|
||||
"organization",
|
||||
"reviewed_at",
|
||||
"decision",
|
||||
"notes",
|
||||
"evidence_ref",
|
||||
"evidence_sha256",
|
||||
"reviewed_protocol_version",
|
||||
"reviewed_case_set_sha256",
|
||||
}
|
||||
approval = manifest["approval"]
|
||||
self.assertTrue(required_approval_fields.issubset(approval))
|
||||
for field in required_approval_fields:
|
||||
self.assertIsNone(approval[field])
|
||||
technical_gate_text = " ".join(manifest["technical_gates"])
|
||||
for field in required_approval_fields:
|
||||
self.assertIn(f"approval.{field}", technical_gate_text)
|
||||
authorities = {source["authority"] for source in manifest["sources"]}
|
||||
self.assertEqual(authorities, {"SAMHSA", "NIMH", "대한민국 보건복지부"})
|
||||
self.assertEqual(set(approval), required_approval_fields)
|
||||
self.assertEqual(approval["decision"], status_decisions[status])
|
||||
assessments = [case["reviewer_assessment"] for case in case_set["cases"]]
|
||||
if status == "pending_external_review":
|
||||
self.assertTrue(all(value is None for value in approval.values()))
|
||||
self.assertTrue(
|
||||
all(value is None for assessment in assessments for value in assessment.values()),
|
||||
)
|
||||
else:
|
||||
completed_fields = required_approval_fields - {"notes"}
|
||||
self.assertTrue(all(approval[field] for field in completed_fields))
|
||||
self.assertTrue(all(assessment["decision"] for assessment in assessments))
|
||||
self.assertTrue(all(assessment["rationale"] for assessment in assessments))
|
||||
self.assertTrue(all(assessment["reviewed_at"] for assessment in assessments))
|
||||
|
||||
source_ids = {source["source_id"] for source in manifest["sources"]}
|
||||
self.assertEqual(
|
||||
source_ids,
|
||||
{"samhsa_safe_t", "nimh_youth_outpatient_bssa", "mohw_109", "nice_ng225"},
|
||||
)
|
||||
self.assertGreaterEqual(len(manifest["review_sequence"]), 6)
|
||||
self.assertGreaterEqual(len(manifest["technical_gates"]), 5)
|
||||
self.assertGreaterEqual(len(manifest["technical_gates"]), 6)
|
||||
self.assertIn("외부 임상 검토와 승인을 대신하지 않는다", manifest["external_review_boundary"])
|
||||
|
||||
checker = _load_clinical_review_checker()
|
||||
self.assertEqual(checker.validate_review_contract(repo_root=repo_path()), [])
|
||||
|
||||
def test_synthetic_crisis_review_cases_contain_only_technical_invariants(self) -> None:
|
||||
case_set = json.loads(
|
||||
repo_path("data", "clinical", "p1-crisis-review-cases.json").read_text(
|
||||
encoding="utf-8",
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
case_set["content_safety"],
|
||||
{
|
||||
"synthetic_only": True,
|
||||
"method_or_means_detail": "forbidden",
|
||||
"clinical_answer_included": False,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
{case["case_id"] for case in case_set["cases"]},
|
||||
{f"P1-CRISIS-{index:03d}" for index in range(1, 7)},
|
||||
)
|
||||
for case in case_set["cases"]:
|
||||
with self.subTest(case_id=case["case_id"]):
|
||||
self.assertFalse(case["synthetic_scenario"]["method_or_means_detail_present"])
|
||||
self.assertGreaterEqual(len(case["technical_invariants"]), 1)
|
||||
self.assertNotIn("clinical_answer", case)
|
||||
|
||||
def test_crisis_protocol_technical_gates(self) -> None:
|
||||
counselor_inquiry = guardrail.classify_crisis(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue