diff --git a/viz/app/search_page.js b/viz/app/search_page.js index dd258c7..05672fe 100644 --- a/viz/app/search_page.js +++ b/viz/app/search_page.js @@ -3,6 +3,7 @@ import { tokenizeQuery, matchesAllTokens, displaySku, keySkuForRow, parsePriceTo import { loadIndex, loadRecent, loadSavedQuery, saveQuery } from "./state.js"; import { aggregateBySku } from "./catalog.js"; import { loadSkuRules } from "./mapping.js"; +import { smwsDistilleryCodesForQueryPrefix, smwsDistilleryCodeFromName } from "./smws.js"; export function renderSearch($app) { $app.innerHTML = ` @@ -387,7 +388,28 @@ export function renderSearch($app) { if (!tokens.length) return; const matches = allAgg.filter((it) => matchesAllTokens(it.searchText, tokens)); - renderAggregates(matches); + + // If query prefixes an SMWS distillery name, also surface SMWS bottles by code XXX.YYY where XXX matches. + const wantCodes = new Set(smwsDistilleryCodesForQueryPrefix($q.value)); + if (!wantCodes.size) { + renderAggregates(matches); + return; + } + + const seen = new Set(matches.map((it) => String(it?.sku || ""))); + const extra = []; + for (const it of allAgg) { + const sku = String(it?.sku || ""); + if (!sku || seen.has(sku)) continue; + const dCode = smwsDistilleryCodeFromName(it?.name || ""); + if (dCode && wantCodes.has(String(dCode))) { + extra.push(it); + seen.add(sku); + } + } + + // Put SMWS distillery matches first, then normal search matches. + renderAggregates([...extra, ...matches]); } $results.innerHTML = `
Loading index…
`; diff --git a/viz/app/smws.js b/viz/app/smws.js new file mode 100644 index 0000000..0083e0a --- /dev/null +++ b/viz/app/smws.js @@ -0,0 +1,197 @@ +// viz/app/smws.js +import { normSearchText } from "./sku.js"; + +const DISTILLERIES = [ + { code: "1", name: "Glenfarclas" }, + { code: "2", name: "Glenlivet" }, + { code: "3", name: "Bowmore" }, + { code: "4", name: "Highland Park" }, + { code: "5", name: "Auchentoshan" }, + { code: "6", name: "Macduff" }, + { code: "7", name: "Longmorn" }, + { code: "8", name: "Tamdhu" }, + { code: "9", name: "Glen Grant" }, + { code: "10", name: "Bunnahabhain" }, + { code: "11", name: "Tomatin" }, + { code: "12", name: "BenRiach" }, + { code: "13", name: "Dalmore" }, + { code: "14", name: "Talisker" }, + { code: "15", name: "Glenfiddich" }, + { code: "16", name: "Glenturret" }, + { code: "17", name: "Scapa" }, + { code: "18", name: "Inchgower" }, + { code: "19", name: "Glen Garioch" }, + { code: "20", name: "Inverleven" }, + { code: "21", name: "Glenglassaugh" }, + { code: "22", name: "Glenkinchie" }, + { code: "23", name: "Bruichladdich" }, + { code: "24", name: "Macallan" }, + { code: "25", name: "Rosebank" }, + { code: "26", name: "Clynelish" }, + { code: "27", name: "Springbank" }, + { code: "28", name: "Tullibardine" }, + { code: "29", name: "Laphroaig" }, + { code: "30", name: "Glenrothes" }, + { code: "31", name: "Isle of Jura" }, + { code: "32", name: "Edradour" }, + { code: "33", name: "Ardbeg" }, + { code: "34", name: "Tamnavulin" }, + { code: "35", name: "Glen Moray" }, + { code: "36", name: "Benrinnes" }, + { code: "37", name: "Cragganmore" }, + { code: "38", name: "Caperdonich" }, + { code: "39", name: "Linkwood" }, + { code: "40", name: "Balvenie" }, + { code: "41", name: "Dailuaine" }, + { code: "42", name: "Tobermory" }, + { code: "43", name: "Port Ellen" }, + { code: "44", name: "Craigellachie" }, + { code: "45", name: "Dallas Dhu" }, + { code: "46", name: "Glenlossie" }, + { code: "47", name: "Benromach" }, + { code: "48", name: "Balmenach" }, + { code: "49", name: "St. Magdalene" }, + { code: "50", name: "Bladnoch" }, + { code: "51", name: "Bushmills" }, + { code: "52", name: "Old Pulteney" }, + { code: "53", name: "Caol Ila" }, + { code: "54", name: "Aberlour" }, + { code: "55", name: "Royal Brackla" }, + { code: "56", name: "Coleburn" }, + { code: "57", name: "Glen Mhor" }, + { code: "58", name: "Strathisla" }, + { code: "59", name: "Teaninich" }, + { code: "60", name: "Aberfeldy" }, + { code: "61", name: "Brora" }, + { code: "62", name: "Glenlochy" }, + { code: "63", name: "Glentauchers" }, + { code: "64", name: "Mannochmore" }, + { code: "65", name: "Imperial" }, + { code: "66", name: "Ardmore" }, + { code: "67", name: "Banff" }, + { code: "68", name: "Blair Athol" }, + { code: "69", name: "Glen Albyn" }, + { code: "70", name: "Balblair" }, + { code: "71", name: "Glenburgie" }, + { code: "72", name: "Miltonduff" }, + { code: "73", name: "Aultmore" }, + { code: "74", name: "North Port" }, + { code: "75", name: "Glenury / Glenury Royal" }, + { code: "76", name: "Mortlach" }, + { code: "77", name: "Glen Ord" }, + { code: "78", name: "Ben Nevis" }, + { code: "79", name: "Deanston" }, + { code: "80", name: "Glen Spey" }, + { code: "81", name: "Glen Keith" }, + { code: "82", name: "Glencadam" }, + { code: "83", name: "Convalmore" }, + { code: "84", name: "Glendullan" }, + { code: "85", name: "Glen Elgin" }, + { code: "86", name: "Glenesk" }, + { code: "87", name: "Millburn" }, + { code: "88", name: "Speyburn" }, + { code: "89", name: "Tomintoul" }, + { code: "90", name: "Pittyvaich" }, + { code: "91", name: "Dufftown" }, + { code: "92", name: "Lochside" }, + { code: "93", name: "Glen Scotia" }, + { code: "94", name: "Fettercairn" }, + { code: "95", name: "Auchroisk" }, + { code: "96", name: "GlenDronach" }, + { code: "97", name: "Littlemill" }, + { code: "98", name: "Inverleven" }, + { code: "99", name: "Glenugie" }, + { code: "100", name: "Strathmill" }, + { code: "101", name: "Knockando" }, + { code: "102", name: "Dalwhinnie" }, + { code: "103", name: "Royal Lochnagar" }, + { code: "104", name: "Glenburgie (Glencraig)" }, + { code: "105", name: "Tormore" }, + { code: "106", name: "Cardhu" }, + { code: "107", name: "Glenallachie" }, + { code: "108", name: "Allt-a-Bhainne" }, + { code: "109", name: "Miltonduff (Mosstowie)" }, + { code: "110", name: "Oban" }, + { code: "111", name: "Lagavulin" }, + { code: "112", name: "Loch Lomond (Inchmurrin / Inchmoan)" }, + { code: "113", name: "Braeval (Braes of Glenlivet)" }, + { code: "114", name: "Springbank (Longrow)" }, + { code: "115", name: "Knockdhu (AnCnoc)" }, + { code: "116", name: "Yoichi" }, + { code: "117", name: "Cooley (Unpeated)" }, + { code: "118", name: "Cooley / Connemara (Peated)" }, + { code: "119", name: "Yamazaki" }, + { code: "120", name: "Hakushu" }, + { code: "121", name: "Isle of Arran" }, + { code: "122", name: "Loch Lomond (Croftengea)" }, + { code: "123", name: "Glengoyne" }, + { code: "124", name: "Miyagikyo" }, + { code: "125", name: "Glenmorangie" }, + { code: "126", name: "Springbank (Hazelburn)" }, + { code: "127", name: "Bruichladdich (Port Charlotte)" }, + { code: "128", name: "Penderyn" }, + { code: "129", name: "Kilchoman" }, + { code: "130", name: "Chichibu" }, + { code: "131", name: "Hanyu" }, + { code: "132", name: "Karuizawa" }, + { code: "133", name: "Westland" }, + { code: "134", name: "Paul John" }, + { code: "135", name: "Loch Lomond" }, + { code: "136", name: "Eden Mill" }, + { code: "137", name: "St. George’s (The English Whisky Co.)" }, + { code: "138", name: "Nantou" }, + { code: "139", name: "Kavalan" }, + { code: "140", name: "Balcones" }, + { code: "141", name: "Fary Lochan" }, + { code: "142", name: "Breuckelen Distilling" }, + { code: "143", name: "Copperworks Distilling Co." }, + { code: "144", name: "High Coast Distillery" }, + { code: "145", name: "Smögen Whisky" }, + { code: "146", name: "Cotswolds" }, + { code: "147", name: "Archie Rose" }, + { code: "148", name: "Starward" }, + { code: "149", name: "Ardnamurchan" }, + { code: "150", name: "West Cork Distillers" }, + { code: "151", name: "Mackmyra" }, + { code: "152", name: "Shelter Point" }, + { code: "153", name: "Thy Whisky" }, + { code: "154", name: "Mosgaard Whisky" }, + { code: "155", name: "Milk & Honey Distillery" }, + { code: "156", name: "Glasgow Distillery" }, + { code: "157", name: "Distillerie de Warenghem" }, + { code: "158", name: "Yuza Distillery" }, + { code: "159", name: "Mars Shinshu" }, + { code: "160", name: "Mars Tsunuki" }, + { code: "161", name: "Nc'nean Distillery" }, + { code: "162", name: "Isle of Raasay" }, + { code: "163", name: "Isle of Harris Distillery" }, + { code: "164", name: "Penderyn" }, + { code: "165", name: "Wolfburn" }, +]; + +const DIST = DISTILLERIES.map((d) => ({ + code: String(d.code), + nameNorm: normSearchText(d.name), +})); + +const SMWS_WORD_RE = /\bsmws\b/i; +const SMWS_CODE_RE = /\b(\d{1,3})\.(\d{1,4})\b/; + +export function smwsDistilleryCodesForQueryPrefix(qRaw) { + const q = normSearchText(qRaw); + if (!q || q.length < 2) return []; + + const out = new Set(); + for (const d of DIST) { + if (d.nameNorm.startsWith(q)) out.add(d.code); + } + return Array.from(out); +} + +// If a listing name contains "SMWS" and an SMWS code like 35.123, returns "35" (distillery code). +export function smwsDistilleryCodeFromName(name) { + const s = String(name || ""); + if (!SMWS_WORD_RE.test(s)) return ""; + const m = s.match(SMWS_CODE_RE); + return m ? String(m[1] || "") : ""; +}