import type { MediaAssetKind } from "@rakazo/contracts"; import { notImplemented } from "./not-implemented.js"; export interface ClusterAsset { id: string; kind: MediaAssetKind; capturedAt: Date | null; gpsLat: number | null; gpsLon: number | null; deviceModel: string | null; albumNames: string[]; /** Mean frame embedding; null when not analyzed yet. */ embedding: Float32Array | null; } export interface SessionOptions { /** New session after a gap longer than this. Default 45. */ gapMinutes: number; /** New session after a location jump larger than this. Default 2. */ maxDistanceKm: number; /** New session on calendar-day change when the gap exceeds this. Default 3. */ dayBreakHours: number; /** Merge adjacent sessions whose mean embeddings reach this cosine. Default 0.85. */ mergeSimilarity: number; /** ...and whose gap is below this. Default 2. */ mergeMaxGapHours: number; /** IANA zone for calendar-day boundaries. Default "UTC". */ timeZone: string; } export const DEFAULT_SESSION_OPTIONS: SessionOptions = { gapMinutes: 45, maxDistanceKm: 2, dayBreakHours: 3, mergeSimilarity: 0.85, mergeMaxGapHours: 2, timeZone: "UTC", }; export interface SessionCluster { /** Stable across re-clustering: derived from the earliest asset id of the session. */ key: string; assetIds: string[]; startAt: Date | null; endAt: Date | null; centroid: { lat: number; lon: number } | null; } /** * Deterministic session grouping (spec 4.4). Assets without capturedAt form * one trailing "undated" session; exact duplicates (same id) are ignored. */ export function buildSessions( _assets: ClusterAsset[], _options?: Partial, ): SessionCluster[] { return notImplemented("buildSessions"); } export interface VectorItem { id: string; vector: Float32Array; } /** Agglomerative average-linkage clustering; returns groups with stable keys. */ export function clusterThemes( _items: VectorItem[], _options?: { similarity?: number; minSize?: number }, ): Array<{ key: string; ids: string[] }> { return notImplemented("clusterThemes"); } /** Group face tracks into person suggestions by identity embedding. Local only. */ export function clusterFaces( _tracks: VectorItem[], _options?: { similarity?: number }, ): Array<{ key: string; ids: string[]; centroid: Float32Array }> { return notImplemented("clusterFaces"); } /** Rule-based title from date range, place centroid, and dominant album. No model involved. */ export function ruleLabel( _cluster: { startAt: Date | null; endAt: Date | null; albumNames: string[] }, _locale?: string, ): string { return notImplemented("ruleLabel"); }