Codex Lab 환경(iran-airstrike-replay-codex)에서 검증 완료된 어구 모선 자동 추론 + 검토 워크플로우 전체를 이식. ## Python (prediction/) - gear_parent_inference(1,428줄): 다층 점수 모델 (correlation + name + track + prior bonus) - gear_parent_episode(631줄): Episode 연속성 (Jaccard + 공간거리) - gear_name_rules: 모선 이름 정규화 + 4자 미만 필터 - scheduler: 추론 호출 단계 추가 (4.8) - fleet_tracker/kcgdb: SQL qualified_table() 동적화 - gear_correlation: timestamp 필드 추가 ## DB (database/migration/ 012~015) - 후보 스냅샷, resolution, episode, 라벨 세션, 제외 관리 테이블 9개 + VIEW 2개 ## Backend (Java) - 12개 DTO/Controller (ParentInferenceWorkflowController 등) - GroupPolygonService: parent_resolution LEFT JOIN + 15개 API 메서드 ## Frontend - ParentReviewPanel: 모선 검토 대시보드 - vesselAnalysis: 10개 신규 API 함수 + 6개 타입 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
24 KiB
Gear Parent Inference Dataflow Paper
초록
이 문서는 iran-airstrike-replay-codex의 한국 수역 어구 모선 추적 체계를 코드 기준으로 복원하는 통합 기술 문서다. 범위는 snpdb 5분 궤적 적재, 인메모리 캐시 유지, 어구 그룹 검출, 서브클러스터 생성, 1h/1h-fb/6h 폴리곤 스냅샷 저장, correlation 기반 후보 점수화, coverage-aware parent inference, episode_id 기반 연속성 계층, backend read model, review/exclusion/label v2까지 포함한다. 문서의 목적은 “현재 무엇이 구현되어 있고, 각 경우의 수에서 어떤 분기 규칙이 적용되는가”를 한 문서에서 복원 가능하게 만드는 것이다.
1. 범위와 전제
1.1 구현 기준
- frontend:
frontend/ - backend:
backend/ - prediction:
prediction/ - schema migration:
database/migration/012_gear_parent_inference.sql,database/migration/014_gear_parent_workflow_v2_phase1.sql,database/migration/015_gear_parent_episode_tracking.sql
1.2 실행 환경
- lab backend:
rocky-211:18083 - lab prediction:
redis-211:18091 - lab schema:
kcg_lab - 로컬 프론트 진입점:
yarn dev:lab,yarn dev:lab:ssh
1.3 문서의 구분
- 구현됨:
- 현재 repo 코드와 lab 배포에 이미 반영된 규칙
- 후속 확장 후보:
- episode continuity 위에서 추가로 올릴
focus mode, richer episode lineage API, calibration report
- episode continuity 위에서 추가로 올릴
2. 문제 정의
이 시스템은 한국 수역에서 AIS 신호를 이용해 아래 문제를 단계적으로 푼다.
- 최근 24시간의 선박/어구 궤적을 메모리 캐시에 유지한다.
- 동일한 어구 이름 계열을 공간적으로 묶어 어구 그룹을 만든다.
- 각 그룹에 대해
1h,1h-fb,6h스냅샷을 생성한다. - 주변 선박 또는 잘못 분류된 어구 AIS를 후보로 수집하고 correlation 점수를 만든다.
- 후보를 모선 추론 점수로 다시 환산한다.
- 사람이 라벨/제외를 누적해 모델 정확도 고도화용 데이터셋을 만든다.
핵심 난점은 아래 세 가지다.
- DB 적재 지연 때문에 live incremental cache와 fresh reload가 다를 수 있다.
- 같은
parent_name아래에서도 실제로는 여러 공간 덩어리로 갈라질 수 있다. - 짧은 항적이
track/proximity/activity에서 과대평가될 수 있다.
3. 전체 아키텍처 흐름
flowchart LR
A["signal.t_vessel_tracks_5min<br/>5분 bucket linestringM"] --> B["prediction/db/snpdb.py<br/>safe bucket + overlap backfill"]
B --> C["prediction/cache/vessel_store.py<br/>24h in-memory cache"]
C --> D["prediction/fleet_tracker.py<br/>gear_identity_log / snapshot"]
C --> E["prediction/algorithms/polygon_builder.py<br/>gear group detect + sub-cluster + snapshots"]
E --> F["kcg_lab.group_polygon_snapshots"]
C --> G["prediction/algorithms/gear_correlation.py<br/>raw metrics + EMA score"]
G --> H["kcg_lab.gear_correlation_raw_metrics"]
G --> I["kcg_lab.gear_correlation_scores"]
F --> J["prediction/algorithms/gear_parent_inference.py<br/>candidate build + scoring + status"]
H --> J
I --> J
K["v2 exclusions / labels"] --> J
J --> L["kcg_lab.gear_group_parent_candidate_snapshots"]
J --> M["kcg_lab.gear_group_parent_resolution"]
J --> N["kcg_lab.gear_parent_label_tracking_cycles"]
F --> O["backend GroupPolygonService"]
L --> O
M --> O
N --> O
O --> P["frontend ParentReviewPanel"]
4. 원천 데이터와 시간 모델
4.1 원천 데이터 형식
원천은 signal.t_vessel_tracks_5min이며, 1 row = 1 MMSI = 5분 구간의 궤적 전체를 LineStringM으로 보관한다. 실제 위치 포인트는 ST_DumpPoints(track_geom)로 분해하고, 각 점의 timestamp는 ST_M((dp).geom)에서 꺼낸다. 구현 위치는 prediction/db/snpdb.py다.
4.2 safe watermark
현재 구현은 “DB 적재가 완료된 bucket만 읽는다”는 원칙을 따른다.
prediction/time_bucket.pycompute_safe_bucket()compute_initial_window_start()compute_incremental_window_start()
- 기본값:
SNPDB_SAFE_DELAY_MINSNPDB_BACKFILL_BUCKETS
핵심 규칙:
- 초기 적재는
now - safe_delay를 5분 내림한safe_bucket까지만 읽는다. - 증분 적재는
last_bucket - backfill_window부터safe_bucket까지 다시 읽는다. - live cache는
timestamp가 아니라time_bucket기준으로 24시간 cutoff를 맞춘다.
4.3 왜 safe watermark가 필요한가
time_bucket > last_bucket만 사용하면, 늦게 들어온 같은 bucket row를 영구히 놓칠 수 있다. 현재 구현은 overlap backfill과 dedupe로 이 drift를 줄인다.
- 조회:
prediction/db/snpdb.py - 병합 dedupe:
prediction/cache/vessel_store.py
5. Stage 1: 캐시 적재와 유지
5.1 초기 적재
prediction/main.py는 시작 시 vessel_store.load_initial(24)를 호출한다.
prediction/cache/vessel_store.py의 규칙:
snpdb.fetch_all_tracks(hours)로 최근 24시간을 safe bucket까지 읽는다.- MMSI별 DataFrame으로
_tracks를 구성한다. - 최대
time_bucket을_last_bucket으로 저장한다. - static info와 permit registry를 함께 refresh한다.
5.2 증분 병합
스케줄러는 snpdb.fetch_incremental(vessel_store.last_bucket)로 overlap backfill 구간을 다시 읽는다.
merge_incremental() 규칙:
- 기존 DataFrame과 새 batch를 합친다.
timestamp,time_bucket으로 정렬한다.timestamp기준 중복은keep='last'로 제거한다.- batch의 최대
time_bucket이 더 크면_last_bucket을 갱신한다.
5.3 stale eviction
evict_stale()는 safe bucket 기준 24시간 이전 포인트를 제거한다. time_bucket이 있으면 bucket 기준, 없으면 timestamp 기준으로 fallback한다.
6. Stage 2: 어구 identity 추출
prediction/fleet_tracker.py는 어구 이름 패턴에서 parent_name, gear_index_1, gear_index_2를 파싱하고 gear_identity_log를 관리한다.
6.1 이름 기반 필터
공통 규칙은 prediction/algorithms/gear_name_rules.py에 있다.
- 정규화:
- 대문자화
- 공백,
_,-,%제거
- 추적 가능 최소 길이:
- 정규화 길이
>= 4
- 정규화 길이
fleet_tracker.py와 polygon_builder.py는 모두 is_trackable_parent_name()을 사용한다. 즉 짧은 이름은 추론 이전, 그룹 생성 이전 단계부터 제외된다.
6.2 identity log 동작
fleet_tracker.py의 핵심 분기:
- 같은 MMSI + 같은 이름:
- 기존 활성 row의
last_seen_at, 위치만 갱신
- 같은 MMSI + 다른 이름:
- 기존 row 비활성화
- 새 row insert
- 다른 MMSI + 같은 이름:
- 기존 row 비활성화
- 새 MMSI로 row insert
- 기존
gear_correlation_scores.target_mmsi를 새 MMSI로 이전
7. Stage 3: 어구 그룹 생성과 서브클러스터
실제 어구 그룹은 prediction/algorithms/polygon_builder.py의 detect_gear_groups()가 만든다.
7.1 1차 그룹화
규칙:
- 최신 position 이름이 어구 패턴에 맞아야 한다.
STALE_SEC를 넘는 오래된 신호는 제외한다.440,441MMSI는 어구 AIS 미사용으로 간주해 제외한다.is_trackable_parent_name(parent_raw)를 만족해야 한다.- 같은
parent_name은 공백 제거 버전으로 묶는다.
7.2 서브클러스터 생성
같은 이름 아래에서도 거리 기반 연결성으로 덩어리를 나눈다.
- 거리 임계치:
MAX_DIST_DEG = 0.15 - 연결 규칙:
- 각 어구가 클러스터 내 최소 1개와
MAX_DIST_DEG이내면 같은 연결 요소
- 각 어구가 클러스터 내 최소 1개와
- 구현:
- Union-Find
모선이 이미 있으면, 모선과 가장 가까운 클러스터를 seed cluster로 간주한다.
7.3 sub_cluster_id 부여 규칙
현재 구현은 아래와 같다.
- 클러스터가 1개면
sub_cluster_id = 0 - 클러스터가 여러 개면
1..N - 이후 동일
parent_key의 두 서브그룹이 다시 근접 병합되면sub_cluster_id = 0
즉 sub_cluster_id는 영구 식별자가 아니라 “그 시점의 공간 분리 라벨”이다.
7.4 병합 규칙
동일 parent_key의 두 그룹이 다시 가까워지면:
- 멤버를 합친다.
- 부모 MMSI가 없는 큰 그룹에 작은 그룹의
parent_mmsi를 승계할 수 있다. sub_cluster_id = 0으로 재설정한다.
7.5 스냅샷 생성 규칙
build_all_group_snapshots()는 각 그룹에 대해 1h, 1h-fb, 6h 스냅샷을 만든다.
1h- 같은
parent_name전체 기준 1시간 활성 멤버 수>= 2
- 같은
1h-fb- 같은
parent_name전체 기준 1시간 활성 멤버 수< 2 - 리플레이/일치율 추적용
- 라이브 현황에서 제외
- 같은
6h- 6시간 내 stale이 아니어야 함
추가 규칙:
- 서브클러스터 내 1h 활성 멤버가 2개 미만이면 최신 2개로 fallback display를 만든다.
- 수역 외(
GEAR_OUT_ZONE)인데 멤버 수가MIN_GEAR_GROUP_SIZE미만이면 스킵한다. - 모선이 있고, 멤버와 충분히 근접하면
members[].isParent = true로 같이 넣는다.
8. Stage 4: correlation 모델
prediction/algorithms/gear_correlation.py는 어구 그룹별 raw metric과 EMA score를 만든다.
8.1 후보 생성
입력:
- group center
- group radius
- active ratio
- group member MMSI set
출력 후보:
- 선박 후보(
VESSEL) - 잘못 분류된 어구 후보(
GEAR_BUOY)
후보 수는 그룹당 최대 30개로 제한된다.
8.2 raw metric
선박 후보는 최근 6시간 항적 기반으로 아래 값을 만든다.
proximity_ratiovisit_scoreactivity_syncdtw_similarity
어구 후보는 단순 거리 기반 proximity_ratio만 사용한다.
8.3 EMA score
모델 파라미터(gear_correlation_param_models)별로 아래를 수행한다.
- composite score 계산
- 이전 score와 streak를 읽는다
update_score()로 EMA 갱신- threshold 이상이거나 기존 row가 있으면 upsert
반대로 이번 사이클 후보군에서 빠진 기존 항목은 OUT_OF_RANGE로 fast decay된다.
8.4 correlation 산출물
gear_correlation_raw_metricsgear_correlation_scores
여기까지는 “잠재적 모선/근접 대상”의 score이고, 최종 parent inference는 아직 아니다.
9. Stage 5: parent inference
prediction/algorithms/gear_parent_inference.py가 최종 모선 추론을 수행한다.
전체 진입점은 run_gear_parent_inference(vessel_store, gear_groups, conn)이다.
9.1 전체 분기 개요
flowchart TD
A["active gear group"] --> B{"direct parent member<br/>exists?"}
B -- yes --> C["DIRECT_PARENT_MATCH<br/>fresh resolution upsert"]
B -- no --> D{"trackable parent name?"}
D -- no --> E["SKIPPED_SHORT_NAME"]
D -- yes --> F["build candidate set"]
F --> G{"candidate exists?"}
G -- no --> H["NO_CANDIDATE"]
G -- yes --> I["score + rank + margin + stable cycles"]
I --> J{"auto promotion rule?"}
J -- yes --> K["AUTO_PROMOTED"]
J -- no --> L{"top score >= 0.60?"}
L -- yes --> M["REVIEW_REQUIRED"]
L -- no --> N["UNRESOLVED"]
9.1.1 episode continuity 선행 단계
현재 구현에서 run_gear_parent_inference()는 후보 점수를 만들기 전에 먼저 prediction/algorithms/gear_parent_episode.py를 호출해 active 그룹의 continuity를 계산한다.
입력:
- 현재 cycle
gear_groups - 정규화된
parent_name - 최근
6hactivegear_group_episodes - 최근
24hepisode prior,7dlineage prior,30dlabel prior 집계
핵심 규칙:
- continuity score는
0.75 * member_jaccard + 0.25 * center_support다. - 중심점 지원값은
12nm이내일수록 커진다. - continuity score가 충분하거나, overlap member가 있고 거리 조건을 만족하면 연결 후보로 본다.
- 두 개 이상 active episode가 하나의 현재 cluster로 들어오면
MERGE_NEW다. - 하나의 episode가 여러 현재 cluster로 갈라지면 하나는
SPLIT_CONTINUE, 나머지는SPLIT_NEW다. - 아무 previous episode와도 연결되지 않으면
NEW다. - 현재 cycle과 연결되지 못한 active episode는
EXPIRED또는MERGED로 종료한다.
현재 저장되는 continuity 메타데이터:
gear_group_parent_candidate_snapshots.episode_idgear_group_parent_resolution.episode_idgear_group_parent_resolution.continuity_sourcegear_group_parent_resolution.continuity_scoregear_group_parent_resolution.prior_bonus_totalgear_group_episodesgear_group_episode_snapshots
9.2 direct parent 보강
최신 어구 그룹에 아래 중 하나가 있으면 후보 추론 대신 직접 모선 매칭으로 처리한다.
members[].isParent = truegroup.parent_mmsi존재
이 경우:
status = DIRECT_PARENT_MATCHdecision_source = DIRECT_PARENT_MATCHconfidence = 1.0candidateCount = 0
단, 기존 상태가 MANUAL_CONFIRMED면 그 수동 상태를 유지한다.
9.3 짧은 이름 스킵
정규화 이름 길이 < 4면:
- 후보 생성 자체를 수행하지 않는다.
status = SKIPPED_SHORT_NAMEdecision_source = AUTO_SKIP
9.4 후보 집합
후보 집합은 아래의 합집합이다.
- default correlation model 상위 후보
- registry name exact bucket
- 기존 resolution의
selected_parent_mmsi또는 이전 top candidate
여기에 아래를 적용한다.
- active global exclusion 제거
- active group exclusion 제거
- 최근 reject cooldown 후보 제거
9.5 이름 점수
현재 구현 규칙:
- 원문 완전일치:
1.0 - 정규화 완전일치:
0.8 - prefix/contains:
0.5 - 숫자를 제거한 순수 문자 부분만 동일:
0.3 - 그 외:
0.0
비교 대상:
parent_name- 후보 AIS 이름
- registry
name_cn - registry
name_en
9.6 coverage-aware evidence
짧은 항적 과대평가를 막기 위해 raw score와 effective score를 분리한다.
evidence에 남는 값:
trackPointCounttrackSpanMinutesoverlapPointCountoverlapSpanMinutesinZonePointCountinZoneSpanMinutestrackCoverageFactorvisitCoverageFactoractivityCoverageFactorcoverageFactor
현재 최종 점수에는 raw가 아니라 adjusted score가 들어간다.
9.7 점수 식
가중치 합은 아래다.
0.40 * base_corr0.15 * name_match0.15 * track_similarity_effective0.10 * visit_effective0.05 * proximity_effective0.05 * activity_effective0.10 * stability+ registry_bonus(0.05)
그 다음 별도 후가산:
412/413MMSI 보너스+0.15- 단,
preBonusScore >= 0.30일 때만 적용 episode/lineage/label prior bonus- 최근 동일 episode
24h - 동일 lineage
7d - 라벨 세션
30d - 총합 cap
0.20
- 최근 동일 episode
9.8 상태 전이
분기 조건:
NO_CANDIDATE- 후보가 하나도 없을 때
AUTO_PROMOTEDtarget_type == VESSEL- candidate source에
CORRELATION포함 final_score >= auto_promotion_thresholdmargin >= auto_promotion_marginstable_cycles >= auto_promotion_stable_cycles
REVIEW_REQUIREDfinal_score >= 0.60
UNRESOLVED- 나머지
추가 예외:
- 기존 상태가
MANUAL_CONFIRMED면 수동 상태를 유지한다. - active label session이 있으면 tracking row를 별도로 적재한다.
9.9 산출물
gear_group_parent_candidate_snapshotsgear_group_parent_resolutiongear_parent_label_tracking_cyclesgear_group_episodesgear_group_episode_snapshots
10. Stage 6: backend read model
backend의 중심은 backend/.../GroupPolygonService.java다.
10.1 최신 1h만 라이브로 간주
group list, review queue, detail API는 모두 최신 전역 1h 스냅샷만 기준으로 삼는다.
핵심 효과:
1h-fb는 라이브 현황에서 기본 제외된다.- 이미 사라진 과거 sub-cluster는 detail API에서 다시 보이지 않는다.
10.2 stale inference 차단
resolution.last_evaluated_at >= group.snapshot_time인 경우만 join한다.
즉 최신 group snapshot보다 오래된 candidate/resolution은 detail/review/list에서 숨긴다. 이 규칙이 ZHEDAIYU02433, ZHEDAIYU02394 유형 stale 표시를 막는다.
10.3 detail API 의미
/api/kcg/vessel-analysis/groups/{groupKey}/parent-inference
현재 의미:
- 해당 그룹의 최신 전역
1hlive sub-cluster 집합 - 각 sub-cluster의 fresh resolution
- 각 sub-cluster의 latest candidate snapshot
11. Stage 7: review / exclusion / label v2
v2 Phase 1은 “자동 추론 결과”와 “사람 판단 데이터”를 분리하는 구조다.
11.1 사람 판단 저장소
gear_parent_candidate_exclusionsgear_parent_label_sessionsgear_parent_label_tracking_cycles
11.2 액션 의미
- 그룹 제외:
- 특정
group_key + sub_cluster_id에서 특정 후보 MMSI를 일정 기간 제거
- 특정
- 전체 후보 제외:
- 특정 MMSI를 모든 그룹 후보군에서 제거
- 정답 라벨:
- 특정 그룹에 대해 정답 parent MMSI를
1/3/5일세션으로 지정 - prediction은 이후 cycle마다 top1/top3 여부를 추적
- 특정 그룹에 대해 정답 parent MMSI를
11.3 why v2
기존 MANUAL_CONFIRMED/REJECT는 운영 override 성격이 강했고, “모델 정확도 평가용 백데이터”와 섞였다. v2는 이 둘을 분리해 라벨을 평가 데이터로 쓰도록 한다.
12. 실제 경우의 수 분기표
| 경우 | 구현 위치 | 현재 동작 |
|---|---|---|
이름 길이 < 4 |
gear_name_rules.py, fleet_tracker.py, polygon_builder.py, gear_parent_inference.py |
identity/grouping/inference 단계에서 제외 또는 SKIPPED_SHORT_NAME |
| 직접 모선 포함 | polygon_builder.py, gear_parent_inference.py |
DIRECT_PARENT_MATCH fresh resolution |
| 같은 이름, 멀리 떨어진 어구 | polygon_builder.py |
별도 sub-cluster 생성 |
| 두 서브클러스터가 다시 근접 | polygon_builder.py |
하나로 병합, sub_cluster_id = 0 |
group 전체 1h 활성 멤버 < 2 |
polygon_builder.py |
1h-fb 생성, live 현황 제외 |
| 후보가 하나도 없음 | gear_parent_inference.py |
NO_CANDIDATE |
| 짧은 항적이 우연히 근접 | gear_parent_inference.py |
coverage-aware 보정으로 effective score 감소 |
| stale old inference가 남아 있음 | GroupPolygonService.java |
최신 group snapshot보다 오래되면 숨김 |
| 직접 parent가 이미 있음 | gear_parent_inference.py |
후보 계산 대신 direct parent resolution |
13. sub_cluster_id의 한계
현재 코드에서 sub_cluster_id는 영구 identity가 아니다.
이유:
- 같은 이름 그룹의 공간 분리 수가 cycle마다 달라질 수 있다.
- 병합되면
0으로 재설정된다. - 멤버가 추가/이탈해도 기존 번호 의미가 유지된다고 보장할 수 없다.
따라서 group_key + sub_cluster_id는 “현재 cycle의 공간 덩어리”를 가리키는 키로는 유효하지만, 장기 연속 추적 키로는 부적합하다.
14. Stage 8: episode_id continuity + prior bonus
14.1 목적
현재 구현의 episode_id는 “같은 어구 덩어리의 시간적 연속성”을 추적하는 별도 식별자다. sub_cluster_id를 대체하지 않고, 그 위에 얹는 계층이다.
핵심 목적:
- 작은 멤버 변화는 같은 episode로 이어 붙인다.
- 구조적 split/merge는 continuity source로 기록한다.
- long-memory는
stable_cycles직접 승계가 아니라 약한 prior bonus로만 전달한다.
14.2 현재 저장소
gear_group_episodes- active/merged/expired episode 현재 상태
gear_group_episode_snapshots- cycle별 episode 스냅샷
gear_group_parent_candidate_snapshotsepisode_id,normalized_parent_name,episode_prior_bonus,lineage_prior_bonus,label_prior_bonus
gear_group_parent_resolutionepisode_id,continuity_source,continuity_score,prior_bonus_total
14.3 continuity score
현재 continuity score는 아래다.
continuity_score =
0.75 * member_jaccard
+ 0.25 * center_support
member_jaccard- 현재/이전 episode 멤버 MMSI Jaccard
center_support- 중심점 거리
12nm이내일수록 높아지는 값
- 중심점 거리
연결 후보 판단:
- continuity score
>= 0.45 - 또는 overlap member가 있고 거리 조건을 만족하면 연결 후보로 인정
14.4 continuity source 규칙
NEW- 어떤 이전 episode와도 연결되지 않음
CONTINUED- 1:1 continuity
SPLIT_CONTINUE- 하나의 이전 episode가 여러 현재 cluster로 갈라졌고, 그중 주 가지
SPLIT_NEW- split로 새로 생성된 가지
MERGE_NEW- 2개 이상 active episode가 의미 있게 하나의 현재 cluster로 합쳐짐
DIRECT_PARENT_MATCH- 직접 모선 포함 그룹이 fresh resolution으로 정리되는 경우의 최종 resolution source
14.5 merge / split / expire
현재 구현 규칙:
- split
- 가장 유사한 현재 cluster 1개는 기존 episode 유지
- 나머지는 새 episode 생성
- 새 episode에는
split_from_episode_id저장
- merge
- 2개 이상 previous episode가 같은 현재 cluster로 의미 있게 들어오면 새 episode 생성
- 이전 episode들은
MERGED,merged_into_episode_id = 새 episode
- expire
- 최근
6hactive episode가 현재 cycle 어떤 cluster와도 연결되지 않으면EXPIRED
14.6 prior bonus 계층
현재 final score에는 signal score 뒤에 아래 prior bonus가 후가산된다.
episode_prior_bonus- 최근 동일 episode
24h - cap
0.10
- 최근 동일 episode
lineage_prior_bonus- 동일 정규화 이름 lineage
7d - cap
0.05
- 동일 정규화 이름 lineage
label_prior_bonus- 동일 lineage 라벨 세션
30d - cap
0.10
- 동일 lineage 라벨 세션
- 총합 cap
0.20
현재 후보가 이미 candidate set에 들어온 경우에만 적용하며, 과거 점수를 직접 carry하는 대신 약한 보너스로만 사용한다.
14.7 병합 후 후보 관성
질문 사례처럼 A episode 후보 a, B episode 후보 b가 있다가 병합 후 b가 더 적합해질 수 있다. 현재 구현은 병합 시 무조건 A를 유지하지 않고 새 episode를 생성해 A/B 둘 다의 history를 prior bonus 풀에서 재평가한다. 따라서 b는 완전 신규 후보처럼 0에서 시작하지 않지만, A의 과거 stable_cycles가 그대로 지배하지도 않는다.
15. 현재 episode 상태 흐름
stateDiagram-v2
[*] --> Active
Active --> Active: "CONTINUED / 소규모 멤버 변동"
Active --> Active: "SPLIT_CONTINUE"
Active --> Active: "MERGE_NEW로 새 episode 생성 후 연결"
Active --> Merged: "merged_into_episode_id 기록"
Active --> Expired: "최근 6h continuity 없음"
Merged --> [*]
Expired --> [*]
16. 결론
현재 구현은 아래를 모두 포함한다.
- safe watermark + overlap backfill 기반 incremental 안정화
- 짧은 이름 그룹 제거
- 거리 기반 sub-cluster와
1h/1h-fb/6h스냅샷 - correlation + parent inference 분리
- coverage-aware score 보정
- stale inference 차단
- direct parent supplement
- v2 exclusion/label/tracking 저장소
episode_idcontinuity와 prior bonus
남은 과제는 episode 자체보다, 이 continuity 계층을 read model과 시각화에서 더 설명력 있게 노출하는 것이다. 즉 다음 단계의 핵심은 episode 도입이 아니라, episode lineage API, calibration report, richer review analytics를 얹는 일이다.
17. 참고 코드
prediction/main.pyprediction/time_bucket.pyprediction/db/snpdb.pyprediction/cache/vessel_store.pyprediction/fleet_tracker.pyprediction/algorithms/gear_name_rules.pyprediction/algorithms/polygon_builder.pyprediction/algorithms/gear_correlation.pyprediction/algorithms/gear_parent_episode.pyprediction/algorithms/gear_parent_inference.pybackend/src/main/java/gc/mda/kcg/domain/fleet/GroupPolygonService.javabackend/src/main/java/gc/mda/kcg/domain/fleet/ParentInferenceWorkflowController.javadatabase/migration/012_gear_parent_inference.sqldatabase/migration/014_gear_parent_workflow_v2_phase1.sqldatabase/migration/015_gear_parent_episode_tracking.sql