a4e29629fc
feat(detection): GEAR_IDENTITY_COLLISION 탐지 패턴 추가
...
동일 어구 이름이 서로 다른 MMSI 로 같은 5분 사이클에 동시 AIS 송출되는
공존 케이스를 신규 탐지 패턴으로 분리해 기록·분류한다. 부수 효과로
fleet_tracker.track_gear_identity 의 PK 충돌로 인한 사이클 실패도 해소.
Prediction
- algorithms/gear_identity.py: detect_gear_name_collisions + classify_severity
- fleet_tracker.py: 공존/교체 분기 분리, UPSERT helper, savepoint 점수 이전
- output/event_generator.py: run_gear_identity_collision_events 추가
- scheduler.py: track_gear_identity 직후 이벤트 승격 호출
Backend (domain/analysis)
- GearIdentityCollision 엔티티 + Repository(Specification+stats)
- GearIdentityCollisionService (@Transactional readOnly / @Auditable resolve)
- GearCollisionController /api/analysis/gear-collisions (list/stats/detail/resolve)
- GearCollisionResponse / StatsResponse / ResolveRequest (record)
DB
- V030__gear_identity_collision.sql: gear_identity_collisions 테이블
+ auth_perm_tree 엔트리(detection:gear-collision nav_sort=950) + 역할별 권한
Frontend
- shared/constants/gearCollisionStatuses.ts + catalogRegistry 등록
- services/gearCollisionApi.ts (list/stats/get/resolve)
- features/detection/GearCollisionDetection.tsx (PageContainer+Section+DataTable
+ 분류 액션 폼, design system SSOT 준수)
- componentRegistry + feature index + i18n detection.json / common.json(ko/en)
2026-04-17 06:53:12 +09:00
6fb0b04992
feat(prediction): 경량 분석 riskScore 해상도 개선 + vessel_type 매핑
...
경량 경로 선박 60.8%가 45점 고정으로 수렴하고 98.6%가 vessel_type
UNKNOWN 으로만 기록되던 문제를 해결한다.
riskScore (compute_lightweight_risk_score)
- dark_suspicion_score(0~100) 직접 반영: min(30, score*0.3)
- EEZ_OR_BEYOND 기선 근접도 가산 (12NM 내 +15, 24NM 내 +8)
- dark_history_24h 가산 (dark_suspicion_score 미반영 케이스만)
- 허가 이력 +20 → +8/+15 차등 (dark_suspicion_score 있을 때 이중계산 방지)
- gap_duration_min 4구간 차등 (fallback: 720m/180m/60m/30m)
vessel_type (신규 vessel_type_mapping.py)
- fleet_vessels fishery_code → VesselType 매핑:
PT/PT-S/OT → TRAWL, GN → GILLNET, PS → PURSE, FC → CARGO
- GILLNET / CARGO 2개 값 신규 추가 (기존 TRAWL/PURSE/LONGLINE/TRAP/UNKNOWN)
- scheduler.py 경량 경로에서 등록선은 매핑, 미등록선은 UNKNOWN 유지
배포 후 검증 (redis-211 15:15 사이클)
- risk_score 분포: 45점 60.8% → 0% (11~40 범위 고르게 분산)
- vessel_type: UNKNOWN 98.6% → 89.1% (886척이 구체 유형으로 전환,
TRAWL 518 / LONGLINE 171 / TRAP 78 / PURSE 73 / GILLNET 38 / CARGO 8)
- 412354335 샘플: 45 MEDIUM 고정 → 20 LOW (dss=40 × 0.3 + 축소 허가)
2026-04-16 15:19:55 +09:00
68e690d791
refactor(prediction): pair_trawl tier 분류 + join_key time_bucket 전환
...
두 가지 근본 버그를 동시에 해결:
1. Join key 버그 — raw AIS timestamp(ms 단위) inner join 은 두 선박 간 우연히
일치하는 확률이 거의 0. vessel_store._tracks 의 time_bucket(5분 리샘플)
컬럼을 우선 사용. _pair_join_key() 헬퍼로 fallback 지원.
2. AND 게이트 0건 문제 — 스펙 100%(2h 연속 + 500m + SOG 2-4 + sog_delta 0.5 +
cog 10°)를 전부 요구하면 실제 공조 페어를 놓침. Tier 분류로 재설계:
- STRONG : 스펙 100% (24 cycles, 기존 조건)
- PROBABLE: 800m / SOG 1.5-5 / sog_delta 1.0 / cog 20° / 12 cycles + 0.6 ratio
- SUSPECT : 동일 완화 조건 / 6 cycles + 0.3 ratio (플래그만)
G-06 판정은 STRONG/PROBABLE 만. SUSPECT 는 약한 신호로 노출.
거부 사유 카운터(REJECT_COUNTERS) + tier 카운트를 사이클별 로그 출력.
'조건이 엄격한건지 실제 페어가 없는건지' 원인 구분 가능.
피드백 메모리: feedback_detection_tier.md
2026-04-16 09:47:08 +09:00
f5374a5316
refactor(prediction): pair_trawl sog/cog on-demand 계산 + 중국 MID 413/414 추가
...
- pair_trawl._ensure_sog_cog(): _trajectory_similarity 진입 시 sog/cog 없으면
vessel_store._compute_sog_cog() 로 haversine 계산 (tracks + timestamp 만 있으면 OK)
- pool 을 vessel_store._tracks 전체(55k)로 원복: 한국 440xxx/러시아 273xxx 페어 탐색 가능
- base 필터 중국 MID 확장: 412 → 412/413/414 (본토/홍콩/마카오)
- df_targets groupby 우회 제거 (불필요한 결합)
2026-04-16 09:29:38 +09:00
1a065840bd
feat(prediction): G-02 금어기 + G-03 미등록 어구 탐지 추가 (A-4)
...
V029 fishery_permit_cn 스키마를 입력으로 하여 보류 중이던 G-02/G-03
판정 함수를 신설. classify_gear_violations() 시그니처에 permit_periods,
registered_fishery_code, observation_ts 매개변수 추가.
- G-02 (CLOSED_SEASON_FISHING, score 18): 관측 시각이 fishing_period_1/2
허가 기간 밖이면 금어기 조업
- G-03 (UNREGISTERED_GEAR, score 12): 감지 어구가 fishery_code 허용 어구
집합(PT→TRAWL/PT-S, GN→GILLNET, PS→PURSE, OT→TRAWL, FC→금지)에 없음
- fleet_tracker: _parse_period_range() 'YYYY/MM/DD - YYYY/MM/DD' 파서 +
get_permit_periods() + get_registered_fishery_code()
- violation_classifier: CLOSED_SEASON_FISHING / UNREGISTERED_GEAR judgment →
ILLEGAL_GEAR 카테고리 매핑
데이터 부재(permit_periods 빈 값, fishery_code 미등록) 시 판정 보류 → False.
검증 목표: 1시간 내 G-02/G-03 각 ≥ 1건
2026-04-16 09:12:07 +09:00
d817e4cbbf
perf(prediction): pair_trawl base 확장 + 임계 완화 (A-1)
...
- SIMILARITY_OBSERVE 0.50 → 0.45 (pool 확대 후 recall 확보)
- min_common_samples 6 → 4 (비분류 선박 샘플 부족 보정)
- base_mmsis에 조업 속력대(1.5~5.0kn) 중국 412* 전체 추가
(classifications 500척만으로 bbox 기점 부족 → 실제 공조 페어 누락)
검증 목표: 10분 사이클에서 pair_detected ≥ 1
2026-04-16 09:06:50 +09:00
8ff04a8cca
feat(prediction): DAR-03 탐지 로직 보강 + 한중어업협정 906척 레지스트리 적재
...
- V029: kcg.fishery_permit_cn 신규 테이블(연단위, permit_year+permit_no 복합 유니크) + fleet_vessels permit_year/fishery_code 컬럼
- load_fishery_permit_cn.py: xls → DB 적재 스크립트, 906척 + 497 신청인사 upsert
- G-04/G-05/G-06 Dead code 해결: classify_gear_violations 호출 연결, dir() 버그 제거
- find_pair_candidates: bbox 1차 + 궤적 유사도(location/sog_corr/cog_alignment) 2차, role 가점
- spoofing 산식 교체: 1시간 윈도우 + teleport 절대 가점 + extreme 50kn 단독 0.6 확정
- transshipment 선종 완화: shipTy 부분일치 + 412* FISHING 간주
- gear_code DB write 경로 신설 + fleet_tracker API 3개 추가
- cron 스크립트: fishery_permit/pair_type/fleet_role 신규 섹션
2026-04-16 07:43:24 +09:00
2ee8a0e7ff
feat(detection): DAR-03 어구 탐지 워크플로우 + 모선 검토 UI + 24h 리플레이 통합
...
- prediction: G-01/G-04/G-05/G-06 위반 분류 + 쌍끌이 공조 탐지 추가
- backend: 모선 확정/제외 API + signal-batch 항적 프록시 + ParentResolution 점수 근거 필드 확장
- frontend: 어구 탐지 그리드 다중필터/지도 flyTo, 후보 검토 패널(점수 근거+확정/제외), 24h convex hull 리플레이 + TripsLayer 애니메이션
- gitignore: 루트 .venv/ 추가
2026-04-15 13:26:15 +09:00
45371315ba
feat: prediction 알고리즘 재설계 + 프론트 CRUD 권한 가드 보완 ( #29 )
2026-04-13 11:08:11 +09:00
e5d123e4c5
feat(prediction): dark 의심 점수화 + transship 베테랑 관점 재설계
...
12h 누적 분석 결과 dark/transship이 운영 불가 수준으로 판정되어
탐지 철학을 근본부터 전환.
## dark 재설계: 넓은 탐지 + 의도적 OFF 의심 점수화
기존 "필터 제외" 방식에서 "넓게 기록 + 점수 산출 + 등급별 알람"으로 전환.
해경 베테랑 관점의 8가지 패턴을 가점 합산하여 0~100점 산출.
- P1 이동 중 OFF (gap 직전 SOG > 2kn)
- P2 민감 수역 경계 근처 OFF (영해/접속수역/특정조업수역)
- P3 반복 이력 (7일 내 재발) — 가장 강력
- P4 gap 후 이동거리 비정상 (은폐 이동)
- P5 주간 조업 시간 OFF
- P6 gap 직전 이상 행동 (teleport/급변)
- P7 무허가 선박 가점
- P8 장기 gap (3h/6h 구간별)
- 감점: gap 시작 위치가 한국 AIS 수신 커버리지 밖
완전 제외:
- 어구 AIS (GEAR_PATTERN 매칭, fleet_tracker SSOT)
- 한국 선박 (MMSI 440*, 441*) — 해경 관할 아님
등급: CRITICAL(70+) / HIGH(50~69) / WATCH(30~49) / NONE
이벤트는 HIGH 이상만 생성 (WATCH는 DB 저장만).
신규 함수:
- algorithms/dark_vessel.py: analyze_dark_pattern, compute_dark_suspicion
- scheduler.py: _is_dark_excluded, _fetch_dark_history (사이클당 1회 7일 이력 일괄 조회)
pipeline path + lightweight path 모두 동일 로직 적용.
결과는 features JSONB에 {dark_suspicion_score, dark_patterns,
dark_tier, dark_history_7d, dark_history_24h, gap_start_*} 저장.
## transship 재설계: 베테랑 함정근무자 기준
한정된 함정 자원으로 단속 출동을 결정할 수 있는 신뢰도 확보.
상수 재조정:
- SOG_THRESHOLD_KN: 2.0 → 1.0 (완전 정박만)
- PROXIMITY_DEG: 0.001 → 0.0007 (~77m)
- SUSPECT_DURATION_MIN: 60 → 45 (gap tolerance 있음)
- PAIR_EXPIRY_MIN: 120 → 180
- GAP_TOLERANCE_CYCLES: 2 신규 (GPS 노이즈 완화)
필수 조건 (모두 충족):
- 한국 EEZ 관할 수역 이내
- 환적 불가 선종 제외 (passenger/military/tanker/pilot/tug/sar)
- 어구 AIS 양쪽 제외
- 45분 이상 지속 (miss_count 2 사이클까지 용인)
점수 체계 (base 40):
- 야간(KST 20~04): +15
- 무허가 가점: +20
- COG 편차 > 45°: +20 (나란히 가는 선단 배제)
- 지속 ≥ 90분: +20
- 영해/접속수역 위치: +15
등급: CRITICAL(90+) / HIGH(70~89) / WATCH(50~69)
WATCH는 저장 없이 로그만. HIGH/CRITICAL만 이벤트.
pair_history 구조 확장:
- 기존: {(a,b): datetime}
- 신규: {(a,b): {'first_seen', 'last_seen', 'miss_count', 'last_lat/lon/cog_a/cog_b'}}
- miss_count > GAP_TOLERANCE_CYCLES면 삭제 (즉시 리셋 아님)
## event_generator 룰 교체
- dark_vessel_long 룰 제거 → dark_critical, dark_high (features.dark_tier 기반)
- transship 룰 제거 → transship_critical, transship_high (features.transship_tier 기반)
- DEDUP: ILLEGAL_TRANSSHIP 67→181, DARK_VESSEL 127→131, ZONE_DEPARTURE 127→89
## 공통 정리
- scheduler.py의 _gear_re 삭제, fleet_tracker.GEAR_PATTERN 단일 SSOT로 통합
2026-04-09 07:42:15 +09:00
0a4d023c76
fix(prediction): output 5종 이상 정상화 (stats/event/lightweight)
...
5가지 출력 이상 동시 해결:
1. stats_aggregator (이상 1, 5)
- aggregate_hourly에 by_category, by_zone JSON 집계 추가
- hour_start를 KST 기준으로 변경 (대시보드 표기와 boundary 일치)
2. event_generator 룰 정리 (이상 2, 3, 4)
- critical_risk 임계값 90→70 (risk.py CRITICAL 분류와 일치)
- territorial_sea_violation, contiguous_zone_high_risk, special_zone_entry 신설
(실측 zone_code: TERRITORIAL_SEA/CONTIGUOUS_ZONE/ZONE_*)
- 잘못된 NLL/SPECIAL_FISHING_* 룰 제거
- HIGH_RISK_VESSEL 신규 카테고리 (50~69 MEDIUM, 70+ CRITICAL)
- break 제거: 한 분석결과가 여러 카테고리에 동시 매칭 가능
3. dedup window prime 분산 (이상 5)
- 30/60/120/360분 → 33/67/127/367분
- 5분 사이클 boundary와 LCM 회피하여 정시 일제 만료 패턴 완화
4. lightweight path 신호 보강 (이상 2, 3, 4 근본 해결)
- vessel_store._tracks의 24h 누적 궤적으로 dark/spoof/speed_jump 산출
- 6,500 vessels(전체 93%)의 is_dark, spoofing_score가 비로소 채워짐
- compute_lightweight_risk_score에 dark gap, spoofing 가점 추가
(max 60→100 가능, CRITICAL 도달 가능)
시간 처리 원칙 적용:
- DB 컬럼은 모두 timestamptz 확인 완료
- aggregate_hourly KST aware datetime 사용
- pandas Timestamp는 source-internal 비교만 (안전)
2026-04-08 15:18:18 +09:00
da37a00b8e
fix: prediction 5가지 이슈 수정 — 모든 파이프라인 정상 동작
...
## 이슈 1: gear_correlation Decimal → float TypeError
- prediction/algorithms/gear_correlation.py:785
- _load_all_scores()가 NUMERIC 컬럼을 Decimal로 읽어 float 상수와 연산 시 실패
- float() 명시 변환으로 수정
- 효과: gear correlation 24,474 raw metrics + 3,966 scores 정상 기록
## 이슈 2: violation_classifier classified=0 문제
- prediction/output/violation_classifier.py
- result.get('id')는 AnalysisResult에 없어 항상 None → 모든 UPDATE 건너뜀
- 존재하지 않는 permit_status/gear_judgment 필드에 의존
- (mmsi, analyzed_at) 기준 UPDATE로 변경
- 중국 선박(412/413*) + EEZ 진입은 permit 없어도 EEZ_VIOLATION 판정
- 효과: classified=0 → classified=4~6/cycle
## 이슈 3: kpi_writer 모두 0 (tracking_active 외)
- prediction/output/kpi_writer.py:27
- date.today() + timezone.utc 혼용 → 현재 시각이 UTC로는 아직 '어제'라 '오늘 >= today_start' 쿼리가 0 반환
- KST 기준으로 today_start 계산
- 효과: realtime_detection 0 → 7,107, illegal_transship 0 → 5,033
## 이슈 4: stats_daily 오늘 0건
- prediction/output/stats_aggregator.py:96, 194
- aggregate_daily/monthly가 UTC 경계 사용
- KST 기준 자정으로 수정
- 효과: 2026-04-08 detections 0 → 543,656, events 0 → 5,253
## 이슈 5: parent workflow 테이블 누락 컬럼 (V005 ↔ prediction 불일치)
V016 마이그레이션으로 일괄 추가:
- gear_parent_label_sessions: label_parent_name, normalized_parent_name,
duration_days, actor, comment, metadata, updated_at 등 8개 컬럼
- gear_group_parent_resolution: parent_name, normalized_parent_name,
selected_parent_name, confidence, decision_source, top_score, second_score,
score_margin, stable_cycles, evidence_summary, episode_id, continuity_*,
prior_bonus_total, last_evaluated_at, last_promoted_at 등 17개 컬럼
- gear_parent_candidate_exclusions: normalized_parent_name, reason_type,
duration_days, metadata, updated_at, active_from, active_until +
candidate_mmsi GENERATED ALWAYS AS (excluded_mmsi) 별칭
- gear_group_parent_candidate_snapshots: parent_name
효과: gear parent inference: 925 groups, 301 direct-match, 1329 candidates,
188 review-required, 925 episode-snapshots 기록 — 전체 모선 워크플로우 정상
## 검증 결과 (e2e)
- analysis cycle: 6,824 vessels, 112초/cycle 정상
- vessel_analysis_results: 10분 13,650건, 총 125만건
- prediction_events: 1시간 138건, 총 12,258건
- prediction_alerts: 1시간 183건
- gear_correlation_scores: 3,966건
- gear_group_parent_resolution: 926건
- stats_hourly: 17행, stats_daily: 오늘 543,656건
- 백엔드 Flyway V016 정상 적용
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 06:47:53 +09:00
e2fc355b2c
feat: S2 prediction 분석 엔진 모노레포 이식
...
iran prediction 47개 Python 파일을 prediction/ 디렉토리로 복제:
- algorithms/ 14개 분석 알고리즘 (어구추론, 다크베셀, 스푸핑, 환적, 위험도 등)
- pipeline/ 7단계 분류 파이프라인
- cache/vessel_store (24h 슬라이딩 윈도우)
- db/ 어댑터 (snpdb 원본조회, kcgdb 결과저장)
- chat/ AI 채팅 (Ollama, 후순위)
- data/ 정적 데이터 (기선, 특정어업수역 GeoJSON)
config.py를 kcgaidb로 재구성 (DB명, 사용자, 비밀번호)
DB 연결 검증 완료 (kcgaidb 37개 테이블 접근 확인)
Makefile에 dev-prediction / dev-all 타겟 추가
CLAUDE.md에 prediction 섹션 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:56:51 +09:00