Split ScrobbleActivity into focused modules

Move track CRUD/listing to ListeningStats and diagnostics
to Maintenance, leaving ScrobbleActivity for scrobbling only.

Closes #111
This commit is contained in:
Claudio Ortolina
2026-03-12 16:51:33 +00:00
parent ee4d2555ee
commit d057f29ba1
13 changed files with 710 additions and 728 deletions
+95 -1
View File
@@ -1,10 +1,12 @@
defmodule MusicLibrary.Maintenance do
@moduledoc """
Context for database maintenance operations and background job monitoring.
Context for database maintenance operations, background job monitoring,
and scrobble data quality diagnostics.
"""
import Ecto.Query
alias LastFm.Track
alias MusicLibrary.BackgroundRepo
alias MusicLibrary.Repo
@@ -39,4 +41,96 @@ defmodule MusicLibrary.Maintenance do
def optimize do
Repo.optimize()
end
# Scrobble data quality diagnostics
@spec count_tracks_missing_artist_musicbrainz_id() :: non_neg_integer()
def count_tracks_missing_artist_musicbrainz_id do
query =
from t in Track,
where:
fragment("json_extract(?, '$.musicbrainz_id') IS NULL", t.artist) or
fragment("json_extract(?, '$.musicbrainz_id') = ''", t.artist),
select: count(t.scrobbled_at_uts)
Repo.one(query) || 0
end
@spec count_tracks_missing_album_musicbrainz_id() :: non_neg_integer()
def count_tracks_missing_album_musicbrainz_id do
query =
from t in Track,
where:
fragment("json_extract(?, '$.musicbrainz_id') IS NULL", t.album) or
fragment("json_extract(?, '$.musicbrainz_id') = ''", t.album),
select: count(t.scrobbled_at_uts)
Repo.one(query) || 0
end
@doc """
Gets artists with missing MusicBrainz IDs, grouped by artist name.
Returns a list of maps with artist name and track count.
"""
@spec get_artists_missing_musicbrainz_id(keyword()) :: [map()]
def get_artists_missing_musicbrainz_id(opts \\ []) do
limit = Keyword.get(opts, :limit)
query =
from t in Track,
where:
fragment("json_extract(?, '$.musicbrainz_id') IS NULL", t.artist) or
fragment("json_extract(?, '$.musicbrainz_id') = ''", t.artist),
select: %{
artist_name: fragment("json_extract(?, '$.name')", t.artist),
track_count: count(t.scrobbled_at_uts)
},
group_by: fragment("json_extract(?, '$.name')", t.artist),
order_by: [desc: count(t.scrobbled_at_uts)]
query =
if limit do
from q in query, limit: ^limit
else
query
end
Repo.all(query)
end
@doc """
Gets albums with missing MusicBrainz IDs, grouped by album title and artist.
Returns a list of maps with album title, artist name, and track count.
"""
@spec get_albums_missing_musicbrainz_id(keyword()) :: [map()]
def get_albums_missing_musicbrainz_id(opts \\ []) do
limit = Keyword.get(opts, :limit)
query =
from t in Track,
where:
fragment("json_extract(?, '$.musicbrainz_id') IS NULL", t.album) or
fragment("json_extract(?, '$.musicbrainz_id') = ''", t.album),
select: %{
album_title: fragment("json_extract(?, '$.title')", t.album),
artist_name: fragment("json_extract(?, '$.name')", t.artist),
track_count: count(t.scrobbled_at_uts)
},
group_by: [
fragment("json_extract(?, '$.title')", t.album),
fragment("json_extract(?, '$.name')", t.artist)
],
order_by: [desc: count(t.scrobbled_at_uts)]
query =
if limit do
from q in query, limit: ^limit
else
query
end
Repo.all(query)
end
end