diff --git a/workers/src/scanner.service.ts b/workers/src/scanner.service.ts index 3e26d4e..a569264 100644 --- a/workers/src/scanner.service.ts +++ b/workers/src/scanner.service.ts @@ -252,6 +252,14 @@ export class ScannerService { ); } + // 5. Put the same credit into the claim graph. Every Vibe generator but + // the fallback reaches tracks through claim_fusion, and only MusicBrainz + // was writing those edges — so a track MusicBrainz has never heard of was + // invisible to the recommender no matter how well tagged the file was. + // `tag` is the lowest-trust source in the spine, so a later MusicBrainz + // claim about the same edge still outranks this one. + await this.writeTagCredits(trackId, String(artistId), featuredIds.map(String)); + console.log(`[Scanner] Successfully processed: ${trackTitle}`); // Trigger external-API enrichment for this track + artist + album. @@ -315,6 +323,34 @@ export class ScannerService { return { id: String(inserted.rows[0].id), name: String(inserted.rows[0].name) }; } + /** + * Record the file's own artist credits as claims. Confidence is 1.0 because + * the tag says this without ambiguity; how much that is worth is decided by + * the trust attached to the `tag` source, not here. + */ + private async writeTagCredits(trackId: string, artistId: string, featuredIds: string[]): Promise { + const credits: Array<[string, string]> = [ + [artistId, 'credited_main_on'], + ...featuredIds.map((id): [string, string] => [id, 'featured_on']), + ]; + for (const [objectId, predicate] of credits) { + try { + await this.pgClient.query( + `INSERT INTO claims ( + subject_type, subject_id, predicate, object_type, object_id, source, confidence + ) VALUES ('track', $1::uuid, $2, 'artist', $3::uuid, 'tag', 1.0) + ON CONFLICT (subject_type, subject_id, predicate, object_type, object_id, source, user_id) + DO UPDATE SET last_reinforced_at = NOW()`, + [trackId, predicate, objectId] + ); + } catch (err) { + // A missing edge costs this track its place in the graph; it must not + // cost the whole scan the file. + console.error(`[Scanner] Failed to write ${predicate} claim for track ${trackId}:`, err); + } + } + } + private async enqueueEnrichment(trackId: string, artistId: string, albumId: string) { const keep = { removeOnComplete: { age: 86400, count: 5000 },