Commit Graph

478 Commits

Author SHA1 Message Date
Claudio Ortolina b9612a2a26 Fix records on this day for partial release dates
SQLite interprets bare years (e.g. "1970") as Julian day numbers
in strftime, producing wrong month-day matches. Filter to only
full YYYY-MM-DD dates before comparing.
2026-04-15 07:29:58 +01:00
Claudio Ortolina dd3bb47989 Add tests for mixed collected/wishlisted matching records 2026-04-14 16:07:14 +01:00
Claudio Ortolina 2c6e6e7ca2 Return matching_records list from top_albums_attach_metadata
Apply the same release-group JSON aggregation pattern to top albums
queries. Add JSON parsing in get_top_albums/1 and
get_top_albums_by_days/2.
2026-04-14 16:05:51 +01:00
Claudio Ortolina cfd267627c Return matching_records list from tracks_with_record_info_query
Replace scalar collected_record_id/wishlisted_record_id subqueries with
a single matching_records JSON array grouped by release group
(records.musicbrainz_id). Add parse_matching_records/1 for JSON parsing
and derive_legacy_record_ids/2 as a temporary bridge for existing
templates. Update tests to use shared musicbrainz_id fixtures.
2026-04-14 14:17:02 +01:00
Claudio Ortolina 9f4135661e Replace scalar record IDs with matching_records JSON array in tracks query
Replaces collected_record_id and wishlisted_record_id correlated
subqueries in tracks_with_record_info_query with a single
matching_records json_group_array subquery that returns all records
sharing the same release ID. Adds parse_matching_records/1 for JSON
deserialization. Updates recent_activity/2 and list_tracks/1 to parse
the JSON at the boundary. Derives legacy collected_record_id and
wishlisted_record_id from matching_records so existing LiveViews keep
working. The get_top_albums test is temporarily skipped pending the
top_albums_attach_metadata update.
2026-04-14 14:12:14 +01:00
Claudio Ortolina 6a6410bcaa Have the LLM use square bracketed links for internal references 2026-04-13 14:24:36 +01:00
Claudio Ortolina e10df1985e Reuse binding in test 2026-04-13 13:34:23 +01:00
Claudio Ortolina b422cccf09 Make format merge assertion order-independent 2026-04-13 13:17:03 +01:00
Claudio Ortolina 726b7d5ef8 Add tests for chat auto-open via query param 2026-04-13 13:16:28 +01:00
Claudio Ortolina bdc1cd3992 Prevent callback return value from halting stream
The delta branch in decode_responses_event now explicitly returns
:ok after invoking the callback, so a callback returning {:error, _}
cannot be mistaken for a stream-level error by decode_events.
2026-04-13 13:14:33 +01:00
Claudio Ortolina 846b92fd4c Add tests for streaming error events in OpenAI.API
Covers error event mid-stream, response.failed events, halt
behavior after errors, and unexpected event type logging.
2026-04-13 13:13:51 +01:00
Claudio Ortolina 9b44b7050f Count unique catalog entries in collection summary
The record_count now reflects the number of grouped catalog lines
the model sees, not raw DB rows. Two copies of the same album on
different formats count as 1 catalog entry.
2026-04-13 13:12:30 +01:00
Claudio Ortolina 8108645718 Shrink collection summary to fit OpenAI token limit
Deduplicate records by musicbrainz_id (merging formats)
and cap genres to 3 per record, reducing the summary from
~32K to ~24K estimated tokens.
2026-04-13 12:33:15 +01:00
Claudio Ortolina 3b8415d8c4 Add Collection Chat to universal search 2026-04-13 10:15:43 +01:00
Claudio Ortolina a509248625 Wire up Chat component in CollectionLive.Index 2026-04-13 10:12:49 +01:00
Claudio Ortolina 34f484cc81 Add CollectionChat StreamProvider 2026-04-13 10:09:50 +01:00
Claudio Ortolina 24a3cc9265 Add Collection.collection_summary/0 2026-04-13 10:08:01 +01:00
Claudio Ortolina d845ab93dd Add collection sentinel UUID to Chats context 2026-04-13 10:06:16 +01:00
Claudio Ortolina 88906e10ad Add ex_slop, configure it, run it and fix issues 2026-04-12 22:07:27 +01:00
Claudio Ortolina 02761428c1 Format map and list query params as JSON 2026-04-11 23:57:40 +01:00
Claudio Ortolina d8c84e787b Optimize ListeningStats query performance
Three independent refactors of lib/music_library/listening_stats.ex,
measured against the dev DB (104k tracks) via bench/listening_stats.exs:

| Query                           | Baseline  | After     | Speedup |
|---------------------------------|-----------|-----------|---------|
| recent_activity(tz, 100)        | 38.98 ms  | 2.87 ms   | 13.6x   |
| list_tracks(page 1, 200)        | 47.30 ms  | 4.81 ms   | 9.8x    |
| get_top_artists_by_days(7)      | 66.51 ms  | 1.02 ms   | 65x     |
| get_top_artists_by_days(30)     | 67.41 ms  | 2.84 ms   | 23.7x   |
| get_top_artists_by_days(365)    | 76.03 ms  | 25.57 ms  | 3.0x    |
| get_top_albums_by_days(7)       | 92.63 ms  | 1.61 ms   | 57.5x   |
| get_top_albums_by_days(30)      | 94.52 ms  | 3.69 ms   | 25.6x   |
| get_top_albums_by_days(365)     | 105.23 ms | 32.81 ms  | 3.2x    |
| get_top_artists(limit: 10)      | 123.96 ms | 101.68 ms | 1.22x   |
| get_top_albums(limit: 10)       | 209.64 ms | 108.23 ms | 1.94x   |

tracks_with_record_info_query/0 now uses correlated scalar subqueries
against record_releases and artist_records instead of materializing
helper subqueries on every call. The cost scales with the outer LIMIT,
not with the size of record_releases.

top_albums_base_query/0 and top_artists_base_query/0 are replaced by
aggregate_query + attach_metadata pairs. The pattern is aggregate first,
attach metadata second: GROUP BY runs against the raw track scan, then
a tiny outer SELECT attaches record_releases / artist_infos lookups for
the <= 10 result rows via correlated subqueries.

tracks_since_query/1 wraps the date-filtered inner scan with limit: -1.
SQLite cannot flatten a subquery that has a LIMIT, so it materializes
the date-bounded subset and the optimizer uses the timestamp index for
the range scan instead of the album/artist composite index. This trick
replaces SQLite's WITH ... AS MATERIALIZED (which ecto_sqlite3 doesn't
expose).

All json_extract(?, '\$.path') fragments use the canonical form rather
than the equivalent ? ->> '\$.path' shorthand. SQLite's index matcher
requires the GROUP BY expression to match the index expression
textually to use the composite index for natural ordering.

collected_releases_query/0 and wishlisted_releases_query/0 are removed
from Collection and Wishlist — they had a single internal caller that
no longer exists after the refactor.

New regression tests lock the semantics that the optimized queries
must preserve:
- count(DISTINCT scrobbled_at_uts) — 579 duplicate timestamps exist in
  the dev DB from rapid Last.fm scrobbles, so replacing with count(*)
  would silently change results
- :artist_id key in list_tracks result maps — ScrobbledTracksLive.Index
  destructures it even though the template body never references it

Closes #148
2026-04-11 23:36:32 +01:00
Claudio Ortolina 82e4882aa9 Assert embedding enqueues in batch test 2026-04-11 21:43:00 +01:00
Claudio Ortolina ea6ecb2661 Assert embedding enqueues in artist info test 2026-04-11 21:43:00 +01:00
Claudio Ortolina a608be7387 Move regenerate_artist_embeddings into Similarity 2026-04-11 21:43:00 +01:00
Claudio Ortolina b7af9557c1 Add MusicBrainz.get_all_releases/1 2026-04-11 21:40:32 +01:00
Claudio Ortolina fcf5adde2f Add tests for QueryReporter 2026-04-10 11:10:12 +01:00
Claudio Ortolina 49d26e1fd0 Switch the release component to a form to enable recovery 2026-04-08 18:22:44 +01:00
Claudio Ortolina 1cfd91278b Use correct api_cooldown for OpenAi.API tests 2026-04-07 10:07:48 +01:00
Claudio Ortolina a5df68c7ad Don't suppress errors when fetching artist Last.fm data
Closes #149
2026-04-05 15:47:36 +01:00
Claudio Ortolina b9be62a500 Async barcode scan batch import for 2+ new records
When barcode scan results contain at least 2 new records,
enqueue individual Oban jobs instead of importing synchronously.
Wishlisted/collected/not_found results still process synchronously.
2026-04-04 10:20:50 +01:00
Claudio Ortolina 2f9460d2cb Regenerate embeddings only when the test representation changes 2026-04-04 08:32:29 +01:00
Claudio Ortolina 4bfe27e6c8 Cancel refresh cover job if image is not available 2026-04-03 12:43:50 +01:00
Claudio Ortolina 851bbd54af Setup swoosh sandbox 2026-04-02 15:26:15 +01:00
Claudio Ortolina 7f406ce455 Replace deprecated {:discard, _} with {:cancel, _} 2026-04-01 09:38:05 +01:00
Claudio Ortolina 553ad2bbb9 Discard worker when no wikipedia entry is present 2026-04-01 08:52:28 +01:00
Claudio Ortolina 6fcca38e27 Make sure to pull artists info when record is updated
Catches the scenario when a new artist is added
2026-04-01 07:11:47 +01:00
Claudio Ortolina e0a277e215 Always open chat links in new tabs 2026-03-31 19:17:58 +01:00
Claudio Ortolina c913dab692 Return controlled errors from genre population
Closes #142
2026-03-30 15:01:42 +01:00
Claudio Ortolina 92a36b915a Harden public asset endpoint against invalid payloads
Closes #143
2026-03-30 14:56:40 +01:00
Claudio Ortolina 45fd414f1b Use friendly error messages in Last.fm callback
Closes #145
2026-03-30 14:42:20 +01:00
Claudio Ortolina b0018db89d Improve error handling for color extraction
Includes a around embedding generation, which was happening twice (the
first call was redundant as record embeddings are always regenerated
after their parent artist embeddings are regenerated).

Closes #144.
2026-03-30 14:29:44 +01:00
Claudio Ortolina 19b7091358 Extend last.fm token diagnostics for maintenance page 2026-03-30 10:55:20 +01:00
Claudio Ortolina 8f8eb0a656 Enable quick actions from universal search 2026-03-29 23:07:53 +01:00
Claudio Ortolina 2e22ace7cf Move RecordsOnThisDayEmail to MusicLibraryWeb namespace 2026-03-28 10:25:03 +00:00
Claudio Ortolina fbcf40b4b3 Remove LastFm.Feed, LastFm.Refresh, and LastFm.Supervisor 2026-03-27 23:17:25 +00:00
Claudio Ortolina 61dd3f45c7 Move refresh config and callers to ListeningStats 2026-03-27 23:14:09 +00:00
Claudio Ortolina 9643e2141c Add update/1 and subscribe/0 to ListeningStats 2026-03-27 23:03:23 +00:00
Claudio Ortolina 96836ae715 Improve tests: better assertions, meaningful tests
Track resulting principles in project conventions
2026-03-27 15:46:31 +00:00
Claudio Ortolina 1301f888f3 Remove "it" from the test descriptions 2026-03-27 08:38:41 +00:00
Claudio Ortolina bf379b9887 Remove unnecessary test 2026-03-26 23:03:42 +00:00