kcg-ai-monitoring/docs/prediction-analysis.md
htlee b37e18d952 docs: prediction-analysis 신규 + 루트/SFR 문서 drift 해소
- docs/prediction-analysis.md 신설 — opus 4.7 독립 리뷰 기반 prediction 구조/방향 심층 분석
  (9개 섹션: 아키텍처·5분 사이클·17 알고리즘·4대 도메인 커버리지·6축 구조 평가·개선 제안 P1~P4·임계값 전수표)
- AGENTS.md / README.md — V001~V016→V030, Python 3.9→3.11+, 14→17 알고리즘 모듈
- docs/architecture.md — /gear-collision 라우트 추가 (26→27 보호 경로)
- docs/sfr-traceability.md — V029→V030, 48→51 테이블, SFR-10 에 GEAR_IDENTITY_COLLISION 추가
- docs/sfr-user-guide.md — 어구 정체성 충돌 페이지 섹션 신설
- docs/system-flow-guide.md — 노드 수 102→115, V030 manifest 미반영 경고
- backend/README.md — "Phase 2 예정" 상태 → 실제 운영 구성 + PR #79 hotfix 요구사항 전면 재작성
2026-04-17 11:20:53 +09:00

22 KiB
Raw Blame 히스토리

Prediction 모듈 심층 분석 — 구조·방향 리뷰

대상: prediction/ (Python 3.11+, FastAPI, APScheduler, 59 .py 파일) 작성일: 2026-04-17 작성 관점: opus 4.7 독립 리뷰 — 정밀도 튜닝이 아닌 방향성·코드 구조 전제: 프로토타입·데모 단계. 정밀도 미흡은 인지된 상태.


TL;DR — 3줄 요약

  1. 뼈대는 튼튼하다. 레이어 분리(algorithms/pipeline/output/db/cache), 순수함수 위주 알고리즘, 카테고리별 dedup 윈도우 분리까지 프로토타입치고는 일관된 설계.
  2. 약한 고리는 오케스트레이터. scheduler.py run_analysis_cycle() 한 함수가 700+ 라인, 지역 try/except + logger.warning 로 흡수된 실패가 많아 "어디서 깨졌는지 조용히 묻힌다". 상태 누적(모듈 전역 _transship_pair_history)도 여기 묶여 있음.
  3. 커버리지 매트릭스는 4/4 이지만 UI 비대칭. prediction 이 생산하는 결과 중 ILLEGAL_FISHING_PATTERN 이벤트·환적 의심은 DB·백엔드까지 도달하지만 전용 detection UI 가 없다. prediction 품질 개선과 무관하게 운영자가 쓸 수 없는 상태.

권고 최우선 3가지 — 신규 알고리즘 추가보다 아래가 선행:

  • P1: 사이클 스테이지 단위 에러 경계(_stage(...) 유틸)로 교체해 실패 스테이지 명시 로깅 + 부분 실패 시에도 후속 단계 진행
  • P1: 하드코딩 임계값(MID prefix, 커버리지 박스, SOG band, 11 pattern 점수) 을 correlation_param_models 패턴처럼 DB/config 로 외부화
  • P1: 환적 전용 + ILLEGAL_FISHING_PATTERN 전용 프론트 페이지 추가 — 이미 DB·API 는 있음

1. 아키텍처 레이어 — 책임과 결합도

prediction/
├── config.py          Pydantic Settings + qualified_table() — SSOT 설정
├── scheduler.py       APScheduler + run_analysis_cycle() (단일 엔트리)
├── main.py            FastAPI app + /health /status /chat 등
├── fleet_tracker.py   상태 보유 (선단 레지스트리, 어구 정체성)
├── time_bucket.py     안전 지연 12분 + backfill 3 버킷
├── algorithms/        17개 모듈 — 순수함수 중심
├── pipeline/          8개 모듈 — 7단계 분류 파이프라인
├── output/            5개 모듈 — event/violation/kpi/stats/alert
├── db/                4개 모듈 — snpdb / kcgdb / signal_api / partition_manager
├── cache/             vessel_store.py — 24h sliding window 인메모리
├── chat/              Ollama + RAG 스텁
├── models/            result.py — AnalysisResult dataclass
├── data/              monitoring zones JSON 정적 설정
└── tests/             time_bucket / gear_parent_episode / gear_parent_inference 3종
레이어 책임 결합도 평가
config.py 환경 + SQL identifier 검증 SSOT, qualified_table() 로 스키마 주입 공격 방지
algorithms/ 탐지 로직 (순수) 대부분 df, params -> dict/tuple. 상호 의존 적음
pipeline/ 7단계 sequential orchestrator.ChineseFishingVesselPipeline.run() 이 DF 를 그대로 파이프
output/ 룰 엔진 + DB write 룰을 lambda 리스트(event_generator.RULES)로 선언적 관리
db/ Connection pool + SQL ⚠️ kcgdb.upsert_results(results) 가 한 트랜잭션에 전부 묶임 (파티션 unique index 활용은 적절)
cache/vessel_store.py 전역 싱글턴, 24h 궤적 인메모리 ⚠️ 모듈 싱글턴 → 테스트 시 mock 어려움
fleet_tracker.py 레지스트리·어구 정체성 상태 ⚠️ 싱글턴 + 모듈 전역 캐시
scheduler.py 전체 오케스트레이션 700+ 라인 모놀리식 — 아래 §2 상세

2. 5분 사이클 시퀀스 — scheduler.py:80-804

사이클 전체가 한 함수 안에서 9단계로 진행된다.

단계 라인 역할 실패 처리
1. 증분 로드 97-106 snpdb.fetch_incremental() → vessel_store merge 전체 try/except 포함
2. 정적 보강 108-112 signal-batch API 호출 전체 try/except
3. 대상 선별 114-128 SOG/COG 계산 + 0건 시 조기 return 조기 반환
4. 파이프라인 122-128 ChineseFishingVesselPipeline.run() 전체 try/except
5. 선단 분석 131-177 fleet_tracker + gear_identity 충돌 감지 ⚠️ 내부 try/except 로 warning, 전진
5.5. 어구 그룹·상관·부모 추론 181-229 polygon_builder + gear_correlation ⚠️ 내부 try/except, 결과 없이 진행
5.9. 쌍끌이 후보·판정 231-303 pair_trawl STRONG/PROBABLE/SUSPECT ⚠️ 내부 try/except
6. 개별 선박 분석 305-515 AnalysisResult 생성 (파이프라인 통과자) 루프 내 continue
6.5. 경량 분석 523-682 파이프라인 미통과 중국 MID — compute_lightweight_risk_score 루프 내 try/except
7. 환적 의심 685-713 detect_transshipment + _transship_pair_history 누적 전체 try/except
8. DB upsert 716-717 kcgdb.upsert_results() + cleanup_old(48h) 전체 try/except
9. 출력 모듈 720-745 violation_classifier → event_generator → kpi_writer → aggregate_hourly/daily → alert_dispatcher ⚠️ 5개 단계를 한 try/except 로 묶음 → 어디서 실패했는지 단일 warning 으로 흡수
10. 채팅 컨텍스트 캐싱 748-791 Redis 전체 try/except

구조적 관찰

  • 전체 try/except 는 있다 (97 ~ 803) — 치명 실패가 다음 사이클을 막지는 않음
  • 그러나 내부 스테이지가 너무 무겁다. 9번째 출력 단계가 5개 모듈을 한 덩어리로 묶어 logger.warning('output modules failed (non-fatal): %s', e) 로 흡수. 어느 모듈이 깨졌는지 디버깅하려면 stacktrace 를 logger.exception 으로 바꿔야 함
  • Lazy import 가 스테이지마다 반복 (from output.event_generator import ... 등). 시작 시간 단축에는 도움이지만 import 오류가 첫 사이클 실행 시점에만 드러남 — 배포 후 5분 지연 발견 경험 다수

권고 (사이클 구조 재정비)

def _stage(name: str, fn, *args, required=False, **kwargs):
    t0 = time.time()
    try:
        result = fn(*args, **kwargs)
        logger.info('stage %s ok in %.2fs', name, time.time() - t0)
        return result
    except Exception as e:
        logger.exception('stage %s failed: %s', name, e)
        if required:
            raise
        return None
  • 각 스테이지를 _stage('pair_detection', _run_pair_detection, ...) 로 감싸면 실패 스테이지 명시 로깅 + stacktrace + 부분 실패 허용 정책을 일관화.
  • 9번 단계의 5개 모듈은 각각 별도 _stage(...) 호출로 쪼갤 것.

3. 알고리즘 카탈로그 (17 모듈 × 담당 도메인)

파일 주 역할 입력 출력 주요 상수
dark_vessel.py AIS gap + 11 패턴 점수화 선박 DF(timestamp, lat, lon, sog, cog) (score 0~100, patterns[], tier) GAP_SUSPICIOUS=6000s, VIOLATION=86400s, KR_COVERAGE box
spoofing.py 텔레포트·극속·BD09 오프셋 선박 DF spoofing_score 0~1 EXTREME_SPEED=50kn (주석 기준), fishing max=25kn
risk.py 종합 risk + 경량 risk 2종 DF, zone, is_permitted, 외부 점수 (risk 0~100, level) tier: 70+=CRITICAL, 50+=HIGH, 30+=MEDIUM
fishing_pattern.py UCAF/UCFT gear SOG band DF, gear (ucaf, ucft) PT 2.5~4.5, OT 2.0~4.0, GN 0.5~2.5
transshipment.py 5단계 필터 파이프라인 DF targets, pair_history, zone_fn list of dict PROXIMITY ~220m, RENDEZVOUS 90min, WATCH 제외
location.py zone 분류, haversine_nm, BD09 (lat, lon) zone, dist_nm 12/24 NM 밴드
gear_correlation.py 멀티모델 EMA + streak vessel_store, gear_groups, conn UPDATE gear_correlation_scores α_base=0.30, polygon=0.70
gear_identity.py 공존 쌍 추출 (V030/PR #73) gear_signals collisions[] CRITICAL_DIST=50km, COEXIST=3회
gear_parent_inference.py 어구 → 모선 assignment gear_groups + tracks parent 후보 + confidence 2-pass: direct-match → candidates
gear_parent_episode.py 에피소드 delineation (first_seen~last_seen) gear_signals 시계열 episodes[] gap tolerance
gear_violation.py G-01~G-06 통합 판정 (DAR-03) DF + zone + pair_result + permits g_codes[], evidence, score G-06=20pts, G-02=18pts, G-01=15pts
gear_name_rules.py 어구 이름 정규표현식 string parent_code (Optional) regex set
pair_trawl.py 쌍끌이 tier 분류 DF×2, 6h (pair_detected, tier, pair_type) PROXIMITY=0.27NM, MIN_SYNC=2h
track_similarity.py DTW 궤적 유사도 DF×2 0~1 -
fleet.py leader/follower/independent DF, tracker role -
polygon_builder.py gear group convex hull vessel_store, companies 스냅샷[] 시간버킷
vessel_type_mapping.py fishery_code → vessel_type 폴백 fishery_code 'TRAWL'/'PT'/... -

관찰:

  • 대부분 순수 함수 → 재사용·단위테스트 용이. 단 gear_correlation.run_gear_correlationconn 을 받아 DB 를 직접 UPDATE 함 (알고리즘 + I/O 혼재)
  • 상수가 모듈 상단에 모여 있는 것은 좋으나 config 외부화는 안 됨. 현장 운영자가 임계값을 바꾸려면 코드 배포 필요

4. 4대 도메인 커버리지 매트릭스

도메인 담당 파일 접근 방식 구조적 강점 구조적 공백
Dark Vessel dark_vessel.py + risk.py + compute_dark_suspicion gap(100분 임계) + 11 패턴 점수 + 4-tier (CRITICAL≥70) 패턴 분리(P1~P11) + 한국 커버리지 감점(50pt)으로 자연 gap 구분 MID/커버리지 박스 하드코딩. 감점 로직은 탐지 누락 위험(coverage box 바깥=자연 gap 가정) — 중국 EEZ 해역 근접 공해에서 의도적 OFF 가 감점 맞을 수 있음
Gear Tracking gear_correlation + gear_identity + gear_parent_inference + polygon_builder 어구 이름 regex → 공존/교체 분기 + 멀티모델 EMA + 모선 추론 + 폴리곤 공존(GEAR_IDENTITY_COLLISION)을 1순위 증거로 재정의 (V030). SAVEPOINT 로 PK 충돌 격리 어구 신호는 이름 regex 의존. 이름이 GEAR_PATTERN 에 맞지 않으면 파이프라인 전체 진입 실패
Chinese Fishing pipeline/ 7단계 + MID 412/413/414 필터 preprocess → behavior → resample → feature → classify → cluster → seasonal + rule-based 분류 룰 + 군집으로 해석 가능 MID prefix 하드코딩 2곳 중복. 전체 인구(55k)대비 500척만 파이프라인 통과 — 경량 분석(§6)으로 보완하나 여전히 <10%
Illegal Pattern gear_violation (G-01~G-06) + event_generator.RULES (15+ 룰) + transshipment 5단계 임계값 기반 독립 룰 × 카테고리별 dedup 윈도우 룰이 lambda 리스트로 선언적 UI 미노출 — DB prediction_events 카테고리 ILLEGAL_* 는 생산되나 전용 detection UI 없음. 운영자는 EventList(/event-list) 에서만 조회. 환적도 동일 문제

5. 코드 구조 평가 (6축)

평가 근거
관심사 분리 B+ algorithms / pipeline / output / db 레이어는 깔끔. 단 scheduler 는 오케스트레이터가 아니라 메가 함수
재사용성 A- 17 알고리즘 모듈 중 ~15개가 순수함수. run_gear_correlation 만 conn 혼재
테스트 가능성 C+ unit test 3개만 (time_bucket / gear_parent_episode / gear_parent_inference). vessel_store / fleet_tracker 싱글턴 → integration test 어려움
에러 격리 C 사이클 전체 try/except + 내부 지역 try/except 혼재. 출력 5모듈이 한 덩어리 → 실패 지점 특정 불가
동시성 A- ThreadedConnectionPool(1,5), max_instances=1 스케줄러 — 단일 프로세스 가정 하에서 안전
설정 가능성 C- 임계값 대부분 파일 상수. correlation_param_models 패턴만 DB 기반 (예외)

주목할 잘된 점

  • Dedup 윈도우 카테고리별 차등 (event_generator.py:26-39) — 5분 boundary 집단 만료를 피하기 위해 33/67/89/127/131/181/367분 등 의도적으로 5의 배수 회피. 룰 기반 탐지의 대표적인 "튜닝 knob" 이 코드에 명시.
  • gear_identity 공존/교체 분기 (fleet_tracker.py 트랜잭션 설계) — SAVEPOINT 로 부분 실패를 사이클 전체 abort 와 분리. 이전 13h 공백 사고의 재발 방지 설계가 구조에 반영됨.
  • Lightweight path — 파이프라인 통과 못한 중국 MID 를 경량 경로로 계속 커버 (scheduler.py:523-682). "정밀 vs 커버리지" 를 두 경로로 나누는 의사결정 자체는 탁월.

구조적 채무

  • 환경 분기 부재: config.py 에 dev/prod 분기가 없음. .env 파일 하나에 의존. 로컬 실행 시 운영 DB 를 건드리는 위험 (config.py:8-22)
  • 상태 있는 모듈 전역 변수: _transship_pair_history, _last_run, _scheduler (scheduler.py:16-26). 테스트 격리 어렵고, 재시작 시 pair 누적 상태 증발
  • DB 쓰기 산재: kcgdb.upsert_results / save_group_snapshots / gear_correlation UPDATE / gear_identity UPSERT / prediction_events INSERT 가 서로 다른 트랜잭션. 한 사이클 원자성 X — 의도적일 수 있으나 명시 설계 문서 없음

6. 방향성 진단 — 프로토타입 → MVP → 운영

지금 강점

  • 룰 기반 탐지를 탄탄히 다져둔 토대 — 임계값이 드러나 있고 dedup 설계가 명시적. 향후 ML overlay 를 얹을 때 "어디에 얹을지" 가 명확 (dark_suspicion score, transship score, gear_violation score 가 모두 연속값으로 산출)
  • 운영자 의사결정 통합 설계 — V030 GEAR_IDENTITY_COLLISION 에서 status(OPEN/REVIEWED/CONFIRMED_ILLEGAL/FALSE_POSITIVE) 가 severity 재계산을 억제하는 패턴 — 사람 loop back 이 설계된 유일한 자리. 다른 도메인에도 이 패턴 확장 가능

지금 약점

  • ML 부재 — sklearn/torch 없음. 2026 기준 프로토타입으로는 적절하나, sequence anomaly (dark gap 의 시계열 반복 패턴) 나 behavioral classifier 는 룰만으론 한계
  • 하드코딩 지대: MID prefix(4개소), KR coverage box, SOG band, 11 패턴 점수, 5단계 transship 임계 — 모두 "이 프로젝트에서 튜닝해야 할 핫스팟" 인데 DB/config 분리 안 됨
  • UI 비대칭: prediction_events.category='ILLEGAL_FISHING_PATTERN' 이 생산되지만 전용 UI 없음. 환적도 동일. 결과적으로 prediction 이 만드는 가치의 일부가 운영자에게 도달하지 못함
  • 테스트 빈곤: 17 알고리즘 중 3개만 유닛테스트. 사이클 단위 integration test 전혀 없음 — 사이클 회귀가 항상 운영 로그로만 드러남 (13h 공백 사고가 대표 사례)

7. 구조적 개선 제안 (우선순위별)

P1 — 지금 해야 할 것 (운영 안정성)

  1. 사이클 스테이지 단위 에러 경계_stage(name, fn, required=False) 유틸로 9번 출력 5모듈을 쪼갤 것. logger.exception 으로 stacktrace 보존. required=Truefetch_incremental 같은 fatal 에만 적용 → 실패 시 조기 반환
  2. 임계값 외부화correlation_param_models 패턴을 확장해 detection_params 테이블 신설 (algo_name, param_key, value, active_from, active_to). 배포 없이 해상도 튜닝 가능. 운영자 권한으로 접근 시 감사 로그
  3. ILLEGAL_FISHING_PATTERN 전용 페이지 + 환적 전용 페이지 — 백엔드 API·DB 는 이미 존재. 프론트만 GearCollisionDetection 패턴으로 추가 (PageContainer + DataTable + Badge intent)
  4. 사이클 부분 원자성 명시 — DB 쓰기 경계 문서화 (어디까지가 한 트랜잭션인지). 최소한 architecture.md 또는 신설 docs/prediction-transactions.md 에 다이어그램

P2 — 다음 (품질 확보)

  1. 알고리즘 유닛테스트 커버리지 — 17 모듈 중 최소 10 개 (dark_vessel 11 패턴 / transshipment 5단계 / gear_violation 6 G-code / spoofing / risk) 에 fixture 기반 테스트. tests/fixtures/ 에 AIS DF CSV 샘플
  2. DB fixture integration test — testcontainers-python 으로 PostgreSQL 띄워 한 사이클 실행 + 결과 테이블 assert. CI 에서 돌릴 수 있도록 데이터 10 척 x 1h 정도 경량
  3. vessel_store / fleet_tracker 의존성 주입 개편 — 모듈 싱글턴 → AnalysisContext dataclass 로 명시 주입. 테스트 mock 가능
  4. MID prefix·커버리지 box 를 monitoring_zones JSON 연장 — 이미 data/monitoring_zones.json 이 있음. 동일 포맷으로 mid_prefixes.json / kr_coverage.json 추가

P3 — 중기 (가치 확장)

  1. ML overlay 타겟 설정 — dark_suspicion score (11 패턴 합산) 은 classifier training target 으로 최적. GCN/Transformer 로 "gap 시퀀스가 의도적인가" 를 학습. 룰 유지 + 게이트만 ML 로 대체 (shadow mode 로 비교)
  2. correlation 파라미터 MLOps 연동correlation_param_models 를 MLflow 로 실험 기록 → 성능 좋은 모델 자동 active 전환
  3. AIS 벤치마크 데이터셋 — 한중어업협정 906척 중 과거 단속 이력 있는 선박을 positive label. 현재 매칭률 53%+ 이므로 샘플 확보 가능. tier 별 precision/recall 산출

P4 — 장기 (스케일)

  1. multi-process / async — APScheduler + 단일 스레드 한계. 현 55k 선박 / 2.3M points / 110초 사이클에서 8k 중국 증가 + 한국 확장 시 5분 주기 내 완료 불가 예측. asyncio + ray / dask 로 스테이지 병렬
  2. Event bus 분리 — 지금은 prediction_events INSERT 가 동기. outbox 패턴으로 비동기 분리 시 백엔드/프론트 실시간 push 기반 (WebSocket) 로 진화 가능

8. 부록 — 임계값 전수표 (외부화 우선순위)

위치 상수 현재값 P1 외부화 비고
scheduler.py:28 _KR_DOMESTIC_PREFIXES ('440','441') 한국 MID
scheduler.py:140,247 중국 MID prefix '412' '413' '414' 하드코딩 2곳 mid_prefixes.json
scheduler.py:256 pair pool SOG band 1.5 <= mean_sog <= 5.0 조업 속력대
dark_vessel.py:6-8 GAP 임계 3종 6000/10800/86400s 100분/3h/24h
dark_vessel.py:11-12 _KR_COVERAGE_LAT/LON 32.0~39.5 / 124.0~132.0 AIS 수신 박스
dark_vessel.py:257-359 11 패턴 점수 P1~P11 10~30 pt 분산 ⚠️ 탐지 정책 튜닝 대상
event_generator.py:26-39 DEDUP_WINDOWS 12 카테고리 33~367분 ⚠️ 이미 의도적
config.py:25-37 파이프라인 주기 등 5/24/60/30/12/3 env .env 로 이미 가능
transshipment.py PROXIMITY / RENDEZVOUS 0.002deg / 90min 환적 민감도
pair_trawl.py PROXIMITY / SOG_Δ / COG_Δ / MIN_SYNC 0.27NM / 0.5kn / 10° / 2h ⚠️ tier 재분류 기준

P1: 배포 없이 튜닝 가능해야 할 것 ⚠️: 튜닝 자체가 탐지 정책 변경 → 릴리즈 노트 필요


9. 관련 파일 인덱스

연관 운영 문서:


변경 이력

일자 내용
2026-04-17 초판 — opus 4.7 독립 리뷰. 구조/방향 중심 + 우선순위별 개선 제안