From dc1cd2eb19fdd387b624872fc682771fd5554f79 Mon Sep 17 00:00:00 2001 From: DC Date: Sat, 19 Sep 2026 08:33:11 -0600 Subject: [PATCH] fix(smugmug): match event names whose only token is a number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit matchEventFolder stripped every pure-digit token from a folder's UrlName via a `!/^\d+$/` filter. That filter existed only to drop the conventional "YYYY-MM-DD-" prefix, but it is indiscriminate: it also erases digits that are part of the event name itself. An event named "The 912" tokenizes to exactly ["912"] — "the" is a STOPWORD — so the one token that identifies it was removed from the folder side too. Its gallery, 2026-09-12-The-912-Autocross, reduced to an empty contentTokens array, hit the `continue`, and the function returned null. The photos link silently never rendered. Strip only the leading date prefix, then tokenize the remainder, and reuse that same match for the date-proximity term instead of re-running a second regex. For date-prefixed folders the resulting token set is identical to before; digits inside the name now survive. This cannot loosen matching. Extra content tokens only ever lower `reverse` (matchCount / contentTokens.length) and never affect `forward`, so keeping in-name digits can only make a match stricter — except where the digit is the token we actually want. Not a regression: this code last changed in #99, well before the event. "The 912" is simply the first event name to trip it. Adds regression coverage for the match, the article-omitted folder variant, and three false-positive guards: an unrelated same-day folder, a same-named folder far from the event date, and nearest-of-two when both share the numeric token. --- apps/web/src/lib/smugmug.ts | 21 +++++--- apps/web/tests/smugmug.test.ts | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/apps/web/src/lib/smugmug.ts b/apps/web/src/lib/smugmug.ts index 52b3289..1e7ab93 100644 --- a/apps/web/src/lib/smugmug.ts +++ b/apps/web/src/lib/smugmug.ts @@ -50,6 +50,12 @@ const STOPWORDS = new Set([ "an", ]); +// Folder names are conventionally "YYYY-MM-DD-Event-Name". Strip that prefix +// before tokenizing rather than filtering every pure-digit token: an event +// name can BE a number ("The 912"), and a blanket digit filter erases it from +// the folder side too, leaving nothing to match against. +const DATE_PREFIX = /^(\d{4}-\d{2}-\d{2})[-_ ]?/; + interface FolderSummary { urlName: string; webUri: string; @@ -77,10 +83,13 @@ export function matchEventFolder( let bestUri: string | null = null; for (const folder of folders) { - // Content tokens: strip pure-digit tokens (date prefix like 2026, 05, 17) from folder name - const contentTokens = tokenize(folder.urlName).filter( - (t) => !/^\d+$/.test(t) - ); + // Drop the leading date prefix (e.g. "2026-04-25-") and tokenize the rest. + // Digits inside the name itself are kept — they can be the whole name. + const dateMatch = DATE_PREFIX.exec(folder.urlName); + const bareName = dateMatch + ? folder.urlName.slice(dateMatch[0].length) + : folder.urlName; + const contentTokens = tokenize(bareName); if (contentTokens.length === 0) continue; const matchCount = eventTokens.filter((t) => @@ -93,8 +102,8 @@ export function matchEventFolder( const reverse = matchCount / contentTokens.length; const tScore = Math.max(forward, reverse); - // Parse date from UrlName prefix (e.g. "2026-04-25-blooming-cones") - const dateMatch = /^(\d{4}-\d{2}-\d{2})/.exec(folder.urlName); + // Reuse the prefix match above for date proximity; fall back to DateAdded + // for folders that don't follow the date-prefix convention. const folderDate = dateMatch ? new Date(dateMatch[1] + "T00:00:00Z") : new Date(folder.dateAdded); diff --git a/apps/web/tests/smugmug.test.ts b/apps/web/tests/smugmug.test.ts index 742ae26..b4703e4 100644 --- a/apps/web/tests/smugmug.test.ts +++ b/apps/web/tests/smugmug.test.ts @@ -114,6 +114,98 @@ describe("matchEventFolder", () => { const combinedLabel = matchEventFolder(combinedFolders, "Cone in 60 Seconds", combinedDate); expect(combinedLabel).toBe(sessionA); }); + + // An event name can consist entirely of a number ("The 912" — "the" is a + // stopword, leaving just "912"). The folder side used to strip every + // pure-digit token to drop the "YYYY-MM-DD-" prefix, which erased the 912 + // from the folder name too, so no folder had any content token left to + // match and the photos link silently never rendered. + describe("numeric event names", () => { + const nineTwelveDate = new Date("2026-09-12T00:00:00Z"); + const nineTwelveUri = + "https://rmrpca.smugmug.com/Autocross/2026/2026-09-12-The-912-Autocross"; + const nineTwelveFolders = [ + { + urlName: "2026-09-12-The-912-Autocross", + webUri: nineTwelveUri, + dateAdded: "2026-09-13T12:00:00Z", + }, + ]; + + it("matches an event whose only token is a number", () => { + expect(matchEventFolder(nineTwelveFolders, "The 912", nineTwelveDate)).toBe( + nineTwelveUri, + ); + }); + + it("matches when the folder omits the leading article", () => { + expect( + matchEventFolder( + [ + { + urlName: "2026-09-12-912-Autocross", + webUri: nineTwelveUri, + dateAdded: "2026-09-13T12:00:00Z", + }, + ], + "The 912", + nineTwelveDate, + ), + ).toBe(nineTwelveUri); + }); + + // A lone numeric token must not match anything that merely shares its date. + it("does not match a same-day folder with no shared token", () => { + expect( + matchEventFolder( + [ + { + urlName: "2026-09-12-Fall-Finale", + webUri: "WRONG", + dateAdded: "2026-09-13T12:00:00Z", + }, + ], + "The 912", + nineTwelveDate, + ), + ).toBeNull(); + }); + + // Date proximity still gates a perfect token score: 0.6 * 1.0 + 0.4 * 0 + // is exactly the threshold, and the comparison is strictly greater-than. + it("does not match a same-named folder far from the event date", () => { + expect( + matchEventFolder( + [ + { + urlName: "2026-07-04-The-912", + webUri: "WRONG", + dateAdded: "2026-07-05T12:00:00Z", + }, + ], + "The 912", + nineTwelveDate, + ), + ).toBeNull(); + }); + + it("picks the date-nearest folder when several share the numeric token", () => { + expect( + matchEventFolder( + [ + ...nineTwelveFolders, + { + urlName: "2026-05-02-912-Tribute", + webUri: "WRONG", + dateAdded: "2026-05-03T12:00:00Z", + }, + ], + "The 912", + nineTwelveDate, + ), + ).toBe(nineTwelveUri); + }); + }); }); // PR #99 review: the SMUGMUG_* env fallbacks (and the "rmrpca"/"Autocross"