[데이터파이프라인] VOC 분류 시스템 - AI 기반 고객 피드백 자동 분류
목적
-
빌딩 관리 서비스에서 수집되는 고객 VOC(Voice of Customer) 데이터를 AI 기반으로 자동 분류(VOC주제, 작업유형)
VOC 원문 주제 대분류 주제 중분류 작업유형 대분류 작업유형 중분류 화장실이 너무 더러워요 환경 청결/미화 작업 청소/정리 에어컨이 안 켜져요 시설 냉난방/공조 작업 유지보수 주차장 조명이 너무 어두워요 시설 전기/조명 작업 교체 -
각 빌딩에서 월간 VOC 현황 보고서를 HTML로 다운로드할 수 있게 한다 (통계 및 워드 클라우드 포함 대시보드)
개요
- 매월 1일 cron 배치로 전월 VOC 데이터 분석 → 자동 태깅 → HTML 리포트 생성 → S3 업로드
- 자동 태깅 결과를 Google Sheets에 업로드하여, 비개발자인 도메인 전문가(빌딩 관리자)가 직접 검수할 수 있도록 한다
- 드롭다운 메뉴로 분류 체계(taxonomy) 선택 → 오분류 항목만 수정
- 코드 수정 없이 브라우저에서 바로 검수 가능
- 검수 완료 후 refresh 모드로 HTML 재생성하여 검수 결과 즉각 반영
- 축적된 검수 데이터로 매월 AI 모델 재학습 → 자동 태깅 정확도 지속 개선 (Human-in-the-loop)
전체 아키텍처
%%{init: {'flowchart': {'nodeSpacing': 30, 'rankSpacing': 40}}}%%
flowchart TB
subgraph DataSource["데이터 소스"]
DB[(PostgreSQL<br/>VOC 테이블)]
TAX[(voc_taxonomy<br/>분류 체계)]
end
subgraph Training[" "]
direction TB
TTitle[["학습 파이프라인<br/>(nlp_model_ml.py)"]]
T1[검수 완료 데이터 로드]
T2[텍스트 전처리<br/>Tokenization]
T3[KoBERT 파인튜닝<br/>주제/작업유형 분류]
T4[모델 저장<br/>S3 + Local]
TTitle ~~~ T1
T1 --> T2 --> T3 --> T4
end
subgraph Inference[" "]
direction TB
ITitle[["추론 파이프라인<br/>(nlp_model.py)"]]
I1[VOC 데이터 추출]
I2[파인튜닝 모델 로드]
I3[텍스트 전처리]
I4[자동 태깅<br/>주제/작업유형 분류]
I5[태깅 CSV 저장]
ITitle ~~~ I1
I1 --> I3 --> I4 --> I5
I2 --> I4
end
style TTitle fill:#e1f5fe,stroke:#01579b
style ITitle fill:#fff3e0,stroke:#e65100
subgraph Batch["월간 배치 오케스트레이터 (run_monthly.py)"]
direction TB
B1[EventBridge<br/>매월 1일 02:00]
B2[run_monthly.py]
B3{실행 모드?}
B1 --> B2 --> B3
end
subgraph ModeFull["mode: full"]
F1[nlp_model.py<br/>자동 태깅]
F2[keyword_analysis.py<br/>HTML 생성]
F3[S3 업로드]
F4[검수시트 업로드]
F1 --> F2 --> F3 --> F4
end
subgraph ModeRefresh["mode: refresh"]
R1[검수시트에서<br/>검수 데이터 다운로드]
R2[S3에서<br/>기존 태깅 CSV 다운로드]
R3[검수 데이터 병합<br/>reviewed CSV 생성]
R4[keyword_analysis.py<br/>HTML 재생성]
R5[S3 업로드<br/>HTML 덮어쓰기]
R1 --> R3
R2 --> R3
R3 --> R4 --> R5
end
subgraph Storage["저장소"]
S3[(S3<br/>tagged CSV<br/>dashboard HTML<br/>models)]
GS[Google Sheets<br/>검수 스프레드시트]
end
subgraph Review["검수 워크플로우"]
RV1[자동 태깅 결과 업로드]
RV2[검수자 수동 검수]
RV3[검수완료 = Y]
RV1 --> RV2 --> RV3
end
%% 데이터 흐름
DB --> I1
T4 --> I2
B3 -->|full| ModeFull
B3 -->|refresh| ModeRefresh
F3 --> S3
F4 --> GS
GS --> RV1
RV3 -->|학습 데이터로 활용| T1
RV3 -->|refresh 트리거| R1
R1 --> GS
R2 --> S3
R5 --> S3
핵심 흐름:
- 파인튜닝: 검수 완료 데이터 →
nlp_model_ml.py→ KoBERT 파인튜닝 및 저장 - 추론 (full 모드): VOC 원천 데이터 →
nlp_model.py→ 자동 태깅 → HTML 생성 → S3/검수시트 업로드 - 검수 반영 (refresh 모드): 검수시트 + S3 기존 CSV → 병합 → HTML 재생성 → S3 덮어쓰기
- 검수: Google Sheets에서 검수 → 검수 데이터 축적 → 다음 파인튜닝에 반영
프로젝트 구조
voc-nlp/
├── batch/
│ ├── run_monthly.py # 배치 오케스트레이터 (메인)
│ ├── nlp_model_ml.py # KoBERT 파인튜닝 (Training)
│ ├── nlp_model.py # 파인튜닝 모델로 태깅 (Inference)
│ ├── nlp_model_core.py # 전처리/분류 핵심 함수
│ ├── keyword_analysis.py # 키워드 분석 + 시각화
│ ├── report_html.py # HTML 리포트 렌더링
│ ├── s3_uploader.py # S3 업로드 모듈
│ ├── gspread_manager.py # Google Sheets 연동
│ ├── common_db.py # DB 연결 공통 모듈
│ └── cron_monthly.sh # cron 실행 스크립트
├── models/ # 학습된 모델 저장
│ ├── kobert_subject/ # 주제 분류 모델
│ └── kobert_work/ # 작업유형 분류 모델
├── output/
│ ├── tagging/ # 태깅 결과 CSV
│ ├── html/ # 대시보드 HTML
│ └── reviewed/ # 검수 반영 CSV
└── requirements.txt
실행 모드
run_monthly.py는 3가지 모드를 지원한다.
| 모드 | 설명 | 처리 순서 |
|---|---|---|
full |
전체 처리 (기본) | 태깅 → HTML → S3 → 검수시트 |
refresh |
검수 반영 | 검수시트 다운로드 → 기존CSV 병합 → HTML 재생성 → S3 |
tagging-only |
태깅만 | 태깅 → 검수시트 (HTML 생성 안함) |
사용 예시:
# [모델 파인튜닝] 검수 데이터로 KoBERT 파인튜닝
python nlp_model_ml.py --train --months 202512
# [1단계] 자동태깅 + HTML + 검수시트 업로드
python run_monthly.py --mode full --all-buildings --auto-month
# [2단계] 검수 완료 후 HTML 재생성
python run_monthly.py --mode refresh --all-buildings --auto-month
# 개발 환경에서 특정 빌딩 테스트
python run_monthly.py --env dev --mode full --building-id 95 --year 2025 --month 12
S3 경로 구조
s3://hdcl-csp-prod/stat/voc/
├── {yyyymm}/{building_id}/
│ ├── tagged_{building_id}_{yyyymm}_{run_id}.csv
│ └── dashboard_{building_id}_{yyyymm}_{run_id}.html
└── models/
├── kobert_subject_{version}/
│ ├── config.json
│ ├── pytorch_model.bin
│ └── label_mapping.json
└── kobert_work_{version}/
Human-in-the-loop 워크플로우
flowchart LR
subgraph Step1["1. 추론 (full)"]
A1[nlp_model.py]
A2[KoBERT자동 태깅]
end
subgraph Step2["2. 검수"]
B1[Google Sheets]
B2[수동 검수완료]
end
subgraph Step3["3. 반영 (refresh)"]
C1[HTML 재생성]
C2[검수된결과로 리포트]
end
subgraph Step4["4. 학습"]
D1[nlp_model_ml.py]
D2[KoBERT파인튜닝정확도↑]
end
Step1 --> Step2 --> Step3 --> Step4
Step4 -.->|다음 월| Step1
월간 운영 사이클:
- 매월 1일:
full모드 자동 실행 → KoBERT 자동 태깅 + HTML 생성 + 검수시트 업로드 - 매월 1~10일: 검수자가 Google Sheets에서 드롭다운으로 태깅 검수/수정
- 매월 10일 이후:
refresh모드 실행 → 검수 반영된 HTML 재생성 - 매월 20일: 축적된 검수 데이터로 KoBERT 재학습
cron 스케줄 설정
월간 배치 - full 모드 (매월 1일 02:00 KST):
0 2 1 * * /home/ssm-user/jupyter/batch/cron_monthly.sh >> /home/ssm-user/jupyter/logs/cron_monthly.log 2>&1
월간 배치 - refresh 모드 (매월 15일 02:00 KST):
0 2 15 * * /home/ssm-user/jupyter/batch/cron_refresh.sh >> /home/ssm-user/jupyter/logs/cron_refresh.log 2>&1
월간 KoBERT 파인튜닝 (매월 20일 04:00 KST):
0 4 20 * * /home/ssm-user/jupyter/batch/cron_train.sh >> /home/ssm-user/jupyter/logs/cron_train.log 2>&1
환경 설정
.env 파일:
# DB
DB_HOST=your-db-host
DB_PORT=5432
DB_NAME=your-db-name
DB_USER=your-user
DB_PASSWORD=your-password
# S3
S3_BUCKET=hdcl-csp-prod
S3_PREFIX=stat/voc
# 경로
BASE_DIR=/home/ssm-user/jupyter
OUT_DIR=/home/ssm-user/jupyter/output
LOG_DIR=/home/ssm-user/jupyter/logs
MODEL_DIR=/home/ssm-user/jupyter/models
모델 성능 모니터링
학습 시 성능 지표를 기록하고, 정확도 추이를 모니터링한다.
# 학습 결과 예시
{
"version": "20260116",
"train_samples": 1523,
"subject": {
"accuracy": 0.8742,
"num_labels": 24
},
"work": {
"accuracy": 0.8156,
"num_labels": 18
}
}
검수 데이터가 축적될수록 KoBERT 파인튜닝 품질이 향상되며, 자동 태깅 정확도가 개선되어 검수 부담이 줄어드는 선순환 구조를 목표로 한다.