From 8ff04a8ccab2b456465bcff6bde0fd3d88c33dbf Mon Sep 17 00:00:00 2001 From: htlee Date: Thu, 16 Apr 2026 07:43:24 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat(prediction):=20DAR-03=20=ED=83=90?= =?UTF-8?q?=EC=A7=80=20=EB=A1=9C=EC=A7=81=20=EB=B3=B4=EA=B0=95=20+=20?= =?UTF-8?q?=ED=95=9C=EC=A4=91=EC=96=B4=EC=97=85=ED=98=91=EC=A0=95=20906?= =?UTF-8?q?=EC=B2=99=20=EB=A0=88=EC=A7=80=EC=8A=A4=ED=8A=B8=EB=A6=AC=20?= =?UTF-8?q?=EC=A0=81=EC=9E=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 신규 섹션 --- .../db/migration/V029__fishery_permit_cn.sql | 72 ++++ docs/중국어선_허가현황_20260106.xls | Bin 0 -> 482304 bytes prediction/algorithms/gear_violation.py | 2 +- prediction/algorithms/pair_trawl.py | 235 +++++++++++- prediction/algorithms/spoofing.py | 41 +- prediction/algorithms/transshipment.py | 25 +- prediction/db/kcgdb.py | 6 +- prediction/fleet_tracker.py | 58 ++- prediction/models/result.py | 4 + prediction/output/violation_classifier.py | 2 +- prediction/scheduler.py | 117 ++++-- prediction/scripts/diagnostic-snapshot.sh | 63 +++ .../scripts/hourly-analysis-snapshot.sh | 62 +++ prediction/scripts/load_fishery_permit_cn.py | 358 ++++++++++++++++++ 14 files changed, 975 insertions(+), 70 deletions(-) create mode 100644 backend/src/main/resources/db/migration/V029__fishery_permit_cn.sql create mode 100644 docs/중국어선_허가현황_20260106.xls create mode 100644 prediction/scripts/load_fishery_permit_cn.py diff --git a/backend/src/main/resources/db/migration/V029__fishery_permit_cn.sql b/backend/src/main/resources/db/migration/V029__fishery_permit_cn.sql new file mode 100644 index 0000000..8939c4b --- /dev/null +++ b/backend/src/main/resources/db/migration/V029__fishery_permit_cn.sql @@ -0,0 +1,72 @@ +-- V026: 한중어업협정 중국어선 허가현황 원본 테이블 + fleet_vessels 연도 컬럼 +-- 출처: docs/중국어선_허가현황_YYYYMMDD.xls (연단위 갱신) + +-- ===== 1. fishery_permit_cn : 허가현황 원본 스냅샷 ===== +CREATE TABLE IF NOT EXISTS kcg.fishery_permit_cn ( + id BIGSERIAL PRIMARY KEY, + permit_year INTEGER NOT NULL, + permit_no VARCHAR(30) NOT NULL, + fishery_type VARCHAR(60), -- 업종 (2척식저인망어업 등) + fishery_code VARCHAR(10) NOT NULL, -- 업종코드 (PT/PT-S/GN/FC/PS/OT) + name_cn VARCHAR(100) NOT NULL, + name_en VARCHAR(200) NOT NULL, + applicant_cn VARCHAR(100), + applicant_en VARCHAR(200), + applicant_addr_cn VARCHAR(300), + applicant_addr_en VARCHAR(300), + registration_no VARCHAR(100), + tonnage NUMERIC(10,2), + port_cn VARCHAR(100), + port_en VARCHAR(200), + callsign VARCHAR(40), + engine_power NUMERIC(10,2), + length_m NUMERIC(6,2), + beam_m NUMERIC(6,2), + depth_m NUMERIC(6,2), + fishing_zones VARCHAR(30), -- Ⅱ,Ⅲ 등 + fishing_period_1 VARCHAR(50), + fishing_period_2 VARCHAR(50), + catch_quota_t NUMERIC(10,2), + cumulative_quota_t NUMERIC(10,2), + refrig_hold_count INTEGER, + freezer_hold_count INTEGER, + admin_sanction TEXT, + parent_permit_no VARCHAR(30), -- 부속선(PT-S)이 참조하는 본선 허가번호 + volume_enclosed NUMERIC(10,2), + volume_above_deck NUMERIC(10,2), + volume_below_deck NUMERIC(10,2), + volume_excluded NUMERIC(10,2), + raw_data JSONB, + source_file VARCHAR(255), + loaded_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE (permit_year, permit_no) +); + +CREATE INDEX idx_fishery_permit_cn_name_cn ON kcg.fishery_permit_cn(permit_year, name_cn); +CREATE INDEX idx_fishery_permit_cn_name_en ON kcg.fishery_permit_cn(permit_year, LOWER(name_en)); +CREATE INDEX idx_fishery_permit_cn_code ON kcg.fishery_permit_cn(permit_year, fishery_code); +CREATE INDEX idx_fishery_permit_cn_parent ON kcg.fishery_permit_cn(permit_year, parent_permit_no); + +COMMENT ON TABLE kcg.fishery_permit_cn IS '한중어업협정 중국어선 허가현황 원본 스냅샷 (연단위 갱신)'; +COMMENT ON COLUMN kcg.fishery_permit_cn.permit_year IS '허가 연도 (파일명 YYYY에서 추출)'; +COMMENT ON COLUMN kcg.fishery_permit_cn.fishery_code IS 'PT(쌍끌이 본선)/PT-S(부속선)/GN(자망)/FC(운반)/PS(선망)/OT(외끌이)'; +COMMENT ON COLUMN kcg.fishery_permit_cn.parent_permit_no IS 'PT-S(부속선)가 소속된 본선의 허가번호'; + +-- ===== 2. fleet_vessels 확장 : 연도 + 업종코드 추적 ===== +ALTER TABLE kcg.fleet_vessels ADD COLUMN IF NOT EXISTS permit_year INTEGER; +ALTER TABLE kcg.fleet_vessels ADD COLUMN IF NOT EXISTS fishery_code VARCHAR(10); + +CREATE INDEX IF NOT EXISTS idx_fleet_vessels_permit_year ON kcg.fleet_vessels(permit_year); +CREATE INDEX IF NOT EXISTS idx_fleet_vessels_fishery_code ON kcg.fleet_vessels(fishery_code); + +COMMENT ON COLUMN kcg.fleet_vessels.permit_year IS '허가 연도. fleet_tracker는 현재 연도만 조회'; +COMMENT ON COLUMN kcg.fleet_vessels.fishery_code IS 'fishery_permit_cn.fishery_code 복제 (PT/PT-S/GN/FC/PS/OT)'; + +-- ===== 3. V014 데모 seed 제거 ===== +-- 기존 6행 데모 vessels 제거 (실제 허가현황만 남김). +-- fleet_companies id=1,2는 vessel_permit_master가 FK로 참조하여 삭제 불가 — 잔존 허용 +-- (loader 실행 시 실 허가 신청인 회사가 별도 id로 upsert됨) +DELETE FROM kcg.fleet_vessels WHERE permit_no IN ( + 'ZY-2024-001','ZY-2024-002','ZY-2024-003', + 'ZY-2024-010','ZY-2024-011','ZY-2024-012' +); diff --git a/docs/중국어선_허가현황_20260106.xls b/docs/중국어선_허가현황_20260106.xls new file mode 100644 index 0000000000000000000000000000000000000000..e01f2f7b7908ce7cc2c3387707dd76e117313d46 GIT binary patch literal 482304 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

?p*JLhLBS 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 literal 0 HcmV?d00001 diff --git a/prediction/algorithms/gear_violation.py b/prediction/algorithms/gear_violation.py index 7ea2cdc..623e60f 100644 --- a/prediction/algorithms/gear_violation.py +++ b/prediction/algorithms/gear_violation.py @@ -27,7 +27,7 @@ SIGNAL_CYCLING_GAP_MIN = 30 # minutes SIGNAL_CYCLING_MIN_COUNT = 2 # G-05 thresholds -GEAR_DRIFT_THRESHOLD_NM = 0.405 # ≈ 750m (보수적, 조류보정 없음) +GEAR_DRIFT_THRESHOLD_NM = 0.270 # ≈ 500m (DAR-03 스펙, 조류 보정 전) # Fixed gear types (stow net, gillnet, trap) FIXED_GEAR_TYPES = {'GN', 'TRAP', 'FYK', 'FPO', 'GNS', 'GND'} diff --git a/prediction/algorithms/pair_trawl.py b/prediction/algorithms/pair_trawl.py index 472d746..683fb72 100644 --- a/prediction/algorithms/pair_trawl.py +++ b/prediction/algorithms/pair_trawl.py @@ -14,6 +14,7 @@ from __future__ import annotations import logging import math +from typing import Optional import pandas as pd @@ -133,6 +134,9 @@ def detect_pair_trawl( df_b: pd.DataFrame, mmsi_a: str, mmsi_b: str, + role_a: str = 'UNKNOWN', + role_b: str = 'UNKNOWN', + similarity: float = 0.0, ) -> dict: """쌍끌이 트롤 공조 탐지 (DAR-03 G-06). @@ -141,20 +145,11 @@ def detect_pair_trawl( df_b: 선박 B의 AIS DataFrame. 필수 컬럼: timestamp, lat, lon, sog, cog mmsi_a: 선박 A MMSI mmsi_b: 선박 B MMSI (결과의 pair_mmsi에 기록) + role_a/role_b: 'FISHING'|'CARRIER'|'PT_MAIN'|'PT_SUB'|'UNKNOWN' + similarity: find_pair_candidates가 계산한 1차 유사도 (0~1) Returns: - { - 'pair_detected': bool, - 'sync_duration_min': float, - 'max_sync_block_min': float, - 'mean_separation_nm': float, - 'sog_delta_mean': float, - 'cog_delta_mean': float, - 'simultaneous_gap_min': float, - 'g_codes': list[str], - 'confidence': float, - 'pair_mmsi': str, - } + 위 필드들 + role_a/role_b/similarity/bonus/pair_type """ required_cols = {'timestamp', 'lat', 'lon', 'sog', 'cog'} @@ -259,12 +254,24 @@ def detect_pair_trawl( 4, ) + # ── role 매칭 가점 (DAR-03 선종 정합) ───────────────────── + bonus = 0 + pair_type = 'GENERIC' + if role_a == 'PT_MAIN' and role_b == 'PT_SUB': + bonus = 15; pair_type = 'PT_REGISTERED' + elif role_b == 'PT_MAIN' and role_a == 'PT_SUB': + bonus = 15; pair_type = 'PT_REGISTERED' + elif role_a == 'FISHING' and role_b == 'FISHING': + bonus = 10; pair_type = 'COOP_FISHING' + elif {role_a, role_b} == {'FISHING', 'CARRIER'}: + bonus = 15; pair_type = 'TRANSSHIP_LIKE' + logger.info( 'pair_trawl(%s, %s): detected — sync=%.0fmin max_block=%.0fmin ' - 'sep=%.3fnm confidence=%.3f g_codes=%s', + 'sep=%.3fnm confidence=%.3f bonus=%d type=%s g_codes=%s', mmsi_a, mmsi_b, sync_duration_min, max_sync_block_min, - mean_separation_nm, confidence, g_codes, + mean_separation_nm, confidence, bonus, pair_type, g_codes, ) return { @@ -278,9 +285,209 @@ def detect_pair_trawl( 'g_codes': g_codes, 'confidence': confidence, 'pair_mmsi': mmsi_b, + 'role_a': role_a, + 'role_b': role_b, + 'similarity': round(similarity, 4), + 'bonus': bonus, + 'pair_type': pair_type, } +def _classify_role( + mmsi: str, + info: dict, + pt_registered: set[str], + pt_sub_registered: set[str], +) -> str: + """페어 후보 role 판정. 반환: PT_MAIN/PT_SUB/FISHING/CARRIER/UNKNOWN.""" + if mmsi in pt_sub_registered: + return 'PT_SUB' + if mmsi in pt_registered: + return 'PT_MAIN' + kind = info.get('ship_kind_code', '') if info else '' + ship_ty = (info.get('ship_ty') or info.get('vessel_type') or '') if info else '' + if kind == '000020': + return 'FISHING' + if kind in ('000023', '000024'): + return 'CARRIER' + # 중국 412* 허가 어선은 ship_kind 없어도 FISHING 간주 + if mmsi.startswith('412'): + return 'FISHING' + st = ship_ty.lower() if isinstance(ship_ty, str) else '' + if any(k in st for k in ('cargo', 'tanker', 'supply', 'carrier')): + return 'CARRIER' + if 'fishing' in st: + return 'FISHING' + return 'UNKNOWN' + + +def _trajectory_similarity( + df_a: pd.DataFrame, df_b: pd.DataFrame, min_samples: int = 6, +) -> tuple[float, int, dict]: + """두 선박의 공통 타임스탬프 기반 궤적 유사도 (0~1). + + Returns: (similarity, common_samples, breakdown) + breakdown = {location_score, sog_corr, cog_alignment} + """ + if df_a.empty or df_b.empty: + return 0.0, 0, {} + cols = {'timestamp', 'lat', 'lon', 'sog', 'cog'} + if cols - set(df_a.columns) or cols - set(df_b.columns): + return 0.0, 0, {} + try: + a = df_a[['timestamp', 'lat', 'lon', 'sog', 'cog']].copy() + b = df_b[['timestamp', 'lat', 'lon', 'sog', 'cog']].copy() + a['timestamp'] = pd.to_datetime(a['timestamp']) + b['timestamp'] = pd.to_datetime(b['timestamp']) + m = pd.merge( + a.rename(columns={'lat': 'la', 'lon': 'oa', 'sog': 'sa', 'cog': 'ca'}), + b.rename(columns={'lat': 'lb', 'lon': 'ob', 'sog': 'sb', 'cog': 'cb'}), + on='timestamp', how='inner', + ) + except Exception: + return 0.0, 0, {} + n = len(m) + if n < min_samples: + return 0.0, n, {} + + # location: 평균 거리 기반 (500m 이내 1.0 → 1km 0.0 선형) + dists = [ + haversine_nm(r.la, r.oa, r.lb, r.ob) + for r in m.itertuples(index=False) + ] + mean_dist_nm = sum(dists) / len(dists) + location_score = max(0.0, min(1.0, 1.0 - (mean_dist_nm - PROXIMITY_NM) / PROXIMITY_NM)) + + # sog 상관 (Pearson) + try: + sa = m['sa'].astype(float) + sb = m['sb'].astype(float) + if sa.std() > 0 and sb.std() > 0: + sog_corr = abs(float(sa.corr(sb))) + else: + sog_corr = 1.0 if (sa - sb).abs().mean() < 0.5 else 0.0 + except Exception: + sog_corr = 0.0 + + # cog 방향 일치: 평균 각도차 → 10°=1.0, 90°=0.0 선형 + cog_deltas = [ + _cog_delta(float(r.ca), float(r.cb)) + for r in m.itertuples(index=False) + ] + mean_cog_delta = sum(cog_deltas) / len(cog_deltas) + cog_alignment = max(0.0, min(1.0, 1.0 - (mean_cog_delta - COG_DELTA_MAX) / 80.0)) + + similarity = 0.4 * location_score + 0.3 * sog_corr + 0.3 * cog_alignment + return round(similarity, 4), n, { + 'location_score': round(location_score, 3), + 'sog_corr': round(sog_corr, 3), + 'cog_alignment': round(cog_alignment, 3), + 'mean_distance_nm': round(mean_dist_nm, 4), + 'mean_cog_delta_deg': round(mean_cog_delta, 2), + } + + +# bbox 1차 탐색 반경 (도 단위) +BBOX_DEG = 0.01 # 약 1.1km — 주변 후보만 컷 +SIMILARITY_PAIR = 0.70 # 유력 페어 +SIMILARITY_OBSERVE = 0.50 # 관찰 페어 + + +def find_pair_candidates( + base_mmsis: set[str], + vessel_dfs: dict[str, pd.DataFrame], + get_vessel_info, + pt_registered: Optional[set[str]] = None, + pt_sub_registered: Optional[set[str]] = None, + bbox_deg: float = BBOX_DEG, + min_common_samples: int = 6, + similarity_threshold: float = SIMILARITY_OBSERVE, +) -> list[dict]: + """base 선박별로 bbox 1차 → 궤적 유사도 2차로 페어 후보 반환. + + base 조건: PT 등록 선박, 어선(000020), 중국 412* 허가선 등 최소 1척 어선 성격. + target: 인접 격자 내 모든 선박. 궤적 유사도 ≥ similarity_threshold 이면 후보. + + Returns: [{'base_mmsi', 'target_mmsi', 'similarity', 'common_samples', + 'base_role', 'target_role', 'breakdown'}, ...] + """ + pt_registered = pt_registered or set() + pt_sub_registered = pt_sub_registered or set() + + # 각 선박의 최근 위치 → grid 빌드 + last_pos: dict[str, dict] = {} + for mmsi, df in vessel_dfs.items(): + if df.empty or not all(c in df.columns for c in ('lat', 'lon')): + continue + try: + row = df.iloc[-1] + last_pos[mmsi] = {'lat': float(row['lat']), 'lon': float(row['lon'])} + except Exception: + continue + + def _cell(lat: float, lon: float) -> tuple[int, int]: + return (int(lat / bbox_deg), int(lon / bbox_deg)) + + grid: dict[tuple[int, int], list[str]] = {} + for mmsi, p in last_pos.items(): + grid.setdefault(_cell(p['lat'], p['lon']), []).append(mmsi) + + checked: set[tuple[str, str]] = set() + results: list[dict] = [] + + for base in base_mmsis: + if base not in last_pos: + continue + bp = last_pos[base] + bc = _cell(bp['lat'], bp['lon']) + # 인접 9 cell + neighbors: list[str] = [] + for dr in (-1, 0, 1): + for dc in (-1, 0, 1): + neighbors.extend(grid.get((bc[0] + dr, bc[1] + dc), [])) + for target in neighbors: + if target == base: + continue + key = (base, target) if base < target else (target, base) + if key in checked: + continue + checked.add(key) + + sim, n_common, breakdown = _trajectory_similarity( + vessel_dfs[base], vessel_dfs[target], min_common_samples, + ) + if sim < similarity_threshold: + continue + + info_a = get_vessel_info(base) if get_vessel_info else {} + info_b = get_vessel_info(target) if get_vessel_info else {} + role_a = _classify_role(base, info_a, pt_registered, pt_sub_registered) + role_b = _classify_role(target, info_b, pt_registered, pt_sub_registered) + + # 최소 1척이 어선 성격이어야 함 + fishing_like = {'FISHING', 'PT_MAIN', 'PT_SUB'} + if role_a not in fishing_like and role_b not in fishing_like: + continue + + results.append({ + 'base_mmsi': base, + 'target_mmsi': target, + 'similarity': sim, + 'common_samples': n_common, + 'base_role': role_a, + 'target_role': role_b, + 'breakdown': breakdown, + 'is_strong': sim >= SIMILARITY_PAIR, + }) + + logger.info( + 'find_pair_candidates: base=%d, pool=%d → candidates=%d (strong=%d)', + len(base_mmsis), len(last_pos), len(results), + sum(1 for r in results if r['is_strong']), + ) + return results + + def scan_unregistered_pairs( vessel_dfs: dict[str, pd.DataFrame], registered_pairs: set[tuple[str, str]], diff --git a/prediction/algorithms/spoofing.py b/prediction/algorithms/spoofing.py index a75db08..bbac4fb 100644 --- a/prediction/algorithms/spoofing.py +++ b/prediction/algorithms/spoofing.py @@ -2,6 +2,8 @@ import pandas as pd from algorithms.location import haversine_nm, bd09_to_wgs84, compute_bd09_offset # noqa: F401 MAX_FISHING_SPEED_KNOTS = 25.0 +EXTREME_SPEED_KNOTS = 50.0 # 물리적 불가능 속도 (단독으로 spoofing 확정) +RECENT_WINDOW_BUCKETS = 12 # 최근 12 x 5min = 1시간 윈도우로 분모 제한 def detect_teleportation(df_vessel: pd.DataFrame, @@ -51,24 +53,45 @@ def count_speed_jumps(df_vessel: pd.DataFrame, threshold_knots: float = 10.0) -> def compute_spoofing_score(df_vessel: pd.DataFrame) -> float: - """종합 GPS 스푸핑 점수 (0~1).""" + """종합 GPS 스푸핑 점수 (0~1). + + 분모를 24h 누적 길이가 아닌 최근 1시간 윈도우로 제한하고, + 물리적 불가능 속도(>50kn)는 단독으로 강한 가점을 부여한다. + """ if len(df_vessel) < 2: return 0.0 + # 최근 N 버킷(약 1시간) 기준으로 분모 고정 + window_df = df_vessel.tail(RECENT_WINDOW_BUCKETS) + n_window = max(len(window_df), 2) + score = 0.0 - n = len(df_vessel) - # 순간이동 비율 - teleports = detect_teleportation(df_vessel) + # 1) 순간이동 — 절대 가점 (건당 0.2) + extreme 단독 확정 가점 0.6 + teleports = detect_teleportation(window_df) if teleports: - score += min(0.4, len(teleports) / n * 10) + score += min(0.4, len(teleports) * 0.20) + extreme = any(t.get('implied_kn', 0) >= EXTREME_SPEED_KNOTS for t in teleports) + if extreme: + score = max(score, 0.6) - # SOG 급변 비율 - jumps = count_speed_jumps(df_vessel) + # 2) max_speed 컬럼이 있으면 window 내 최고속 직접 확인 (SNPDB 집계값 활용) + if 'max_speed' in window_df.columns: + try: + peak_kn = float(window_df['max_speed'].max()) + if peak_kn >= EXTREME_SPEED_KNOTS: + score = max(score, 0.6) + elif peak_kn > MAX_FISHING_SPEED_KNOTS: + score += 0.15 + except (TypeError, ValueError): + pass + + # 3) SOG 급변 — 윈도우 비율 가점 + jumps = count_speed_jumps(window_df) if jumps > 0: - score += min(0.3, jumps / n * 5) + score += min(0.3, jumps / n_window * 3) - # BD09 오프셋 — 중국 선박(412*)은 좌표계 차이로 항상 ~300m이므로 제외 + # 4) BD09 오프셋 — 비중국 선박만 (중국 412*는 좌표계 차이로 노이즈) mmsi_str = str(df_vessel.iloc[0].get('mmsi', '')) if 'mmsi' in df_vessel.columns else '' if not mmsi_str.startswith('412'): mid_idx = len(df_vessel) // 2 diff --git a/prediction/algorithms/transshipment.py b/prediction/algorithms/transshipment.py index 508bccb..040ba72 100644 --- a/prediction/algorithms/transshipment.py +++ b/prediction/algorithms/transshipment.py @@ -45,6 +45,8 @@ _EXCLUDED_SHIP_TY = frozenset({ 'Tug', 'Pilot Boat', 'Search And Rescue', 'Law Enforcement', 'AtoN', 'Anti Pollution', 'Passenger', 'Medical Transport', }) +# shipTy 텍스트에 포함되면 CARRIER 로 승격 (부분일치, 대소문자 무시) +_CARRIER_HINTS = ('cargo', 'tanker', 'supply', 'carrier', 'reefer') # ────────────────────────────────────────────────────────────── # 감시영역 로드 @@ -101,24 +103,27 @@ def _is_in_transship_zone(lat: float, lon: float) -> Optional[str]: def _classify_vessel_role( ship_kind_code: str, ship_ty: str, + mmsi: str = '', ) -> str: """선박 역할 분류: 'FISHING', 'CARRIER', 'EXCLUDED', 'UNKNOWN'""" + st_lower = ship_ty.lower() if isinstance(ship_ty, str) else '' if ship_kind_code in _FISHING_KINDS: return 'FISHING' if ship_kind_code in _CARRIER_KINDS: - # 화물선/유조선 — shipTy가 예인선/관공선이면 제외 if ship_ty in _EXCLUDED_SHIP_TY: return 'EXCLUDED' return 'CARRIER' - # 000027(기타) / 000028(미분류): shipTy 텍스트로 엄격 판정 - if ship_kind_code in ('000027', '000028'): - if ship_ty == 'Cargo': - return 'CARRIER' - if ship_ty == 'Tanker': - return 'CARRIER' + # 000027/000028/기타: shipTy 텍스트 부분일치로 완화 + if ship_kind_code in ('000027', '000028', ''): if ship_ty in _EXCLUDED_SHIP_TY: return 'EXCLUDED' - # N/A, Vessel, 기타 → UNKNOWN (환적 후보에서 제외) + if any(h in st_lower for h in _CARRIER_HINTS): + return 'CARRIER' + if 'fishing' in st_lower: + return 'FISHING' + # 중국 412* 허가어선은 ship_kind/shipTy 불명이어도 FISHING 간주 + if mmsi.startswith('412'): + return 'FISHING' return 'UNKNOWN' # 000021(함정), 000022(여객), 000025(관공) → 제외 if ship_kind_code in ('000021', '000022', '000025'): @@ -127,7 +132,7 @@ def _classify_vessel_role( def _is_transship_pair(role_a: str, role_b: str) -> bool: - """어선 + 운반선 조합만 True.""" + """이종 쌍(어선↔운반선)만 True. 동종 어선 공조는 pair_trawl 경로(COOP_FISHING)에서 처리.""" return (role_a == 'FISHING' and role_b == 'CARRIER') or \ (role_b == 'FISHING' and role_a == 'CARRIER') @@ -355,7 +360,7 @@ def detect_transshipment( info = get_vessel_info(mmsi) if get_vessel_info else {} kind = info.get('ship_kind_code', '') ship_ty = info.get('ship_ty', info.get('vessel_type', '')) - role = _classify_vessel_role(kind, ship_ty) + role = _classify_vessel_role(kind, ship_ty, mmsi) if role in ('EXCLUDED', 'UNKNOWN'): continue diff --git a/prediction/db/kcgdb.py b/prediction/db/kcgdb.py index cb0fc21..05d463d 100644 --- a/prediction/db/kcgdb.py +++ b/prediction/db/kcgdb.py @@ -79,7 +79,8 @@ def upsert_results(results: list['AnalysisResult']) -> int: risk_score, risk_level, transship_suspect, transship_pair_mmsi, transship_duration_min, features, - gear_judgment + gear_judgment, + gear_code ) VALUES %s ON CONFLICT (mmsi, analyzed_at) DO UPDATE SET vessel_type = EXCLUDED.vessel_type, @@ -106,7 +107,8 @@ def upsert_results(results: list['AnalysisResult']) -> int: transship_pair_mmsi = EXCLUDED.transship_pair_mmsi, transship_duration_min = EXCLUDED.transship_duration_min, features = EXCLUDED.features, - gear_judgment = EXCLUDED.gear_judgment + gear_judgment = EXCLUDED.gear_judgment, + gear_code = EXCLUDED.gear_code """ try: diff --git a/prediction/fleet_tracker.py b/prediction/fleet_tracker.py index 5f54bc6..7c49e29 100644 --- a/prediction/fleet_tracker.py +++ b/prediction/fleet_tracker.py @@ -43,10 +43,13 @@ class FleetTracker: cur.execute(f'SELECT id, name_cn, name_en FROM {FLEET_COMPANIES}') self._companies = {r[0]: {'name_cn': r[1], 'name_en': r[2]} for r in cur.fetchall()} + # 현재 연도 허가만 조회 (연단위 갱신 정책, permit_year NULL은 legacy 허용) cur.execute( f"""SELECT id, company_id, permit_no, name_cn, name_en, tonnage, gear_code, fleet_role, pair_vessel_id, mmsi - FROM {FLEET_VESSELS}""" + FROM {FLEET_VESSELS} + WHERE permit_year = EXTRACT(YEAR FROM now())::int + OR permit_year IS NULL""" ) self._vessels = {} self._name_cn_map = {} @@ -103,6 +106,59 @@ class FleetTracker: return None return self._vessels.get(vid, {}).get('gear_code') + def get_pt_registered_mmsis(self) -> set[str]: + """쌍끌이(PT / PT-S) 로 등록된 선박의 MMSI 집합. + + fishery_code 미도입 환경에서도 legacy gear_code='C21' 로 동작. + """ + result: set[str] = set() + for v in self._vessels.values(): + gc = v.get('gear_code') or '' + mmsi = v.get('mmsi') + if mmsi and gc in ('C21',): + result.add(mmsi) + return result + + def get_gear_episodes(self, mmsi: str, conn, hours: int = 24) -> list[dict]: + """gear_identity_log 에서 최근 N시간 신호 에피소드 목록을 반환. + + G-04 MMSI 조작 탐지용. first_seen_at / last_seen_at 포함. + """ + try: + cur = conn.cursor() + cur.execute( + f"""SELECT first_seen_at, last_seen_at + FROM {GEAR_IDENTITY_LOG} + WHERE mmsi = %s + AND first_seen_at > NOW() - (%s || ' hours')::interval + ORDER BY first_seen_at""", + (mmsi, str(hours)), + ) + rows = cur.fetchall() + cur.close() + return [{'first_seen_at': r[0], 'last_seen_at': r[1]} for r in rows] + except Exception as exc: + logger.warning('get_gear_episodes 실패 [mmsi=%s]: %s', mmsi, exc) + return [] + + def get_gear_positions( + self, mmsi: str, df_vessel: Optional[pd.DataFrame] = None, + ) -> list[tuple[float, float]]: + """고정어구 위치 목록(lat, lon). G-05 drift 탐지용. + + 현재 단계에서는 선박 DataFrame(track 좌표)을 그대로 전달하는 경로만 지원. + 향후 gear_tracking_snapshot 테이블이 도입되면 DB 경로 추가. + """ + if df_vessel is None or len(df_vessel) == 0: + return [] + try: + lats = df_vessel['lat'].tolist() + lons = df_vessel['lon'].tolist() + return list(zip(lats, lons)) + except Exception as exc: + logger.warning('get_gear_positions 실패 [mmsi=%s]: %s', mmsi, exc) + return [] + def match_ais_to_registry(self, ais_vessels: list[dict], conn) -> None: """AIS 선박을 등록 선단에 매칭. DB 업데이트. diff --git a/prediction/models/result.py b/prediction/models/result.py index 53c0883..91a22cb 100644 --- a/prediction/models/result.py +++ b/prediction/models/result.py @@ -55,6 +55,9 @@ class AnalysisResult: # ALGO 09: 어구 위반 판정 gear_judgment: str = '' + # 등록 선박 어구 코드 (fleet_vessels.gear_code: C21=PT, C22=OT 등) + gear_code: Optional[str] = None + # 메타 analyzed_at: Optional[datetime] = None @@ -120,4 +123,5 @@ class AnalysisResult: _i(self.transship_duration_min), json.dumps(safe_features), str(self.gear_judgment) if self.gear_judgment else None, + str(self.gear_code) if self.gear_code else None, ) diff --git a/prediction/output/violation_classifier.py b/prediction/output/violation_classifier.py index ac4f730..379a5f3 100644 --- a/prediction/output/violation_classifier.py +++ b/prediction/output/violation_classifier.py @@ -58,7 +58,7 @@ def classify_violations(result: dict) -> list[str]: if transship: violations.append('ILLEGAL_TRANSSHIP') - # 어구 불법 (gear_judgment이 있는 경우만 — 현재는 scheduler에서 채우지 않음) + # 어구 불법 (gear_judgment은 classify_gear_violations()로 채워짐: G-01/G-04/G-05/G-06) gear_judgment = result.get('gear_judgment', '') or '' if gear_judgment in ('NO_PERMIT', 'GEAR_MISMATCH', 'ZONE_VIOLATION', 'SEASON_VIOLATION', 'PAIR_TRAWL'): violations.append('ILLEGAL_GEAR') diff --git a/prediction/scheduler.py b/prediction/scheduler.py index 8d382c4..91e39f1 100644 --- a/prediction/scheduler.py +++ b/prediction/scheduler.py @@ -203,6 +203,50 @@ def run_analysis_cycle(): except Exception as e: logger.warning('gear correlation failed: %s', e) + # 4.9 페어 후보 탐색 (bbox 1차 + 궤적 유사도 2차 → G-06 pair_trawl 판정) + pair_results: dict[str, dict] = {} + try: + from algorithms.pair_trawl import find_pair_candidates, detect_pair_trawl + + pt_registered = fleet_tracker.get_pt_registered_mmsis() + pt_sub_registered: set[str] = set() # TODO: fishery_code=PT-S 구분 + base_mmsis: set[str] = {c['mmsi'] for c in classifications} + base_mmsis |= pt_registered + + # pool은 전체 24h 누적 tracks (중국 어선 중심 8k+ 선박) + pool_tracks = getattr(vessel_store, '_tracks', {}) or vessel_dfs + pair_candidates = find_pair_candidates( + base_mmsis=base_mmsis, + vessel_dfs=pool_tracks, + get_vessel_info=vessel_store.get_vessel_info, + pt_registered=pt_registered, + pt_sub_registered=pt_sub_registered, + ) + pt_det = 0; coop_det = 0 + for cand in pair_candidates: + ma, mb = cand['base_mmsi'], cand['target_mmsi'] + if ma not in pool_tracks or mb not in pool_tracks: + continue + result = detect_pair_trawl( + pool_tracks[ma], pool_tracks[mb], ma, mb, + role_a=cand['base_role'], role_b=cand['target_role'], + similarity=cand['similarity'], + ) + if not result.get('pair_detected'): + continue + pair_results[ma] = {**result, 'pair_mmsi': mb} + pair_results[mb] = {**result, 'pair_mmsi': ma} + if result.get('pair_type') == 'PT_REGISTERED': + pt_det += 1 + elif result.get('pair_type') == 'COOP_FISHING': + coop_det += 1 + logger.info( + 'pair detection: candidates=%d, detected=%d (pt=%d, coop=%d)', + len(pair_candidates), len(pair_results) // 2, pt_det, coop_det, + ) + except Exception as e: + logger.warning('pair detection failed: %s', e) + # 5. 선박별 추가 알고리즘 → AnalysisResult 생성 # dark 이력 일괄 조회 (7일 history) — 사이클당 1회 now_kst_hour = datetime.now(_KST).hour @@ -228,8 +272,11 @@ def run_analysis_cycle(): gear_map = {'TRAWL': 'OT', 'PT': 'PT', 'PURSE': 'PS', 'LONGLINE': 'GN', 'TRAP': 'TRAP'} # fleet_registry gear_code C21(쌍끌이) → vessel_type 'PT' 오버라이드 vtype = c['vessel_type'] - if hasattr(fleet_tracker, 'get_vessel_gear_code') and fleet_tracker.get_vessel_gear_code(mmsi) == 'C21': - vtype = 'PT' + registered_gear_code: Optional[str] = None + if hasattr(fleet_tracker, 'get_vessel_gear_code'): + registered_gear_code = fleet_tracker.get_vessel_gear_code(mmsi) + if registered_gear_code == 'C21': + vtype = 'PT' gear = gear_map.get(vtype, 'OT') ucaf = compute_ucaf_score(df_v, gear) ucft = compute_ucft_score(df_v) @@ -291,37 +338,38 @@ def run_analysis_cycle(): if 'state' in df_v.columns and len(df_v) > 0: activity = df_v['state'].mode().iloc[0] - # ── G-01 수역-어구 불일치 체크 ── - g_codes: list[str] = [] - gear_judgment = '' + # ── G-01/G-04/G-05/G-06 통합 판정 (DAR-03) ── + from algorithms.gear_violation import classify_gear_violations + + pair_result = pair_results.get(mmsi) + if pair_result and not pair_result.get('pair_detected'): + pair_result = None + # G-06 판정은 pair_type='PT_REGISTERED' 또는 엄격 sync 조건 만족일 때만 + if pair_result and pair_result.get('pair_type') not in ('PT_REGISTERED', 'TRANSSHIP_LIKE', 'COOP_FISHING', 'GENERIC'): + pair_result = None + + gear_episodes: list = [] + gear_positions: list = [] + if gear in ('GN', 'TRAP', 'FYK', 'FPO', 'GNS', 'GND'): + try: + with kcgdb.get_conn() as gv_conn: + gear_episodes = fleet_tracker.get_gear_episodes(mmsi, gv_conn, hours=24) + gear_positions = fleet_tracker.get_gear_positions(mmsi, df_v) + except Exception as e: + logger.debug('gear episode/pos 조회 실패 [%s]: %s', mmsi, e) + + gv = classify_gear_violations( + mmsi=mmsi, gear_type=gear, zone_info=zone_info, + df_vessel=df_v, pair_result=pair_result, + is_permitted=is_permitted, + gear_episodes=gear_episodes or None, + gear_positions=gear_positions or None, + ) + g_codes = gv['g_codes'] + gear_judgment = gv['gear_judgment'] + gear_violation_score = gv['gear_violation_score'] + gear_violation_evidence = gv['evidence'] zone_code = zone_info.get('zone', 'EEZ_OR_BEYOND') - allowed_gears = zone_info.get('allowed_gears', []) - if zone_code.startswith('ZONE_') and allowed_gears and gear not in allowed_gears: - g_codes.append('G-01') - gear_judgment = 'ZONE_VIOLATION' - - # pair_trawl 결과 병합 (Phase 2에서 pair_results dict 채워짐) - pair_result = pair_results.get(mmsi) if 'pair_results' in dir() else None - if pair_result and pair_result.get('pair_detected'): - g_codes.extend(pair_result.get('g_codes', [])) - if not gear_judgment: - gear_judgment = 'PAIR_TRAWL' - - gear_violation_score = 0 - gear_violation_evidence: dict = {} - if 'G-01' in g_codes: - gear_violation_score += 15 - gear_violation_evidence['G-01'] = { - 'zone': zone_code, 'gear': gear, - 'allowed': allowed_gears, - } - if 'G-06' in g_codes and pair_result: - gear_violation_score += 20 - gear_violation_evidence['G-06'] = { - 'sync_duration_min': pair_result.get('sync_duration_min', 0), - 'mean_separation_nm': pair_result.get('mean_separation_nm', 0), - 'pair_mmsi': pair_result.get('pair_mmsi', ''), - } # risk_score에 어구 위반 가산 final_risk = min(100, risk_score + gear_violation_score) @@ -368,6 +416,7 @@ def run_analysis_cycle(): risk_level=final_risk_level, features=merged_features, gear_judgment=gear_judgment, + gear_code=registered_gear_code, )) logger.info( @@ -511,6 +560,10 @@ def run_analysis_cycle(): transship_pair_mmsi='', transship_duration_min=0, features=dark_features, + gear_code=( + fleet_tracker.get_vessel_gear_code(mmsi) + if hasattr(fleet_tracker, 'get_vessel_gear_code') else None + ), )) lw_count += 1 logger.info( diff --git a/prediction/scripts/diagnostic-snapshot.sh b/prediction/scripts/diagnostic-snapshot.sh index 96fe03f..6bc7da7 100644 --- a/prediction/scripts/diagnostic-snapshot.sh +++ b/prediction/scripts/diagnostic-snapshot.sh @@ -229,6 +229,18 @@ WHERE analyzed_at > now() - interval '5 minutes' ORDER BY risk_score DESC LIMIT 10; SQL +echo "" +echo "--- 4-5.5 pair_type 분포 (DAR-03 base-target 탐색, 5min) ---" +$PSQL_TABLE << 'SQL' +SELECT coalesce(features->>'pair_type', '(none)') pair_type, + count(*) cnt, + round(avg((features->>'similarity')::numeric)::numeric, 3) avg_sim +FROM kcg.vessel_analysis_results +WHERE analyzed_at > now() - interval '5 minutes' + AND features->>'pair_trawl_detected' = 'true' +GROUP BY pair_type ORDER BY cnt DESC; +SQL + echo "" echo "--- 4-6. GEAR_ILLEGAL 이벤트 ---" $PSQL_TABLE << 'SQL' @@ -299,6 +311,57 @@ journalctl -u kcg-ai-prediction --since '6 minutes ago' --no-pager 2>/dev/null | grep -E 'analysis cycle:|lightweight|pipeline dark:|event_generator:|pair_trawl|gear_violation|GEAR_ILLEGAL|ERROR|Traceback' | \ tail -20 +#=================================================================== +# PART 7.5: 한중어업협정 레지스트리 매칭 (V029) +#=================================================================== +echo "" +echo "=================================================================" +echo "PART 7.5: FISHERY PERMIT CN REGISTRY 매칭 현황" +echo "=================================================================" + +echo "" +echo "--- 7.5-1. fleet_vessels 매칭 현황 (현재 연도) ---" +$PSQL_TABLE << 'SQL' +SELECT permit_year, + count(*) total, + count(mmsi) with_mmsi, + round(count(mmsi)::numeric / NULLIF(count(*),0) * 100, 1) match_pct +FROM kcg.fleet_vessels +WHERE permit_year IS NOT NULL +GROUP BY permit_year ORDER BY permit_year DESC; +SQL + +echo "" +echo "--- 7.5-2. fishery_code 별 매칭률 (현재 연도) ---" +$PSQL_TABLE << 'SQL' +SELECT fishery_code, count(*) total, count(mmsi) matched, + round(count(mmsi)::numeric / NULLIF(count(*),0) * 100, 1) pct +FROM kcg.fleet_vessels +WHERE permit_year = EXTRACT(YEAR FROM now())::int +GROUP BY fishery_code ORDER BY total DESC; +SQL + +echo "" +echo "--- 7.5-3. vessel_analysis_results.gear_code 분포 (last 5min) ---" +$PSQL_TABLE << 'SQL' +SELECT coalesce(gear_code, '(null)') gear_code, + count(*) cnt, + round(avg(risk_score)::numeric, 1) avg_risk +FROM kcg.vessel_analysis_results +WHERE analyzed_at > now() - interval '5 minutes' +GROUP BY gear_code ORDER BY cnt DESC LIMIT 15; +SQL + +echo "" +echo "--- 7.5-4. 최근 매칭된 선박 (top 10 by last_seen_at) ---" +$PSQL_TABLE << 'SQL' +SELECT permit_no, fishery_code, name_cn, mmsi, match_method, + match_confidence, last_seen_at +FROM kcg.fleet_vessels +WHERE permit_year = EXTRACT(YEAR FROM now())::int AND mmsi IS NOT NULL +ORDER BY last_seen_at DESC NULLS LAST LIMIT 10; +SQL + #=================================================================== # PART 8: 해역별 종합 교차 #=================================================================== diff --git a/prediction/scripts/hourly-analysis-snapshot.sh b/prediction/scripts/hourly-analysis-snapshot.sh index d7c5efa..4f7b450 100755 --- a/prediction/scripts/hourly-analysis-snapshot.sh +++ b/prediction/scripts/hourly-analysis-snapshot.sh @@ -130,6 +130,57 @@ SELECT FROM kcg.vessel_analysis_results WHERE analyzed_at > now() - interval '1 hour'; +\echo +\echo =================================================================== +\echo === FISHERY PERMIT CN REGISTRY (V029 - 한중어업협정) +\echo =================================================================== + +\echo +\echo === P1. fishery_permit_cn year-by-year === +SELECT permit_year, count(*) permits, + count(DISTINCT fishery_code) codes, + count(DISTINCT applicant_cn) applicants, + max(loaded_at) loaded_at +FROM kcg.fishery_permit_cn +GROUP BY permit_year ORDER BY permit_year DESC; + +\echo +\echo === P2. fleet_vessels matching (current year = registry) === +SELECT permit_year, + count(*) total, + count(mmsi) with_mmsi, + round(count(mmsi)::numeric / NULLIF(count(*),0) * 100, 1) match_pct, + max(last_seen_at) last_match +FROM kcg.fleet_vessels +WHERE permit_year IS NOT NULL +GROUP BY permit_year ORDER BY permit_year DESC; + +\echo +\echo === P3. fleet_vessels breakdown by fishery_code (current year) === +SELECT fishery_code, count(*) total, count(mmsi) matched, + round(count(mmsi)::numeric / NULLIF(count(*),0) * 100, 1) pct +FROM kcg.fleet_vessels +WHERE permit_year = EXTRACT(YEAR FROM now())::int +GROUP BY fishery_code ORDER BY total DESC; + +\echo +\echo === P4. vessel_analysis_results.gear_code distribution (last 1h) === +SELECT coalesce(gear_code, '(null)') gear_code, + count(*) cnt, + round(avg(risk_score)::numeric, 1) avg_risk +FROM kcg.vessel_analysis_results +WHERE analyzed_at > now() - interval '1 hour' +GROUP BY gear_code ORDER BY cnt DESC LIMIT 15; + +\echo +\echo === P5. fleet_role distribution (last 1h, from registry match) === +SELECT fleet_role, count(*) cnt, + count(*) FILTER (WHERE fleet_is_leader) is_leader, + round(avg(risk_score)::numeric, 1) avg_risk +FROM kcg.vessel_analysis_results +WHERE analyzed_at > now() - interval '1 hour' +GROUP BY fleet_role ORDER BY cnt DESC; + \echo \echo === G1. PIPELINE vessel_type distribution === SELECT vessel_type, count(*), @@ -196,6 +247,17 @@ WHERE analyzed_at > now() - interval '1 hour' AND vessel_type != 'UNKNOWN' AND zone_code LIKE 'ZONE_%' GROUP BY zone_code, vessel_type ORDER BY zone_code, vessel_type; +\echo +\echo === D3.5 pair_type distribution (DAR-03 base-target 탐색) === +SELECT coalesce(features->>'pair_type', '(none)') pair_type, + count(*) cnt, + round(avg((features->>'similarity')::numeric)::numeric, 3) avg_sim, + round(avg((features->>'confidence')::numeric)::numeric, 3) avg_conf +FROM kcg.vessel_analysis_results +WHERE analyzed_at > now() - interval '1 hour' + AND features->>'pair_trawl_detected' = 'true' +GROUP BY pair_type ORDER BY cnt DESC; + \echo \echo === D4. G-06 pair trawl detections === SELECT mmsi, zone_code, vessel_type, risk_score, diff --git a/prediction/scripts/load_fishery_permit_cn.py b/prediction/scripts/load_fishery_permit_cn.py new file mode 100644 index 0000000..4ee8bb1 --- /dev/null +++ b/prediction/scripts/load_fishery_permit_cn.py @@ -0,0 +1,358 @@ +"""한중어업협정 중국어선 허가현황 XLS → kcgdb 적재. + +Usage: + python3 prediction/scripts/load_fishery_permit_cn.py + # 또는 기본 경로(docs/중국어선_허가현황_YYYYMMDD.xls 최신) 자동 탐색 + python3 prediction/scripts/load_fishery_permit_cn.py + +수행: + 1) XLS 파싱 → kcg.fishery_permit_cn upsert (permit_year + permit_no 복합 유니크) + 2) 신청인(중국어) 기준 kcg.fleet_companies upsert + 3) 해당 연도 레코드를 kcg.fleet_vessels로 동기화 + - PT-S(부속선)는 parent_permit_no로 본선 pair_vessel_id 연결 + - fleet_role: FC=TRANSPORT, PT=MAIN, PT-S=CREW, 기타=MAIN +""" +from __future__ import annotations + +import json +import logging +import os +import re +import sys +from pathlib import Path +from typing import Optional + +import pandas as pd +import psycopg2 +import psycopg2.extras + +logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s') +logger = logging.getLogger(__name__) + + +def _env(key: str, default: Optional[str] = None) -> str: + v = os.environ.get(key, default) + if v is None: + raise SystemExit(f'환경변수 {key} 가 필요합니다') + return v + + +def _find_latest_xls(docs_dir: Path) -> Path: + pattern = re.compile(r'중국어선_허가현황_(\d{8})\.xls$') + candidates = [] + for p in docs_dir.glob('중국어선_허가현황_*.xls'): + m = pattern.search(p.name) + if m: + candidates.append((m.group(1), p)) + if not candidates: + raise SystemExit(f'{docs_dir} 에서 허가현황 XLS를 찾지 못했습니다') + candidates.sort() + return candidates[-1][1] + + +def _extract_year(path: Path) -> int: + m = re.search(r'(\d{4})\d{4}', path.name) + if not m: + raise SystemExit(f'파일명에서 연도 추출 실패: {path.name}') + return int(m.group(1)) + + +FISHERY_ROLE = { + 'FC': 'TRANSPORT', + 'PT': 'MAIN', + 'PT-S': 'CREW', + 'GN': 'MAIN', + 'PS': 'MAIN', + 'OT': 'MAIN', +} + +# 업종코드 → fleet_tracker가 쓰는 레거시 gear_code 매핑 +# (vessel_type 오버라이드 로직이 C21을 체크하므로 PT만 C21) +GEAR_CODE_LEGACY = { + 'PT': 'C21', + 'PT-S': 'C21', + 'GN': 'C22', + 'OT': 'C22', + 'PS': 'PS', + 'FC': 'FC', +} + + +def _clean(v) -> Optional[str]: + if v is None: + return None + if isinstance(v, float) and pd.isna(v): + return None + s = str(v).strip() + return s if s else None + + +def _num(v) -> Optional[float]: + if v is None: + return None + try: + if isinstance(v, float) and pd.isna(v): + return None + return float(v) + except (TypeError, ValueError): + return None + + +def _int(v) -> Optional[int]: + f = _num(v) + return int(f) if f is not None else None + + +def parse_xls(path: Path) -> list[dict]: + df = pd.read_excel(path, engine='xlrd', header=1) + rows: list[dict] = [] + for _, r in df.iterrows(): + permit_no = _clean(r.get('허가번호')) + fishery_code = _clean(r.get('업종코드')) + name_cn = _clean(r.get('선박명(중국)')) + name_en = _clean(r.get('선박명(로마)')) + if not (permit_no and fishery_code and name_cn and name_en): + continue + rows.append({ + 'permit_no': permit_no, + 'fishery_type': _clean(r.get('업종')), + 'fishery_code': fishery_code, + 'name_cn': name_cn, + 'name_en': name_en, + 'applicant_cn': _clean(r.get('신청인(중국)')), + 'applicant_en': _clean(r.get('신청인(로마)')), + 'applicant_addr_cn': _clean(r.get('신청인주소(중국)')), + 'applicant_addr_en': _clean(r.get('신청인주소(로마)')), + 'registration_no': _clean(r.get('선박등록번호')), + 'tonnage': _num(r.get('톤수')), + 'port_cn': _clean(r.get('선적항(중국)')), + 'port_en': _clean(r.get('선적항(로마)')), + 'callsign': _clean(r.get('호출부호')), + 'engine_power': _num(r.get('기관출력(마력)')), + 'length_m': _num(r.get('길이(m)')), + 'beam_m': _num(r.get('폭(m)')), + 'depth_m': _num(r.get('깊이(m)')), + 'fishing_zones': _clean(r.get('조업수역')), + 'fishing_period_1': _clean(r.get('조업기간1')), + 'fishing_period_2': _clean(r.get('조업기간2')), + 'catch_quota_t': _num(r.get('어획할당량(톤)')), + 'cumulative_quota_t': _num(r.get('현어기의\n신규(첫)허가부터 \n누적 어획할당량(톤)')), + 'refrig_hold_count': _int(r.get('냉장 어창의 수')), + 'freezer_hold_count': _int(r.get('냉동 어창의 수')), + 'admin_sanction': _clean(r.get('행정처분일자/행정처분기관/위반내용')), + 'parent_permit_no': _clean(r.get('본선의\n허가번호')), + 'volume_enclosed': _num(r.get('용적\n폐위장소(㎥)')), + 'volume_above_deck': _num(r.get('용적\n상갑판 위(㎥)')), + 'volume_below_deck': _num(r.get('용적\n상갑판 아래(㎥)')), + 'volume_excluded': _num(r.get('용적\n제외장소(㎥)')), + 'raw': {k: (None if (isinstance(v, float) and pd.isna(v)) else v) for k, v in r.items()}, + }) + logger.info('파싱된 허가 수: %d', len(rows)) + return rows + + +def upsert_permits(cur, year: int, source_file: str, rows: list[dict]) -> None: + sql = """ + INSERT INTO kcg.fishery_permit_cn ( + permit_year, permit_no, fishery_type, fishery_code, + name_cn, name_en, applicant_cn, applicant_en, + applicant_addr_cn, applicant_addr_en, registration_no, tonnage, + port_cn, port_en, callsign, engine_power, + length_m, beam_m, depth_m, fishing_zones, + fishing_period_1, fishing_period_2, catch_quota_t, cumulative_quota_t, + refrig_hold_count, freezer_hold_count, admin_sanction, parent_permit_no, + volume_enclosed, volume_above_deck, volume_below_deck, volume_excluded, + raw_data, source_file + ) VALUES %s + ON CONFLICT (permit_year, permit_no) DO UPDATE SET + fishery_type = EXCLUDED.fishery_type, + fishery_code = EXCLUDED.fishery_code, + name_cn = EXCLUDED.name_cn, + name_en = EXCLUDED.name_en, + applicant_cn = EXCLUDED.applicant_cn, + applicant_en = EXCLUDED.applicant_en, + applicant_addr_cn = EXCLUDED.applicant_addr_cn, + applicant_addr_en = EXCLUDED.applicant_addr_en, + registration_no = EXCLUDED.registration_no, + tonnage = EXCLUDED.tonnage, + port_cn = EXCLUDED.port_cn, + port_en = EXCLUDED.port_en, + callsign = EXCLUDED.callsign, + engine_power = EXCLUDED.engine_power, + length_m = EXCLUDED.length_m, + beam_m = EXCLUDED.beam_m, + depth_m = EXCLUDED.depth_m, + fishing_zones = EXCLUDED.fishing_zones, + fishing_period_1 = EXCLUDED.fishing_period_1, + fishing_period_2 = EXCLUDED.fishing_period_2, + catch_quota_t = EXCLUDED.catch_quota_t, + cumulative_quota_t = EXCLUDED.cumulative_quota_t, + refrig_hold_count = EXCLUDED.refrig_hold_count, + freezer_hold_count = EXCLUDED.freezer_hold_count, + admin_sanction = EXCLUDED.admin_sanction, + parent_permit_no = EXCLUDED.parent_permit_no, + volume_enclosed = EXCLUDED.volume_enclosed, + volume_above_deck = EXCLUDED.volume_above_deck, + volume_below_deck = EXCLUDED.volume_below_deck, + volume_excluded = EXCLUDED.volume_excluded, + raw_data = EXCLUDED.raw_data, + source_file = EXCLUDED.source_file, + loaded_at = now() + """ + tuples = [ + ( + year, r['permit_no'], r['fishery_type'], r['fishery_code'], + r['name_cn'], r['name_en'], r['applicant_cn'], r['applicant_en'], + r['applicant_addr_cn'], r['applicant_addr_en'], r['registration_no'], r['tonnage'], + r['port_cn'], r['port_en'], r['callsign'], r['engine_power'], + r['length_m'], r['beam_m'], r['depth_m'], r['fishing_zones'], + r['fishing_period_1'], r['fishing_period_2'], r['catch_quota_t'], r['cumulative_quota_t'], + r['refrig_hold_count'], r['freezer_hold_count'], r['admin_sanction'], r['parent_permit_no'], + r['volume_enclosed'], r['volume_above_deck'], r['volume_below_deck'], r['volume_excluded'], + json.dumps({k: (v.isoformat() if hasattr(v, 'isoformat') else v) for k, v in r['raw'].items()}, ensure_ascii=False, default=str), + source_file, + ) + for r in rows + ] + psycopg2.extras.execute_values(cur, sql, tuples, page_size=200) + logger.info('fishery_permit_cn upsert: %d rows', len(tuples)) + + +def upsert_companies(cur, rows: list[dict]) -> dict[str, int]: + """신청인(중국어) 기준 fleet_companies upsert → {applicant_cn: company_id}.""" + applicants: dict[str, dict] = {} + for r in rows: + key = r['applicant_cn'] or r['applicant_en'] or 'UNKNOWN' + applicants.setdefault(key, {'name_cn': r['applicant_cn'], 'name_en': r['applicant_en']}) + result: dict[str, int] = {} + for key, meta in applicants.items(): + cur.execute( + """ + INSERT INTO kcg.fleet_companies (name_cn, name_en, country) + VALUES (%s, %s, 'CN') + ON CONFLICT DO NOTHING + """, + (meta['name_cn'], meta['name_en']), + ) + cur.execute( + 'SELECT id FROM kcg.fleet_companies WHERE name_cn IS NOT DISTINCT FROM %s AND name_en IS NOT DISTINCT FROM %s', + (meta['name_cn'], meta['name_en']), + ) + row = cur.fetchone() + if row: + result[key] = row[0] + logger.info('fleet_companies upsert: %d 신청인', len(result)) + return result + + +def sync_fleet_vessels(cur, year: int, rows: list[dict], company_map: dict[str, int]) -> None: + """해당 연도 허가를 fleet_vessels로 동기화. permit_no+permit_year 기준 upsert.""" + # 기존 연도 데이터 먼저 비우기 (허가 취소된 선박 정리) + cur.execute( + 'DELETE FROM kcg.fleet_vessels WHERE permit_year = %s AND permit_no NOT IN %s', + (year, tuple([r['permit_no'] for r in rows]) or ('',)), + ) + inserted = 0 + updated = 0 + permit_to_id: dict[str, int] = {} + for r in rows: + company_key = r['applicant_cn'] or r['applicant_en'] or 'UNKNOWN' + company_id = company_map.get(company_key) + if company_id is None: + continue + fishery_code = r['fishery_code'] + legacy_gear = GEAR_CODE_LEGACY.get(fishery_code, fishery_code) + fleet_role = FISHERY_ROLE.get(fishery_code, 'MAIN') + cur.execute( + """ + INSERT INTO kcg.fleet_vessels ( + company_id, permit_no, name_cn, name_en, tonnage, + gear_code, fishery_code, fleet_role, permit_year, updated_at + ) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, now()) + ON CONFLICT DO NOTHING + RETURNING id + """, + ( + company_id, r['permit_no'], r['name_cn'], r['name_en'], r['tonnage'], + legacy_gear, fishery_code, fleet_role, year, + ), + ) + ret = cur.fetchone() + if ret: + permit_to_id[r['permit_no']] = ret[0] + inserted += 1 + else: + cur.execute( + """ + UPDATE kcg.fleet_vessels SET + company_id = %s, name_cn = %s, name_en = %s, tonnage = %s, + gear_code = %s, fishery_code = %s, fleet_role = %s, updated_at = now() + WHERE permit_year = %s AND permit_no = %s + RETURNING id + """, + ( + company_id, r['name_cn'], r['name_en'], r['tonnage'], + legacy_gear, fishery_code, fleet_role, year, r['permit_no'], + ), + ) + ret = cur.fetchone() + if ret: + permit_to_id[r['permit_no']] = ret[0] + updated += 1 + logger.info('fleet_vessels 동기화: inserted=%d, updated=%d (year=%d)', inserted, updated, year) + + # PT-S(부속선) → 본선 pair_vessel_id 연결 + pair_linked = 0 + for r in rows: + if r['fishery_code'] != 'PT-S' or not r['parent_permit_no']: + continue + child_id = permit_to_id.get(r['permit_no']) + parent_id = permit_to_id.get(r['parent_permit_no']) + if child_id and parent_id: + cur.execute( + 'UPDATE kcg.fleet_vessels SET pair_vessel_id = %s WHERE id = %s', + (parent_id, child_id), + ) + cur.execute( + 'UPDATE kcg.fleet_vessels SET pair_vessel_id = %s WHERE id = %s AND pair_vessel_id IS NULL', + (child_id, parent_id), + ) + pair_linked += 1 + logger.info('PT-S pair 연결: %d 건', pair_linked) + + +def main() -> None: + repo_root = Path(__file__).resolve().parents[2] + if len(sys.argv) >= 2: + xls_path = Path(sys.argv[1]) + else: + xls_path = _find_latest_xls(repo_root / 'docs') + if not xls_path.exists(): + raise SystemExit(f'파일 없음: {xls_path}') + + year = _extract_year(xls_path) + logger.info('source=%s year=%d', xls_path.name, year) + + rows = parse_xls(xls_path) + + conn = psycopg2.connect( + host=_env('KCGDB_HOST'), + port=int(_env('KCGDB_PORT', '5432')), + dbname=_env('KCGDB_NAME'), + user=_env('KCGDB_USER'), + password=_env('KCGDB_PASSWORD'), + ) + try: + with conn: + with conn.cursor() as cur: + upsert_permits(cur, year, xls_path.name, rows) + company_map = upsert_companies(cur, rows) + sync_fleet_vessels(cur, year, rows, company_map) + logger.info('완료') + finally: + conn.close() + + +if __name__ == '__main__': + main() From 9d538cffd8f5d7161fc5d554b13eabbb7522c580 Mon Sep 17 00:00:00 2001 From: htlee Date: Thu, 16 Apr 2026 07:45:14 +0900 Subject: [PATCH 2/3] =?UTF-8?q?docs:=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=85=B8=ED=8A=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/RELEASE-NOTES.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index 2cf60ce..5022be5 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -4,6 +4,24 @@ ## [Unreleased] +### 추가 +- **한중어업협정 중국어선 허가현황 레지스트리** — `kcg.fishery_permit_cn` 신규 테이블(29컬럼, 연단위 스냅샷). V029 마이그레이션 + `fleet_vessels.permit_year/fishery_code` 컬럼 추가. `load_fishery_permit_cn.py`로 연도별 XLS → DB 적재(906척/497 신청인사) +- **페어 탐색 재설계** — `find_pair_candidates()` bbox 1차(인접 9 cell) + 궤적 유사도 2차(location/sog_corr/cog_alignment). 동종 어선 페어도 허용, role 가점(PT_REGISTERED/COOP_FISHING/TRANSSHIP_LIKE) +- **fleet_tracker API 3개** — `get_pt_registered_mmsis` / `get_gear_episodes` / `get_gear_positions` + +### 수정 +- **DAR-03 G-04/G-05/G-06 Dead code 해결** — `classify_gear_violations()` scheduler 호출 연결. `if 'pair_results' in dir()` 버그 제거. 사이클당 G-05 303건 / G-04 1건 탐지 시작 +- **spoofing 산식** — 24h 희석 버그 → 최근 1h 윈도우 + teleport 절대 가점(건당 0.20) + extreme(>50kn) 단독 발견 시 score=max(0.6) 확정 +- **gear_code DB write 경로** — `AnalysisResult.gear_code` 필드 + `kcgdb.upsert_results()` INSERT/UPDATE + scheduler 두 경로에서 `fleet_tracker.get_vessel_gear_code()` 호출 + +### 변경 +- **transshipment 선종 완화** — `_CARRIER_HINTS`(cargo/tanker/supply/carrier/reefer) 부분일치 + 412* 중국어선 FISHING 간주 +- **gear drift 임계** — 750m → **500m** (DAR-03 스펙 정합) +- **fleet_tracker 현재 연도 필터** — `WHERE permit_year = EXTRACT(YEAR FROM now())::int OR permit_year IS NULL` + +### 기타 +- cron 스크립트 신규 섹션: hourly P1~P5(허가/매칭/gear_code/fleet_role) + D3.5(pair_type) / diagnostic PART 7.5 + 4-5.5 + ## [2026-04-15] ### 추가 From dd0a934203d6b69e052f89ebdf964bfd06e796d4 Mon Sep 17 00:00:00 2001 From: htlee Date: Thu, 16 Apr 2026 07:48:23 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=85=B8=ED=8A=B8=20=EC=A0=95=EB=A6=AC=20(2026-04-16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/RELEASE-NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index 5022be5..1beeeec 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -4,6 +4,8 @@ ## [Unreleased] +## [2026-04-16] + ### 추가 - **한중어업협정 중국어선 허가현황 레지스트리** — `kcg.fishery_permit_cn` 신규 테이블(29컬럼, 연단위 스냅샷). V029 마이그레이션 + `fleet_vessels.permit_year/fishery_code` 컬럼 추가. `load_fishery_permit_cn.py`로 연도별 XLS → DB 적재(906척/497 신청인사) - **페어 탐색 재설계** — `find_pair_candidates()` bbox 1차(인접 9 cell) + 궤적 유사도 2차(location/sog_corr/cog_alignment). 동종 어선 페어도 허용, role 가점(PT_REGISTERED/COOP_FISHING/TRANSSHIP_LIKE)