First commit of claude's rework in django + vanillajs fronted
This commit is contained in:
60
apps/tools/analyzer/grouper.py
Normal file
60
apps/tools/analyzer/grouper.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from datetime import timedelta
|
||||
import pandas as pd
|
||||
|
||||
OUTLIER_FACTOR = 5
|
||||
|
||||
|
||||
def detect_groups(df: pd.DataFrame, outlier_factor: float = OUTLIER_FACTOR,
|
||||
manual_splits: list | None = None,
|
||||
forced_splits: list | None = None) -> list:
|
||||
"""Split shots into groups.
|
||||
|
||||
forced_splits: when provided, ONLY these split positions are used — auto-detection
|
||||
is bypassed entirely. Use this for user-defined groupings from the visual editor.
|
||||
|
||||
manual_splits: added on top of auto-detected splits (when forced_splits is None).
|
||||
Both auto+manual mechanisms are merged and deduplicated.
|
||||
"""
|
||||
if len(df) <= 1:
|
||||
return [df]
|
||||
|
||||
def _build_groups(all_splits):
|
||||
if not all_splits:
|
||||
return [df]
|
||||
groups = []
|
||||
prev = 0
|
||||
for pos in all_splits:
|
||||
group = df.iloc[prev:pos]
|
||||
if len(group) > 0:
|
||||
groups.append(group.reset_index(drop=True))
|
||||
prev = pos
|
||||
last = df.iloc[prev:]
|
||||
if len(last) > 0:
|
||||
groups.append(last.reset_index(drop=True))
|
||||
return groups
|
||||
|
||||
# Forced mode: user controls exact split positions, no auto-detection
|
||||
if forced_splits is not None:
|
||||
valid = sorted(s for s in forced_splits if 0 < s < len(df))
|
||||
return _build_groups(valid)
|
||||
|
||||
times = df["time"]
|
||||
diffs = times.diff().dropna()
|
||||
|
||||
if diffs.empty:
|
||||
return [df]
|
||||
|
||||
median_gap = diffs.median()
|
||||
|
||||
# Auto-detect splits based on time gaps
|
||||
auto_splits: set[int] = set()
|
||||
if median_gap != timedelta(0):
|
||||
threshold = outlier_factor * median_gap
|
||||
for idx, gap in diffs.items():
|
||||
if gap > threshold:
|
||||
pos = df.index.get_loc(idx)
|
||||
auto_splits.add(pos)
|
||||
|
||||
# Merge with manual splits (filter to valid range)
|
||||
extra = set(manual_splits) if manual_splits else set()
|
||||
return _build_groups(sorted(auto_splits | extra))
|
||||
Reference in New Issue
Block a user