vehicles 0.6.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 +274 -0
- data/README.md +55 -10
- data/data/PROVENANCE-vehicles.md +12 -0
- data/data/plates/PROVENANCE.md +5 -4
- 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/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 +1 -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 +553 -118
- data/data/plates/dk.yml +1647 -0
- data/data/plates/ee.yml +1275 -0
- data/data/plates/es.yml +228 -32
- 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 +44 -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 +319 -96
- 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 +17 -3
- 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 +21 -2
- data/lib/vehicles/plates/series.rb +172 -18
- data/lib/vehicles/plates/template_fill.rb +188 -0
- data/lib/vehicles/plates/templates.rb +162 -0
- data/lib/vehicles/plates.rb +12 -0
- data/lib/vehicles/providers/hosted_provider.rb +28 -55
- 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 +32 -16
- data/rakelib/data_refresh.rake +109 -0
- data/rakelib/plates_refresh.rake +29 -0
- data/sig/vehicles.rbs +18 -0
- metadata +164 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c9ab45bf3b30e2ab2688becf7e36d4c67a1f40267324cec67b3df7955eb874db
|
|
4
|
+
data.tar.gz: 2fac8f7b9d499000730c0ecb3e4fa98d2555a06ce1c7ce8e5d82a1c730ef9573
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a2ac35c05cbd762cd5f818291c30792c1b8e69c18efe699f5b9408f9a35b23bd2646217f4333ca09ca7ef75c54c464eb2dedfdf4433cb7a9563712d69116c90c
|
|
7
|
+
data.tar.gz: bfcc408a806116de713664e4acf300edc515e9bf0a98075f4fa943520683e13b1b0700fa616635e2cf3cc2e73d08db8f97ba4e3dd606ed726ef0b5d653827076
|
data/.rubocop_todo.yml
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# This configuration was generated by
|
|
2
|
+
# `rubocop --auto-gen-config --no-exclude-limit`
|
|
3
|
+
# on 2026-10-10 04:04:37 UTC using RuboCop version 1.91.0.
|
|
4
|
+
# The point is for the user to remove these configuration records
|
|
5
|
+
# one by one as the offenses are removed from the code base.
|
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
|
8
|
+
|
|
9
|
+
# Offense count: 5
|
|
10
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
11
|
+
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
|
12
|
+
# SupportedStyles: with_first_argument, with_fixed_indentation
|
|
13
|
+
Layout/ArgumentAlignment:
|
|
14
|
+
Exclude:
|
|
15
|
+
- 'lib/vehicles/plates/series.rb'
|
|
16
|
+
- 'test/plates_test.rb'
|
|
17
|
+
|
|
18
|
+
# Offense count: 1
|
|
19
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
20
|
+
# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines.
|
|
21
|
+
Layout/EmptyLineBetweenDefs:
|
|
22
|
+
Exclude:
|
|
23
|
+
- 'test/plates_test.rb'
|
|
24
|
+
|
|
25
|
+
# Offense count: 1
|
|
26
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
27
|
+
# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment.
|
|
28
|
+
Layout/ExtraSpacing:
|
|
29
|
+
Exclude:
|
|
30
|
+
- 'lib/vehicles/plates/series.rb'
|
|
31
|
+
|
|
32
|
+
# Offense count: 1
|
|
33
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
34
|
+
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
|
35
|
+
# SupportedStyles: aligned, indented, indented_relative_to_receiver
|
|
36
|
+
Layout/MultilineMethodCallIndentation:
|
|
37
|
+
Exclude:
|
|
38
|
+
- 'lib/vehicles/plates/jurisdiction.rb'
|
|
39
|
+
|
|
40
|
+
# Offense count: 1
|
|
41
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
42
|
+
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
|
43
|
+
# SupportedStyles: aligned, indented
|
|
44
|
+
Layout/MultilineOperationIndentation:
|
|
45
|
+
Exclude:
|
|
46
|
+
- 'lib/vehicles/plates/series.rb'
|
|
47
|
+
|
|
48
|
+
# Offense count: 1
|
|
49
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
50
|
+
# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator, EnforcedStyleForRationalLiterals.
|
|
51
|
+
# SupportedStylesForExponentOperator: space, no_space
|
|
52
|
+
# SupportedStylesForRationalLiterals: space, no_space
|
|
53
|
+
Layout/SpaceAroundOperators:
|
|
54
|
+
Exclude:
|
|
55
|
+
- 'lib/vehicles/plates/series.rb'
|
|
56
|
+
|
|
57
|
+
# Offense count: 58
|
|
58
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
59
|
+
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets.
|
|
60
|
+
# SupportedStyles: space, no_space, compact
|
|
61
|
+
# SupportedStylesForEmptyBrackets: space, no_space
|
|
62
|
+
Layout/SpaceInsideArrayLiteralBrackets:
|
|
63
|
+
Exclude:
|
|
64
|
+
- 'lib/vehicles/plates.rb'
|
|
65
|
+
- 'lib/vehicles/plates/jurisdiction.rb'
|
|
66
|
+
- 'lib/vehicles/plates/series.rb'
|
|
67
|
+
- 'test/plates_test.rb'
|
|
68
|
+
|
|
69
|
+
# Offense count: 1
|
|
70
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
71
|
+
Lint/RedundantDirGlobSort:
|
|
72
|
+
Exclude:
|
|
73
|
+
- 'lib/vehicles/plates.rb'
|
|
74
|
+
|
|
75
|
+
# Offense count: 4
|
|
76
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
|
77
|
+
Metrics/AbcSize:
|
|
78
|
+
Exclude:
|
|
79
|
+
- 'lib/vehicles/plates/series.rb'
|
|
80
|
+
|
|
81
|
+
# Offense count: 1
|
|
82
|
+
# Configuration parameters: CountBlocks, CountModifierForms, Max.
|
|
83
|
+
Metrics/BlockNesting:
|
|
84
|
+
Exclude:
|
|
85
|
+
- 'lib/vehicles/plates/series.rb'
|
|
86
|
+
|
|
87
|
+
# Offense count: 1
|
|
88
|
+
# Configuration parameters: CountComments, Max, CountAsOne.
|
|
89
|
+
Metrics/ClassLength:
|
|
90
|
+
Exclude:
|
|
91
|
+
- 'lib/vehicles/mcp_server.rb'
|
|
92
|
+
- 'lib/vehicles/plates/series.rb'
|
|
93
|
+
|
|
94
|
+
# Offense count: 2
|
|
95
|
+
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
96
|
+
Metrics/MethodLength:
|
|
97
|
+
Exclude:
|
|
98
|
+
- 'lib/vehicles/plates/series.rb'
|
|
99
|
+
|
|
100
|
+
# Offense count: 1
|
|
101
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
102
|
+
Minitest/AssertPredicate:
|
|
103
|
+
Exclude:
|
|
104
|
+
- 'test/model_test.rb'
|
|
105
|
+
|
|
106
|
+
# Offense count: 11
|
|
107
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
108
|
+
Minitest/EmptyLineBeforeAssertionMethods:
|
|
109
|
+
Exclude:
|
|
110
|
+
- 'test/model_test.rb'
|
|
111
|
+
- 'test/plates_test.rb'
|
|
112
|
+
|
|
113
|
+
# Offense count: 13
|
|
114
|
+
# Configuration parameters: MinSize.
|
|
115
|
+
Performance/CollectionLiteralInLoop:
|
|
116
|
+
Exclude:
|
|
117
|
+
- 'lib/vehicles/plates/series.rb'
|
|
118
|
+
- 'test/plates_test.rb'
|
|
119
|
+
|
|
120
|
+
# Offense count: 1
|
|
121
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
122
|
+
# Configuration parameters: EnforcedStyle.
|
|
123
|
+
# SupportedStyles: always, always_true, never
|
|
124
|
+
Style/FrozenStringLiteralComment:
|
|
125
|
+
Exclude:
|
|
126
|
+
- '**/*.arb'
|
|
127
|
+
- 'rakelib/plates_refresh.rake'
|
|
128
|
+
|
|
129
|
+
# Offense count: 1
|
|
130
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
131
|
+
Style/NestedTernaryOperator:
|
|
132
|
+
Exclude:
|
|
133
|
+
- 'lib/vehicles/plates/series.rb'
|
|
134
|
+
|
|
135
|
+
# Offense count: 1
|
|
136
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
137
|
+
Style/PerlBackrefs:
|
|
138
|
+
Exclude:
|
|
139
|
+
- 'lib/vehicles/plates/series.rb'
|
|
140
|
+
|
|
141
|
+
# Offense count: 2
|
|
142
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
143
|
+
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
|
|
144
|
+
# SupportedStyles: single_quotes, double_quotes
|
|
145
|
+
Style/StringLiterals:
|
|
146
|
+
Exclude:
|
|
147
|
+
- 'test/plates_test.rb'
|
|
148
|
+
|
|
149
|
+
# Offense count: 3
|
|
150
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
151
|
+
# Configuration parameters: EnforcedStyle.
|
|
152
|
+
# SupportedStyles: single_quotes, double_quotes
|
|
153
|
+
Style/StringLiteralsInInterpolation:
|
|
154
|
+
Exclude:
|
|
155
|
+
- 'lib/vehicles/plates.rb'
|
|
156
|
+
- 'lib/vehicles/plates/series.rb'
|
|
157
|
+
|
|
158
|
+
# Offense count: 1
|
|
159
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
160
|
+
# Configuration parameters: WordRegex.
|
|
161
|
+
# SupportedStyles: percent, brackets
|
|
162
|
+
Style/WordArray:
|
|
163
|
+
EnforcedStyle: percent
|
|
164
|
+
MinSize: 3
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,280 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.0] - 2026-11-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Plate art without sending the plate.** `Vehicles::Plates.render(registration,
|
|
15
|
+
jurisdiction:, region: nil, label: nil)` draws a registration on its series'
|
|
16
|
+
real VehiclesDB plate as an SVG, and nothing personal leaves the machine: the
|
|
17
|
+
gem picks the series locally (the bundled matcher; the only standard-class
|
|
18
|
+
hit, else the only hit — ambiguous plates such as German `MAB1234` answer
|
|
19
|
+
nil instead of the wrong plate), derives the shape (`NNNN LLL`), fetches the
|
|
20
|
+
BLANK template `GET {api_base_url}/plates/:code/:series_id/template.svg?shape=®ion=`
|
|
21
|
+
and fills it here. No API key is sent (the route is public). Returns nil on
|
|
22
|
+
any failure, never raises.
|
|
23
|
+
- `Vehicles::Plates.template(code:, series:, shape: nil, region: nil)`: the
|
|
24
|
+
blank template, or nil. Client-side fences refuse a shape that is not pure
|
|
25
|
+
N/L classes and separators, so a registration passed by mistake never
|
|
26
|
+
reaches the wire. Cached in-process by default; `config.plate_template_cache`
|
|
27
|
+
takes any `read` / `write(key, value, expires_in:)` store (e.g. `Rails.cache`)
|
|
28
|
+
and `config.plate_template_ttl` (default one day) the lifetime. Failures are
|
|
29
|
+
remembered for five minutes, so an unreachable server costs one timeout per
|
|
30
|
+
template per window, not one per call. Keys carry the template schema and
|
|
31
|
+
gem version.
|
|
32
|
+
- `Vehicles::Plates::TemplateFill`: the reference filler, moved verbatim from
|
|
33
|
+
vehiclesdb-web (`fill`, `metadata`, `groups`, and the shared fit formulas
|
|
34
|
+
`fit_size` / `recentered_baseline` the VehiclesDB renderer will call from
|
|
35
|
+
here), plus `shape_of` and a `label:` option for a localized accessible
|
|
36
|
+
name. Standard library only; the file loads on its own in a gem-less Ruby.
|
|
37
|
+
- Templates are inlined into host pages, so both the fetch and
|
|
38
|
+
`TemplateFill.fill` itself refuse a template carrying anything active or
|
|
39
|
+
external (`<script>`, event handlers including `<svg/onload=…>`,
|
|
40
|
+
`javascript:`, non-`data:` hrefs and `url()`s): `TemplateFill.inert?`.
|
|
41
|
+
- `region:` takes subdivision keys only (`75`, `2A`, `971`, `44-bzh`, `BY`,
|
|
42
|
+
`ZH`, `9`), so a registration passed there by mistake is refused locally
|
|
43
|
+
and never sent.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- The HTTP plumbing (timeout, no silent retry, nil on failure) moved from
|
|
48
|
+
`HostedProvider` into `Vehicles::HTTP`, shared by images and plate
|
|
49
|
+
templates. Behaviour of the images calls is unchanged.
|
|
50
|
+
|
|
51
|
+
## [0.7.9] - 2026-11-08
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- **Hosted images now reach the API.** `HostedProvider` requested
|
|
56
|
+
`https://vehiclesdb.com/v1/vehicles/…/images`, but the VehiclesDB API lives
|
|
57
|
+
under **`/api/v1`**: `/v1/…` is the website's HTML 404, so `Model#image` /
|
|
58
|
+
`Model#images` returned nil for every key since 0.5.0. Requests now go to
|
|
59
|
+
`GET {api_base_url}/api/v1/vehicles/:kind/:make_slug/:model_slug/images`
|
|
60
|
+
(`HostedProvider::API_PREFIX`). `config.api_base_url` is still the origin
|
|
61
|
+
and still overridable (`"http://localhost:3030"` for a local server); a
|
|
62
|
+
trailing slash or a proxy path prefix on it is now kept as written
|
|
63
|
+
instead of being dropped by `URI.join`. Pinned by tests over a real local
|
|
64
|
+
socket: the exact request line, the Bearer header, and nil on a 404 HTML
|
|
65
|
+
page, a 401, a non-JSON 200 and a refused connection.
|
|
66
|
+
- **One hosted request is one request.** Net::HTTP re-sends an idempotent
|
|
67
|
+
GET once after a read timeout, so a slow images response was asked for
|
|
68
|
+
twice: double the latency `api_timeout` promises to cap, and two billed
|
|
69
|
+
calls when the second one is served (seen against a cold local server).
|
|
70
|
+
`max_retries` is now 0; pinned by a socket test that counts connections.
|
|
71
|
+
- **A name that exists as several kinds answers with the car.**
|
|
72
|
+
`Vehicles.model("Toyota", "Land Cruiser")` returned the **bus** (the first
|
|
73
|
+
record of four kinds). Without `kind:`, `Make#model` / `Vehicles.model` /
|
|
74
|
+
`Vehicles.find` now prefer the `:car` record and keep the first record
|
|
75
|
+
when there is no car. Over every model name and slug in the bundled data
|
|
76
|
+
this moves 51 names from bus to car — same make and model, never a
|
|
77
|
+
different one.
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- `kind:` on `Vehicles.model` and `Make#model` to pick the kind explicitly:
|
|
82
|
+
`Vehicles.model("Toyota", "Land Cruiser", kind: :bus)`.
|
|
83
|
+
- **Spacing-blind model names.** When no name matches exactly, `Make#model`
|
|
84
|
+
compares letters and digits only, so `"XCeed"` is Kia's `"X Ceed"`, `"A 5"`
|
|
85
|
+
is Audi `"A5"`, `"CX5"` is `"CX-5"`, `"XC 90"` is `"XC90"` — but only when
|
|
86
|
+
that lands on ONE name (never a guess between two), and never partially
|
|
87
|
+
(`"X Cee"` stays nil). The model validator answers the same way: spacing
|
|
88
|
+
is not a typo.
|
|
89
|
+
- **BMW's Spanish/Italian `"Serie N"`** reads as `"N Series"`
|
|
90
|
+
(`Vehicles.model("BMW", "Serie 3")` → 3 Series); a real `"Serie …"` name
|
|
91
|
+
(`"Serie X1"`) still wins.
|
|
92
|
+
- Measured on a Spanish carpooling app's 1,329 free-text vehicles (make/model
|
|
93
|
+
typed or picked in Spanish): 17 more make/model pairs (23 vehicles) now
|
|
94
|
+
resolve, and the 3 Land Cruisers resolve to the car. Internal double
|
|
95
|
+
spaces were already collapsed by `Vehicles.normalize`; now pinned by a test.
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
|
|
99
|
+
- `Model#years` / `Model#segment` no longer spend a request: the API serves
|
|
100
|
+
no endpoint for them yet (the old `/v1/models/:slug` never existed), so
|
|
101
|
+
the hosted provider answers nil without touching the network and the
|
|
102
|
+
chain falls through to the local data, exactly as before. They light up
|
|
103
|
+
when the API ships them. `HostedProvider.reset!` (its per-process cache)
|
|
104
|
+
is gone with it.
|
|
105
|
+
- README: the plates endpoints are `/api/v1/plates/…`.
|
|
106
|
+
|
|
107
|
+
## [0.7.8] - 2026-10-10
|
|
108
|
+
|
|
109
|
+
### Added
|
|
110
|
+
|
|
111
|
+
- **Typo-tolerant make lookup.** `Vehicles.make`, `Vehicles.models` and
|
|
112
|
+
every make-taking helper now fall back to the closest make by edit
|
|
113
|
+
distance when nothing matches exactly: `"marcedes"` → Mercedes-Benz,
|
|
114
|
+
`"volkswagon"` → Volkswagen, `"DeLorean"` → De Lorean. Bounded
|
|
115
|
+
(Levenshtein 1 for 4-5 characters, 2 from 6 up, never under 4), stdlib
|
|
116
|
+
only, and never a guess: when two different makes are equally close
|
|
117
|
+
(`"Acra"`: Acura / Acma) the answer is `nil`. Exact, alias and slug
|
|
118
|
+
matches always win; `Vehicles.find` runs its exact pass over every
|
|
119
|
+
make/model split before retrying a misspelled make, so no answer that
|
|
120
|
+
resolved in 0.7.7 changes. Opt out per call with `fuzzy: false`
|
|
121
|
+
(`make`, `models`, `model`, `model_options`, `find`). Guards: optimal
|
|
122
|
+
string alignment (an adjacent swap is one edit — `"telsa"` ties Tesla/
|
|
123
|
+
Temsa → nil, never Temsa); a candidate never has fewer words than the
|
|
124
|
+
query (`"Volvo V 60"` cannot become Volvo "60"); every word keeps its
|
|
125
|
+
first letter (`"citroen e"` is not Citroën DS); the configured
|
|
126
|
+
`other_label` is never fuzzy-matched. Verified: 0 changed / 0 lost
|
|
127
|
+
answers over 37,050 `find` and 65,871 web-resolver-style queries.
|
|
128
|
+
- `Model#country_ranks` / `Model#rank_in(country)` — the model's rank
|
|
129
|
+
among all models of its kind in that country (dataset key
|
|
130
|
+
`country_ranks`, from `catalog/` `popularity.by_country[cc].rank`).
|
|
131
|
+
- `Model#mass_decile` — per-kind registration-mass decile (1 = heaviest),
|
|
132
|
+
the dataset's `popularity.mass_decile` (2026.10.1+, pipeline#255 /
|
|
133
|
+
ruling R3); nil-safe on older snapshots (the bundled 2026.10.0 has none).
|
|
134
|
+
`global_decile` stays what it is — a presence average.
|
|
135
|
+
`top_models(by: :mass_decile)` ranks by it (records without it are
|
|
136
|
+
excluded, so it is `[]` on 2026.10.0 data); `by:` defaults to
|
|
137
|
+
`:global_decile`; anything else raises ArgumentError. With `country:`
|
|
138
|
+
the country's rank still sorts first (the decile breaks ties).
|
|
139
|
+
`Model#to_h` gains `:mass_decile`.
|
|
140
|
+
- `rake "data:refresh[<tag>]"` — refreshes `data/vehicles.json` from a
|
|
141
|
+
dataset release tag and writes `data/PROVENANCE-vehicles.md`; fills
|
|
142
|
+
`country_ranks` from the same tag's catalog until the projection ships
|
|
143
|
+
the key natively (then it asserts equality instead).
|
|
144
|
+
|
|
145
|
+
### Changed
|
|
146
|
+
|
|
147
|
+
- **`top_models(country:)` orders by that country's own rank.** Until now a
|
|
148
|
+
country-filtered list kept the GLOBAL order (`global_decile`, a
|
|
149
|
+
presence-weighted average across countries), so the UK top-10 read in
|
|
150
|
+
everybody else's order. Models available there without a rank there
|
|
151
|
+
follow in the old order; data without `country_ranks` degrades to the
|
|
152
|
+
0.7.7 order. Ranks are per kind — pass `kind:` for a clean ranking.
|
|
153
|
+
- Bundled dataset refreshed 2026.07.4 → **2026.10.0** (14,997 models, 15
|
|
154
|
+
countries; `dist/vehicles.json` at tag v2026.10.0 + `country_ranks`).
|
|
155
|
+
- The make and model validators stay EXACT (`fuzzy: false`): a typo never
|
|
156
|
+
validates as a stored make.
|
|
157
|
+
|
|
158
|
+
- Fuzzy matching is for human-typed names only: Symbols, input containing
|
|
159
|
+
`/`, `_`, `+` or `\` (ids, paths), hyphen-edged strings and single-case
|
|
160
|
+
hyphenated slugs (`marcedes-benz`, `MARCEDES-BENZ`) resolve exactly or
|
|
161
|
+
not at all; the MCP server's tools are exact-only.
|
|
162
|
+
- CI: the 111 rubocop offenses inherited from main are frozen (110 in a
|
|
163
|
+
generated `.rubocop_todo.yml`, plus `plates/series.rb` in `.rubocop.yml`'s
|
|
164
|
+
own ClassLength Exclude, which overrides the todo) — no code changes — so
|
|
165
|
+
the Lint job is green again; new code is fully linted.
|
|
166
|
+
|
|
167
|
+
### Upgrade notes
|
|
168
|
+
|
|
169
|
+
- Apps that route or 404 on `Vehicles.make(param)`: slug-shaped params
|
|
170
|
+
(`marcedes-benz`, anything with `/`, `_`, `+`, a Symbol) never fuzz, but
|
|
171
|
+
a single-word param still does (`/makes/marcedes` → param `"marcedes"` →
|
|
172
|
+
Mercedes-Benz). If a misspelled URL must 404 (or 301 to the canonical
|
|
173
|
+
slug), call `Vehicles.make(param, fuzzy: false)` there.
|
|
174
|
+
|
|
175
|
+
## [0.7.7] - 2026-08-03
|
|
176
|
+
|
|
177
|
+
### Changed
|
|
178
|
+
|
|
179
|
+
- Plates dataset refreshed — the overnight swarm passes' settlements
|
|
180
|
+
land: the Cypriot centre date mark, Brazil's square-dot printed form
|
|
181
|
+
and photographed moto split, San Marino's structured legend/sigla
|
|
182
|
+
fields, Ukraine's statutory line counts, California's pixel-sampled
|
|
183
|
+
colours and corner-sticker guides, the Texas exempt restatement,
|
|
184
|
+
Korea's corporate characters block, Thailand's seven-photo
|
|
185
|
+
cross-check, and China's two-row trailer + NEV dedicated-mark flips
|
|
186
|
+
(#288-#290).
|
|
187
|
+
|
|
188
|
+
## [0.7.5] - 2026-08-03
|
|
189
|
+
|
|
190
|
+
### Added
|
|
191
|
+
|
|
192
|
+
- `Plates.decode_table(name)` — the _decode lookups (district/office/
|
|
193
|
+
use-mark tables) exposed to consumers, enabling serial-driven
|
|
194
|
+
subregion resolution (renderers picking the right Land arms from a
|
|
195
|
+
district prefix).
|
|
196
|
+
|
|
197
|
+
## [0.7.4] - 2026-08-03
|
|
198
|
+
|
|
199
|
+
### Added
|
|
200
|
+
|
|
201
|
+
- South Korea (21 series + hangul decode tables) — corpus: 124
|
|
202
|
+
jurisdictions / 1,381 series.
|
|
203
|
+
|
|
204
|
+
## [0.7.3] - 2026-08-03
|
|
205
|
+
|
|
206
|
+
### Fixed
|
|
207
|
+
|
|
208
|
+
- Pattern-language literal escapes: ca-nb-truck-2003 introduced "\L"
|
|
209
|
+
(a literal L, vs the any-letter placeholder). New Series#display_pattern
|
|
210
|
+
resolves escapes for display and slot walks; the round-trip test
|
|
211
|
+
substitutes placeholders escape-aware. Escape convention flagged to the
|
|
212
|
+
data maintainers for a ruling.
|
|
213
|
+
|
|
214
|
+
## [0.7.2] - 2026-08-03
|
|
215
|
+
|
|
216
|
+
### Added
|
|
217
|
+
|
|
218
|
+
- The L3 wave: 28 jurisdiction files across five continents (jp cn in
|
|
219
|
+
th my br mx ar nz za ua + Australia's 8 states + Canada's 13
|
|
220
|
+
provinces/territories), 415 series and 10 more decode tables incl.
|
|
221
|
+
Japan's 138-mark statutory office table. Corpus now: 123
|
|
222
|
+
jurisdictions / 1,360 series / 34 decode tables.
|
|
223
|
+
|
|
224
|
+
## [0.7.1] - 2026-08-02
|
|
225
|
+
|
|
226
|
+
### Added
|
|
227
|
+
|
|
228
|
+
- Plates corpus refreshed to 91 jurisdictions (the EU/EEA wave-2 landing:
|
|
229
|
+
ie dk no is pt lu pl cz sk hu ro si hr ee lv lt gr cy mt + microstates)
|
|
230
|
+
with 22 decode tables.
|
|
231
|
+
|
|
232
|
+
## [0.7.0] - 2026-08-02
|
|
233
|
+
|
|
234
|
+
### Added
|
|
235
|
+
|
|
236
|
+
- The FULL plates corpus ships in the gem: 67 jurisdictions / 606 series
|
|
237
|
+
(the 8-country L1 EU wave, all 50 US states + DC + 5 territories, and
|
|
238
|
+
the redated pilots) + 10 decode tables — up from the 4 pilot files.
|
|
239
|
+
The art tier stays in the data repo; the gem bundles format data.
|
|
240
|
+
|
|
241
|
+
## [0.6.4] - 2026-08-02
|
|
242
|
+
|
|
243
|
+
### Changed
|
|
244
|
+
|
|
245
|
+
- Bundled plates data refreshed from the redate pass: es-provincial is now
|
|
246
|
+
1971-2000 pinned to Decreto 2046/1971 at both ends (was a 1999-2000
|
|
247
|
+
instrument window), plus the 1900-1971 Spanish numeric era, the German
|
|
248
|
+
1956-2000 DIN era, 11 German and 10 Florida period corrections — all
|
|
249
|
+
cited to gazette primaries.
|
|
250
|
+
- `approximate_start?` covers the two new honesty vocabularies
|
|
251
|
+
(instrument-in-force-upper-bound, secondary-locator-unverified), so
|
|
252
|
+
upper-bounded and uncited-secondary starts render with the "~".
|
|
253
|
+
|
|
254
|
+
## [0.6.3] - 2026-08-02
|
|
255
|
+
|
|
256
|
+
### Changed
|
|
257
|
+
|
|
258
|
+
- Plate period labels read naturally: open series say "2000 → today", and
|
|
259
|
+
instrument-dated starts mark approximation as "~1999" instead of the
|
|
260
|
+
cryptic "≤1999". New `Series#approximate_start?` so displays can explain
|
|
261
|
+
the tilde ("documented from this year; the format may be older").
|
|
262
|
+
|
|
263
|
+
## [0.6.2] - 2026-08-02
|
|
264
|
+
|
|
265
|
+
### Fixed
|
|
266
|
+
|
|
267
|
+
- `Plates::Series#format_serial` reconstructed variable-length serials by
|
|
268
|
+
walking the fixed display pattern, misplacing separators ("M1234LA" came
|
|
269
|
+
back as "M1-234L-A" instead of "M-1234-LA"). The pattern walk now applies
|
|
270
|
+
only when the serial exactly fills the pattern; length-mismatched serials
|
|
271
|
+
reconstruct from the regex segmented at its separators (new
|
|
272
|
+
`Series.segment_separators`, riding the dataset separator contract), and
|
|
273
|
+
a serial neither path can place comes back untouched rather than
|
|
274
|
+
mis-punctuated.
|
|
275
|
+
|
|
276
|
+
### Changed
|
|
277
|
+
|
|
278
|
+
- `Plates::Series#period_label` no longer presents instrument-dated starts
|
|
279
|
+
as the series era: rows whose `period_evidence` is `instrument-in-force`
|
|
280
|
+
or `instrument-window` label as "≤1999–2000" ("began by", not "began
|
|
281
|
+
in"). New `Series#period_evidence`, `#issued_regexp`,
|
|
282
|
+
`#issued_separators` readers for consumers mirroring the reconstruction.
|
|
283
|
+
|
|
10
284
|
## [0.6.1] - 2026-08-01
|
|
11
285
|
|
|
12
286
|
### Added
|
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
|
|
|
@@ -451,6 +470,31 @@ Vehicles.plates(:es).series # => 18 series: formats, periods, sourced designs
|
|
|
451
470
|
|
|
452
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.
|
|
453
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
|
+
|
|
454
498
|
## 🔓 More with VehiclesDB
|
|
455
499
|
|
|
456
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:
|
|
@@ -550,6 +594,7 @@ Vehicles.catalog(kind: :car) # => { "Audi" => ["A3", ...], ... }
|
|
|
550
594
|
Vehicles.make("Audi") # => Vehicles::Make | nil
|
|
551
595
|
Vehicles.find("audi a3") # => Vehicles::Model | nil (one free-text string)
|
|
552
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)
|
|
553
598
|
Vehicles.search("a3") # => [Vehicles::Model]
|
|
554
599
|
|
|
555
600
|
# Popularity & availability (open data, measured from official registers)
|
|
@@ -584,7 +629,7 @@ Vehicles.reload! # drop the in-memory dataset (reload fro
|
|
|
584
629
|
|
|
585
630
|
## Where the data comes from
|
|
586
631
|
|
|
587
|
-
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.
|
|
588
633
|
|
|
589
634
|
Every record is shaped like this:
|
|
590
635
|
|
|
@@ -602,7 +647,7 @@ Every record is shaped like this:
|
|
|
602
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.
|
|
603
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.
|
|
604
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).
|
|
605
|
-
- **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.
|
|
606
651
|
- **The gem code:** MIT.
|
|
607
652
|
|
|
608
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.
|
|
@@ -618,7 +663,7 @@ No magic, just good defaults:
|
|
|
618
663
|
|
|
619
664
|
## Roadmap
|
|
620
665
|
|
|
621
|
-
Bundled today:
|
|
666
|
+
Bundled today: ~15k models across 6 kinds and 15 countries, with popularity +
|
|
622
667
|
availability, plus the MCP server. On the way:
|
|
623
668
|
|
|
624
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).
|
data/data/plates/PROVENANCE.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
# Bundled plates dataset (PRD-PLATES
|
|
1
|
+
# Bundled plates dataset (PRD-PLATES, full corpus)
|
|
2
2
|
|
|
3
|
-
Copy of `plates/` from the public data repo
|
|
3
|
+
Copy of `plates/` from the public data repo (EXCLUDING `_art/`),
|
|
4
|
+
refreshed on gem releases:
|
|
4
5
|
|
|
5
6
|
- **Source**: https://github.com/vehiclesdb/vehiclesdb `plates/`
|
|
6
|
-
- **Commit**: `
|
|
7
|
+
- **Commit**: `c7dd65c6f4144d39c62706b7be10231a12bc27e1`
|
|
7
8
|
- **License**: CC-BY 4.0 (see the data repo's LICENSE + ATTRIBUTION.md)
|
|
8
9
|
|
|
9
|
-
Refresh:
|
|
10
|
+
Refresh: `rake plates:refresh` (this file is generated by it).
|