# HUTAMS — 지능형 무전 관제 플랫폼 아키텍처 문서

> **H**eavy-rail **U**nified **T**raffic **A**nalysis & **M**onitoring **S**ystem  
> LTE-R 기반 무전 통신을 실시간으로 수집·전사·분석하는 AI 관제 플랫폼

---

## 목차

1. [프로젝트 개요](#1-프로젝트-개요)
2. [전체 파이프라인 다이어그램](#2-전체-파이프라인-다이어그램)
3. [디렉토리 구조](#3-디렉토리-구조)
4. [핵심 파이프라인 상세](#4-핵심-파이프라인-상세)
5. [API 엔드포인트 스펙](#5-api-엔드포인트-스펙)
6. [환경변수(.env) 설정법](#6-환경변수env-설정법)
7. [서버 구동 & 운영 가이드](#7-서버-구동--운영-가이드)

---

## 1. 프로젝트 개요

| 항목 | 내용 |
|---|---|
| **언어** | Python 3.11+ |
| **웹 프레임워크** | FastAPI 0.115 + Uvicorn |
| **STT 엔진** | faster-whisper (`large-v3-turbo`, INT8 양자화) |
| **LLM** | Qwen3 0.8B GGUF (llama-cpp-python, GPU 가속) |
| **DB** | SQLite (whisper.db via SQLAlchemy) |
| **프론트엔드** | Vanilla JS + Bootstrap 5 |
| **실시간 통신** | WebSocket (FastAPI native) |

---

## 2. 전체 파이프라인 다이어그램

```
[ 오디오 소스 ]
   │  Line-in / 마이크 (mic 모드)
   │  로컬 wav 파일     (mock 모드)
   ↓
[ RadioListener / MockAudioListener ]  ← 백그라운드 데몬 스레드
   │  VAD (RMS 임계치 기반 음성구간 감지)
   │  침묵 1.8초 지속 → 녹음 종료 → 임시 .wav 생성
   ↓
[ app/services/stt_service.py :: WhisperSTTService ]
   │  faster-whisper → full_text + segments[] 생성
   │  domain_dict.post_process_correction() (RapidFuzz 교정)
   │  HARDCODED_FIXES (신호질로→신호 진로 등)
   │  guess_speaker (룰 정규식 → LLM 체이닝 fallback)
   ↓
[ app/services/llm_service.py :: LocalLLMService ]
   │  Qwen3 0.8B GGUF
   │  제목 / 요약 / 키워드 / 긴급도 추출
   │  _parse_response() — <think> 태그 제거 후 정규식 파싱
   ↓
[ SQLite (whisper.db) ]
   │  TranscriptionRecord — LLM 메타 포함
   │  TranscriptionSegment — 화자 뱃지 포함
   ↓
[ WebSocket ws_manager.broadcast() ]
   ↓
[ 브라우저 (index.html) ]
   ├── Live Mode: 타자기 효과 즉시 렌더링
   └── Test Mode: 업로드 → REST POST → 동일 렌더링
```

---

## 3. 디렉토리 구조

```
Wisper/
├── app/
│   ├── core/
│   │   ├── config.py          # Pydantic Settings (환경변수 관리)
│   │   ├── dictionary.py      # 철도 도메인 사전 + 하드코딩 교정
│   │   └── exceptions.py      # 도메인 예외 클래스
│   ├── db/
│   │   ├── database.py        # SQLAlchemy 엔진 + SessionLocal
│   │   └── models.py          # ORM 모델 (TranscriptionRecord, TranscriptionSegment)
│   ├── models/
│   │   ├── stt.py             # STTRequest / STTResponse / STTSegment Pydantic 모델
│   │   └── record.py          # RecordListResponse 등 API 응답 모델
│   ├── routers/
│   │   └── records.py         # GET /api/v1/records 라우터
│   ├── services/
│   │   ├── audio_listener.py      # RadioListener (실제 마이크 VAD 감청)
│   │   ├── audio_parser.py        # pydub 포맷 변환 + 무음 제거
│   │   ├── llm_service.py         # LocalLLMService (Qwen3 GGUF 래퍼)
│   │   ├── mock_audio_listener.py # MockAudioListener (파일 스트리밍 시뮬레이터)
│   │   └── stt_service.py         # WhisperSTTService + guess_speaker()
│   ├── static/
│   │   └── audio/             # 영구 저장된 무전 오디오 파일 (/static/audio/{filename})
│   ├── templates/
│   │   └── index.html         # 대시보드 SPA (Bootstrap 5 + Vanilla JS)
│   ├── main.py                # FastAPI 앱 진입점 / lifespan / WS 매니저
│   └── .env                   # 로컬 환경변수 오버라이드
├── whisper.db                 # SQLite 데이터베이스
└── ARCHITECTURE.md            # 이 문서
```

---

## 4. 핵심 파이프라인 상세

### 4.1 VAD (음성 활동 감지)

| 파라미터 | 기본값 | 설명 |
|---|---|---|
| `THRESHOLD` | 600 | RMS 볼륨 임계치 (0~32767 범위) |
| `SILENCE_LIMIT` | 1.8s | 침묵 지속 시간 초과 시 발화 종료 판정 |
| `PRE_ROLL_SECS` | 0.3s | 발화 시작 직전 버퍼 (첫 음절 잘림 방지) |
| `RECORD_TIMEOUT` | 30s | 단일 녹음 최대 시간 (무한 루프 방지) |

### 4.2 화자 분리 (Speaker Diarization)

1차: **룰 기반 정규식** (`guess_speaker`)
- "전철 OO" 패턴 → 관제
- 숫자 + "열차" 패턴 → 열차
- 짧은 응답어 + Gap ≥ 1.5s → 교차 할당 휴리스틱

2차: **LLM 체이닝 Fallback** (`guess_speaker_with_llm`)
- 룰에서 "미상" 반환 시 Qwen3 0.8B 추론 (max_tokens=256)
- `<think>` 블록 제거 후 "관제" / "열차" 키워드 파싱

### 4.3 LLM 메타데이터 추출

```
system: "철도 관제 무전 분석 시스템. 지정된 포맷으로만 답변."
user:   1-Shot 예시 → 실제 무전 텍스트
format: 제목: ... / 요약: ... / 키워드: ... / 긴급도: 긴급|일반
```

---

## 5. API 엔드포인트 스펙

| Method | Path | 설명 |
|---|---|---|
| `GET` | `/` | 대시보드 HTML |
| `POST` | `/api/v1/transcribe` | 오디오 파일 업로드 → STT + LLM 분석 |
| `GET` | `/api/v1/records` | 이력 목록 조회 (`?limit=50&skip=0&keyword=검색어`) |
| `GET` | `/static/audio/{filename}` | 저장된 무전 오디오 스트리밍 |
| `WS` | `/api/v1/ws/live` | 실시간 감청 결과 WebSocket 구독 |

### POST /api/v1/transcribe 요청 형식

```
Content-Type: multipart/form-data
Fields:
  audio        : UploadFile  (m4a, mp3, wav 등)
  language     : str         (기본: "ko")
  base_datetime: ISO8601 str (선택, 예: 2026-03-07T09:00:00+09:00)
```

### WebSocket 수신 메시지 포맷

```json
{
  "type": "stt_result",
  "text": "좌천 하선 궤도 검측차 신호 진로 확인...",
  "title": "좌천 하선 신호 진로 확인",
  "summary": "궤도 검측차가 신호 진로를 확인하고 통과함.",
  "keywords": "좌천 하선, 검측차, 신호, 통과",
  "urgency": "일반",
  "language": "ko",
  "segments": [
    {
      "start_sec": 8.11,
      "end_sec": 41.12,
      "text": "좌천 하선 있는 궤도 검측차는...",
      "speaker": "관제"
    }
  ]
}
```

---

## 6. 환경변수(.env) 설정법

`app/.env` 파일(또는 프로젝트 루트 `.env`)에 아래 항목을 설정:

```ini
# ─── Whisper ─────────────────────────────────────
WHISPER_MODEL_NAME=large-v3-turbo

# ─── LLM ─────────────────────────────────────────
LLM_ENABLED=true
LLM_MODEL_PATH=./Qwen3.5-0.8B-Q4_K_M.gguf
# LLM_ENABLED=false 로 설정 시 LLM 분석 전체 스킵

# ─── 실시간 감청 소스 ─────────────────────────────
# "mock" : 로컬 파일 시뮬레이션 (개발/테스트용)
# "mic"  : 서버 PC 실제 마이크/Line-in (상용 운영용)
AUDIO_SOURCE=mock
MOCK_AUDIO_PATH=./sample1.m4a
```

---

## 7. 서버 구동 & 운영 가이드

### 개발 서버 기동

```powershell
# 프로젝트 루트에서
.\.venv\Scripts\uvicorn.exe app.main:app --host 127.0.0.1 --port 28000
```

### Mock 시뮬레이션 모드 확인 절차

1. `.env`에서 `AUDIO_SOURCE=mock` 설정 (기본값)
2. `MOCK_AUDIO_PATH=./sample1.m4a` 경로 확인
3. 서버 기동 후 `http://127.0.0.1:28000` 접속
4. 우측 상단 **Live Mode** 토글 ON
5. 서버 로그에서 `🧪 [Mock] 발화 감지`, `📡 WebSocket broadcast` 확인
6. 브라우저 우측 화면에 타자기 효과로 무전 내용이 실시간 렌더링됨

### 실제 마이크 감청 전환 방법

```ini
# app/.env
AUDIO_SOURCE=mic
```

재기동 시 `🎙️ RadioListener(실마이크) 기동` 로그 확인 후,  
Line-in 단자에 무전 수신기 연결 → Live Mode 토글 ON으로 즉시 감청 시작.
