From b04e96c45754d34eecd34f4997cd4f13d56c9c92 Mon Sep 17 00:00:00 2001 From: htlee Date: Wed, 1 Apr 2026 16:07:09 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=201h=20=ED=99=9C=EC=84=B1=20=ED=8C=90?= =?UTF-8?q?=EC=A0=95=EC=9D=84=20parent=5Fname=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=ED=95=A9=EC=82=B0=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 서브클러스터 분리 후 개별 서브그룹의 1h 멤버가 2개 미만이더라도, parent_name 전체(모든 서브클러스터 합산)에서 1h 활성 멤버 >= 2이면 resolution='1h'로 저장하여 라이브 현황에 표시. 결과: 라이브 1h 그룹 5개 → 927개 정상 복구 Co-Authored-By: Claude Opus 4.6 (1M context) --- prediction/algorithms/polygon_builder.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/prediction/algorithms/polygon_builder.py b/prediction/algorithms/polygon_builder.py index 2520806..db6e6ff 100644 --- a/prediction/algorithms/polygon_builder.py +++ b/prediction/algorithms/polygon_builder.py @@ -414,6 +414,16 @@ def build_all_group_snapshots( # ── GEAR 타입: detect_gear_groups 결과 → 1h/6h 듀얼 스냅샷 ──── gear_groups = detect_gear_groups(vessel_store, now=now) + # parent_name 기준 전체 1h 활성 멤버 합산 (서브클러스터 분리 전) + parent_active_1h: dict[str, int] = {} + for group in gear_groups: + pn = group['parent_name'] + cnt = sum( + 1 for gm in group['members'] + if _get_time_bucket_age(gm.get('mmsi'), all_positions, now) <= DISPLAY_STALE_SEC + ) + parent_active_1h[pn] = parent_active_1h.get(pn, 0) + cnt + for group in gear_groups: parent_name: str = group['parent_name'] parent_mmsi: Optional[str] = group['parent_mmsi'] @@ -422,17 +432,15 @@ def build_all_group_snapshots( if not gear_members: continue - # ── 1h 활성 멤버 필터 ── + # ── 1h 활성 멤버 필터 (이 서브클러스터 내) ── active_members_1h = [ gm for gm in gear_members if _get_time_bucket_age(gm.get('mmsi'), all_positions, now) <= DISPLAY_STALE_SEC ] - active_count_1h = len(active_members_1h) - # fallback: 1h < 2이면 time_bucket 최신 2개 유지 (리플레이/일치율 추적용) - # 라이브 현황에서는 active_count_1h로 필터 (fallback 그룹 제외) + # fallback: 서브클러스터 내 1h < 2이면 time_bucket 최신 2개 유지 display_members_1h = active_members_1h - if active_count_1h < 2 and len(gear_members) >= 2: + if len(active_members_1h) < 2 and len(gear_members) >= 2: sorted_by_age = sorted( gear_members, key=lambda gm: _get_time_bucket_age(gm.get('mmsi'), all_positions, now), @@ -447,8 +455,9 @@ def build_all_group_snapshots( display_members_6h = gear_members # ── resolution별 스냅샷 생성 ── - # 1h-fb: fallback (실제 1h 활성 < 2) — 리플레이/일치율 추적용, 라이브 현황에서 제외 - res_1h = '1h' if active_count_1h >= 2 else '1h-fb' + # 1h-fb: parent_name 전체 1h 활성 < 2 → 리플레이/일치율 추적용, 라이브 현황에서 제외 + # parent_name 전체 기준으로 판단 (서브클러스터 분리로 개별 멤버가 적어져도 그룹 전체가 활성이면 1h) + res_1h = '1h' if parent_active_1h.get(parent_name, 0) >= 2 else '1h-fb' for resolution, members_for_snap in [(res_1h, display_members_1h), ('6h', display_members_6h)]: if len(members_for_snap) < 2: continue From 5c85afea22b812b38548c48059f5a0db24b35585 Mon Sep 17 00:00:00 2001 From: htlee Date: Wed, 1 Apr 2026 16:47:22 +0900 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=85=B8=ED=8A=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20(1h?= =?UTF-8?q?=20=ED=99=9C=EC=84=B1=20=ED=8C=90=EC=A0=95=20=EC=88=98=EC=A0=95?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/RELEASE-NOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index 11a4bc0..114ee98 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -4,6 +4,9 @@ ## [Unreleased] +### 수정 +- 1h 활성 판정을 parent_name 전체 합산 기준으로 변경 (서브클러스터 분리 후 개별 소수 문제 해결) + ## [2026-04-01.2] ### 추가