- GearParentFlowViewer: React Flow 기반 인터랙티브 흐름도 - gear-parent-flow.html: standalone entry point - vite.config.ts: multi-entry 빌드 (main + gearParentFlow) - App.tsx: FLOW 링크 추가 - @xyflow/react 의존성 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
326 lines
18 KiB
JSON
326 lines
18 KiB
JSON
{
|
|
"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에 기록" }
|
|
]
|
|
}
|