Commit Graph
9 Commits
Author SHA1 Message Date
vicrodh 0740a7368f chore(reco): drop the unused rand dependency
Declared since the identity-guardrail round, never used; verified by
grep and a clean build without it.

cc
2026-08-30 22:17:41 -06:00
vicrodh 6d8c951a38 chore(deps): rusqlite 0.40 (u64 casts; a latent two-statement execute fixed)
rusqlite 0.40 removed ToSql/FromSql for u64 (SQLite integers are i64):
every u64 column now reads/writes through an explicit i64 cast. It also
rejects a multi-statement execute() instead of silently running only the
first statement — which is what catalog.rs's editions/logical_albums
prune had been doing all along; it is an execute_batch now, so the
logical_albums prune finally runs.

cc
2026-08-29 08:36:21 -06:00
vicrodh 6caab7b71a feat(reco): identity guardrail replaces the genre blocklist; rank by score
- INCOMPATIBLE_GENRES / INCOMPATIBLE_TITLE_KEYWORDS removed: a global
  blocklist papered over an identity bug (name search returning a
  homonym) and punished every user's taste for it.
- artist_guardrail: ArtistLookup seam (mockable), id before text
  (qobuz:{id} nodes resolve by id; MBIDs hit the mb_qobuz_artist_match
  cache before search_artists), validation relative to the playlist
  seeds (shared level-1 genre root or a shared MusicBrainz tag), homonym
  tie-break by seed neighbourhood / label, reject when no signal exists.
- suggestions: stable sort by similarity_score desc, dedup keeping the
  higher score, THEN truncate (the shuffle threw the score away and the
  dedup kept the first hit while claiming the best).
- Eight network-free tests cover the rules; rand is now unused here.

cx
2026-08-28 20:30:00 -06:00
vicrodh 9cc44f5d1d feat(reco): wire generate_playlist_suggestions into qbz-core (T6)
Add QbzCore::generate_playlist_suggestions + a per-user artist_vectors store
field (Arc<tokio::Mutex<Option<ArtistVectorStore>>>) with async
set_artist_vectors / clear_artist_vectors. It resolves each playlist artist NAME
to a confident MusicBrainz id (reusing musicbrainz_resolve_artist; the names come
from the playlist's own Qobuz tracks so they're already canonical, making the
Tauri command's extra Qobuz-name re-fetch unnecessary), then runs the
SuggestionsEngine over the core-owned clients + vector store.

Also aligns the qbz-reco builder/engine handle types to qbz-core's reality (the
plan's by-ref pattern doesn't fit): mb_client = Arc<MusicBrainzClient> (no lock,
Send+Sync); mb_cache = Arc<std::sync::Mutex<...>> (locked only in await-free
windows); Qobuz client = Arc<RwLock<Option<QobuzClient>>> (unwrapped per access).
The vector store stays tokio::Mutex (held across awaits by the engine). No
dependency cycle (qbz-reco does not depend on qbz-core). qbz-core checks clean;
23 qbz-reco tests green.

cc
2026-06-26 20:58:37 -06:00
vicrodh 90a8240671 feat(reco): port SuggestionsEngine (direct-weight ranking + quality gates)
T5: 1:1 port of the artist_vectors SuggestionsEngine into qbz-reco (~880 lines;
structure matches the Tauri source function-for-function). generate_suggestions:
ensure each seed vector (builder.ensure_vector), gate on a non-empty combined
playlist vector, rank candidates by DIRECT summed relationship weight via
store.get_all_related_artists (NOT cosine/find_nearest, D3), then pull + dedup +
exclude tracks from Qobuz with the validate_qobuz_artist + has_incompatible_genre
gates and optional reason strings. Keeps Tauri's Arc<Mutex>/RwLock ownership
(Send-safe). DTO reconciliation: crate::api -> qbz_qobuz::QobuzClient +
qbz_models::Track (4 Qobuz calls mapped to real signatures, field-by-field).
Adds rand (0.10, workspace) + qbz-models deps. 23 qbz-reco tests green.

cc
2026-06-26 20:45:17 -06:00
vicrodh 7049a9f0d9 feat(reco): port ArtistVectorBuilder on the crate clients
T4: 1:1 port of the artist_vectors vector builder into qbz-reco. Keeps Tauri's
Arc<Mutex>/RwLock ownership of the store + MusicBrainz client/cache + Qobuz
client (NOT the plan's by-ref pattern): MusicBrainzCache holds a !Sync rusqlite
Connection, so a borrowed &cache held across the MB-API await would make the
spawned suggestions future !Send. The interleaved short-lived locking (drop the
guard before each await) is the faithful + Send-safe shape. Swaps only
crate::api::QobuzClient -> qbz_qobuz::QobuzClient (get_similar_artists ->
SearchResultsPage<Artist>); the MusicBrainz types already live in
qbz_integrations so extract_relationships ports verbatim. Drops the dead
resolve_qobuz_to_mbid (D3). 21 qbz-reco tests green.

cc
2026-06-26 20:34:25 -06:00
vicrodh 43d67335f0 feat(reco): port ArtistVectorStore (3-table SQLite, keep schema)
T3: 1:1 port of the artist_vectors vector store into qbz-reco. open_at(base)
opens <base>/cache/artist_vectors.db (WAL) with the 3-table schema kept
byte-identical (CREATE IF NOT EXISTS) for cross-frontend reuse. Ports the
in-memory artist index, get_or_create_idx, set_vector (delete-then-insert per
(artist,source) + metadata nnz), get_vector (SUM GROUP BY), has_fresh_vector,
get_artist_name, and the production ranking get_related_artists /
get_all_related_artists (SUM ORDER BY total_weight DESC). Drops the dead
find_nearest cosine path (D3) and the Tauri State/tokio wrapper (lifecycle in
the frontend/core per ADR-006). 4 store tests, 20 qbz-reco tests green.

cc
2026-06-26 20:20:49 -06:00
vicrodh 4ec50f710d feat(reco): port RelationshipWeights
T2: 1:1 port of the artist_vectors RelationshipWeights (12 fields, Default, the
band_focused/similarity_focused presets, and the weight_for_mb_relation /
weight_for_source maps) with its tests. The presets are unused in production but
ported verbatim for parity (pub lib API, no dead-code warnings).

cc
2026-06-26 20:15:59 -06:00
vicrodh 65de984988 feat(reco): scaffold qbz-reco crate + port SparseVector math
New frontend-agnostic crate qbz-reco (ADR-006) for the playlist Suggested Songs
engine (sub-plan 2). T1: 1:1 port of the Tauri artist_vectors SparseVector
(sorted parallel index/value arrays, set/get, dot merge-join, magnitude/
normalize, serde) with its full test suite (13/13 green). Drops the dead
top_k_similar helper (its only caller find_nearest is dropped per epic D3);
keeps cosine_similarity for parity + tests but not for ranking.

cc
2026-06-26 20:15:00 -06:00