diff --git a/frontend/src/hooks/useGearReplayLayers.ts b/frontend/src/hooks/useGearReplayLayers.ts index a420228..cfd29b9 100644 --- a/frontend/src/hooks/useGearReplayLayers.ts +++ b/frontend/src/hooks/useGearReplayLayers.ts @@ -412,7 +412,7 @@ export function useGearReplayLayers( } } - // 8. Operational polygons (per model — union of member positions + high-score correlation vessels) + // 8. Operational polygons (멤버 위치 + enabledVessels ON인 연관 선박으로 폴리곤 생성) for (const [mn, items] of correlationByModel) { if (!enabledModels.has(mn)) continue; const color = MODEL_COLORS[mn] ?? '#94a3b8'; @@ -420,13 +420,15 @@ export function useGearReplayLayers( const extraPts: [number, number][] = []; for (const c of items as GearCorrelationItem[]) { - if (c.score < 0.7) continue; + // enabledVessels로 개별 on/off 제어 (토글 대응) + if (!enabledVessels.has(c.targetMmsi)) continue; const cp = corrPositions.find(p => p.mmsi === c.targetMmsi); if (cp) extraPts.push([cp.lon, cp.lat]); } if (extraPts.length === 0) continue; - const opPolygon = buildInterpPolygon([...memberPts, ...extraPts]); + const basePts = enabledModels.has('identity') ? memberPts : []; + const opPolygon = buildInterpPolygon([...basePts, ...extraPts]); if (!opPolygon) continue; layers.push(new PolygonLayer({ diff --git a/frontend/src/services/vesselAnalysis.ts b/frontend/src/services/vesselAnalysis.ts index 5eea1b7..e18b9c1 100644 --- a/frontend/src/services/vesselAnalysis.ts +++ b/frontend/src/services/vesselAnalysis.ts @@ -144,8 +144,9 @@ export interface CorrelationTrackPoint { export interface CorrelationVesselTrack { mmsi: string; name: string; + type: string; // 'VESSEL' | 'GEAR' score: number; - modelName: string; + models: Record; // { modelName: score } track: CorrelationTrackPoint[]; } diff --git a/prediction/main.py b/prediction/main.py index 30ae293..00c9b33 100644 --- a/prediction/main.py +++ b/prediction/main.py @@ -78,8 +78,9 @@ def get_correlation_tracks( ): """Return correlated vessels with their track history for map rendering. - Queries gear_correlation_scores (default model) and enriches with + Queries gear_correlation_scores (ALL active models) and enriches with 24h track data from in-memory vessel_store. + Each vessel includes which models detected it. """ from cache.vessel_store import vessel_store @@ -87,7 +88,7 @@ def get_correlation_tracks( conn = kcgdb.get_conn() cur = conn.cursor() - # Get correlated vessels from default model + # Get correlated vessels from ALL active models cur.execute(""" SELECT s.target_mmsi, s.target_type, s.target_name, s.current_score, m.name AS model_name @@ -95,7 +96,6 @@ def get_correlation_tracks( JOIN kcg.correlation_param_models m ON s.model_id = m.id WHERE s.group_key = %s AND s.current_score >= %s - AND m.is_default = TRUE AND m.is_active = TRUE ORDER BY s.current_score DESC """, (group_key, min_score)) @@ -107,31 +107,41 @@ def get_correlation_tracks( if not rows: return {'groupKey': group_key, 'vessels': []} - # Collect target MMSIs - vessel_info = [] - mmsis = [] + # Group by MMSI: collect all models per vessel, keep highest score + vessel_map: dict[str, dict] = {} for row in rows: - vessel_info.append({ - 'mmsi': row[0], - 'type': row[1], - 'name': row[2] or '', - 'score': float(row[3]), - 'modelName': row[4], - }) - mmsis.append(row[0]) + mmsi = row[0] + model_name = row[4] + score = float(row[3]) + if mmsi not in vessel_map: + vessel_map[mmsi] = { + 'mmsi': mmsi, + 'type': row[1], + 'name': row[2] or '', + 'score': score, + 'models': {model_name: score}, + } + else: + entry = vessel_map[mmsi] + entry['models'][model_name] = score + if score > entry['score']: + entry['score'] = score + + mmsis = list(vessel_map.keys()) # Get tracks from vessel_store tracks = vessel_store.get_vessel_tracks(mmsis, hours) # Build response vessels = [] - for info in vessel_info: + for info in vessel_map.values(): track = tracks.get(info['mmsi'], []) vessels.append({ 'mmsi': info['mmsi'], 'name': info['name'], + 'type': info['type'], 'score': info['score'], - 'modelName': info['modelName'], + 'models': info['models'], # {modelName: score, ...} 'track': track, })