# QA Log — CVOT-MACEAdjudicate-Kor

빌드 일자: 2026-05-26
도메인: DM | 카테고리: 인체실험 도구 (RCT/observational study CEC 운영)
빌드 경로: `projects/2026-05-26-1-cvot-mace-adjudicate-kor/`

## 1. Python 구문 체크
```
python3 -c "import ast; [ast.parse(open(f).read()) for f in ['main.py', 'app.py', 'modules/ingest.py', 'modules/classify.py', 'modules/adjudicate.py', 'modules/sensitivity.py']]"
```
결과: **PASS** (AST OK, exit 0)

## 2. CLI --help 출력
```
python3 main.py --help
```
결과: **PASS** — argparse 결과가 깔끔히 출력됨. 무거운 의존성은 main() 내부에서 lazy-import 하므로 deps 미설치 시에도 --help는 동작함.

## 3. 데이터 파일 샘플 로드
```
python3 -c "import csv; list(csv.DictReader(open('data/synthetic_packets.csv')))[:3]"
```
결과: **PASS** — 40건 packet, 첫 3건 정상 로드.
- packets: 40
- troponin: 110 timepoints
- adjudications: 85 (40 events × 2 + 5 third-reader)

## 4. CLI demo 실행
```
python3 main.py --demo
```
결과: **PASS**

- ingest: 40 packets, charter=DEMO-CVOT-001, include_type2_mi=False
- classify:
  - MI: Type 1 = 12, not MI = 4, Type 2 (excluded) = 6
  - Stroke: Ischemic = 6, Hemorrhagic = 2
  - HF: HHF = 6
  - CV_death: fatal_MI = 4
  - 합계 40 ✅
- adjudicate: paired=40, discordance=0.15, overall kappa=0.821
  (요구사항 ~15% 불일치율과 일치)
- sensitivity:
  - 3p-MACE (Type2 MI in)  : N=30
  - 3p-MACE (Type2 MI out) : N=24  ← 차이 6 = Type 2 MI 수와 일치 ✅
  - 4p-MACE (Type2 MI in)  : N=36
  - 4p-MACE (Type2 MI out) : N=30
  - 5p-MACE 동일 (이 데모에는 UA hospitalization 사건 없음 → 4p와 동일)
- report: `reports/run.docx` 생성됨 (python-docx OK)

## 5. 빌드 중 발견된 이슈 & 수정

### 이슈 A: Type 1 MI 12건이 전부 "Type 4a"로 분류됨
- **원인**: discharge_summary에 "PCI performed"(치료) 키워드가 들어 있어
  `_PCI_KEYWORDS` 분기가 먼저 트리거되었음. UDM 2018에서 Type 4a는
  *peri-procedural* MI이며, 자발성 MI에 PCI 치료가 따른 경우는 여전히 Type 1.
- **수정**: `modules/classify.py`에서 peri-PCI 키워드를
  ("peri-PCI", "during PCI", "post-procedural", "procedural MI", "stent placement",
  "post-pci elevation")로 좁히고, plaque rupture/STEMI 증거가 있으면 Type 1로 가도록 가드 추가.

### 이슈 B: Type 2 MI in/out 결과가 동일
- **원인**: 분류기가 Type 2를 "Type 2 (excluded)"로 라벨링했지만,
  sensitivity의 `_MI_LABELS_BROAD` set에 그 문자열이 없어 broad 집계에서도 제외됨.
- **수정**: `modules/sensitivity.py` `is_mi()`에서 include_type2=True 일 때
  "Type 2 (excluded)" 라벨도 Type 2로 환원해서 카운트하도록 조정.

이상 2건 수정 후 재실행 시 정상 분포 확인.

## 6. 검수 결과 종합

| 항목 | 결과 |
|---|---|
| AST 구문 체크 | ✅ |
| CLI --help | ✅ |
| 데이터 CSV 샘플 로드 | ✅ |
| `--demo` end-to-end 실행 | ✅ |
| .docx 리포트 생성 | ✅ |
| 의학 디스클레이머 (README + app.py + main.py) | ✅ |
| 외부 네트워크 호출 없음 | ✅ |
| Offline standalone | ✅ |
| 합성 데이터 분포 (Type1 30%, Type2 15%, SCD 10%, IS 15%, HS 5%, HHF 15%, noise 10%) | ✅ |
| Adjudicator 불일치율 ~15% | ✅ (0.15) |

**종합: ✅ PASS**

## 7. 의도 부합 점검

요청 vs 결과:
- ✅ event packet ingest + de-identification + charter 매핑 → `modules/ingest.py`
- ✅ UDM 2018 MI Type 1/2/3/4a/4b/4c/5 자동 보조 분류 → `modules/classify.py:classify_mi`
- ✅ AHA/ASA stroke (ischemic/hemorrhagic/SAH/TIA) → `classify_stroke`
- ✅ ESC HF (BNP/NT-proBNP/diuretic/LOS) → `classify_hf`
- ✅ CV death 인과성 카테고리 → `classify_cv_death`
- ✅ 2-reader paired blinded + 3rd-reader/panel routing → `modules/adjudicate.py`
- ✅ Cohen's kappa, drift dashboard (per-quarter), turnaround, workload
- ✅ 3p/4p/5p × Type2 in/out sensitivity grid → `modules/sensitivity.py`
- ✅ DSMB-ready interim summary + DOCX export
- ✅ Streamlit standalone app
- ✅ CLI `--demo` 모드
- ✅ 합성 데이터 (40건) 포함, 외부 네트워크 호출 없음

추가 구현 (요청에 없었으나 강건성 향상을 위해 추가):
- 의존성 없을 때 docx 대신 markdown으로 fallback 출력
- charter YAML 미존재 시 DEFAULT_CHARTER fallback
- pandas/streamlit 미설치 시 친절한 안내 메시지 후 종료

## 8. 재현 방법

```bash
cd "projects/2026-05-26-1-cvot-mace-adjudicate-kor"
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

# CLI
python3 main.py --demo

# Streamlit UI
streamlit run app.py
```

리포트 출력: `reports/run.docx` (또는 docx 미설치 시 `reports/run.md`).
