Skip to content

0.7.8: typo-tolerant make lookup, top_models(country:) by the country's own rank, dataset 2026.10.0 - #5

Merged
rameerez merged 9 commits into
mainfrom
s4w/devq-gem
Oct 10, 2026
Merged

rameerez merged 9 commits into
mainfrom
s4w/devq-gem

Conversation

@rameerez

Copy link
Copy Markdown
Collaborator

0.7.8 — typo-tolerant make lookup, per-country top_models, dataset 2026.10.0

Do not gem push from this PR — publishing is owner/release-workflow only. This PR bumps the version and ships the code + bundled data.

Why

  1. top_models(country:) sorted by GLOBAL popularity after filtering (bug filed by the SEO session: the Ukraine page and ?country= strips were ordered by everybody else's popularity). global_decile is a presence-weighted average across countries (pipeline#205); a UK list must be in UK order.
  2. Coordinator request (STRETCH 2026-10-10): production logs show developers typing misspelled makes ("marcedes" twice from one mechanic) and getting nothing. Exact/alias first, then a bounded edit-distance fallback, never guessing between two equally close makes.
  3. The bundled snapshot was 2026.07.4, three releases behind.

What

  • Typo tolerance (lib/vehicles/fuzzy.rb, Dataset#find_make): after exact name/slug/alias/built-in alias, the closest make by optimal string alignment (Levenshtein + adjacent swap = 1 edit), stdlib only. Budget: none under 4 characters, 1 edit for 4-5, 2 from 6 (within the requested ≤2). Unique best only — two makes tied → nil ("Acra": Acura/Acma; "telsa": Tesla/Temsa). Guards, each added after a measured failure: a candidate may not have fewer words than the query ("Volvo V 60" → Volvo "60" was possible); every word keeps its first letter ("citroen e" → Citroën DS was possible through the web resolver's prefix loop); the configured other_label never fuzzy-matches ("Otra" → Ora). fuzzy: false opts out on make, models, model, model_options, find. Both validators stay exact — a typo never validates as a stored make. Vehicles.find runs its exact pass over every split before a fuzzy retry.
  • Per-country ranking: Model#country_ranks / #rank_in(:gb); top_models(country:) orders by that country's own rank (ranks are per kind — pass kind:), models without a rank there follow in the old order, data without the key degrades to the 0.7.7 order. US/CA cars have no ranks (presence-only sources) → global order (documented).
  • Data: rake "data:refresh[v2026.10.0]" (new, rakelib/data_refresh.rake, tags only) writes data/vehicles.json = the release's dist/vehicles.json plus only country_ranks, merged from the same tag's open catalog/<kind>/models.json popularity.by_country[cc].rank; stripping the key gives the release bytes exactly. When the projection ships the key natively (companion pipeline PR, emit.rb write_dist_json!), the task asserts equality instead. data/PROVENANCE-vehicles.md records tag/commit.
  • README (typo paragraph + guards, per-country line, rank_in, 15 countries / ~15k models), CHANGELOG 0.7.8, MCP top_models note, version 0.7.8.

Evidence

  • bundle exec rake test: 213 runs, 189,522 assertions, 0 failures/errors/skips (base: 192). Required tests: marcedes→mercedes-benz, volkswagon→volkswagen, tie→nil (Acra, fixture asserts the tie is real in the data).
  • bundle exec rubocop: 0 new offenses (main already carries 111; identical per-file/per-cop tally).
  • Parity with 0.7.7 on the same 2026.10.0 data: 0 changed / 0 lost answers on 28,795 "Make Model" + slug queries through the verbatim web resolver (app/models/resolver.rb), on 1,255 DEVQ A1 sweep queries, and (Opus verifier) on 37,050 find + 65,871 web-resolver-loop queries; 3,284 newly resolved, none >2 edits from its model.
  • Labelled random-typo sample (verifier): make() right 1,351/1,869 (1-edit) and 714/1,179 (2-edit); wrong-make 9 and 15 — mostly first-letter typos (renley→Riley), the documented trade-off of the first-letter rule. find wrong-make: 0.
  • make fuzzy path 0.5 ms/call; exact hits unchanged (0.004 ms).
  • Data: country_ranks on 14,880 models, 0 differences from the catalog ranks across all 6 kinds; top-20 per country equals an independent catalog computation for 88/90 kind×country pairs (the 2 = us/ca cars, no ranks).

Reviews (I-11 + codex)

  • codex (codex exec review --base origin/main), round 1: [P2] out-of-budget candidate accepted (Vehicles.make("z" * 28) → Saxas Nutzfahrzeuge Werdau) — fixed a802168 + regression test; [P2] fuzzy: not forwarded through models/model/model_options — fixed a802168. Round 2: no actionable regressions.
  • Opus I-11 verifier (independent re-derivation): d1263f9 PASS-WITH-FIXES (P2 transposition telsa→Temsa; P2 Other label; P2 web resolver loop [web-side, see below]; 5×P3 docs/rake) — all gem items fixed f9130ff/f36a6a3; re-check at f9130ff PASS (OSA vs textbook over 104,000 comparisons: 0 mismatches; rake refs/tags-only verified incl. v2026.10.0;echo pwned rejected).
  • DEVQ sweep found two more before the verifier: word-swallowing (Volvo V 60) 3bca6a8, first-letter (Citroën e-C3) d1263f9.

For the web session (not in this PR)

  • GET /api/v1/makes/:make_slug/models?country=gb does not use top_models; makes_controller#models sorts by name, or [global_decile, name] with order=popular (measured ford/gb: Mondeo #46 first, Fiesta Add first-class "Other / not in the list" escape hatch (v0.4.0) #1 absent from the first six). After bumping to 0.7.8: sort by m.rank_in(country) || Float::INFINITY first when country is given.
  • resolver.rb rung 3 calls Vehicles.make longest-prefix-first; with 0.7.8 run an exact pass (fuzzy: false) before a fuzzy pass and label typo hits (make_typo:), lower confidence — today's corpus is clean, but by data, not design.
  • Routing (Catalog.find_make) should pass fuzzy: false or 301 to the canonical slug, so /makes/marcedes does not serve a 200 duplicate.

🤖 Generated with Claude Code

rameerez and others added 7 commits October 10, 2026 03:50
…y's own rank + dataset 2026.10.0

- Vehicles.make / models / find: bounded Levenshtein fallback after exact,
  alias and slug matching (1 edit for 4-5 chars, 2 from 6, none under 4),
  unique-best only — two equally close makes return nil ("Acra": Acura/Acma).
  "marcedes" -> Mercedes-Benz, "volkswagon" -> Volkswagen. fuzzy: false opts
  out; both validators are exact-only. find runs its exact pass first, so no
  0.7.7 answer changes.
- Model#country_ranks / #rank_in; top_models(country:) sorts by that rank
  (unranked-there after, global order; data without the key = 0.7.7 order).
- Bundled snapshot 2026.07.4 -> 2026.10.0 via new rake "data:refresh[tag]",
  which merges country_ranks from the same tag's catalog/ (asserts equality
  once the projection ships the key natively — vehiclesdb-pipeline emit).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…dels/model/model_options

Codex review findings (both P2, both applied):
- Fuzzy.unique_closest accepted a lone length-compatible key at limit + 1
  (`best` starts there): Vehicles.make("z" * 28) returned Saxas Nutzfahrzeuge
  Werdau. Now gated on the limit; regression test added.
- models / model / model_options changed behaviour through make() but could
  not take the exact-only opt-out; they now accept and forward fuzzy:.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Found by the DEVQ A1 resolver sweep: "volvo v" is two edits from "volvo",
so in find's fuzzy pass the make prefix swallowed the model's letter and
"Volvo V 60" resolved to Volvo "60" (same for "Mercedes-Benz B 180" ->
"180"). Word-deleting matches are now rejected; same-word-count typos
("marcedes benz") and fewer-words queries ("DeLorean" -> "De Lorean")
still resolve. Regression test added.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Found by the DEVQ A1 parity run through the web resolver's longest-prefix
loop: "citroen e" is two edits from "citroen ds", so "Citroën e-C3" became
Citroën DS C3 (and "peugeot e"/"volkswagen e" pulled e-5008 / e-Up onto the
ICE ids before the word-count rule). Word-for-word initials must now agree.
Parity: 1,255 A1 sweep queries resolve identically to production 0.7.7.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 (P2) Fuzzy.distance is now optimal string alignment: an adjacent swap is
  one edit, so "telsa" ties Tesla/Temsa -> nil instead of confidently Temsa
  (also "seta" no longer -> Setra). Early exit checks two rows.
2 (P2) the configured other_label never fuzzy-matches ("Otra" -> Ora broke
  models(other_label, include_other: true)).
4 (P3) MCP top_models note describes the per-country order.
5 (P3) README rank_in example (the Corolla is gb #51; use the Golf, #2);
  remaining "14 countries" -> 15, South America added.
6 (P3) README: ranks are per kind; us/ca cars have no ranks (global order).
7 (P3) README + CHANGELOG document the word-count / first-letter / swap guards.
8 (P3) data:refresh accepts refs/tags only; array-form git calls.
3 (P2, web) is filed for the coordinator: resolver.rb rung 3 should run an
  exact pass (fuzzy: false) before a fuzzy one and label typo hits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…exact MCP tools, CI lint green

Coordinator additions (STRETCH 2026-10-10):
- Model#mass_decile (popularity.mass_decile, data 2026.10.1+, pipeline#255,
  ruling R3), nil-safe on the bundled 2026.10.0; top_models(by:
  :mass_decile) (unranked excluded; :global_decile stays the default);
  README separates the two questions.
- Web-session requirement (verifier-grade): fuzzy never fires on input
  containing "/" or slug-shaped ("marcedes-benz"): "marcedes-benz/c-class",
  "/makes/marcedes", "car/marcedes-benz/c-class" -> nil, as in 0.7.7.
  Human names ("Marcedes-Benz C-Class", "marcedes") still resolve. MCP tools
  (search_makes, get_model) exact-only. Tests pin every case.
- CI: Ruby 3.4/rails_8.0 Lint job failed on main too (111 inherited
  offenses); frozen in .rubocop_todo.yml (generated, no code change);
  lib/vehicles/plates/series.rb added to .rubocop.yml's own ClassLength
  Exclude (it overrides the todo). Ruby 3.3/rails_8.0 failure was a
  rubygems.org network error during bundle install (re-run).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@rameerez

Copy link
Copy Markdown
Collaborator Author

Delta c16818d (coordinator additions, STRETCH 2026-10-10):

  • Model#mass_decile (dataset popularity.mass_decile, 2026.10.1+, pipeline#255 / ruling R3) — nil-safe on the bundled 2026.10.0; top_models(by: :mass_decile) (records without it excluded → [] on 2026.10.0; default stays :global_decile; other values raise). README separates presence-average vs mass ordering.
  • Fuzzy NEVER fires on id/path/slug-shaped input (any /, or all-lowercase hyphenated): marcedes-benz, marcedes-benz/c-class, /makes/marcedes, car/marcedes-benz/c-class → nil (same as 0.7.7). Human names still resolve. MCP search_makes/get_model exact-only. Tests: test/id_shaped_input_test.rb, test/mass_decile_test.rb.
  • CI Lint (Ruby 3.4/rails_8.0) failed on main too (111 inherited rubocop offenses, red since 0.7.7): frozen in a generated .rubocop_todo.yml, no code change; new code fully linted (0 new offenses vs main). Ruby 3.3/rails_8.0 failure was a rubygems.org network error in bundle install.
    Local: 221 runs, 0F/0E/0S; bundle exec rubocop rc=0. Opus I-11 re-verification of this delta running.

…e stays exact

F1 (P2) Fuzzy.eligible_input? also rejects Symbols, any of / _ + \, leading/
  trailing/double hyphens and UPPER-case hyphenated slugs — each fuzzed for
  every typo (:marcedes_benz, "MARCEDES-BENZ", "marcedes+benz", …); 0.7.7
  returns nil for all. Mixed case ("Marcedes-Benz") and plain words still
  fuzz. Tests pin the verifier's full list.
F2 (P3) CHANGELOG upgrade note: single-word route params still fuzz.
F3 (P3) Model#mass_decile never raises on a non-Hash popularity.
F4 (P3) docs: by: with country (rank first), to_h :mass_decile, 110+1 frozen.
Manager re-check: 223 runs 0F/0E/0S; rubocop rc=0; 30,643 make+find queries
vs 0.7.7 code on the same 2026.10.0 data: 0 changed, 0 lost.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@rameerez

Copy link
Copy Markdown
Collaborator Author

REL merge gate (lane REL, owner-authorised stretch 2026-10-10) — independent check of the delta c16818d..22c4807, since the delta verifier's verdict text was not posted here.

  • Identity: 9 commits, author [email protected] on every one; no foreign e-mail in any message. CI 7/7 SUCCESS at 22c4807 (Ruby 3.4 / rails_8.0 was red on main since 0.7.7 and is green here via the frozen .rubocop_todo.yml).
  • Delta read (lib only): Fuzzy.eligible_input? now also refuses Symbols, any / _ + \, a leading/trailing/double hyphen and single-case hyphenated tokens (marcedes-benz, MARCEDES-BENZ), keeping mixed-case and plain words human; Model#mass_decile reads popularity.mass_decile only when popularity is a Hash (nil-safe on 2026.10.0); a tie-break comment on top_models(country:, by:). Tests added: test/id_shaped_input_test.rb, test/mass_decile_test.rb. Nothing else in the delta touches behaviour. This matches the stated PASS-WITH-FIXES list (P2 machine shapes + P3s) and the web session's requirement that fuzzy never fires on id/slug/path input; MCP tools stay fuzzy: false (search_makes, get_model) or take no make string (top_models, search_models).
  • Merged-tree run (trial worktree, branch already up to date with main): bundle exec rake test 223 runs / 189,562 assertions / 0 failures / 0 errors / 0 skips; bundle exec rubocop 52 files, no offenses.
  • The gem repo's only workflow is tests.yml; merging publishes nothing to RubyGems (gem push stays owner-only).

@rameerez
rameerez merged commit 760e480 into main Oct 10, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant