from photoflow.core.models import ClusterMember from app.clustering import _epoch, _median def coverage_members(candidate, assets_by_id, *, outlier_factor: float = 8.0) -> list: member_ids = [i for i in candidate.member_ids if i in assets_by_id] members = sorted((assets_by_id[i] for i in member_ids), key=lambda a: a.taken_at) epochs = [_epoch(a.taken_at) for a in members] gaps = [b - a for a, b in zip(epochs, epochs[1:])] base = _median(gaps) if gaps else 0.0 out = [] for idx, a in enumerate(members): is_outlier = False if candidate.seed_tag and base > 0: left = epochs[idx] - epochs[idx - 1] if idx > 0 else 0 right = epochs[idx + 1] - epochs[idx] if idx < len(members) - 1 else 0 nearest = min([g for g in (left, right) if g > 0], default=0) if nearest > outlier_factor * base: is_outlier = True out.append(ClusterMember( cluster_id=0, immich_id=a.immich_id, member_confidence=candidate.confidence, is_outlier=is_outlier, included=True, flagged_coverage=False)) if candidate.seed_tag and candidate.start_at and candidate.end_at: mset = set(member_ids) for a in sorted(assets_by_id.values(), key=lambda x: x.taken_at): if a.immich_id in mset: continue if candidate.start_at <= a.taken_at <= candidate.end_at: out.append(ClusterMember( cluster_id=0, immich_id=a.immich_id, member_confidence=0.0, is_outlier=False, included=False, flagged_coverage=True)) return out