diff --git a/packages/tourmaline/src/lib/analysis/aggregator.ts b/packages/tourmaline/src/lib/analysis/aggregator.ts index 1729781..a14e6bf 100644 --- a/packages/tourmaline/src/lib/analysis/aggregator.ts +++ b/packages/tourmaline/src/lib/analysis/aggregator.ts @@ -34,6 +34,8 @@ export interface AggregatedData { trackTimestamps: Map; /** artist → count of distinct ISO weeks played in. */ artistWeeksActive: Map; + /** artist → count of distinct tracks played. */ + artistTrackCounts: Map; // ── Derived stats computed in snapshot() ────────────────────────────── eddingtonNumber: number; /** How many more days at eddingtonNumber+1 scrobbles are needed to advance. */ @@ -439,6 +441,9 @@ export class Aggregator { artistWeeksActive: new Map( [...this.artistWeeks.entries()].map(([name, weeks]) => [name, weeks.size]), ), + artistTrackCounts: new Map( + [...this.artistTracks.entries()].map(([name, tracks]) => [name, tracks.size]), + ), eddingtonNumber, daysToNextEddington: daysToNextEddingtonNumber, artistCutoverPoint, diff --git a/packages/tourmaline/src/lib/client/load-profile.ts b/packages/tourmaline/src/lib/client/load-profile.ts index 31b3d0c..9c57fb8 100644 --- a/packages/tourmaline/src/lib/client/load-profile.ts +++ b/packages/tourmaline/src/lib/client/load-profile.ts @@ -137,6 +137,15 @@ export function computeProfile( return { name, artist, gapDays: g.gapDays, count: g.count }; }); + const topArtistsByTrackCount = [...data.artistTrackCounts.entries()] + .map(([name, trackCount]) => ({ + name, + trackCount, + count: data.artistPlayCounts.get(name) ?? 0, + })) + .sort((a, b) => b.trackCount - a.trackCount) + .slice(0, 10); + const profile: ListenerProfile = { did, handle, @@ -183,6 +192,7 @@ export function computeProfile( topArtistsByWeeksActive, topArtistGaps, topTrackGaps, + topArtistsByTrackCount, scrobbleMilestones: data.scrobbleMilestones, artistMilestones: data.artistMilestones, trackMilestones: data.trackMilestones, diff --git a/packages/tourmaline/src/lib/types.ts b/packages/tourmaline/src/lib/types.ts index 0786576..33675ab 100644 --- a/packages/tourmaline/src/lib/types.ts +++ b/packages/tourmaline/src/lib/types.ts @@ -136,6 +136,8 @@ export interface ListenerProfile { /** Top 10 artists/tracks by longest gap between two consecutive listens (min 5 plays). */ topArtistGaps: Array<{ name: string; gapDays: number; count: number }>; topTrackGaps: Array<{ name: string; artist: string; gapDays: number; count: number }>; + /** Top 10 artists by distinct tracks played — breadth within one artist's catalogue. */ + topArtistsByTrackCount: Array<{ name: string; trackCount: number; count: number }>; /** Per-month most statistically unusual artist (highest z-score). */ unusualMonths: UnusualMonth[]; // ── Ported from lastfm-stats-web ───────────────────────────────────── diff --git a/packages/tourmaline/src/routes/profile/[did]/+page.svelte b/packages/tourmaline/src/routes/profile/[did]/+page.svelte index f227c49..1231543 100644 --- a/packages/tourmaline/src/routes/profile/[did]/+page.svelte +++ b/packages/tourmaline/src/routes/profile/[did]/+page.svelte @@ -35,6 +35,7 @@ import WeeksActive from './WeeksActive.svelte'; import ListeningCadence from './ListeningCadence.svelte'; import BiggestGaps from './BiggestGaps.svelte'; + import CatalogueDepth from './CatalogueDepth.svelte'; import DateRangePicker from './DateRangePicker.svelte'; import ListeningPhases from './ListeningPhases.svelte'; import StoryRecap from './StoryRecap.svelte'; @@ -535,6 +536,7 @@ +
diff --git a/packages/tourmaline/src/routes/profile/[did]/CatalogueDepth.svelte b/packages/tourmaline/src/routes/profile/[did]/CatalogueDepth.svelte new file mode 100644 index 0000000..102c7c6 --- /dev/null +++ b/packages/tourmaline/src/routes/profile/[did]/CatalogueDepth.svelte @@ -0,0 +1,28 @@ + + +{#if artists.length > 0} +
+
+
+

Deepest Catalogues

+

Artists you've explored the most distinct tracks from

+
+
+ +
+
+
    + {#each artists as artist, i (artist.name)} +
  1. + {i + 1} + {artist.name} + {artist.trackCount} tracks +
  2. + {/each} +
+
+{/if} diff --git a/packages/tourmaline/src/routes/profile/[did]/CatalogueTab.svelte b/packages/tourmaline/src/routes/profile/[did]/CatalogueTab.svelte index 94bbe9c..4d4d9e1 100644 --- a/packages/tourmaline/src/routes/profile/[did]/CatalogueTab.svelte +++ b/packages/tourmaline/src/routes/profile/[did]/CatalogueTab.svelte @@ -6,6 +6,7 @@ import WeeksActive from './WeeksActive.svelte'; import ListeningCadence from './ListeningCadence.svelte'; import BiggestGaps from './BiggestGaps.svelte'; + import CatalogueDepth from './CatalogueDepth.svelte'; import type { ListenerProfile } from '$lib/types'; import type { OnThisDayEntry } from '$lib/analysis/on-this-day'; @@ -84,6 +85,7 @@ + {#if profile.discoveredArtists.length > 0 || profile.discoveredTracks.length > 0 || profile.discoveredAlbums.length > 0}