[agent] fix: canonicalize postal_code on insert + read, migrate legacy rows #3
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "agent/fix-postal-code-search-bug"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Fixes the V1.2 bug where
circulaires searchsilently missed ~620 priced IGA/Metro items. Root cause:UpsertFlyerstored the user's typed postal code verbatim (e.g."H0H 0H0"with a space), butbuildCurrentFlyersSQLdoes an exact-matchWHERE postal_code = ?against the canonical form, so the dirty rows were invisible to search.Three-part fix (per the issue's recommendation #1+#3, plus defense in depth):
UpsertFlyernow runs the incomingf.PostalCodethroughflipp.NormalizePostalCodebefore the INSERT. The db layer owns the invariant that storedpostal_codevalues are always in canonical A1A1A1 form.buildCurrentFlyersSQLalso canonicalizes itspostalCodeparameter. Defense in depth: a user who typescirculaires show --postal "H0H 0H0"now gets the same result as if they typed the canonical form.NormalizePostalCodesrewrites any pre-V1.3 dirty rows on firstdb.Open. Called frommigrate()when the storedschema_version < 2. Idempotent.flipp.normalizePostalCode(lowercase) is now exported asflipp.NormalizePostalCodeso the db package can use it.Why
The bug was silent (no error, just wrong results) and affected a primary user-facing command. With this fix, a fresh fetch + search returns the full ~1,000 items across IGA/Metro/Maxi, not just the ~19 from the one banner flyer that happened to land in the right form.
Followed the user's "no workarounds — fix root cause together" rule: normalized on insert (prevents new dirty data), normalized on read (defense in depth), AND migrated legacy data (cleans up existing dirty rows so users with old caches get correct results immediately, not "next fetch after this PR").
Files changed (6)
internal/db/repo.go—UpsertFlyernormalizes; newNormalizePostalCodesmigration function;buildCurrentFlyersSQLnormalizes its parameterinternal/db/schema.go— schema_version 1→2;migrate()now runs per-version data migrations; newcurrentVersion()helperinternal/db/postal_normalization_test.go(new) — 3 regression tests (insert, read, migration)internal/flipp/postal.go— exportNormalizePostalCodeinternal/flipp/flyers.go— update internal call siteinternal/flipp/postal_test.go— update test for the exported nameTests
3 new regression tests, all failing before the fix and passing after:
TestUpsertFlyer_NormalizesPostalCode— proves the insert path stores canonical form (was RED with"H0H 0H0"stored verbatim, now GREEN)TestBuildCurrentFlyersSQL_NormalizesParameter— proves the read path canonicalizes the parameter (was RED withargs[0]="h0h 0h0", now GREEN)TestNormalizePostalCodes_RewritesDirtyRows— seeds three rows (spaced, dashed, already-canonical) via raw SQL, runs the migration, asserts all canonical and second call is no-opFull suite (10 pre-existing + 3 new = 13 tests):
Build:
Verification (the user-visible one)
Before this fix:
After this fix (after
circulaires fetch && circulaires search ...):The local DB will be auto-migrated on next
db.Open— no user action needed. Idempotent; a secondfetchis a no-op for the migration.Notes
dbnow importsflipp(data layer depending on API client layer). The function is 5 lines and creating a newinternal/postalpackage for one function is over-engineering. If a second non-flippcaller appears, extract to a shared package.bin/circulaireswas rebuilt as part of the fix verification. The fresh binary contains the canonical postal code (H0H0H0form) in the error messages and CLI help, replacing the old non-canonical form that the previous build had baked in.Closes #1.
0404d54053to5792451265