229 lines
8.1 KiB
Markdown
229 lines
8.1 KiB
Markdown
# 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으로 즉시 감청 시작.
|