vehicles 0.4.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +164 -0
- data/CHANGELOG.md +321 -0
- data/README.md +88 -15
- data/data/PROVENANCE-vehicles.md +12 -0
- data/data/plates/PROVENANCE.md +10 -0
- data/data/plates/_decode/at-districts.yml +287 -0
- data/data/plates/_decode/ch-cantons.yml +110 -0
- data/data/plates/_decode/cn-issuing-offices.yml +463 -0
- data/data/plates/_decode/cn-provinces.yml +614 -0
- data/data/plates/_decode/cz-kraj.yml +159 -0
- data/data/plates/_decode/dk-serial-blocks.yml +327 -0
- data/data/plates/_decode/es-provinces.yml +116 -0
- data/data/plates/_decode/fr-departements.yml +191 -0
- data/data/plates/_decode/gb-age-identifiers.yml +170 -0
- data/data/plates/_decode/gb-memory-tags.yml +202 -0
- data/data/plates/_decode/gr-prefectures.yml +1408 -0
- data/data/plates/_decode/hr-cities.yml +315 -0
- data/data/plates/_decode/ie-counties.yml +227 -0
- data/data/plates/_decode/in-state-codes.yml +169 -0
- data/data/plates/_decode/is-districts-1938.yml +116 -0
- data/data/plates/_decode/it-provinces.yml +348 -0
- data/data/plates/_decode/jp-office-marks.yml +960 -0
- data/data/plates/_decode/jp-use-marks.yml +205 -0
- data/data/plates/_decode/kr-authority-marks.yml +146 -0
- data/data/plates/_decode/kr-use-syllables.yml +194 -0
- data/data/plates/_decode/lu-series-order.yml +638 -0
- data/data/plates/_decode/mx-entity-series.yml +380 -0
- data/data/plates/_decode/my-index-marks.yml +271 -0
- data/data/plates/_decode/no-counties-1913.yml +148 -0
- data/data/plates/_decode/no-districts.yml +901 -0
- data/data/plates/_decode/pl-powiats.yml +567 -0
- data/data/plates/_decode/pt-trailer-services.yml +96 -0
- data/data/plates/_decode/ro-counties.yml +213 -0
- data/data/plates/_decode/se-diplomatic.yml +220 -0
- data/data/plates/_decode/si-regions.yml +123 -0
- data/data/plates/_decode/sk-districts.yml +314 -0
- data/data/plates/_decode/th-registration-elements.yml +208 -0
- data/data/plates/_decode/ua-regions.yml +393 -0
- data/data/plates/_decode/za-licence-marks.yml +215 -0
- data/data/plates/_meta/classes.yml +26 -0
- data/data/plates/_meta/separators.yml +102 -0
- data/data/plates/ad.yml +961 -0
- data/data/plates/ar.yml +762 -0
- data/data/plates/at.yml +1424 -0
- data/data/plates/au-act.yml +472 -0
- data/data/plates/au-nsw.yml +282 -0
- data/data/plates/au-nt.yml +431 -0
- data/data/plates/au-qld.yml +272 -0
- data/data/plates/au-sa.yml +248 -0
- data/data/plates/au-tas.yml +302 -0
- data/data/plates/au-vic.yml +215 -0
- data/data/plates/au-wa.yml +309 -0
- data/data/plates/be.yml +1840 -0
- data/data/plates/br.yml +1152 -0
- data/data/plates/ca-ab.yml +430 -0
- data/data/plates/ca-bc.yml +697 -0
- data/data/plates/ca-mb.yml +499 -0
- data/data/plates/ca-nb.yml +537 -0
- data/data/plates/ca-nl.yml +743 -0
- data/data/plates/ca-ns.yml +808 -0
- data/data/plates/ca-nt.yml +610 -0
- data/data/plates/ca-nu.yml +280 -0
- data/data/plates/ca-on.yml +881 -0
- data/data/plates/ca-pe.yml +592 -0
- data/data/plates/ca-qc.yml +1036 -0
- data/data/plates/ca-sk.yml +256 -0
- data/data/plates/ca-yt.yml +656 -0
- data/data/plates/ch.yml +1273 -0
- data/data/plates/cn.yml +1431 -0
- data/data/plates/cy.yml +688 -0
- data/data/plates/cz.yml +1179 -0
- data/data/plates/de.yml +1107 -0
- data/data/plates/dk.yml +1647 -0
- data/data/plates/ee.yml +1275 -0
- data/data/plates/es.yml +888 -0
- data/data/plates/fi.yml +1271 -0
- data/data/plates/fr.yml +1265 -0
- data/data/plates/gb.yml +1188 -0
- data/data/plates/gr.yml +721 -0
- data/data/plates/hr.yml +860 -0
- data/data/plates/hu.yml +959 -0
- data/data/plates/ie.yml +775 -0
- data/data/plates/in.yml +1172 -0
- data/data/plates/is.yml +667 -0
- data/data/plates/it.yml +1317 -0
- data/data/plates/jp.yml +3650 -0
- data/data/plates/kr.yml +1375 -0
- data/data/plates/li.yml +1010 -0
- data/data/plates/lt.yml +879 -0
- data/data/plates/lu.yml +842 -0
- data/data/plates/lv.yml +924 -0
- data/data/plates/mc.yml +1024 -0
- data/data/plates/mt.yml +854 -0
- data/data/plates/mx.yml +1372 -0
- data/data/plates/my.yml +685 -0
- data/data/plates/nl.yml +1016 -0
- data/data/plates/no.yml +1018 -0
- data/data/plates/nz.yml +1699 -0
- data/data/plates/pl.yml +1296 -0
- data/data/plates/pt.yml +847 -0
- data/data/plates/ro.yml +785 -0
- data/data/plates/se.yml +1091 -0
- data/data/plates/si.yml +1105 -0
- data/data/plates/sk.yml +1439 -0
- data/data/plates/sm.yml +1119 -0
- data/data/plates/th.yml +1150 -0
- data/data/plates/ua.yml +1672 -0
- data/data/plates/us-ak.yml +763 -0
- data/data/plates/us-al.yml +457 -0
- data/data/plates/us-ar.yml +427 -0
- data/data/plates/us-as.yml +454 -0
- data/data/plates/us-az.yml +430 -0
- data/data/plates/us-ca.yml +892 -0
- data/data/plates/us-co.yml +452 -0
- data/data/plates/us-ct.yml +427 -0
- data/data/plates/us-dc.yml +614 -0
- data/data/plates/us-de.yml +551 -0
- data/data/plates/us-fl.yml +741 -0
- data/data/plates/us-ga.yml +644 -0
- data/data/plates/us-gu.yml +658 -0
- data/data/plates/us-hi.yml +585 -0
- data/data/plates/us-ia.yml +438 -0
- data/data/plates/us-id.yml +531 -0
- data/data/plates/us-il.yml +516 -0
- data/data/plates/us-in.yml +324 -0
- data/data/plates/us-ks.yml +351 -0
- data/data/plates/us-ky.yml +770 -0
- data/data/plates/us-la.yml +568 -0
- data/data/plates/us-ma.yml +379 -0
- data/data/plates/us-md.yml +536 -0
- data/data/plates/us-me.yml +445 -0
- data/data/plates/us-mi.yml +408 -0
- data/data/plates/us-mn.yml +411 -0
- data/data/plates/us-mo.yml +356 -0
- data/data/plates/us-mp.yml +402 -0
- data/data/plates/us-ms.yml +432 -0
- data/data/plates/us-mt.yml +619 -0
- data/data/plates/us-nc.yml +602 -0
- data/data/plates/us-nd.yml +419 -0
- data/data/plates/us-ne.yml +483 -0
- data/data/plates/us-nh.yml +394 -0
- data/data/plates/us-nj.yml +547 -0
- data/data/plates/us-nm.yml +574 -0
- data/data/plates/us-nv.yml +655 -0
- data/data/plates/us-ny.yml +592 -0
- data/data/plates/us-oh.yml +404 -0
- data/data/plates/us-ok.yml +513 -0
- data/data/plates/us-or.yml +392 -0
- data/data/plates/us-pa.yml +506 -0
- data/data/plates/us-pr.yml +507 -0
- data/data/plates/us-ri.yml +445 -0
- data/data/plates/us-sc.yml +605 -0
- data/data/plates/us-sd.yml +428 -0
- data/data/plates/us-tn.yml +471 -0
- data/data/plates/us-tx.yml +535 -0
- data/data/plates/us-ut.yml +384 -0
- data/data/plates/us-va.yml +567 -0
- data/data/plates/us-vi.yml +489 -0
- data/data/plates/us-vt.yml +458 -0
- data/data/plates/us-wa.yml +484 -0
- data/data/plates/us-wi.yml +509 -0
- data/data/plates/us-wv.yml +446 -0
- data/data/plates/us-wy.yml +523 -0
- data/data/plates/va.yml +604 -0
- data/data/plates/za.yml +1141 -0
- data/data/vehicles.json +160653 -105090
- data/lib/vehicles/configuration.rb +18 -2
- data/lib/vehicles/dataset.rb +73 -18
- data/lib/vehicles/fuzzy.rb +115 -0
- data/lib/vehicles/http.rb +48 -0
- data/lib/vehicles/make.rb +36 -6
- data/lib/vehicles/mcp_server.rb +5 -5
- data/lib/vehicles/model.rb +42 -6
- data/lib/vehicles/plates/jurisdiction.rb +32 -0
- data/lib/vehicles/plates/series.rb +291 -0
- data/lib/vehicles/plates/template_fill.rb +188 -0
- data/lib/vehicles/plates/templates.rb +162 -0
- data/lib/vehicles/plates.rb +122 -0
- data/lib/vehicles/providers/hosted_provider.rb +49 -58
- data/lib/vehicles/providers/local_provider.rb +4 -3
- data/lib/vehicles/validators/vehicle_make_validator.rb +3 -1
- data/lib/vehicles/validators/vehicle_model_validator.rb +1 -1
- data/lib/vehicles/version.rb +1 -1
- data/lib/vehicles.rb +53 -16
- data/rakelib/data_refresh.rake +109 -0
- data/rakelib/plates_refresh.rake +29 -0
- data/sig/vehicles.rbs +18 -0
- metadata +174 -2
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> [!TIP]
|
|
6
6
|
> **🚀 Ship your next Rails app 10x faster!** I've built **[RailsFast](https://railsfast.com/?ref=vehicles)**, a production-ready Rails boilerplate template that comes with everything you need to launch a software business in days, not weeks. Go [check it out](https://railsfast.com/?ref=vehicles)!
|
|
7
7
|
|
|
8
|
-
`vehicles` gives your Rails app a clean, curated list of vehicle makes and models — **~
|
|
8
|
+
`vehicles` gives your Rails app a clean, curated list of vehicle makes and models — **~15k models across 850+ makes: cars, motorcycles, mopeds, vans, trucks and buses**, reconciled from official registers of 15 countries — ready for dropdowns, search, and validation. No API keys, no database table, no migration — it works **fully offline** the second you `bundle install`, because the data ships inside the gem. (Optionally, it can [refresh](#staying-current-optional) the data without a gem upgrade.)
|
|
9
9
|
|
|
10
10
|
✨ Perfect for marketplaces, carpooling & rideshare apps, fleet tools, parking & EV-charging apps, insurance and booking forms — anywhere a user has to pick their vehicle.
|
|
11
11
|
|
|
@@ -75,12 +75,15 @@ Vehicles.models("toyota") # case-insensitive
|
|
|
75
75
|
Vehicles.models("VW") # => Volkswagen (common abbreviation)
|
|
76
76
|
Vehicles.models("merc") # => Mercedes-Benz
|
|
77
77
|
Vehicles.models("alfa-romeo") # slug form
|
|
78
|
+
Vehicles.models("marcedes") # => Mercedes-Benz (typo: one make is clearly closest)
|
|
78
79
|
```
|
|
79
80
|
|
|
81
|
+
Typos resolve too: when nothing matches exactly, the closest make within a small edit distance wins (1 edit up to 5 characters, 2 from 6 up, none under 4) — but only if ONE make is closest, so `"Acra"` (one edit from both Acura and Acma) stays `nil` rather than guessing. Pass `fuzzy: false` (`Vehicles.make("marcedes", fuzzy: false) # => nil`) when you need exact-only matching; the validators always do, so a typo never validates as a stored make. Three more guards keep it from guessing: a swap of two neighbouring letters counts as one edit (`"telsa"` is equally close to Tesla and Temsa, so it stays `nil`); a match may not drop a whole word (so `"Volvo V 60"` never turns into Volvo "60"); and every word keeps its first letter (so `"citroen e"` is not Citroën DS). Your configured `other_label` never fuzzy-matches a real make. Machine input never fuzzes either: Symbols, anything with `/`, `_`, `+` or `\` (ids like `car/mercedes-benz/c-class`, paths like `/makes/marcedes`), and single-case hyphenated slugs (`marcedes-benz`, `MARCEDES-BENZ`) resolve exactly or return `nil` — so routes and stored ids can never land on a guessed make. The MCP server tools are exact-only.
|
|
82
|
+
|
|
80
83
|
Unknown make? You get an empty array, never an exception:
|
|
81
84
|
|
|
82
85
|
```ruby
|
|
83
|
-
Vehicles.models("
|
|
86
|
+
Vehicles.models("Zzyzx Motors") # => []
|
|
84
87
|
```
|
|
85
88
|
|
|
86
89
|
## Smart lookup & search
|
|
@@ -179,8 +182,18 @@ car.popular? # => true decile ≤ 2 (and `false` when unranked —
|
|
|
179
182
|
car.availability # => ["es", "fi", "gb", ...] countries with official evidence
|
|
180
183
|
car.available_in?(:th) # => true
|
|
181
184
|
|
|
182
|
-
# The most popular models — perfect for "common choices" pre-filled pickers
|
|
183
|
-
|
|
185
|
+
# The most popular models — perfect for "common choices" pre-filled pickers.
|
|
186
|
+
# With `country:`, ordered by THAT country's own registration rank (ranks are per kind,
|
|
187
|
+
# so pass `kind:`; countries whose sources record presence only — us, ca for cars —
|
|
188
|
+
# have no ranks and keep the global order):
|
|
189
|
+
Vehicles.top_models(kind: :car, country: :gb, limit: 3).map(&:name) # => ["Fiesta", "Golf", "Corsa"]
|
|
190
|
+
Vehicles.find("vw golf").rank_in(:gb) # => 2 rank among all GB cars (nil where unranked)
|
|
191
|
+
|
|
192
|
+
# Two different "popularity" questions — pick the one you mean:
|
|
193
|
+
car.global_decile # presence average: "popular in MANY countries" (equal country weight)
|
|
194
|
+
car.mass_decile # registration mass per kind: "how many EXIST" (1 = heaviest 10%)
|
|
195
|
+
# nil until your data is 2026.10.1+ (refresh! or a newer gem)
|
|
196
|
+
Vehicles.top_models(kind: :car, by: :mass_decile, limit: 10) # [] on older snapshots
|
|
184
197
|
Vehicles.top_models(kind: :motorcycle, limit: 10).map(&:full_name)
|
|
185
198
|
```
|
|
186
199
|
|
|
@@ -303,7 +316,7 @@ They're forgiving the same way the lookups are (aliases, case, slugs), and they
|
|
|
303
316
|
|
|
304
317
|
### An "Other / not in the list" escape hatch
|
|
305
318
|
|
|
306
|
-
Make/model dropdowns should never be a dead end — ~
|
|
319
|
+
Make/model dropdowns should never be a dead end — ~15k models is a lot, but
|
|
307
320
|
someone always shows up with the one car that isn't there. Turn on a first-class
|
|
308
321
|
"Other" option and the pieces agree by construction: `include_other:` adds it to
|
|
309
322
|
the picker, `allow_other:` lets it through validation.
|
|
@@ -398,6 +411,12 @@ car&.body_type # => :hatchback
|
|
|
398
411
|
car&.suv? # => false
|
|
399
412
|
```
|
|
400
413
|
|
|
414
|
+
It reads what people actually type: case, accents and spacing don't matter
|
|
415
|
+
(`"XCeed"` is Kia's `"X Ceed"`, `"A 5"` is `"A5"`), BMW's Spanish/Italian
|
|
416
|
+
`"Serie 3"` is the `"3 Series"`, and a name that exists as several kinds
|
|
417
|
+
(Toyota's Land Cruiser: car, van, truck, bus) answers with the car unless you
|
|
418
|
+
pass `kind:` — `Vehicles.model("Toyota", "Land Cruiser", kind: :bus)`.
|
|
419
|
+
|
|
401
420
|
**Only accept the kinds you support.** Filter every list/lookup by `kind:` so
|
|
402
421
|
the picker only ever offers vehicles your app handles — and widening later (when
|
|
403
422
|
new kind packs ship, or when you decide to accept them) is a one-word change, not
|
|
@@ -422,8 +441,8 @@ Vehicles.find("vw golf").available_in?(:nz) # => true
|
|
|
422
441
|
Vehicles.top_models(kind: :car, country: :es) # country-scoped popularity
|
|
423
442
|
```
|
|
424
443
|
|
|
425
|
-
The bundled snapshot is **global** (official registers of
|
|
426
|
-
Europe, North America, SE Asia, Oceania and Ukraine — see [Where the data
|
|
444
|
+
The bundled snapshot is **global** (official registers of 15 countries across
|
|
445
|
+
Europe, North America, South America, SE Asia, Oceania and Ukraine — see [Where the data
|
|
427
446
|
comes from](#where-the-data-comes-from)), so the region gate accepts every
|
|
428
447
|
region query — `region: :eu` callers from 0.1.x keep working unchanged:
|
|
429
448
|
|
|
@@ -432,6 +451,50 @@ Vehicles.makes(region: :eu) # fine — a global snapshot covers the EU
|
|
|
432
451
|
Vehicles.region # => :global
|
|
433
452
|
```
|
|
434
453
|
|
|
454
|
+
## License plates
|
|
455
|
+
|
|
456
|
+
The gem bundles the VehiclesDB **registration-mark dataset** (PRD-PLATES): every plate series per jurisdiction, researched to the issuing authority's own documents — formats, eras, alphabets, designs. Ask it whether a typed registration is real:
|
|
457
|
+
|
|
458
|
+
```ruby
|
|
459
|
+
Vehicles.plate("12-GB-BD", jurisdiction: :nl).valid? # => true — exact, as issued
|
|
460
|
+
Vehicles.plate("12gbbd", jurisdiction: :nl).suggestion # => "12-GB-BD" (did you mean)
|
|
461
|
+
Vehicles.plate("12-AB-CD", jurisdiction: :nl).strict? # => false — vowels aren't issued
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Matching is **two-tier**: exact as-issued first, then a separator-forgiving tier — `"1234XYZ"`, `"1234 xyz"` and `"1234-XYZ"` all resolve to the same serial, with `suggestion` giving the formatting the plate actually wears. Leniency forgives punctuation, **never the alphabet**: the strict regexes encode what the authority really issues (the Dutch vowel purge, the Spanish consonant-triplet rule…). `Match#strict?` separates authority-alphabet hits from recall-only catch-alls (export plates accept any current shape, by law).
|
|
465
|
+
|
|
466
|
+
```ruby
|
|
467
|
+
Vehicles.plates # => every jurisdiction
|
|
468
|
+
Vehicles.plates(:es).series # => 18 series: formats, periods, sourced designs
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Pilot coverage (gate L0): 🇳🇱 🇪🇸 🇩🇪 + US-Florida, 73 series, corpus-proven in NL. Same posture as everything else here: bundled, offline, forgiving in, honest out.
|
|
472
|
+
|
|
473
|
+
**Prefer HTTP?** The same matcher and the full format corpus are hosted endpoints on the [VehiclesDB API](https://vehiclesdb.com/api): `GET /api/v1/plates/{code}` (a jurisdiction's every series, with 1:1 SVG render URLs) and `GET /api/v1/plates/{code}/match?plate=…` (validity + as-issued formatting + a render of *that* registration on the real plate design). Free API keys at [vehiclesdb.com](https://vehiclesdb.com).
|
|
474
|
+
|
|
475
|
+
### Drawing a plate without sending it anywhere
|
|
476
|
+
|
|
477
|
+
A registration is personal data. To show a user their own plate on the real VehiclesDB plate art, the gem fetches a **blank template** (the series, the registration's *shape* — `NNNN LLL`, character classes only — and an optional region) and fills it **locally**:
|
|
478
|
+
|
|
479
|
+
```ruby
|
|
480
|
+
Vehicles::Plates.render("1234 BCD", jurisdiction: :es)
|
|
481
|
+
# => "<svg … aria-label=\"Spain plate 1234 BCD\">…</svg>" (nil when it can't)
|
|
482
|
+
|
|
483
|
+
Vehicles::Plates.render("1234 BCD", jurisdiction: :es, label: "Matrícula 1234 BCD") # your language
|
|
484
|
+
Vehicles::Plates.render("AB-123-CD", jurisdiction: :fr, region: "75") # FR département band
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Only `GET {api_base_url}/plates/es/es-national-2000/template.svg?shape=NNNN_LLL` goes on the wire: no registration, no API key (the route is public). Anything ambiguous (a German `MAB1234` could be `M-AB` or `MA-B`) or unreachable answers `nil` — keep drawing whatever you drew before. Templates are cached in-process; share them across processes with any store that has `read` / `write(key, value, expires_in:)`:
|
|
488
|
+
|
|
489
|
+
```ruby
|
|
490
|
+
Vehicles.configure do |config|
|
|
491
|
+
config.plate_template_cache = Rails.cache
|
|
492
|
+
config.plate_template_ttl = 7 * 24 * 3600 # templates are public and change only with the plate art
|
|
493
|
+
end
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
The pieces are public too: `Vehicles::Plates.template(code:, series:, shape:, region:)` returns the blank SVG, and `Vehicles::Plates::TemplateFill.fill(svg, registration)` is the dependency-free filler (stdlib `json` only) if you fetch templates yourself — in a browser, say.
|
|
497
|
+
|
|
435
498
|
## 🔓 More with VehiclesDB
|
|
436
499
|
|
|
437
500
|
`vehicles` is the free, open-source SDK for [**VehiclesDB**](https://vehiclesdb.com) — a hosted API for richer vehicle data. The gem is **fully standalone and always will be**; pointing it at VehiclesDB is purely additive. Drop in an API key and the same objects you already use light up with more:
|
|
@@ -442,11 +505,20 @@ Vehicles.configure do |config|
|
|
|
442
505
|
end
|
|
443
506
|
|
|
444
507
|
car = Vehicles.find("vw golf")
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
car.image
|
|
449
|
-
car.image(
|
|
508
|
+
|
|
509
|
+
# Rendered vehicle imagery — LIVE today. Mint a key at
|
|
510
|
+
# https://vehiclesdb.com/settings/api-keys
|
|
511
|
+
car.image # => "https://vehiclesdb.com/…/md.webp" (640×360)
|
|
512
|
+
car.image(size: :lg) # :sm 320×180 · :md 640×360 · :lg 1280×720
|
|
513
|
+
car.image(color: "red") # color-accurate; un-rendered colors fall back honestly
|
|
514
|
+
car.images(color: "red") # the full payload: every variant + dimensions,
|
|
515
|
+
# rendered palette, served-vs-requested color
|
|
516
|
+
# color slugs are the gem's own canon — Vehicles.colors — so a stored
|
|
517
|
+
# dropdown value maps 1:1 to the rendered imagery.
|
|
518
|
+
|
|
519
|
+
# Enrichment — the wired-up seam; ships next. nil until then, same objects.
|
|
520
|
+
car.years # => 1974..2024 production years
|
|
521
|
+
car.segment # => :hatchback / :hot_hatch editorial segment
|
|
450
522
|
```
|
|
451
523
|
|
|
452
524
|
Under the hood this is a simple **provider** model: a `LocalProvider` (the bundled data) is always available, and a `HostedProvider` activates only when an API key is configured. Calls prefer the hosted data when it's there and **fall back to the local data otherwise — never raising, never blocking** your request:
|
|
@@ -522,6 +594,7 @@ Vehicles.catalog(kind: :car) # => { "Audi" => ["A3", ...], ... }
|
|
|
522
594
|
Vehicles.make("Audi") # => Vehicles::Make | nil
|
|
523
595
|
Vehicles.find("audi a3") # => Vehicles::Model | nil (one free-text string)
|
|
524
596
|
Vehicles.model("Audi", "A3") # => Vehicles::Model | nil (a stored make+model pair)
|
|
597
|
+
Vehicles.model("Toyota", "Land Cruiser", kind: :bus) # pick a kind (default: the car)
|
|
525
598
|
Vehicles.search("a3") # => [Vehicles::Model]
|
|
526
599
|
|
|
527
600
|
# Popularity & availability (open data, measured from official registers)
|
|
@@ -556,7 +629,7 @@ Vehicles.reload! # drop the in-memory dataset (reload fro
|
|
|
556
629
|
|
|
557
630
|
## Where the data comes from
|
|
558
631
|
|
|
559
|
-
The bundled dataset is [**VehiclesDB**](https://github.com/vehiclesdb/vehiclesdb) — an open dataset **reconciled from official vehicle registers of
|
|
632
|
+
The bundled dataset is [**VehiclesDB**](https://github.com/vehiclesdb/vehiclesdb) — an open dataset **reconciled from official vehicle registers of 15 countries on 5 continents** (the Dutch RDW, UK DfT, Spanish DGT, Finnish Traficom, German KBA, US EPA, New Zealand's Motor Vehicle Register, Thailand's DLT, Ukraine's MVS, and more — the exact list for the bundled 2026.10.0 snapshot is `countries` in the data repo's `manifest.json`). A model ships only when **two independent official sources corroborate it** — or one shows a registration count no typo could produce — so you get real nameplates without the registry noise.
|
|
560
633
|
|
|
561
634
|
Every record is shaped like this:
|
|
562
635
|
|
|
@@ -574,7 +647,7 @@ Every record is shaped like this:
|
|
|
574
647
|
- **Nameplate-level, on purpose.** "Golf" covers the GTI, the Variant, the R — that's what a dropdown wants. Two-wheelers keep displacement granularity ("Wave110i", "CBR600RR") because that's how riders and registers both speak. Per-trim and per-generation detail is a VehiclesDB API concern.
|
|
575
648
|
- **Popularity is measured**, from real registration/fleet counts — never scraped rankings. **Availability is evidence** (a country appears because an official source shows the model there), not marketing history.
|
|
576
649
|
- **Source data:** openly-licensed government sources only, every license pinned and verified at build time — [full source list](https://github.com/vehiclesdb/vehiclesdb/blob/main/SOURCES.md) and [attribution notices](https://github.com/vehiclesdb/vehiclesdb/blob/main/ATTRIBUTION.md).
|
|
577
|
-
- **This dataset** (`data/vehicles.json`): **CC-BY 4.0**.
|
|
650
|
+
- **This dataset** (`data/vehicles.json`): **CC-BY 4.0**. Visible credit with a link is a condition of the license (§3(a)). The form we request: *"Vehicle data by [VehiclesDB](https://vehiclesdb.com)"* — on websites, a followable link (deep links to the make/model page you used are even better); in apps, name us in an about/credits screen. Ready-made snippets (HTML, Markdown, BibTeX, badge): [vehiclesdb.com/attribution](https://vehiclesdb.com/attribution). Prefer not to credit, or need the enriched paid layer? [Commercial license](https://vehiclesdb.com) — it waives only OUR credit, never the upstream register notices.
|
|
578
651
|
- **The gem code:** MIT.
|
|
579
652
|
|
|
580
653
|
The data is versioned (`Vehicles.data_version`). Each gem release bundles a known snapshot — the offline, deterministic floor. To get newer data, either upgrade the gem or enable [refresh](#staying-current-optional) (which pulls published releases without a gem bump). Either way it's explicit and versioned — no silent mutations.
|
|
@@ -590,7 +663,7 @@ No magic, just good defaults:
|
|
|
590
663
|
|
|
591
664
|
## Roadmap
|
|
592
665
|
|
|
593
|
-
Bundled today:
|
|
666
|
+
Bundled today: ~15k models across 6 kinds and 15 countries, with popularity +
|
|
594
667
|
availability, plus the MCP server. On the way:
|
|
595
668
|
|
|
596
669
|
- 🌍 More **countries** (Switzerland next; the dataset grows monthly)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Bundled vehicles dataset
|
|
2
|
+
|
|
3
|
+
`data/vehicles.json` is the public data repo's `dist/vehicles.json`
|
|
4
|
+
(the gem projection) at a release tag:
|
|
5
|
+
|
|
6
|
+
- **Source**: https://github.com/vehiclesdb/vehiclesdb `dist/vehicles.json`
|
|
7
|
+
- **Tag**: `v2026.10.0` (commit `794c27b1881fe561182ff2ee797683cc96047f20`), dataset version `2026.10.0`
|
|
8
|
+
- **country_ranks**: 0 model(s) carried it natively; 14880 merged from
|
|
9
|
+
the same tag's `catalog/<kind>/models.json` `popularity.by_country[cc].rank`
|
|
10
|
+
- **License**: CC-BY 4.0 (see the data repo's LICENSE + ATTRIBUTION.md)
|
|
11
|
+
|
|
12
|
+
Refresh: `rake "data:refresh[<tag>]"` (this file is generated by it).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Bundled plates dataset (PRD-PLATES, full corpus)
|
|
2
|
+
|
|
3
|
+
Copy of `plates/` from the public data repo (EXCLUDING `_art/`),
|
|
4
|
+
refreshed on gem releases:
|
|
5
|
+
|
|
6
|
+
- **Source**: https://github.com/vehiclesdb/vehiclesdb `plates/`
|
|
7
|
+
- **Commit**: `c7dd65c6f4144d39c62706b7be10231a12bc27e1`
|
|
8
|
+
- **License**: CC-BY 4.0 (see the data repo's LICENSE + ATTRIBUTION.md)
|
|
9
|
+
|
|
10
|
+
Refresh: `rake plates:refresh` (this file is generated by it).
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# plates/_decode/at-districts.yml — Austrian Behördenbezeichnungen im Kennzeichen.
|
|
2
|
+
#
|
|
3
|
+
# THE SOURCE FINDING, and it is the opposite of Germany's: the Austrian
|
|
4
|
+
# district code list IS IN THE REGULATION. § 48 Abs. 5 KFG 1967 delegates it
|
|
5
|
+
# ("Durch Verordnung sind die Bezeichnung der Behörde und die sachlichen
|
|
6
|
+
# Bereiche, das System der Zeichen, unter denen die Fahrzeuge bei der Behörde
|
|
7
|
+
# vorzumerken sind (Abs. 4) ... festzusetzen"), and § 26 Abs. 1 KDV 1967
|
|
8
|
+
# discharges the delegation by pointing at ANLAGE 5d: "Die Bezeichnung der den
|
|
9
|
+
# Zulassungsschein ausstellenden Behörde im Kennzeichen bestimmt sich nach
|
|
10
|
+
# Anlage 5d." Anlage 5d is a CLOSED, PRIMARY, 98-row table printed inside the
|
|
11
|
+
# regulation — so unlike the German ~400-code Bundesanzeiger list (PRD-PLATES
|
|
12
|
+
# §2.4), it is authored here in full, verbatim, in the annex's own order
|
|
13
|
+
# (by Bundesland, then as the annex prints them).
|
|
14
|
+
#
|
|
15
|
+
# The non-geographic prefixes are NOT in Anlage 5d — they are in § 26 KDV
|
|
16
|
+
# itself (Abs. 2 the federal letter A, Abs. 3 the nine Land letters, Abs. 4 the
|
|
17
|
+
# seven sachliche Bereiche, Abs. 5 the diplomatic D and consular K suffixes).
|
|
18
|
+
# They are recorded below in their own blocks, because a parse API needs all
|
|
19
|
+
# four sets to resolve a prefix and because several of them COLLIDE with
|
|
20
|
+
# Anlage 5d district codes (see `collisions`).
|
|
21
|
+
#
|
|
22
|
+
# Referenced by: plates/at.yml (at-standard, at-motorcycle, at-moped,
|
|
23
|
+
# at-historic, at-electric, at-dealer-probefahrt, at-temporary-*,
|
|
24
|
+
# at-trailer-foreign, at-wunschkennzeichen) via region_encoding.
|
|
25
|
+
#
|
|
26
|
+
# PERIOD DISCIPLINE (PRD-PLATES §2.4): these are the codes in the CONSOLIDATED
|
|
27
|
+
# Anlage 5d in force on the access date (Fassung im RIS seit 26.09.2024,
|
|
28
|
+
# NOR40265416). Austrian district reform has repeatedly merged Bezirke
|
|
29
|
+
# (Bruck-Mürzzuschlag, Hartberg-Fürstenfeld, Murtal and Südoststeiermark are
|
|
30
|
+
# themselves 2012/2013 merger codes), and codes withdrawn from the annex stay
|
|
31
|
+
# on the road because a plate is only exchanged on re-registration. WITHDRAWN
|
|
32
|
+
# CODES ARE THEREFORE NOT IN THIS TABLE AND ARE A RECORDED GAP for L4 — a
|
|
33
|
+
# decode of a physical plate whose prefix is absent here must be treated as
|
|
34
|
+
# unresolved, never as invalid.
|
|
35
|
+
jurisdiction: at
|
|
36
|
+
topic: districts
|
|
37
|
+
authority:
|
|
38
|
+
name: "Kraftfahrgesetz-Durchführungsverordnung 1967 (KDV 1967), Anlage 5d (zu § 26 Abs. 1)"
|
|
39
|
+
url: "https://www.ris.bka.gv.at/NormDokument.wxe?Abfrage=Bundesnormen&Gesetzesnummer=10011385&Anlage=5d"
|
|
40
|
+
sources:
|
|
41
|
+
- "https://www.ris.bka.gv.at/NormDokument.wxe?Abfrage=Bundesnormen&Gesetzesnummer=10011385&Anlage=5d # Anlage 5d 'Bezeichnung der Behörde im Kennzeichen', all 98 rows in nine Bundesland blocks; Dokumentnummer NOR40265416, im RIS seit 26.09.2024. Accessed 2026-08-02"
|
|
42
|
+
- "https://www.ris.bka.gv.at/NormDokument.wxe?Abfrage=Bundesnormen&Gesetzesnummer=10011385&Paragraf=26 # § 26 KDV 1967: Abs. 1 (delegation to Anlage 5d), Abs. 2 (letter A), Abs. 3 (nine Land letters), Abs. 4 (sachliche Bereiche BP/FV/PT/BD/BH/JW/FW), Abs. 5 (diplomatic D / consular K, and the Graz exception), Abs. 6 (the Vormerkzeichen grammar), Abs. 7 (hyphen for the Wappen in writing). Accessed 2026-08-02"
|
|
43
|
+
- "https://www.ris.bka.gv.at/NormDokument.wxe?Abfrage=Bundesnormen&Gesetzesnummer=10011384&Paragraf=48 # § 48 Abs. 4 KFG 1967: the prefix is 'einem oder zwei Buchstaben als Bezeichnung der Behörde, in deren örtlichem Wirkungsbereich das Fahrzeug zugelassen ist'; Abs. 5 the delegation. Accessed 2026-08-02"
|
|
44
|
+
period: { start: 1989 }
|
|
45
|
+
period_evidence: enacted-commencement
|
|
46
|
+
period_note: >-
|
|
47
|
+
1989 is the commencement of the CODE SYSTEM these rows belong to, not of the
|
|
48
|
+
individual rows: BGBl. Nr. 375/1988 (11. KFG-Novelle) Artikel V Abs. 4 lit. b
|
|
49
|
+
put the rewritten § 48 Abs. 4 KFG (one or two letters for the AUTHORITY, in
|
|
50
|
+
place of the pre-1989 single BUNDESLAND letter) into force on 1 January 1989.
|
|
51
|
+
Individual rows are younger or older than that; per-row periods are a
|
|
52
|
+
recorded gap.
|
|
53
|
+
|
|
54
|
+
# A parse API must carry these, because Austrian prefixes are NOT
|
|
55
|
+
# self-delimiting the way German ones are.
|
|
56
|
+
maximal_munch: true
|
|
57
|
+
maximal_munch_note: >-
|
|
58
|
+
Take the LONGEST matching code. The split point is unambiguous for ORDINARY
|
|
59
|
+
registrations because § 26 Abs. 6 Z 2 lit. e KDV requires the Vormerkzeichen
|
|
60
|
+
to BEGIN WITH A DIGIT — so the prefix is exactly the leading letter run. It is
|
|
61
|
+
genuinely AMBIGUOUS for WUNSCHKENNZEICHEN, whose Vormerkzeichen must begin
|
|
62
|
+
with a LETTER (Z 3 lit. e): "BZAB1" is both B (Bregenz) + "ZAB1" and BZ
|
|
63
|
+
(Bludenz) + "AB1", and nothing in the string resolves it. Record the
|
|
64
|
+
ambiguity; do not average it away.
|
|
65
|
+
|
|
66
|
+
collisions:
|
|
67
|
+
- "B — BH Bregenz (Anlage 5d, Vorarlberg) AND the Burgenland Land letter (§ 26 Abs. 3)"
|
|
68
|
+
- "K — LPD Kärnten/Klagenfurt (Anlage 5d) AND the Kärnten Land letter (§ 26 Abs. 3) AND the consular suffix letter (§ 26 Abs. 5 lit. b)"
|
|
69
|
+
- "S — LPD Salzburg/Salzburg (Anlage 5d) AND the Salzburg Land letter (§ 26 Abs. 3)"
|
|
70
|
+
- "W — Landespolizeidirektion Wien (Anlage 5d) AND the Wien Land letter (§ 26 Abs. 3)"
|
|
71
|
+
- "G — LPD Steiermark/Graz (Anlage 5d) AND, per the § 26 Abs. 5 Graz exception, the base of the diplomatic prefix GD and consular prefix GK"
|
|
72
|
+
- "BD — Bundesbus sachlicher Bereich (§ 26 Abs. 4 lit. f) AND the Burgenland diplomatic prefix B+D (§ 26 Abs. 5 lit. a)"
|
|
73
|
+
- "ND — BH Neusiedl am See (Anlage 5d) AND the Niederösterreich diplomatic prefix N+D"
|
|
74
|
+
- "SD — BH Schärding (Anlage 5d) AND the Salzburg diplomatic prefix S+D"
|
|
75
|
+
- "GD — BH Gmünd (Anlage 5d) AND the Graz diplomatic prefix G+D"
|
|
76
|
+
- "NK — BH Neunkirchen (Anlage 5d) AND the Niederösterreich consular prefix N+K"
|
|
77
|
+
- "VK — BH Völkermarkt (Anlage 5d) AND the Vorarlberg consular prefix V+K"
|
|
78
|
+
- "BH — Heeresfahrzeuge (§ 26 Abs. 4 lit. g) AND, in the annex's own prose, the abbreviation for Bezirkshauptmannschaft (which is NOT a code) AND an expressly forbidden Wunschkennzeichen combination (§ 26 Abs. 8 Z 2, 'Blood and Honour')"
|
|
79
|
+
collisions_resolution: >-
|
|
80
|
+
The diplomatic/consular collisions resolve on the SERIAL, not the prefix:
|
|
81
|
+
§ 26 Abs. 6 Z 2 lit. d and e require an ordinary Vormerkzeichen to contain one
|
|
82
|
+
to three letters and to END WITH A LETTER, whereas the Abs. 5
|
|
83
|
+
(diplomatic/consular) Vormerkzeichen need only begin with a digit and is in
|
|
84
|
+
practice digits-only — so "ND-12345" is Niederösterreich diplomatic and
|
|
85
|
+
"ND-123XY" is Neusiedl am See. Where an Abs. 5 Vormerkzeichen does carry a
|
|
86
|
+
trailing letter block the collision is UNRESOLVABLE from the string alone.
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
# ANLAGE 5d — the 98 authority codes, verbatim, in the annex's own order.
|
|
90
|
+
# `authority` is the annex's own wording, abbreviations expanded once:
|
|
91
|
+
# BH. = Bezirkshauptmannschaft, Mag. = Magistrat, Exp. = Expositur,
|
|
92
|
+
# LPD = Landespolizeidirektion.
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
codes:
|
|
95
|
+
# I. BURGENLAND
|
|
96
|
+
- { code: E, land: Burgenland, authority: "Landespolizeidirektion Burgenland für das Gebiet der Gemeinden Eisenstadt und Rust", landeshauptstadt: true }
|
|
97
|
+
- { code: EU, land: Burgenland, authority: "BH. Eisenstadt" }
|
|
98
|
+
- { code: ND, land: Burgenland, authority: "BH. Neusiedl am See" }
|
|
99
|
+
- { code: MA, land: Burgenland, authority: "BH. Mattersburg" }
|
|
100
|
+
- { code: OP, land: Burgenland, authority: "BH. Oberpullendorf" }
|
|
101
|
+
- { code: OW, land: Burgenland, authority: "BH. Oberwart" }
|
|
102
|
+
- { code: GS, land: Burgenland, authority: "BH. Güssing" }
|
|
103
|
+
- { code: JE, land: Burgenland, authority: "BH. Jennersdorf" }
|
|
104
|
+
# II. KÄRNTEN
|
|
105
|
+
- { code: K, land: Kärnten, authority: "Landespolizeidirektion Kärnten für das Gebiet der Gemeinde Klagenfurt am Wörthersee", landeshauptstadt: true }
|
|
106
|
+
- { code: VI, land: Kärnten, authority: "Landespolizeidirektion Kärnten für das Gebiet der Gemeinde Villach" }
|
|
107
|
+
- { code: VL, land: Kärnten, authority: "BH. Villach" }
|
|
108
|
+
- { code: WO, land: Kärnten, authority: "BH. Wolfsberg" }
|
|
109
|
+
- { code: SP, land: Kärnten, authority: "BH. Spittal an der Drau" }
|
|
110
|
+
- { code: FE, land: Kärnten, authority: "BH. Feldkirchen" }
|
|
111
|
+
- { code: HE, land: Kärnten, authority: "BH. Hermagor" }
|
|
112
|
+
- { code: SV, land: Kärnten, authority: "BH. St. Veit an der Glan" }
|
|
113
|
+
- { code: KL, land: Kärnten, authority: "BH. Klagenfurt" }
|
|
114
|
+
- { code: VK, land: Kärnten, authority: "BH. Völkermarkt" }
|
|
115
|
+
# III. NIEDERÖSTERREICH
|
|
116
|
+
- { code: P, land: Niederösterreich, authority: "Landespolizeidirektion Niederösterreich für das Gebiet der Gemeinde St. Pölten", landeshauptstadt: true }
|
|
117
|
+
- { code: AM, land: Niederösterreich, authority: "BH. Amstetten" }
|
|
118
|
+
- { code: BN, land: Niederösterreich, authority: "BH. Baden" }
|
|
119
|
+
- { code: BL, land: Niederösterreich, authority: "BH. Bruck an der Leitha" }
|
|
120
|
+
- { code: GF, land: Niederösterreich, authority: "BH. Gänserndorf" }
|
|
121
|
+
- { code: GD, land: Niederösterreich, authority: "BH. Gmünd" }
|
|
122
|
+
- { code: HL, land: Niederösterreich, authority: "BH. Hollabrunn" }
|
|
123
|
+
- { code: HO, land: Niederösterreich, authority: "BH. Horn" }
|
|
124
|
+
- { code: KO, land: Niederösterreich, authority: "BH. Korneuburg" }
|
|
125
|
+
- { code: KR, land: Niederösterreich, authority: "BH. Krems an der Donau" }
|
|
126
|
+
- { code: LF, land: Niederösterreich, authority: "BH. Lilienfeld" }
|
|
127
|
+
- { code: ME, land: Niederösterreich, authority: "BH. Melk" }
|
|
128
|
+
- { code: MI, land: Niederösterreich, authority: "BH. Mistelbach" }
|
|
129
|
+
- { code: MD, land: Niederösterreich, authority: "BH. Mödling" }
|
|
130
|
+
- { code: NK, land: Niederösterreich, authority: "BH. Neunkirchen" }
|
|
131
|
+
- { code: PL, land: Niederösterreich, authority: "BH. St. Pölten" }
|
|
132
|
+
- { code: SB, land: Niederösterreich, authority: "BH. Scheibbs" }
|
|
133
|
+
- { code: TU, land: Niederösterreich, authority: "BH. Tulln" }
|
|
134
|
+
- { code: WT, land: Niederösterreich, authority: "BH. Waidhofen an der Thaya" }
|
|
135
|
+
- { code: WB, land: Niederösterreich, authority: "BH. Wiener Neustadt" }
|
|
136
|
+
- { code: ZT, land: Niederösterreich, authority: "BH. Zwettl" }
|
|
137
|
+
- { code: WN, land: Niederösterreich, authority: "Landespolizeidirektion Niederösterreich für das Gebiet der Gemeinde Wr. Neustadt" }
|
|
138
|
+
- { code: KS, land: Niederösterreich, authority: "Mag. Krems an der Donau" }
|
|
139
|
+
- { code: WY, land: Niederösterreich, authority: "Mag. Waidhofen an der Ybbs" }
|
|
140
|
+
- { code: SW, land: Niederösterreich, authority: "Landespolizeidirektion Niederösterreich für das Gebiet der Gemeinde Schwechat und die im Gebiet der Gemeinden Fischamend, Klein-Neusiedl und Schwadorf gelegenen Teile des Flughafens Wien-Schwechat" }
|
|
141
|
+
- { code: KG, land: Niederösterreich, authority: "BH Tulln für das Gebiet der Gemeinde Klosterneuburg (Außenstelle Klosterneuburg)" }
|
|
142
|
+
# IV. OBERÖSTERREICH
|
|
143
|
+
- { code: L, land: Oberösterreich, authority: "Landespolizeidirektion Oberösterreich für das Gebiet der Gemeinde Linz", landeshauptstadt: true }
|
|
144
|
+
- { code: BR, land: Oberösterreich, authority: "BH. Braunau am Inn" }
|
|
145
|
+
- { code: EF, land: Oberösterreich, authority: "BH. Eferding" }
|
|
146
|
+
- { code: FR, land: Oberösterreich, authority: "BH. Freistadt" }
|
|
147
|
+
- { code: GM, land: Oberösterreich, authority: "BH. Gmunden" }
|
|
148
|
+
- { code: GR, land: Oberösterreich, authority: "BH. Grieskirchen" }
|
|
149
|
+
- { code: KI, land: Oberösterreich, authority: "BH. Kirchdorf an der Krems" }
|
|
150
|
+
- { code: LL, land: Oberösterreich, authority: "BH. Linz-Land" }
|
|
151
|
+
- { code: PE, land: Oberösterreich, authority: "BH. Perg" }
|
|
152
|
+
- { code: RI, land: Oberösterreich, authority: "BH. Ried im Innkreis" }
|
|
153
|
+
- { code: RO, land: Oberösterreich, authority: "BH. Rohrbach im Mühlkreis" }
|
|
154
|
+
- { code: SD, land: Oberösterreich, authority: "BH. Schärding" }
|
|
155
|
+
- { code: SE, land: Oberösterreich, authority: "BH. Steyr-Land" }
|
|
156
|
+
- { code: UU, land: Oberösterreich, authority: "BH. Urfahr-Umgebung" }
|
|
157
|
+
- { code: VB, land: Oberösterreich, authority: "BH. Vöcklabruck" }
|
|
158
|
+
- { code: WL, land: Oberösterreich, authority: "BH. Wels" }
|
|
159
|
+
- { code: SR, land: Oberösterreich, authority: "Landespolizeidirektion Oberösterreich für das Gebiet der Gemeinde Steyr" }
|
|
160
|
+
- { code: WE, land: Oberösterreich, authority: "Landespolizeidirektion Oberösterreich für das Gebiet der Gemeinde Wels" }
|
|
161
|
+
# V. SALZBURG
|
|
162
|
+
- { code: S, land: Salzburg, authority: "Landespolizeidirektion Salzburg für das Gebiet der Gemeinde Salzburg", landeshauptstadt: true }
|
|
163
|
+
- { code: SL, land: Salzburg, authority: "BH. Salzburg-Umgebung" }
|
|
164
|
+
- { code: HA, land: Salzburg, authority: "BH. Hallein" }
|
|
165
|
+
- { code: JO, land: Salzburg, authority: "BH. St. Johann" }
|
|
166
|
+
- { code: ZE, land: Salzburg, authority: "BH. Zell am See" }
|
|
167
|
+
- { code: TA, land: Salzburg, authority: "BH. Tamsweg", note: "the consolidated annex prints 'BH- Tamsweg' — a typo in the instrument, reproduced here as a finding, not corrected in the source" }
|
|
168
|
+
# VI. STEIERMARK
|
|
169
|
+
- { code: G, land: Steiermark, authority: "Landespolizeidirektion Steiermark für das Gebiet der Gemeinde Graz", landeshauptstadt: true }
|
|
170
|
+
- { code: GU, land: Steiermark, authority: "BH. Graz-Umgebung" }
|
|
171
|
+
- { code: BM, land: Steiermark, authority: "BH. Bruck-Mürzzuschlag" }
|
|
172
|
+
- { code: DL, land: Steiermark, authority: "BH. Deutschlandsberg" }
|
|
173
|
+
- { code: HF, land: Steiermark, authority: "BH. Hartberg-Fürstenfeld" }
|
|
174
|
+
- { code: MT, land: Steiermark, authority: "BH. Murtal" }
|
|
175
|
+
- { code: LB, land: Steiermark, authority: "BH. Leibnitz" }
|
|
176
|
+
- { code: LN, land: Steiermark, authority: "BH. Leoben" }
|
|
177
|
+
- { code: LI, land: Steiermark, authority: "BH. Liezen" }
|
|
178
|
+
- { code: MU, land: Steiermark, authority: "BH. Murau" }
|
|
179
|
+
- { code: SO, land: Steiermark, authority: "BH. Südoststeiermark" }
|
|
180
|
+
- { code: VO, land: Steiermark, authority: "BH. Voitsberg" }
|
|
181
|
+
- { code: WZ, land: Steiermark, authority: "BH. Weiz" }
|
|
182
|
+
- { code: GB, land: Steiermark, authority: "Exp. Gröbming" }
|
|
183
|
+
- { code: LE, land: Steiermark, authority: "Landespolizeidirektion Steiermark für das Gebiet der Gemeinde Leoben" }
|
|
184
|
+
- { code: BA, land: Steiermark, authority: "BH Liezen für das Gebiet der Gemeinden Bad Aussee, Altaussee, Grundlsee und Bad Mitterndorf (Außenstelle Bad Aussee)" }
|
|
185
|
+
# VII. TIROL
|
|
186
|
+
- { code: I, land: Tirol, authority: "Landespolizeidirektion Tirol für das Gebiet der Gemeinde Innsbruck", landeshauptstadt: true }
|
|
187
|
+
- { code: IL, land: Tirol, authority: "BH. Innsbruck" }
|
|
188
|
+
- { code: IM, land: Tirol, authority: "BH. Imst" }
|
|
189
|
+
- { code: KB, land: Tirol, authority: "BH. Kitzbühel" }
|
|
190
|
+
- { code: KU, land: Tirol, authority: "BH. Kufstein" }
|
|
191
|
+
- { code: LA, land: Tirol, authority: "BH. Landeck" }
|
|
192
|
+
- { code: RE, land: Tirol, authority: "BH. Reutte" }
|
|
193
|
+
- { code: SZ, land: Tirol, authority: "BH. Schwaz" }
|
|
194
|
+
- { code: LZ, land: Tirol, authority: "BH. Lienz" }
|
|
195
|
+
# VIII. VORARLBERG
|
|
196
|
+
- { code: B, land: Vorarlberg, authority: "BH. Bregenz" }
|
|
197
|
+
- { code: FK, land: Vorarlberg, authority: "BH. Feldkirch" }
|
|
198
|
+
- { code: BZ, land: Vorarlberg, authority: "BH. Bludenz" }
|
|
199
|
+
- { code: DO, land: Vorarlberg, authority: "BH. Dornbirn" }
|
|
200
|
+
# IX. WIEN
|
|
201
|
+
- { code: W, land: Wien, authority: "Landespolizeidirektion Wien", landeshauptstadt: true }
|
|
202
|
+
|
|
203
|
+
landeshauptstadt_note: >-
|
|
204
|
+
`landeshauptstadt: true` marks the codes this pass reads as
|
|
205
|
+
"in den Landeshauptstädten ... zugewiesen" for the purposes of § 26 Abs. 6
|
|
206
|
+
Z 2 lit. a KDV (one Vormerkzeichen character MORE than elsewhere). It is
|
|
207
|
+
flagged as an INFERENCE, not a primary: the annex names those authorities
|
|
208
|
+
"Landespolizeidirektion X für das Gebiet der Gemeinde <Landeshauptstadt>",
|
|
209
|
+
which is why the nine are picked out, but the KDV never says that the
|
|
210
|
+
extra character attaches to those CODES. Vorarlberg is the case that proves
|
|
211
|
+
the inference is not safe: its Landeshauptstadt is Bregenz, whose plates
|
|
212
|
+
are issued by "BH. Bregenz" (code B) over a whole district, and the annex
|
|
213
|
+
gives Vorarlberg no Landespolizeidirektion row at all — so B is left
|
|
214
|
+
unmarked here and the question is a recorded gap.
|
|
215
|
+
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
# THE NON-GEOGRAPHIC PREFIXES — § 26 KDV itself, not Anlage 5d.
|
|
218
|
+
# ---------------------------------------------------------------------------
|
|
219
|
+
federal_letter:
|
|
220
|
+
source: "https://www.ris.bka.gv.at/NormDokument.wxe?Abfrage=Bundesnormen&Gesetzesnummer=10011385&Paragraf=26 # § 26 Abs. 2"
|
|
221
|
+
A: >-
|
|
222
|
+
Fahrzeuge, die zur Verwendung für den Bundespräsidenten, die Präsidenten des
|
|
223
|
+
Nationalrates, die Präsidenten des Bundesrates, die Mitglieder der
|
|
224
|
+
Bundesregierung, die Staatssekretäre, die Mitglieder der Volksanwaltschaft,
|
|
225
|
+
den Präsidenten oder Vizepräsidenten des Rechnungshofes, des
|
|
226
|
+
Verfassungsgerichtshofes und Verwaltungsgerichtshofes oder des Obersten
|
|
227
|
+
Gerichtshofes bestimmt sind
|
|
228
|
+
|
|
229
|
+
land_letters:
|
|
230
|
+
source: "https://www.ris.bka.gv.at/NormDokument.wxe?Abfrage=Bundesnormen&Gesetzesnummer=10011385&Paragraf=26 # § 26 Abs. 3, verbatim list"
|
|
231
|
+
scope: >-
|
|
232
|
+
Fahrzeuge, die zur Verwendung für die Präsidenten der Landtage sowie für die
|
|
233
|
+
Mitglieder der Landesregierungen sowie für die Mitglieder der
|
|
234
|
+
Landesvolksanwaltschaften bestimmt sind
|
|
235
|
+
codes:
|
|
236
|
+
B: Burgenland
|
|
237
|
+
K: Kärnten
|
|
238
|
+
N: Niederösterreich
|
|
239
|
+
O: Oberösterreich
|
|
240
|
+
S: Salzburg
|
|
241
|
+
ST: Steiermark
|
|
242
|
+
T: Tirol
|
|
243
|
+
V: Vorarlberg
|
|
244
|
+
W: Wien
|
|
245
|
+
note: >-
|
|
246
|
+
ST is the only THREE-position prefix base in the system and the reason the
|
|
247
|
+
§ 26 Abs. 5 Graz exception exists (see diplomatic_consular below). N and O
|
|
248
|
+
appear ONLY here — no Anlage 5d district carries them.
|
|
249
|
+
|
|
250
|
+
sachliche_bereiche:
|
|
251
|
+
source: "https://www.ris.bka.gv.at/NormDokument.wxe?Abfrage=Bundesnormen&Gesetzesnummer=10011385&Paragraf=26 # § 26 Abs. 4 lit. a-i (lit. b repealed by BGBl. II Nr. 275/2007, lit. d by BGBl. II Nr. 376/2002)"
|
|
252
|
+
codes:
|
|
253
|
+
BP: "Fahrzeuge, die zur Verwendung im Bereich der Bundespolizei bestimmt sind"
|
|
254
|
+
FV: "Fahrzeuge, die zur Verwendung im Bereich der Finanzverwaltung bestimmt sind"
|
|
255
|
+
PT: "Fahrzeuge, die zur Verwendung im Bereich der Post bestimmt sind"
|
|
256
|
+
BD: "Omnibusse, die zur Verwendung im Kraftfahrlinienverkehr der Österreichischen Bundesbahnen und der Post- und Telegraphenverwaltung (Bundesbusdienst) bestimmt sind"
|
|
257
|
+
BH: "Heeresfahrzeuge"
|
|
258
|
+
JW: "Fahrzeuge, die zur Verwendung im Bereich der Justizwache bestimmt sind"
|
|
259
|
+
FW: "Fahrzeuge, die zur Verwendung für die Feuerwehr bestimmt sind"
|
|
260
|
+
note: >-
|
|
261
|
+
FW is the ONE prefix whose serial encodes geography: § 26 Abs. 6 Z 1 lit. b
|
|
262
|
+
requires a fire-brigade Vormerkzeichen to "mit zwei oder drei Ziffern
|
|
263
|
+
beginnen und es folgen ein oder zwei Buchstaben, die der Bezeichnung der
|
|
264
|
+
Behörde im Sinne der Anlage 5d entsprechen müssen, in deren Sprengel das
|
|
265
|
+
Fahrzeug zugelassen ist" — so the district code moves to the END of the
|
|
266
|
+
string and the `codes` table above decodes the SUFFIX, not the prefix.
|
|
267
|
+
|
|
268
|
+
diplomatic_consular:
|
|
269
|
+
source: "https://www.ris.bka.gv.at/NormDokument.wxe?Abfrage=Bundesnormen&Gesetzesnummer=10011385&Paragraf=26 # § 26 Abs. 5 lit. a (D) and lit. b (K), plus the Graz exception in the opening sentence"
|
|
270
|
+
mechanism: >-
|
|
271
|
+
For the vehicles listed in § 54 Abs. 3 and Abs. 3a lit. a und b KFG 1967
|
|
272
|
+
(the CD and CC entitlement classes), the sachlicher Bereich is "die
|
|
273
|
+
Buchstaben gemäß Abs. 3" — i.e. the LAND letter — "bei Fahrzeugen mit dem
|
|
274
|
+
Standort in Graz ist an Stelle der Buchstaben gemäß Abs. 3 die Bezeichnung
|
|
275
|
+
der Behörde gemäß Anlage 5d zu verwenden" (so G, not ST). That base letter
|
|
276
|
+
is then followed by D for the diplomatic corps and internationally
|
|
277
|
+
equivalent officials, or K for the consular corps.
|
|
278
|
+
diplomatic_prefixes: [BD, KD, ND, OD, SD, STD, TD, VD, WD, GD]
|
|
279
|
+
consular_prefixes: [BK, KK, NK, OK, SK, STK, TK, VK, WK, GK]
|
|
280
|
+
derivation_note: >-
|
|
281
|
+
The twenty prefixes above are DERIVED by applying the § 26 Abs. 5 rule to
|
|
282
|
+
the § 26 Abs. 3 list plus the Graz exception; the KDV nowhere prints them as
|
|
283
|
+
a table. They are recorded here because a parse API needs them, and marked
|
|
284
|
+
as derived, not quoted. WD is overwhelmingly the populated one (the
|
|
285
|
+
diplomatic corps and the Vienna-based international organisations), and
|
|
286
|
+
several of the others may never have been issued — an unverified claim,
|
|
287
|
+
left unverified.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# plates/_decode/ch-cantons.yml — the Swiss canton letters. DRAFT for verification.
|
|
2
|
+
#
|
|
3
|
+
# THE SOURCE FINDING, and it is the good kind: this table is NOT a Wikipedia
|
|
4
|
+
# scrape and not an enthusiast list. It is enacted law, printed as a two-column
|
|
5
|
+
# table inside the ordinance itself — Verkehrszulassungsverordnung (VZV),
|
|
6
|
+
# SR 741.51, Art. 84 Abs. 1: "Jeder Kanton wird mit zwei grossen Buchstaben wie
|
|
7
|
+
# folgt bezeichnet:" followed by exactly the 26 rows below, in exactly this
|
|
8
|
+
# order. Every row here is that table, verbatim, in the ordinance's own order
|
|
9
|
+
# (the constitutional order of precedence, NOT alphabetical), with the French
|
|
10
|
+
# and Italian canton names taken from the FR and IT language versions of the
|
|
11
|
+
# same consolidated article.
|
|
12
|
+
#
|
|
13
|
+
# This is the German-Anlage-2 case and not the German-district case, and the
|
|
14
|
+
# contrast is worth stating because it drives the schema choice: § 9 Abs. 3 FZV
|
|
15
|
+
# delegates the ~400 German district codes to Bundesanzeiger publications, so
|
|
16
|
+
# plates/de.yml can only cite the MECHANISM; VZV Art. 84 Abs. 1 enumerates all
|
|
17
|
+
# 26 Swiss codes inside the regulation, so this table is closed, primary and
|
|
18
|
+
# complete on its face. There is no delegation, no open tail and no "on
|
|
19
|
+
# application by the canton" clause.
|
|
20
|
+
#
|
|
21
|
+
# Referenced by: plates/ch.yml (ch-standard, ch-standard-trailer, ch-motorcycle,
|
|
22
|
+
# ch-lightmotor-yellow, ch-motorfahrrad, ch-workvehicle-blue,
|
|
23
|
+
# ch-exceptional-brown, ch-agricultural-green, ch-diplomatic-cd,
|
|
24
|
+
# ch-consular-cc, ch-diplomatic-at, ch-provisional, ch-provisional-z,
|
|
25
|
+
# ch-dealer-u, ch-dealer-pre1977, ch-rental-1977) via region_encoding.
|
|
26
|
+
# NOT referenced by ch-military-m: federal military plates carry the single
|
|
27
|
+
# letter M and no canton code at all (VZV Art. 84 Abs. 3).
|
|
28
|
+
jurisdiction: ch
|
|
29
|
+
topic: cantons
|
|
30
|
+
authority:
|
|
31
|
+
name: Verkehrszulassungsverordnung (VZV), SR 741.51, Art. 84 Abs. 1
|
|
32
|
+
url: "https://www.fedlex.admin.ch/eli/cc/1976/2423_2423_2423/de"
|
|
33
|
+
sources:
|
|
34
|
+
- "https://www.fedlex.admin.ch/eli/cc/1976/2423_2423_2423/de # VZV Art. 84 Abs. 1 — the 26-row table, German names and codes, in the ordinance's own order; and the Jura footnote 'Kanton eingefügt durch Ziff. I der V vom 15. Nov. 1978, in Kraft seit 1. Jan. 1979 (AS 1978 1805)'"
|
|
35
|
+
- "https://www.fedlex.admin.ch/eli/cc/1976/2423_2423_2423/fr # OAC art. 84 al. 1 — the same table with the FRENCH canton names"
|
|
36
|
+
- "https://www.fedlex.admin.ch/eli/cc/1976/2423_2423_2423/it # OAC art. 84 cpv. 1 — the same table with the ITALIAN canton names"
|
|
37
|
+
accessed: "2026-08-02"
|
|
38
|
+
period: { start: 1977 }
|
|
39
|
+
period_evidence: instrument-in-force
|
|
40
|
+
period_note: >-
|
|
41
|
+
1977 is the VZV's entry into force (1 January 1977), not the date the two-letter
|
|
42
|
+
canton abbreviations came into use — they are the ordinary Swiss cantonal
|
|
43
|
+
abbreviations and long predate the ordinance. No primary for their origin was
|
|
44
|
+
secured in this pass, so none is asserted. The ONE row with a real, dated,
|
|
45
|
+
primary start is JU: the ordinance's own footnote records the Jura row as
|
|
46
|
+
"Kanton eingefügt durch Ziff. I der V vom 15. Nov. 1978, in Kraft seit
|
|
47
|
+
1. Jan. 1979 (AS 1978 1805)" — the canton of Jura separated from Bern and the
|
|
48
|
+
code entered the table on 1 January 1979. A plate allocated before that date
|
|
49
|
+
cannot read JU, and the territory JU covers read BE.
|
|
50
|
+
|
|
51
|
+
# Decode facts a parse API must carry.
|
|
52
|
+
maximal_munch: false
|
|
53
|
+
maximal_munch_note: >-
|
|
54
|
+
Not applicable, and that is a genuine simplification over Spain: every Swiss
|
|
55
|
+
code is EXACTLY two capital letters (VZV Art. 84 Abs. 1: "mit zwei grossen
|
|
56
|
+
Buchstaben"), so the letter/number boundary is fixed at position 2 and there is
|
|
57
|
+
no longest-match ambiguity of the C / CA / CC / CE kind that es-provinces.yml
|
|
58
|
+
has to warn about.
|
|
59
|
+
decode_semantics: >-
|
|
60
|
+
The code identifies the canton of the KEEPER's Standort, not the vehicle's
|
|
61
|
+
location, origin or manufacture. VZV Art. 77 Abs. 1 defines Standort as "der
|
|
62
|
+
Ort, wo das Fahrzeug nach Gebrauch in der Regel für die Nacht abgestellt wird",
|
|
63
|
+
with the keeper's domicile deemed the Standort in the three cases of Abs. 2.
|
|
64
|
+
Because the plate binds to the keeper (VZV Art. 87 Abs. 1), the code moves when
|
|
65
|
+
the KEEPER moves: VZV Art. 120 returns the annulled certificate and plates to
|
|
66
|
+
the former canton when a vehicle is admitted in another, and VZV Art. 95 Abs. 2
|
|
67
|
+
makes the same rule explicit for mopeds. A decoded canton is therefore a claim
|
|
68
|
+
about a person, not about a vehicle — the sharpest practical consequence of
|
|
69
|
+
Switzerland's owner-binding, and one a vehicle-keyed consumer will get wrong by
|
|
70
|
+
default.
|
|
71
|
+
collisions:
|
|
72
|
+
- "AG (Aargau), AI (Appenzell Innerrhoden), AR (Appenzell Ausserrhoden) — no collision with the diplomatic sign AT, which occupies its own field ahead of the canton letters (VZV Art. 85 Abs. 4), but a naive left-anchored parser reading 'AT BE 12-34' will try AT as a canton code and must not."
|
|
73
|
+
- "The letter M is NOT a canton code: it is the federal military marker of VZV Art. 84 Abs. 3, and it is a SINGLE letter, which is what distinguishes it structurally."
|
|
74
|
+
- "The letter U (dealer marking, VZV Art. 82 Abs. 2 lit. c) and the letter Z (uncleared provisional vehicle, VVV Anhang 2 A Ziff. 3) are markings, not canton codes; the ordinance does not fix their position, so a parser must not assume they trail the number even though that is the observed form."
|
|
75
|
+
codes:
|
|
76
|
+
- { code: ZH, de: "Zürich", fr: "Zurich", it: "Zurigo", period: { start: 1977 } }
|
|
77
|
+
- { code: BE, de: "Bern", fr: "Berne", it: "Berna", period: { start: 1977 } }
|
|
78
|
+
- { code: LU, de: "Luzern", fr: "Lucerne", it: "Lucerna", period: { start: 1977 } }
|
|
79
|
+
- { code: UR, de: "Uri", fr: "Uri", it: "Uri", period: { start: 1977 } }
|
|
80
|
+
- { code: SZ, de: "Schwyz", fr: "Schwyz", it: "Svitto", period: { start: 1977 } }
|
|
81
|
+
- { code: OW, de: "Obwalden", fr: "Obwald", it: "Obvaldo", period: { start: 1977 } }
|
|
82
|
+
- { code: NW, de: "Nidwalden", fr: "Nidwald", it: "Nidvaldo", period: { start: 1977 } }
|
|
83
|
+
- { code: GL, de: "Glarus", fr: "Glaris", it: "Glarona", period: { start: 1977 } }
|
|
84
|
+
- { code: ZG, de: "Zug", fr: "Zoug", it: "Zugo", period: { start: 1977 } }
|
|
85
|
+
- { code: FR, de: "Freiburg", fr: "Fribourg", it: "Friburgo", period: { start: 1977 } }
|
|
86
|
+
- { code: SO, de: "Solothurn", fr: "Soleure", it: "Soletta", period: { start: 1977 } }
|
|
87
|
+
- { code: BS, de: "Basel-Stadt", fr: "Bâle-Ville", it: "Basilea Città", period: { start: 1977 } }
|
|
88
|
+
- { code: BL, de: "Basel-Landschaft", fr: "Bâle-Campagne", it: "Basilea Campagna", period: { start: 1977 } }
|
|
89
|
+
- { code: SH, de: "Schaffhausen", fr: "Schaffhouse", it: "Sciaffusa", period: { start: 1977 } }
|
|
90
|
+
- { code: AR, de: "Appenzell A. Rh", fr: "Appenzell, Rhodes extérieures", it: "Appenzello esterno", period: { start: 1977 } }
|
|
91
|
+
- { code: AI, de: "Appenzell I. Rh.", fr: "Appenzell, Rhodes intérieures", it: "Appenzello interno", period: { start: 1977 } }
|
|
92
|
+
- { code: SG, de: "St. Gallen", fr: "Saint-Gall", it: "San Gallo", period: { start: 1977 } }
|
|
93
|
+
- { code: GR, de: "Graubünden", fr: "Grisons", it: "Grigioni", period: { start: 1977 } }
|
|
94
|
+
- { code: AG, de: "Aargau", fr: "Argovie", it: "Argovia", period: { start: 1977 } }
|
|
95
|
+
- { code: TG, de: "Thurgau", fr: "Thurgovie", it: "Turgovia", period: { start: 1977 } }
|
|
96
|
+
- { code: TI, de: "Tessin", fr: "Tessin", it: "Ticino", period: { start: 1977 } }
|
|
97
|
+
- { code: VD, de: "Waadt", fr: "Vaud", it: "Vaud", period: { start: 1977 } }
|
|
98
|
+
- { code: VS, de: "Wallis", fr: "Valais", it: "Vallese", period: { start: 1977 } }
|
|
99
|
+
- { code: NE, de: "Neuenburg", fr: "Neuchâtel", it: "Neuchâtel", period: { start: 1977 } }
|
|
100
|
+
- { code: GE, de: "Genf", fr: "Genève", it: "Ginevra", period: { start: 1977 } }
|
|
101
|
+
- { code: JU, de: "Jura", fr: "Jura", it: "Giura", period: { start: 1979 }, period_evidence: enabling-amendment, note: "row inserted by Ziff. I der V vom 15. Nov. 1978, in force 1 Jan. 1979 (AS 1978 1805); before that the territory registered as BE" }
|
|
102
|
+
|
|
103
|
+
# Not a decode dimension, recorded so nobody looks for one: the Swiss plate
|
|
104
|
+
# carries NO date, NO issuing-office code below canton level and NO vehicle-type
|
|
105
|
+
# code inside the serial. Vehicle type is signalled by the plate's BACKGROUND
|
|
106
|
+
# COLOUR and SIZE (VZV Art. 82 Abs. 1, Art. 83 Abs. 3), both of which are
|
|
107
|
+
# properties of the physical object rather than of the string. Number ranges are
|
|
108
|
+
# not published: VZV Art. 84 Abs. 2 says only that each per-canton, per-colour,
|
|
109
|
+
# per-marking counter starts "in der Regel mit der Zahl 1".
|
|
110
|
+
no_further_decode: true
|