# 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() — 태그 제거 후 정규식 파싱 ↓ [ 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) - `` 블록 제거 후 "관제" / "열차" 키워드 파싱 ### 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으로 즉시 감청 시작.