Object with library-wide skip metrics
export async function calculateLibrarySkipStatistics() {
try {
const skippedTracks = await getSkippedTracks();
const dailyMetrics = await aggregateDailySkipMetrics();
const weeklyMetrics = await aggregateWeeklySkipMetrics();
const artistMetrics = await aggregateArtistSkipMetrics();
const statistics = await getStatistics();
// Define interface for skip events for type safety
interface SkipEvent {
timestamp: string;
progress: number;
isManualSkip?: boolean;
skipType?: string;
context?: {
type: string;
uri?: string;
name?: string;
id?: string;
};
}
// Create an object to hold library-wide statistics
const libraryStats = {
totalSkips: 0,
totalTracks: 0,
uniqueTracksSkipped: 0,
overallSkipRate: 0,
skipsByType: {
preview: 0,
standard: 0,
near_end: 0,
manual: 0,
auto: 0,
} as Record<string, number>,
artistsWithHighestSkipRates: [] as Array<{
artistName: string;
skipRate: number;
totalSkips: number;
}>,
mostSkippedTracks: [] as Array<{
trackId: string;
trackName: string;
artistName: string;
skipCount: number;
}>,
averageSkipPercentage: 0,
skipTrends: {
daily: {} as Record<string, number>,
weekly: {} as Record<string, number>,
},
lastUpdated: new Date().toISOString(),
};
// Calculate total skips and unique tracks skipped
libraryStats.totalSkips = skippedTracks.reduce(
(sum, track) => sum + (track.skipCount || 0),
0,
);
libraryStats.uniqueTracksSkipped = skippedTracks.length;
// Get total tracks from statistics
libraryStats.totalTracks = statistics.totalUniqueTracks || 0;
// Calculate overall skip rate
if (libraryStats.totalTracks > 0) {
libraryStats.overallSkipRate =
libraryStats.uniqueTracksSkipped / libraryStats.totalTracks;
}
// Aggregate skips by type
skippedTracks.forEach((track) => {
if (track.skipTypes) {
Object.entries(track.skipTypes).forEach(([type, count]) => {
if (type in libraryStats.skipsByType) {
libraryStats.skipsByType[type] += count;
}
});
}
if (track.manualSkipCount) {
libraryStats.skipsByType.manual += track.manualSkipCount;
}
if (track.autoSkipCount) {
libraryStats.skipsByType.auto += track.autoSkipCount;
}
});
// Find artists with highest skip rates
libraryStats.artistsWithHighestSkipRates = Object.values(artistMetrics)
.filter((artist) => artist.totalSkips >= 3) // Only include artists with enough data
.sort((a, b) => b.skipRatio - a.skipRatio)
.slice(0, 10)
.map((artist) => ({
artistName: artist.artistName,
skipRate: artist.skipRatio,
totalSkips: artist.totalSkips,
}));
// Find most skipped tracks
libraryStats.mostSkippedTracks = skippedTracks
.sort((a, b) => (b.skipCount || 0) - (a.skipCount || 0))
.slice(0, 10)
.map((track) => ({
trackId: track.id,
trackName: track.name,
artistName: track.artist,
skipCount: track.skipCount || 0,
}));
// Calculate average skip percentage
const tracksWithEvents = skippedTracks.filter(
(track) => track.skipEvents && track.skipEvents.length > 0,
);
if (tracksWithEvents.length > 0) {
const totalPercentage = tracksWithEvents.reduce((sum: number, track) => {
if (!track.skipEvents) return sum;
const trackAvg =
track.skipEvents.reduce(
(tSum: number, event: SkipEvent) => tSum + event.progress,
0,
) / track.skipEvents.length;
return sum + trackAvg;
}, 0);
libraryStats.averageSkipPercentage =
totalPercentage / tracksWithEvents.length;
}
// Calculate skip trends
// Daily trend
const sortedDays = Object.keys(dailyMetrics).sort();
const recentDays = sortedDays.slice(-14); // Last 14 days
recentDays.forEach((day) => {
libraryStats.skipTrends.daily[day] = dailyMetrics[day].tracksSkipped;
});
// Weekly trend
const sortedWeeks = Object.keys(weeklyMetrics).sort();
const recentWeeks = sortedWeeks.slice(-8); // Last 8 weeks
recentWeeks.forEach((week) => {
libraryStats.skipTrends.weekly[week] = weeklyMetrics[week].totalSkips;
});
// Store the library stats in a separate file
const libraryStatsFilePath = join(
ensureStatisticsDir(),
"library_skip_statistics.json",
);
writeJsonSync(libraryStatsFilePath, libraryStats, { spaces: 2 });
return libraryStats;
} catch (error) {
console.error("Error calculating library skip statistics:", error);
return null;
}
}
Calculates library-wide skip statistics from all collected data