{ "meta": { "title": "어구 모선 추적 데이터 흐름", "version": "2026-04-03", "updatedAt": "2026-04-03", "description": "snpdb 적재부터 review/label workflow와 episode continuity + prior bonus까지의 전체 흐름" }, "nodes": [ { "id": "source_tracks", "label": "5분 원천 궤적", "stage": "원천", "kind": "table", "position": { "x": 0, "y": 20 }, "file": "signal.t_vessel_tracks_5min", "symbol": "signal.t_vessel_tracks_5min", "role": "5분 bucket 단위 AIS 궤적 원천 테이블", "params": ["1 row = 1 MMSI = 5분 linestringM"], "rules": ["bbox 122,31,132,39", "LineStringM dump 후 point timestamp 사용"], "storageReads": [], "storageWrites": [], "outputs": ["mmsi", "time_bucket", "timestamp", "lat", "lon", "raw_sog"], "impacts": ["모든 그룹/점수 계산의 원천 입력"], "status": "implemented" }, { "id": "safe_window", "label": "safe watermark", "stage": "시간 모델", "kind": "function", "position": { "x": 260, "y": 20 }, "file": "prediction/time_bucket.py", "symbol": "compute_safe_bucket / compute_incremental_window_start", "role": "미완결 bucket 차단과 overlap backfill 시작점 계산", "params": ["SNPDB_SAFE_DELAY_MIN", "SNPDB_BACKFILL_BUCKETS"], "rules": ["safe bucket까지만 조회", "last_bucket보다 과거도 일부 재조회"], "storageReads": [], "storageWrites": [], "outputs": ["safe_bucket", "window_start", "from_bucket"], "impacts": ["live cache drift 완화", "재기동 spike 억제"], "status": "implemented" }, { "id": "snpdb_fetch", "label": "snpdb 적재", "stage": "적재", "kind": "module", "position": { "x": 520, "y": 20 }, "file": "prediction/db/snpdb.py", "symbol": "fetch_all_tracks / fetch_incremental", "role": "safe bucket까지 초기/증분 궤적 적재", "params": ["hours=24", "last_bucket"], "rules": ["time_bucket > from_bucket", "time_bucket <= safe_bucket"], "storageReads": ["signal.t_vessel_tracks_5min"], "storageWrites": [], "outputs": ["DataFrame of points"], "impacts": ["VesselStore 초기화와 증분 merge 입력"], "status": "implemented" }, { "id": "vessel_store", "label": "VesselStore 캐시", "stage": "캐시", "kind": "module", "position": { "x": 800, "y": 20 }, "file": "prediction/cache/vessel_store.py", "symbol": "load_initial / merge_incremental / evict_stale", "role": "24시간 sliding in-memory cache 유지", "params": ["_tracks", "_last_bucket"], "rules": ["timestamp dedupe", "safe bucket 기준 24h eviction"], "storageReads": [], "storageWrites": [], "outputs": ["latest positions", "tracks by MMSI"], "impacts": ["identity, grouping, correlation, inference 공통 입력"], "status": "implemented" }, { "id": "gear_identity", "label": "어구 identity", "stage": "정규화", "kind": "module", "position": { "x": 1080, "y": 20 }, "file": "prediction/fleet_tracker.py", "symbol": "track_gear_identity", "role": "어구 이름 패턴 파싱과 gear_identity_log 유지", "params": ["parent_name", "gear_index_1", "gear_index_2"], "rules": ["정규화 길이 4 미만 제외", "같은 이름 다른 MMSI면 identity migration"], "storageReads": ["fleet_vessels"], "storageWrites": ["gear_identity_log", "gear_correlation_scores(target_mmsi transfer)"], "outputs": ["active gear identity rows"], "impacts": ["grouping과 parent_mmsi 보조 입력"], "status": "implemented" }, { "id": "detect_groups", "label": "어구 그룹 검출", "stage": "그룹핑", "kind": "function", "position": { "x": 260, "y": 220 }, "file": "prediction/algorithms/polygon_builder.py", "symbol": "detect_gear_groups", "role": "이름 기반 raw group과 거리 기반 sub-cluster 생성", "params": ["MAX_DIST_DEG=0.15", "STALE_SEC", "is_trackable_parent_name"], "rules": ["440/441 제외", "single cluster면 sc#0", "multi cluster면 sc#1..N", "재병합 시 sc#0"], "storageReads": [], "storageWrites": [], "outputs": ["gear_groups[]"], "impacts": ["sub_cluster_id는 순간 라벨일 뿐 영구 ID가 아님"], "status": "implemented" }, { "id": "group_snapshots", "label": "그룹 스냅샷 생성", "stage": "그룹핑", "kind": "function", "position": { "x": 520, "y": 220 }, "file": "prediction/algorithms/polygon_builder.py", "symbol": "build_all_group_snapshots", "role": "1h/1h-fb/6h polygon snapshot 생성", "params": ["parent_active_1h", "MIN_GEAR_GROUP_SIZE"], "rules": ["1h 활성<2이면 1h-fb", "수역 외 소수 멤버 제외", "parent nearby면 isParent=true"], "storageReads": [], "storageWrites": ["group_polygon_snapshots"], "outputs": ["group snapshots"], "impacts": ["backend live 현황과 parent inference center track 입력"], "status": "implemented" }, { "id": "gear_correlation", "label": "correlation 모델", "stage": "후보 추적", "kind": "module", "position": { "x": 800, "y": 220 }, "file": "prediction/algorithms/gear_correlation.py", "symbol": "run_gear_correlation", "role": "후보 선박/어구 raw metric과 EMA score 계산", "params": ["active models", "track_threshold", "decay_fast", "candidate max=30"], "rules": ["선박은 track 기반", "어구 후보는 GEAR_BUOY", "후보 이탈 시 fast decay"], "storageReads": ["group snapshots", "vessel_store", "correlation_param_models"], "storageWrites": ["gear_correlation_raw_metrics", "gear_correlation_scores"], "outputs": ["raw metrics", "EMA score rows"], "impacts": ["parent inference 후보 seed"], "status": "implemented" }, { "id": "workflow_exclusions", "label": "후보 제외 / 라벨", "stage": "검토 워크플로우", "kind": "table", "position": { "x": 1080, "y": 220 }, "file": "database/migration/014_gear_parent_workflow_v2_phase1.sql", "symbol": "gear_parent_candidate_exclusions / gear_parent_label_sessions", "role": "사람 판단 데이터를 자동 추론과 분리 저장", "params": ["scope=GROUP|GLOBAL", "duration=1|3|5d"], "rules": ["GLOBAL은 모든 그룹에서 제거", "ACTIVE label session만 tracking"], "storageReads": [], "storageWrites": ["gear_parent_candidate_exclusions", "gear_parent_label_sessions"], "outputs": ["active exclusions", "active label sessions"], "impacts": ["parent inference candidate pruning", "label tracking"], "status": "implemented" }, { "id": "parent_inference", "label": "모선 추론", "stage": "최종 추론", "kind": "module", "position": { "x": 260, "y": 420 }, "file": "prediction/algorithms/gear_parent_inference.py", "symbol": "run_gear_parent_inference", "role": "후보 생성, coverage-aware scoring, 상태 전이, resolution 저장", "params": ["auto score 0.72/0.15/3", "review threshold 0.60", "412/413 bonus +15%"], "rules": ["DIRECT_PARENT_MATCH", "SKIPPED_SHORT_NAME", "NO_CANDIDATE", "AUTO_PROMOTED", "REVIEW_REQUIRED", "UNRESOLVED"], "storageReads": ["gear_correlation_scores", "gear_correlation_raw_metrics", "group_polygon_snapshots", "active exclusions", "active label sessions"], "storageWrites": ["gear_group_parent_candidate_snapshots", "gear_group_parent_resolution", "gear_parent_label_tracking_cycles"], "outputs": ["candidate snapshots", "resolution current state", "label tracking rows"], "impacts": ["review queue", "group detail", "future prior feature source"], "status": "implemented" }, { "id": "score_breakdown", "label": "점수 보정", "stage": "최종 추론", "kind": "function", "position": { "x": 520, "y": 420 }, "file": "prediction/algorithms/gear_parent_inference.py", "symbol": "_build_candidate_scores / _build_track_coverage_metrics", "role": "이름, 궤적, 방문, 근접, 활동, 안정성, bonus를 합산", "params": ["name 1.0/0.8/0.5/0.3", "coverage factors", "registry +0.05", "china prefix +0.15"], "rules": ["raw->effective 보정", "preBonusScore>=0.30일 때만 412/413 bonus"], "storageReads": [], "storageWrites": ["candidate evidence JSON"], "outputs": ["final_score", "coverage metrics", "evidenceConfidence"], "impacts": ["review UI 설명력", "future signal/prior 분리 설계"], "status": "implemented" }, { "id": "backend_read_model", "label": "backend read model", "stage": "조회 계층", "kind": "module", "position": { "x": 800, "y": 420 }, "file": "backend/src/main/java/gc/mda/kcg/domain/fleet/GroupPolygonService.java", "symbol": "group list / review queue / detail SQL", "role": "최신 전역 1h live snapshot과 fresh inference만 노출", "params": ["snapshot_time max where resolution=1h"], "rules": ["last_evaluated_at >= snapshot_time", "사라진 과거 sub-cluster 숨김"], "storageReads": ["group_polygon_snapshots", "gear_group_parent_resolution", "gear_group_parent_candidate_snapshots"], "storageWrites": [], "outputs": ["GroupPolygonDto", "GroupParentInferenceDto", "review queue rows"], "impacts": ["stale inference 차단", "프론트 live 상세 일관성"], "status": "implemented" }, { "id": "workflow_api", "label": "workflow API", "stage": "조회 계층", "kind": "module", "position": { "x": 1080, "y": 420 }, "file": "backend/src/main/java/gc/mda/kcg/domain/fleet/ParentInferenceWorkflowController.java", "symbol": "candidate-exclusions / label-sessions endpoints", "role": "그룹 제외, 전역 제외, 라벨 세션, tracking 조회 API", "params": ["POST/GET workflow actions"], "rules": ["activeOnly query", "release/cancel action"], "storageReads": ["workflow tables"], "storageWrites": ["workflow tables", "review log"], "outputs": ["workflow DTO responses"], "impacts": ["human-in-the-loop 데이터 축적"], "status": "implemented" }, { "id": "review_ui", "label": "모선 검토 UI", "stage": "프론트", "kind": "component", "position": { "x": 520, "y": 620 }, "file": "frontend/src/components/korea/ParentReviewPanel.tsx", "symbol": "ParentReviewPanel", "role": "후보 비교, 필터, 라벨/제외 액션, coverage evidence 표시", "params": ["min score", "min gear count", "search", "spatial filter"], "rules": ["30% 미만 후보 비표시", "검색/범위/어구수 필터 AND", "hover 기반 overlay 강조"], "storageReads": ["review/detail API", "localStorage filters"], "storageWrites": ["workflow API actions", "localStorage filters"], "outputs": ["review decisions", "candidate interpretation"], "impacts": ["사람 판단 백데이터 생성"], "status": "implemented" }, { "id": "mermaid_docs", "label": "Mermaid 산출물", "stage": "문서", "kind": "artifact", "position": { "x": 800, "y": 620 }, "file": "docs/generated/gear-parent-flow-overview.md", "symbol": "generated Mermaid docs", "role": "정적 흐름도와 노드 인덱스 문서", "params": ["manifest JSON"], "rules": ["generator 재실행 시 갱신"], "storageReads": ["flow manifest"], "storageWrites": ["docs/generated/*.md", "docs/generated/*.mmd"], "outputs": ["overview flowchart", "node index"], "impacts": ["정적 문서 기반 리뷰/공유"], "status": "implemented" }, { "id": "react_flow_viewer", "label": "React Flow viewer", "stage": "문서", "kind": "component", "position": { "x": 1080, "y": 620 }, "file": "frontend/src/flow/GearParentFlowViewer.tsx", "symbol": "GearParentFlowViewer", "role": "노드 클릭/검색/필터/상세 패널이 있는 인터랙티브 흐름 뷰어", "params": ["stage filter", "search", "node detail"], "rules": ["별도 HTML entry", "manifest를 단일 source로 사용"], "storageReads": ["flow manifest"], "storageWrites": [], "outputs": ["interactive HTML graph"], "impacts": ["개발/검토/설명 자료"], "status": "implemented" }, { "id": "future_episode", "label": "episode continuity", "stage": "후보 추적", "kind": "module", "position": { "x": 260, "y": 620 }, "file": "prediction/algorithms/gear_parent_episode.py", "symbol": "build_episode_plan / compute_prior_bonus_components", "role": "sub_cluster continuity와 episode/lineage/label prior bonus를 계산하는 계층", "params": ["split", "merge", "expire", "24h/7d/30d prior windows"], "rules": ["small member change는 same episode", "true merge는 new episode", "prior bonus는 weak carry-over + cap 0.20"], "storageReads": ["gear_group_episodes", "gear_group_episode_snapshots", "candidate snapshots", "label history"], "storageWrites": ["gear_group_episodes", "gear_group_episode_snapshots"], "outputs": ["episode assignment", "continuity source/score", "prior bonus components"], "impacts": ["장기 기억 기반 추론", "split/merge 이후 후보 관성 완화"], "status": "implemented" } ], "edges": [ { "id": "e1", "source": "source_tracks", "target": "safe_window", "label": "bucket window", "detail": "원천 5분 bucket에 safe delay와 overlap backfill 적용" }, { "id": "e2", "source": "safe_window", "target": "snpdb_fetch", "label": "fetch bounds", "detail": "safe_bucket, from_bucket, window_start 전달" }, { "id": "e3", "source": "snpdb_fetch", "target": "vessel_store", "label": "points", "detail": "초기/증분 point DataFrame 적재" }, { "id": "e4", "source": "vessel_store", "target": "gear_identity", "label": "latest positions", "detail": "어구 이름 패턴과 parent_name 파싱" }, { "id": "e5", "source": "vessel_store", "target": "detect_groups", "label": "latest positions", "detail": "어구 raw group과 서브클러스터 생성" }, { "id": "e6", "source": "detect_groups", "target": "group_snapshots", "label": "gear_groups", "detail": "1h/1h-fb/6h polygon snapshot 생성" }, { "id": "e7", "source": "vessel_store", "target": "gear_correlation", "label": "tracks", "detail": "후보 선박 6h track과 latest positions 입력" }, { "id": "e8", "source": "detect_groups", "target": "gear_correlation", "label": "groups", "detail": "그룹 중심, 반경, active ratio 계산 입력" }, { "id": "e9", "source": "group_snapshots", "target": "backend_read_model", "label": "snapshots", "detail": "최신 1h live group read model 구성" }, { "id": "e10", "source": "group_snapshots", "target": "parent_inference", "label": "center tracks", "detail": "최근 6h 그룹 중심 이동과 live parent membership 입력" }, { "id": "e11", "source": "gear_correlation", "target": "parent_inference", "label": "scores + raw", "detail": "correlation score와 raw metrics 사용" }, { "id": "e11a", "source": "detect_groups", "target": "future_episode", "label": "current clusters", "detail": "현재 gear group 멤버/중심점으로 episode continuity 계산" }, { "id": "e11b", "source": "workflow_exclusions", "target": "future_episode", "label": "label history", "detail": "label session lineage를 label prior 입력으로 사용" }, { "id": "e11c", "source": "future_episode", "target": "parent_inference", "label": "episode assignment", "detail": "episode_id, continuity source, prior aggregate를 candidate build에 반영" }, { "id": "e12", "source": "workflow_exclusions", "target": "parent_inference", "label": "active gates", "detail": "group/global exclusion과 label session을 candidate build에 반영" }, { "id": "e13", "source": "parent_inference", "target": "score_breakdown", "label": "candidate scoring", "detail": "이름/track/visit/proximity/activity/stability와 bonus 계산" }, { "id": "e13a", "source": "future_episode", "target": "score_breakdown", "label": "prior bonus", "detail": "episode/lineage/label prior bonus를 final score 마지막 단계에 가산" }, { "id": "e14", "source": "score_breakdown", "target": "backend_read_model", "label": "fresh candidate/resolution", "detail": "fresh inference만 group detail과 review queue에 노출" }, { "id": "e15", "source": "workflow_api", "target": "workflow_exclusions", "label": "CRUD", "detail": "exclusion/label 생성, 취소, 조회" }, { "id": "e16", "source": "backend_read_model", "target": "review_ui", "label": "review/detail API", "detail": "모선 검토 UI의 기본 데이터 소스" }, { "id": "e17", "source": "workflow_api", "target": "review_ui", "label": "actions", "detail": "라벨/그룹 제외/전체 제외/해제 액션 처리" }, { "id": "e18", "source": "review_ui", "target": "mermaid_docs", "label": "human-readable spec", "detail": "정적 문서와 UI 해석 흐름 연결" }, { "id": "e19", "source": "review_ui", "target": "react_flow_viewer", "label": "same manifest", "detail": "문서와 viewer가 같은 구조 설명을 공유" }, { "id": "e20", "source": "parent_inference", "target": "future_episode", "label": "episode snapshots", "detail": "current resolution과 top candidate를 episode snapshot/history에 기록" } ] }