Object with time-based skip pattern analysis
export async function analyzeTimeBasedPatterns() {
try {
const skippedTracks = await getSkippedTracks();
// Object to hold time patterns
const timePatterns = {
hourlyDistribution: Array(24).fill(0),
peakSkipHours: [] as number[],
dayOfWeekDistribution: Array(7).fill(0), // Sunday is 0, Saturday is 6
timeOfDayDistribution: {
morning: 0, // 5:00 - 11:59
afternoon: 0, // 12:00 - 16:59
evening: 0, // 17:00 - 20:59
night: 0, // 21:00 - 4:59
},
patternsByArtist: {} as Record<
string,
{
hourlyDistribution: number[];
peakHours: number[];
}
>,
lastUpdated: new Date().toISOString(),
};
// Process each skipped track's skip events
skippedTracks.forEach((track) => {
if (!track.skipEvents || track.skipEvents.length === 0) return;
// Initialize this artist in our patterns if not already present
if (track.artist && !timePatterns.patternsByArtist[track.artist]) {
timePatterns.patternsByArtist[track.artist] = {
hourlyDistribution: Array(24).fill(0),
peakHours: [],
};
}
// Process each skip event
track.skipEvents.forEach((event) => {
// Skip if no timestamp
if (!event.timestamp) return;
// Create a safe date from the timestamp
const skipDate = createSafeDate(event.timestamp);
// Skip invalid dates
if (!skipDate) {
return;
}
const hour = skipDate.getHours();
const dayOfWeek = skipDate.getDay();
// Update hourly distribution
timePatterns.hourlyDistribution[hour]++;
// Update day of week distribution
timePatterns.dayOfWeekDistribution[dayOfWeek]++;
// Update time of day distribution
if (hour >= 5 && hour < 12) {
timePatterns.timeOfDayDistribution.morning++;
} else if (hour >= 12 && hour < 17) {
timePatterns.timeOfDayDistribution.afternoon++;
} else if (hour >= 17 && hour < 21) {
timePatterns.timeOfDayDistribution.evening++;
} else {
timePatterns.timeOfDayDistribution.night++;
}
// Update artist-specific pattern
if (track.artist) {
timePatterns.patternsByArtist[track.artist].hourlyDistribution[
hour
]++;
}
});
});
// Find peak hours (hours with skip counts above average)
const hourlyAverage =
timePatterns.hourlyDistribution.reduce((sum, count) => sum + count, 0) /
24;
timePatterns.peakSkipHours = timePatterns.hourlyDistribution
.map((count, hour) => ({ hour, count }))
.filter((entry) => entry.count > hourlyAverage * 1.5) // 50% above average
.sort((a, b) => b.count - a.count)
.map((entry) => entry.hour);
// Calculate peak hours for each artist
Object.keys(timePatterns.patternsByArtist).forEach((artist) => {
const artistHourly =
timePatterns.patternsByArtist[artist].hourlyDistribution;
const artistAverage =
artistHourly.reduce((sum, count) => sum + count, 0) / 24;
timePatterns.patternsByArtist[artist].peakHours = artistHourly
.map((count, hour) => ({ hour, count }))
.filter(
(entry) => entry.count > artistAverage * 1.5 && entry.count >= 3,
) // Significant pattern
.sort((a, b) => b.count - a.count)
.map((entry) => entry.hour);
});
// Store the time patterns in a separate file
const timePatternsFilePath = join(
ensureStatisticsDir(),
"time_based_patterns.json",
);
writeJsonSync(timePatternsFilePath, timePatterns, { spaces: 2 });
return timePatterns;
} catch (error) {
console.error("Error analyzing time-based patterns:", error);
return null;
}
}
Analyzes time-based skip patterns from the collected data