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] || "") : "";
+}