• Not Exported

    Detects patterns where users consistently skip tracks very early in playback

    Analyzes skip events to identify when tracks are habitually skipped during the initial portion of playback. This reveals potential immediate aversion patterns, particularly when grouped by artist or other attributes.

    The algorithm focuses on:

    1. Identifying tracks skipped within the immediate skip threshold (default: first 10%)
    2. Grouping immediate skips by artist to detect potential artist-level patterns
    3. Calculating average progress before skipping to determine pattern severity
    4. Creating confidence scores that consider frequency, consistency, and immediacy

    Immediate skip patterns are valuable for:

    • Identifying strong negative preferences
    • Detecting intro-specific issues (intros that trigger skips)
    • Distinguishing between preview skips and content-based skips

    Parameters

    • skippedTracks: SkippedTrack[]

      Array of tracks with skip event data

    Returns DetectedPattern[]

    Array of detected immediate skip patterns meeting confidence thresholds

    // Detect immediate skip patterns from skip data
    const skipData = await getSkippedTracks();
    const immediatePatterns = detectImmediateSkipPatterns(skipData);

    // Example result:
    // {
    // type: "immediate_skip",
    // confidence: 0.82,
    // description: "Immediately skips tracks by Artist X",
    // details: { avgSkipProgress: 0.05, trackCount: 8, ... }
    // }
    function detectImmediateSkipPatterns(
    skippedTracks: SkippedTrack[],
    ): DetectedPattern[] {
    const patterns: DetectedPattern[] = [];

    // Find tracks that are almost always skipped immediately
    const immediateSkipArtists: Record<
    string,
    {
    artist: string;
    count: number;
    tracks: string[];
    avgProgress: number;
    }
    > = {};

    skippedTracks.forEach((track) => {
    if (!track.skipEvents || track.skipEvents.length === 0) return;

    // Calculate average progress percentage for this track's skips
    const avgProgress =
    track.skipEvents.reduce((sum, event) => {
    // Explicitly cast progress to number, defaulting to 0 if undefined
    const progress =
    typeof event.progress === "number" ? event.progress : 0;
    return sum + progress;
    }, 0) / track.skipEvents.length;

    // Check if it's an immediate skip
    if (avgProgress <= PATTERN_THRESHOLDS.IMMEDIATE_SKIP_THRESHOLD) {
    // Track by artist
    if (!immediateSkipArtists[track.artist]) {
    immediateSkipArtists[track.artist] = {
    artist: track.artist,
    count: 0,
    tracks: [],
    avgProgress: 0,
    };
    }

    immediateSkipArtists[track.artist].count += track.skipEvents.length;
    immediateSkipArtists[track.artist].tracks.push(track.name);
    immediateSkipArtists[track.artist].avgProgress =
    (immediateSkipArtists[track.artist].avgProgress + avgProgress) / 2;
    }
    });

    // Filter for artists with significant immediate skips
    Object.values(immediateSkipArtists).forEach((artistData) => {
    if (
    artistData.count >= PATTERN_THRESHOLDS.MIN_OCCURRENCES &&
    artistData.tracks.length >= 2
    ) {
    const confidence = calculateConfidence(
    1 - artistData.avgProgress, // Higher confidence for lower progress
    artistData.tracks.length,
    artistData.count,
    );

    if (confidence >= PATTERN_THRESHOLDS.CONFIDENCE_THRESHOLD) {
    patterns.push({
    type: PatternType.IMMEDIATE_SKIP,
    confidence,
    description: `Immediately skips tracks by ${artistData.artist}`,
    occurrences: artistData.count,
    relatedItems: [artistData.artist, ...artistData.tracks.slice(0, 3)],
    details: {
    artist: artistData.artist,
    trackCount: artistData.tracks.length,
    avgSkipProgress: artistData.avgProgress,
    affectedTracks: artistData.tracks,
    },
    firstDetected: new Date().toISOString(),
    lastDetected: new Date().toISOString(),
    });
    }
    }
    });

    return patterns;
    }