feat: 트랙 API 전체 모델 확장 + 개별 선박 on/off → 폴리곤 반영
Prediction API:
- /correlation/{group}/tracks: is_default=TRUE 제거 → 모든 활성 모델 조회
- 응답에 models: {modelName: score} 딕셔너리 추가 (모델별 점수)
- MMSI 기준 중복 제거, 최고 점수 유지
Frontend:
- CorrelationVesselTrack 타입: models 필드 추가, type 필드 추가
- 오퍼레이셔널 폴리곤: enabledVessels 기반 on/off 제어
(score 임계값 → 개별 체크박스 토글로 전환)
- identity OFF 시 폴리곤 base points에서 멤버 위치 제외
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
c4186a327d
커밋
8eacbb2c91
@ -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) {
|
for (const [mn, items] of correlationByModel) {
|
||||||
if (!enabledModels.has(mn)) continue;
|
if (!enabledModels.has(mn)) continue;
|
||||||
const color = MODEL_COLORS[mn] ?? '#94a3b8';
|
const color = MODEL_COLORS[mn] ?? '#94a3b8';
|
||||||
@ -420,13 +420,15 @@ export function useGearReplayLayers(
|
|||||||
|
|
||||||
const extraPts: [number, number][] = [];
|
const extraPts: [number, number][] = [];
|
||||||
for (const c of items as GearCorrelationItem[]) {
|
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);
|
const cp = corrPositions.find(p => p.mmsi === c.targetMmsi);
|
||||||
if (cp) extraPts.push([cp.lon, cp.lat]);
|
if (cp) extraPts.push([cp.lon, cp.lat]);
|
||||||
}
|
}
|
||||||
if (extraPts.length === 0) continue;
|
if (extraPts.length === 0) continue;
|
||||||
|
|
||||||
const opPolygon = buildInterpPolygon([...memberPts, ...extraPts]);
|
const basePts = enabledModels.has('identity') ? memberPts : [];
|
||||||
|
const opPolygon = buildInterpPolygon([...basePts, ...extraPts]);
|
||||||
if (!opPolygon) continue;
|
if (!opPolygon) continue;
|
||||||
|
|
||||||
layers.push(new PolygonLayer({
|
layers.push(new PolygonLayer({
|
||||||
|
|||||||
@ -144,8 +144,9 @@ export interface CorrelationTrackPoint {
|
|||||||
export interface CorrelationVesselTrack {
|
export interface CorrelationVesselTrack {
|
||||||
mmsi: string;
|
mmsi: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
type: string; // 'VESSEL' | 'GEAR'
|
||||||
score: number;
|
score: number;
|
||||||
modelName: string;
|
models: Record<string, number>; // { modelName: score }
|
||||||
track: CorrelationTrackPoint[];
|
track: CorrelationTrackPoint[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -78,8 +78,9 @@ def get_correlation_tracks(
|
|||||||
):
|
):
|
||||||
"""Return correlated vessels with their track history for map rendering.
|
"""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.
|
24h track data from in-memory vessel_store.
|
||||||
|
Each vessel includes which models detected it.
|
||||||
"""
|
"""
|
||||||
from cache.vessel_store import vessel_store
|
from cache.vessel_store import vessel_store
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ def get_correlation_tracks(
|
|||||||
conn = kcgdb.get_conn()
|
conn = kcgdb.get_conn()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
# Get correlated vessels from default model
|
# Get correlated vessels from ALL active models
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT s.target_mmsi, s.target_type, s.target_name,
|
SELECT s.target_mmsi, s.target_type, s.target_name,
|
||||||
s.current_score, m.name AS model_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
|
JOIN kcg.correlation_param_models m ON s.model_id = m.id
|
||||||
WHERE s.group_key = %s
|
WHERE s.group_key = %s
|
||||||
AND s.current_score >= %s
|
AND s.current_score >= %s
|
||||||
AND m.is_default = TRUE
|
|
||||||
AND m.is_active = TRUE
|
AND m.is_active = TRUE
|
||||||
ORDER BY s.current_score DESC
|
ORDER BY s.current_score DESC
|
||||||
""", (group_key, min_score))
|
""", (group_key, min_score))
|
||||||
@ -107,31 +107,41 @@ def get_correlation_tracks(
|
|||||||
if not rows:
|
if not rows:
|
||||||
return {'groupKey': group_key, 'vessels': []}
|
return {'groupKey': group_key, 'vessels': []}
|
||||||
|
|
||||||
# Collect target MMSIs
|
# Group by MMSI: collect all models per vessel, keep highest score
|
||||||
vessel_info = []
|
vessel_map: dict[str, dict] = {}
|
||||||
mmsis = []
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
vessel_info.append({
|
mmsi = row[0]
|
||||||
'mmsi': row[0],
|
model_name = row[4]
|
||||||
'type': row[1],
|
score = float(row[3])
|
||||||
'name': row[2] or '',
|
if mmsi not in vessel_map:
|
||||||
'score': float(row[3]),
|
vessel_map[mmsi] = {
|
||||||
'modelName': row[4],
|
'mmsi': mmsi,
|
||||||
})
|
'type': row[1],
|
||||||
mmsis.append(row[0])
|
'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
|
# Get tracks from vessel_store
|
||||||
tracks = vessel_store.get_vessel_tracks(mmsis, hours)
|
tracks = vessel_store.get_vessel_tracks(mmsis, hours)
|
||||||
|
|
||||||
# Build response
|
# Build response
|
||||||
vessels = []
|
vessels = []
|
||||||
for info in vessel_info:
|
for info in vessel_map.values():
|
||||||
track = tracks.get(info['mmsi'], [])
|
track = tracks.get(info['mmsi'], [])
|
||||||
vessels.append({
|
vessels.append({
|
||||||
'mmsi': info['mmsi'],
|
'mmsi': info['mmsi'],
|
||||||
'name': info['name'],
|
'name': info['name'],
|
||||||
|
'type': info['type'],
|
||||||
'score': info['score'],
|
'score': info['score'],
|
||||||
'modelName': info['modelName'],
|
'models': info['models'], # {modelName: score, ...}
|
||||||
'track': track,
|
'track': track,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user