Promise resolving to a detailed context-based skip analysis
// Analyze which contexts lead to most skips
const contextPatterns = await analyzeListeningContextPatterns();
if (contextPatterns.mostSkippedContext) {
console.log(`Most skipped in: ${contextPatterns.mostSkippedContext.type} - ${contextPatterns.mostSkippedContext.name}`);
}
export async function analyzeListeningContextPatterns(): Promise<{
totalSkips: number;
byContextType: Record<
string,
{
count: number;
percentage: number;
topContexts: Array<{
id: string;
name: string;
count: number;
percentage: number;
}>;
}
>;
mostSkippedContext: {
type: string;
id: string;
name: string;
count: number;
} | null;
}> {
try {
// Read skipped tracks from file
const skippedTracksFilePath = join(
getUserDataFolder(),
"skipped_tracks.json",
);
if (!existsSync(skippedTracksFilePath)) {
return {
totalSkips: 0,
byContextType: {},
mostSkippedContext: null,
};
}
const skippedTracks: SkippedTrack[] =
readJsonSync(skippedTracksFilePath, { throws: false }) || [];
// Default result structure
const result = {
totalSkips: 0,
byContextType: {} as Record<
string,
{
count: number;
percentage: number;
topContexts: Array<{
id: string;
name: string;
count: number;
percentage: number;
}>;
}
>,
mostSkippedContext: null as {
type: string;
id: string;
name: string;
count: number;
} | null,
};
// Process each skipped track
skippedTracks.forEach((track: SkippedTrack) => {
if (!track.contextStats) return;
// Create a typed version of contextStats that allows string indexing
type ContextData = {
type: string;
name?: string;
uri?: string;
count: number;
};
type IndexableContextStats = {
[key: string]: {
count: number;
contexts?: Record<string, ContextData>;
};
};
// Cast to the indexable type to avoid TypeScript errors
const indexableContextStats =
track.contextStats as unknown as IndexableContextStats;
// Count total skips that have context data
let trackSkipsWithContext = 0;
// Use for...in loop with the properly typed object
for (const contextType in indexableContextStats) {
if (contextType !== "total" && contextType !== "contexts") {
// Access specific context types and their count values
const contextCount = indexableContextStats[contextType]?.count;
if (typeof contextCount === "number") {
trackSkipsWithContext += contextCount;
}
}
}
result.totalSkips += trackSkipsWithContext;
// Process each context type
for (const contextType in indexableContextStats) {
if (contextType !== "total" && contextType !== "contexts") {
// Initialize context type in result if needed
if (!result.byContextType[contextType]) {
result.byContextType[contextType] = {
count: 0,
percentage: 0,
topContexts: [],
};
}
// Access the count and contexts safely using the typed object
const contextCount = indexableContextStats[contextType]?.count;
const contexts = indexableContextStats[contextType]?.contexts;
// Add counts from this track
if (typeof contextCount === "number") {
result.byContextType[contextType].count += contextCount;
}
// Process individual contexts within this type
if (contexts) {
for (const contextId in contexts) {
const contextData = contexts[contextId];
if (!contextData) continue;
// Find existing context or create new entry
const existingContext = result.byContextType[
contextType
].topContexts.find((c) => c.id === contextId);
if (existingContext) {
existingContext.count += contextData.count || 0;
} else {
result.byContextType[contextType].topContexts.push({
id: contextId,
name: contextData.name || "(Unknown)",
count: contextData.count || 0,
percentage: 0, // Will calculate later
});
}
// Check if this is the most skipped context overall
if (
!result.mostSkippedContext ||
(result.mostSkippedContext &&
(contextData.count || 0) > result.mostSkippedContext.count)
) {
result.mostSkippedContext = {
type: contextType,
id: contextId,
name: contextData.name || "(Unknown)",
count: contextData.count || 0,
};
}
}
}
}
}
});
// Calculate percentages
if (result.totalSkips > 0) {
Object.keys(result.byContextType).forEach((contextType) => {
// Calculate percentage for this context type
result.byContextType[contextType].percentage =
(result.byContextType[contextType].count / result.totalSkips) * 100;
// Sort top contexts by count (descending)
result.byContextType[contextType].topContexts.sort(
(a, b) => b.count - a.count,
);
// Limit to top 5 contexts
result.byContextType[contextType].topContexts = result.byContextType[
contextType
].topContexts.slice(0, 5);
// Calculate percentages for each context
result.byContextType[contextType].topContexts.forEach((context) => {
context.percentage =
(context.count / result.byContextType[contextType].count) * 100;
});
});
}
return result;
} catch (error) {
console.error("Error analyzing listening context patterns:", error);
return {
totalSkips: 0,
byContextType: {},
mostSkippedContext: null,
};
}
}
Analyzes skip patterns based on listening context
Performs a detailed analysis of how skip behavior varies across different listening contexts, such as playlists, albums, and artist pages. This function identifies which contexts tend to produce higher skip rates and which lead to more engaged listening.
The analysis includes:
This analysis helps identify whether certain context types lead to more skipping behavior and can reveal patterns in content curation that may affect listening engagement.