// The import review's match evidence — ONE definition, read by both the // picker's verdict panel and the window's outcome buckets, so the filter // and the panel can never disagree about the same record (the mock's rule, // research/import/render.js evidence()). export const normx = (s) => (s ?? "").toLowerCase().replace(/[^a-z0-9]/g, ""); const EDITION_WORD = /\b(deluxe|expanded|remaster(?:ed)?|anniversary|edition|version|reissue|bonus|explicit|clean|mono|stereo|original|special|limited)\b/gi; /** What a release is CALLED beyond the record's own name: bracketed years a * ripper added, edition words a service adds. Strip both and two namings of * one record meet. */ export const coreTitle = (s) => normx( String(s ?? "") .replace(/[\[(<][^\])>]*[\])>]/g, " ") .replace(EDITION_WORD, " ") .replace(/\b(19|20)\d{2}\b/g, " "), ) || normx(s); /** What is actually known about whether candidate `c` is the same record as * the scanned album (title + artist + your track count). Each row is a * fact the user can check, not a score they have to trust. * * `artist` and `title` must be the RESOLVED names — what the provider was * actually asked about (see import-title.js), never the raw folder. Judging a * candidate against a folder like "[WARP CD 156] Battles - Mirrored - 2007" * turns an exact answer into a mere containment, and files a clean match as * "close". */ export function evidence(c, artist, title, trackCount) { if (!c) return { items: [], verdict: "none", edition: false }; const artistOk = normx(c.artist) === normx(artist); const titleOk = normx(c.title) === normx(title); const titleClose = !titleOk && (normx(c.title).includes(normx(title)) || normx(title).includes(normx(c.title))); // Same record, named for a different edition — the ONE case where a // different track count is expected rather than evidence of the wrong // album. const edition = !titleOk && coreTitle(title) === coreTitle(c.title); const countOk = c.trackCount === trackCount; const items = [ { text: artistOk ? "Artist matches" : `Artist: ${c.artist}`, cls: artistOk ? "ok" : "no" }, { text: titleOk ? "Title matches" : edition ? `Same record, different edition: ${c.title}` : titleClose ? `Title: ${c.title}` : `Different title: ${c.title}`, cls: titleOk ? "ok" : edition || titleClose ? "warn" : "no", }, { text: countOk ? `${c.trackCount} tracks, same` : edition ? `${trackCount} tracks vs ${c.trackCount} — expected on another edition` : `${trackCount} tracks vs ${c.trackCount}`, cls: countOk ? "ok" : edition ? "warn" : "no", }, ]; const bad = items.filter((i) => i.cls === "no").length; let verdict = bad >= 2 ? "bad" : bad === 1 ? "warn" : "ok"; // An edition difference is never "everything agrees". if (edition && verdict === "ok") verdict = "warn"; return { items, verdict, edition }; }