From 3934a0c45da511885bbb2e89eb915502a099e56d Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 11 Jul 2026 12:40:11 +0200 Subject: [PATCH] feat: import stage upserts to replace lower-quality library items --- worker/lyra_worker/pipeline.py | 6 ++++- worker/tests/test_import_upgrade.py | 40 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 worker/tests/test_import_upgrade.py diff --git a/worker/lyra_worker/pipeline.py b/worker/lyra_worker/pipeline.py index e461ccf..8a1d12d 100644 --- a/worker/lyra_worker/pipeline.py +++ b/worker/lyra_worker/pipeline.py @@ -88,7 +88,11 @@ def _import(conn: psycopg.Connection, job_id: str, target: MBTarget, 'INSERT INTO "LibraryItem" (id, "requestId", artist, album, path, source, ' 'format, "qualityClass", "importedAt") ' "VALUES (gen_random_uuid()::text, %s, %s, %s, %s, %s, %s, %s, now()) " - 'ON CONFLICT (artist, album) DO NOTHING', + 'ON CONFLICT (artist, album) DO UPDATE SET ' + ' "requestId" = EXCLUDED."requestId", path = EXCLUDED.path, source = EXCLUDED.source, ' + ' format = EXCLUDED.format, "qualityClass" = EXCLUDED."qualityClass", ' + ' "importedAt" = now() ' + 'WHERE EXCLUDED."qualityClass" > "LibraryItem"."qualityClass"', (request_id, target.artist, target.album, path, winner.source, winner.quality.fmt, quality_class(winner.quality)), ) diff --git a/worker/tests/test_import_upgrade.py b/worker/tests/test_import_upgrade.py new file mode 100644 index 0000000..6ccce9f --- /dev/null +++ b/worker/tests/test_import_upgrade.py @@ -0,0 +1,40 @@ +from lyra_worker.pipeline import _import +from lyra_worker.types import Candidate, Quality +from tests.conftest import insert_request + + +def _winner(fmt, lossless, bit_depth=None, sample_rate=None): + q = Quality(fmt=fmt, lossless=lossless, bit_depth=bit_depth, sample_rate=sample_rate) + return Candidate(source="qobuz", source_ref="ref", matched_artist="A", matched_album="B", + quality=q, track_count=10, source_tier=0) + + +def _library_row(conn, artist, album): + with conn.cursor() as cur: + cur.execute( + 'SELECT path, "qualityClass", source FROM "LibraryItem" WHERE artist = %s AND album = %s', + (artist, album), + ) + return cur.fetchone() + + +def test_higher_quality_replaces(conn): + from lyra_worker.types import MBTarget + + target = MBTarget(artist="A", album="B") + job1 = insert_request(conn, artist="A", album="B") + _import(conn, job1, target, _winner("MP3", False), "/m/lossy") # qualityClass 1 + job2 = insert_request(conn, artist="A", album="B") + _import(conn, job2, target, _winner("FLAC", True, 24, 96000), "/m/hires") # qualityClass 3 + assert _library_row(conn, "A", "B") == ("/m/hires", 3, "qobuz") + + +def test_lower_quality_does_not_replace(conn): + from lyra_worker.types import MBTarget + + target = MBTarget(artist="A", album="B") + job1 = insert_request(conn, artist="A", album="B") + _import(conn, job1, target, _winner("FLAC", True, 24, 96000), "/m/hires") # qualityClass 3 + job2 = insert_request(conn, artist="A", album="B") + _import(conn, job2, target, _winner("MP3", False), "/m/lossy") # qualityClass 1 + assert _library_row(conn, "A", "B") == ("/m/hires", 3, "qobuz")