vehicles 0.9.0 → 0.9.2
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/CHANGELOG.md +57 -7
- data/README.md +353 -69
- data/data/PROVENANCE-vehicles.md +2 -2
- data/data/plates/PROVENANCE.md +2 -2
- data/data/plates/_decode/at-districts.yml +2 -2
- data/data/plates/_decode/cn-issuing-offices.yml +2 -2
- data/data/plates/_decode/cn-provinces.yml +2 -2
- data/data/plates/_decode/cz-kraj.yml +1 -1
- data/data/plates/_decode/dk-serial-blocks.yml +1 -1
- data/data/plates/_decode/es-provinces.yml +1 -1
- data/data/plates/_decode/fr-departements.yml +2 -2
- data/data/plates/_decode/gb-age-identifiers.yml +1 -1
- data/data/plates/_decode/gb-memory-tags.yml +1 -1
- data/data/plates/_decode/gr-prefectures.yml +1 -1
- data/data/plates/_decode/hr-cities.yml +3 -3
- data/data/plates/_decode/ie-counties.yml +3 -3
- data/data/plates/_decode/in-state-codes.yml +2 -2
- data/data/plates/_decode/it-provinces.yml +3 -3
- data/data/plates/_decode/jp-office-marks.yml +2 -2
- data/data/plates/_decode/jp-use-marks.yml +2 -2
- data/data/plates/_decode/kr-authority-marks.yml +1 -1
- data/data/plates/_decode/mx-entity-series.yml +1 -1
- data/data/plates/_decode/my-index-marks.yml +4 -4
- data/data/plates/_decode/pt-trailer-services.yml +1 -1
- data/data/plates/_decode/ro-counties.yml +1 -1
- data/data/plates/_decode/se-diplomatic.yml +1 -1
- data/data/plates/_decode/si-regions.yml +1 -1
- data/data/plates/_decode/ua-regions.yml +1 -1
- data/data/plates/_meta/classes.yml +1 -1
- data/data/plates/_meta/separators.yml +1 -1
- data/data/plates/ad.yml +555 -52
- data/data/plates/ar.yml +8 -8
- data/data/plates/at.yml +91 -10
- data/data/plates/au-act.yml +13 -13
- data/data/plates/au-nsw.yml +9 -9
- data/data/plates/au-nt.yml +13 -14
- data/data/plates/au-qld.yml +7 -7
- data/data/plates/au-sa.yml +7 -7
- data/data/plates/au-tas.yml +10 -10
- data/data/plates/au-vic.yml +6 -6
- data/data/plates/au-wa.yml +8 -8
- data/data/plates/be.yml +11 -11
- data/data/plates/br.yml +14 -14
- data/data/plates/ca-ab.yml +10 -10
- data/data/plates/ca-bc.yml +6 -6
- data/data/plates/ca-mb.yml +4 -4
- data/data/plates/ca-nb.yml +9 -9
- data/data/plates/ca-nl.yml +11 -11
- data/data/plates/ca-ns.yml +12 -12
- data/data/plates/ca-nt.yml +7 -7
- data/data/plates/ca-nu.yml +7 -7
- data/data/plates/ca-on.yml +21 -21
- data/data/plates/ca-pe.yml +13 -13
- data/data/plates/ca-qc.yml +11 -11
- data/data/plates/ca-sk.yml +6 -6
- data/data/plates/ca-yt.yml +6 -6
- data/data/plates/ch.yml +14 -14
- data/data/plates/cn.yml +9 -9
- data/data/plates/cy.yml +6 -6
- data/data/plates/cz.yml +9 -9
- data/data/plates/de.yml +8 -8
- data/data/plates/dk.yml +12 -12
- data/data/plates/ee.yml +13 -13
- data/data/plates/eg.yml +708 -0
- data/data/plates/es.yml +8 -8
- data/data/plates/fi.yml +14 -14
- data/data/plates/fr.yml +12 -12
- data/data/plates/gb.yml +16 -16
- data/data/plates/gr.yml +14 -14
- data/data/plates/hr.yml +15 -15
- data/data/plates/hu.yml +16 -16
- data/data/plates/ie.yml +7 -7
- data/data/plates/in.yml +7 -7
- data/data/plates/is.yml +3 -3
- data/data/plates/it.yml +21 -21
- data/data/plates/jp.yml +121 -121
- data/data/plates/kr.yml +6 -6
- data/data/plates/li.yml +6 -6
- data/data/plates/lt.yml +10 -10
- data/data/plates/lu.yml +4 -4
- data/data/plates/lv.yml +9 -9
- data/data/plates/mc.yml +4 -4
- data/data/plates/mt.yml +5 -5
- data/data/plates/mx.yml +7 -7
- data/data/plates/my.yml +18 -18
- data/data/plates/ng.yml +346 -0
- data/data/plates/nl.yml +5 -5
- data/data/plates/no.yml +7 -7
- data/data/plates/nz.yml +18 -18
- data/data/plates/pl.yml +4 -4
- data/data/plates/pt.yml +13 -5
- data/data/plates/ro.yml +14 -14
- data/data/plates/se.yml +13 -13
- data/data/plates/si.yml +8 -8
- data/data/plates/sk.yml +15 -15
- data/data/plates/sm.yml +7 -7
- data/data/plates/th.yml +10 -10
- data/data/plates/tr.yml +432 -0
- data/data/plates/ua.yml +9 -9
- data/data/plates/us-ak.yml +11 -11
- data/data/plates/us-al.yml +12 -12
- data/data/plates/us-ar.yml +15 -15
- data/data/plates/us-as.yml +10 -10
- data/data/plates/us-az.yml +10 -10
- data/data/plates/us-ca.yml +13 -13
- data/data/plates/us-co.yml +14 -14
- data/data/plates/us-ct.yml +24 -24
- data/data/plates/us-dc.yml +7 -7
- data/data/plates/us-de.yml +10 -10
- data/data/plates/us-fl.yml +17 -17
- data/data/plates/us-ga.yml +10 -10
- data/data/plates/us-gu.yml +11 -11
- data/data/plates/us-hi.yml +11 -11
- data/data/plates/us-ia.yml +9 -9
- data/data/plates/us-id.yml +13 -13
- data/data/plates/us-il.yml +11 -11
- data/data/plates/us-in.yml +9 -9
- data/data/plates/us-ks.yml +8 -8
- data/data/plates/us-ky.yml +11 -11
- data/data/plates/us-la.yml +11 -11
- data/data/plates/us-ma.yml +10 -10
- data/data/plates/us-md.yml +7 -7
- data/data/plates/us-me.yml +16 -16
- data/data/plates/us-mi.yml +8 -8
- data/data/plates/us-mn.yml +13 -13
- data/data/plates/us-mo.yml +13 -13
- data/data/plates/us-mp.yml +12 -12
- data/data/plates/us-ms.yml +8 -8
- data/data/plates/us-mt.yml +12 -12
- data/data/plates/us-nc.yml +12 -12
- data/data/plates/us-nd.yml +10 -10
- data/data/plates/us-ne.yml +10 -10
- data/data/plates/us-nh.yml +8 -8
- data/data/plates/us-nj.yml +8 -8
- data/data/plates/us-nm.yml +14 -14
- data/data/plates/us-nv.yml +18 -18
- data/data/plates/us-ny.yml +17 -17
- data/data/plates/us-oh.yml +13 -13
- data/data/plates/us-ok.yml +12 -12
- data/data/plates/us-or.yml +15 -15
- data/data/plates/us-pa.yml +14 -14
- data/data/plates/us-pr.yml +15 -15
- data/data/plates/us-ri.yml +10 -10
- data/data/plates/us-sc.yml +7 -7
- data/data/plates/us-sd.yml +12 -12
- data/data/plates/us-tn.yml +8 -8
- data/data/plates/us-tx.yml +17 -17
- data/data/plates/us-ut.yml +12 -12
- data/data/plates/us-va.yml +14 -14
- data/data/plates/us-vi.yml +16 -16
- data/data/plates/us-vt.yml +9 -9
- data/data/plates/us-wa.yml +18 -18
- data/data/plates/us-wi.yml +8 -8
- data/data/plates/us-wv.yml +9 -9
- data/data/plates/us-wy.yml +10 -10
- data/data/plates/va.yml +7 -7
- data/data/plates/za.yml +11 -11
- data/data/vehicles.json +121081 -62015
- data/lib/generators/vehicles/templates/initializer.rb +21 -12
- data/lib/generators/vehicles/templates/vehicles_refresh_job.rb +8 -4
- data/lib/vehicles/color.rb +8 -1
- data/lib/vehicles/colors.rb +14 -10
- data/lib/vehicles/configuration.rb +82 -3
- data/lib/vehicles/data_source.rb +220 -0
- data/lib/vehicles/dataset.rb +38 -6
- data/lib/vehicles/dataset_resolution.rb +39 -3
- data/lib/vehicles/dataset_slice.rb +90 -0
- data/lib/vehicles/hosted_result.rb +94 -0
- data/lib/vehicles/http.rb +75 -7
- data/lib/vehicles/id_lookup.rb +37 -0
- data/lib/vehicles/kind_rule.rb +63 -0
- data/lib/vehicles/labels.rb +131 -0
- data/lib/vehicles/make.rb +45 -19
- data/lib/vehicles/model.rb +66 -19
- data/lib/vehicles/model_match.rb +16 -2
- data/lib/vehicles/model_matcher.rb +6 -7
- data/lib/vehicles/plates/series.rb +87 -8
- data/lib/vehicles/plates/template_fill.rb +2 -2
- data/lib/vehicles/plates/templates.rb +9 -5
- data/lib/vehicles/plates.rb +54 -8
- data/lib/vehicles/providers/hosted_provider.rb +145 -32
- data/lib/vehicles/providers/local_provider.rb +2 -2
- data/lib/vehicles/refresh_result.rb +74 -0
- data/lib/vehicles/refresher.rb +190 -54
- data/lib/vehicles/release_checks.rb +72 -0
- data/lib/vehicles/text_matcher.rb +19 -10
- data/lib/vehicles/validators/vehicle_make_validator.rb +18 -1
- data/lib/vehicles/validators/vehicle_model_validator.rb +17 -1
- data/lib/vehicles/version.rb +1 -1
- data/lib/vehicles.rb +119 -67
- data/rakelib/data_refresh.rake +1 -1
- data/rakelib/plates_refresh.rake +1 -1
- data/sig/vehicles.rbs +67 -13
- metadata +13 -2
|
@@ -21,24 +21,33 @@ module Vehicles
|
|
|
21
21
|
|
|
22
22
|
module_function
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
# `kind:` scopes the NAME rungs and only prefers a variant on the id
|
|
25
|
+
# rungs (an id is explicit: "bus/iveco/daily" with kind: :car is still
|
|
26
|
+
# that bus). `kinds:` is a hard constraint on EVERY rung: a record of
|
|
27
|
+
# another kind never answers, so the result is nil when it is the only
|
|
28
|
+
# answer (RESOLUTION.md §3, lanes B/C 2026-11-10).
|
|
29
|
+
def identify(text, kind: nil, kinds: nil)
|
|
25
30
|
raw = text.to_s.strip
|
|
26
31
|
return nil if raw.empty?
|
|
27
32
|
|
|
28
|
-
hint =
|
|
33
|
+
hint = KindRule.via(kind, kinds)
|
|
34
|
+
allowed = KindRule.symbols(kinds)
|
|
29
35
|
ds = Vehicles.dataset
|
|
30
|
-
by_id(ds.models_by_id(raw), :exact, kind, hint + ["exact:#{raw}"]) ||
|
|
31
|
-
by_id(ds.models_by_former_id(raw), :former_id, kind, hint + ["former_id:#{raw}"]) ||
|
|
32
|
-
by_make(ds, text, kind, hint)
|
|
36
|
+
by_id(scoped(ds.models_by_id(raw), allowed), :exact, kind, hint + ["exact:#{raw}"]) ||
|
|
37
|
+
by_id(scoped(ds.models_by_former_id(raw), allowed), :former_id, kind, hint + ["former_id:#{raw}"]) ||
|
|
38
|
+
by_make(ds, text, KindRule.scope(kind, kinds), hint)
|
|
33
39
|
end
|
|
34
40
|
|
|
41
|
+
def scoped(hits, allowed) = hits.select { |m| KindRule.allows?(allowed, m) }
|
|
42
|
+
|
|
35
43
|
# Rungs 1–2: ids are explicit, so `kind:` only chooses among kind
|
|
36
|
-
# variants of the same record
|
|
44
|
+
# variants of the same record (else the collision rule, KindRule.pick).
|
|
45
|
+
# Two different records → ambiguous.
|
|
37
46
|
def by_id(hits, rung, kind, via)
|
|
38
47
|
return nil if hits.empty?
|
|
39
48
|
return ModelMatcher.ambiguous(hits, rung, via) unless hits.map { |m| record(m) }.uniq.one?
|
|
40
49
|
|
|
41
|
-
model = (kind && hits.find { |m| m.kind == kind.to_sym }) ||
|
|
50
|
+
model = (kind && hits.find { |m| m.kind == kind.to_s.to_sym }) || KindRule.pick(hits)
|
|
42
51
|
ModelMatcher.build(model, hits, [rung], via)
|
|
43
52
|
end
|
|
44
53
|
|
|
@@ -89,14 +98,14 @@ module Vehicles
|
|
|
89
98
|
# 2026-07-27; RESOLUTION.md rung 6 says "no OTHER record").
|
|
90
99
|
def whole_text_alias(dataset, query, kind, hint)
|
|
91
100
|
hits = dataset.models_by_alias(query)
|
|
92
|
-
hits = hits
|
|
101
|
+
hits = scoped(hits, KindRule.symbols(kind))
|
|
93
102
|
return nil if hits.empty?
|
|
94
|
-
return nil if (dataset.records_named(query) - hits.map { |m| record(m) }).any?
|
|
103
|
+
return nil if (dataset.records_named(query, KindRule.symbols(kind)) - hits.map { |m| record(m) }).any?
|
|
95
104
|
|
|
96
105
|
via = hint + ["alias:#{query}"]
|
|
97
106
|
return ModelMatcher.ambiguous(hits, :alias, via) unless hits.map { |m| record(m) }.uniq.one?
|
|
98
107
|
|
|
99
|
-
model =
|
|
108
|
+
model = KindRule.pick(hits)
|
|
100
109
|
ModelMatcher.build(model, hits, [:alias], [*via[0...-1], "#{via.last}→#{record(model)}"],
|
|
101
110
|
confidence: WHOLE_TEXT_ALIAS)
|
|
102
111
|
end
|
|
@@ -11,17 +11,34 @@
|
|
|
11
11
|
# Pass `allow_other: true` to accept the "Other / not in the list" escape hatch
|
|
12
12
|
# (see `Vehicles.other?`) — pair it with `Vehicles.make_options(include_other:)`
|
|
13
13
|
# so the dropdown and the validation agree by construction.
|
|
14
|
+
#
|
|
15
|
+
# Scope it like your picker (0.9.2) so a value the picker never offered fails:
|
|
16
|
+
# validates :make, vehicle_make: { kind: :car, country: :es }
|
|
17
|
+
# `kind:` — the make builds that kind; `country:` — it has a model (of that
|
|
18
|
+
# kind) with evidence in that country. Country evidence is presence, not
|
|
19
|
+
# exhaustive eligibility: opt in only when the picker uses the same scope.
|
|
14
20
|
class VehicleMakeValidator < ActiveModel::EachValidator
|
|
15
21
|
def validate_each(record, attribute, value)
|
|
16
22
|
return if value.blank?
|
|
17
23
|
return if options[:allow_other] && Vehicles.other?(value)
|
|
24
|
+
|
|
18
25
|
# Exact-only: a typo must fail validation, not be stored as if it were the
|
|
19
26
|
# make it resembles (Vehicles.make is typo-tolerant since 0.7.8).
|
|
20
|
-
|
|
27
|
+
make = Vehicles.make(value, fuzzy: false)
|
|
28
|
+
return if make && in_scope?(make)
|
|
21
29
|
|
|
22
30
|
record.errors.add(attribute, options[:message] || "is not a recognized vehicle make")
|
|
23
31
|
rescue StandardError => e
|
|
24
32
|
Vehicles.logger&.error("[vehicles] vehicle_make validation error: #{e.message}")
|
|
25
33
|
record.errors.add(attribute, options[:message] || "is not a recognized vehicle make")
|
|
26
34
|
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def in_scope?(make)
|
|
39
|
+
kind = options[:kind]
|
|
40
|
+
return false if kind && !make.kinds.include?(kind.to_sym)
|
|
41
|
+
|
|
42
|
+
options[:country].nil? || make.available_in?(options[:country], kind: kind)
|
|
43
|
+
end
|
|
27
44
|
end
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
#
|
|
14
14
|
# Pass `allow_other: true` to accept the "Other / not in the list" escape hatch
|
|
15
15
|
# (see `Vehicles.other?`) — pair it with `Vehicles.model_options(include_other:)`.
|
|
16
|
+
#
|
|
17
|
+
# Scope it like your picker (0.9.2):
|
|
18
|
+
# validates :model, vehicle_model: { make: :make, kind: :car, country: :es }
|
|
19
|
+
# `kind:` / `kinds:` — only records of those kinds count; `country:` — the
|
|
20
|
+
# record must have evidence in that country (opt-in: presence, not
|
|
21
|
+
# exhaustive eligibility).
|
|
16
22
|
class VehicleModelValidator < ActiveModel::EachValidator
|
|
17
23
|
def validate_each(record, attribute, value)
|
|
18
24
|
return if value.blank?
|
|
@@ -22,11 +28,21 @@ class VehicleModelValidator < ActiveModel::EachValidator
|
|
|
22
28
|
make_value = record.respond_to?(make_attribute) ? record.public_send(make_attribute) : nil
|
|
23
29
|
make = Vehicles.make(make_value, fuzzy: false) # exact, same rule as the make validator
|
|
24
30
|
return if make.nil? # unknown make -> defer to the make validator
|
|
25
|
-
return if make
|
|
31
|
+
return if in_scope?(make, value)
|
|
26
32
|
|
|
27
33
|
record.errors.add(attribute, options[:message] || "is not a recognized #{make.name} model")
|
|
28
34
|
rescue StandardError => e
|
|
29
35
|
Vehicles.logger&.error("[vehicles] vehicle_model validation error: #{e.message}")
|
|
30
36
|
record.errors.add(attribute, options[:message] || "is not a recognized vehicle model")
|
|
31
37
|
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def in_scope?(make, value)
|
|
42
|
+
scope = Vehicles::KindRule.scope(options[:kind], options[:kinds])
|
|
43
|
+
country = options[:country]
|
|
44
|
+
return !make.model(value, kinds: scope).nil? if country.nil?
|
|
45
|
+
|
|
46
|
+
(scope || make.kinds).any? { |k| make.model(value, kind: k)&.available_in?(country) }
|
|
47
|
+
end
|
|
32
48
|
end
|
data/lib/vehicles/version.rb
CHANGED
data/lib/vehicles.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "vehicles/version"
|
|
4
4
|
require_relative "vehicles/configuration"
|
|
5
5
|
require_relative "vehicles/http"
|
|
6
|
+
require_relative "vehicles/kind_rule"
|
|
6
7
|
require_relative "vehicles/make"
|
|
7
8
|
require_relative "vehicles/model"
|
|
8
9
|
require_relative "vehicles/color"
|
|
@@ -12,10 +13,17 @@ require_relative "vehicles/model_matcher"
|
|
|
12
13
|
require_relative "vehicles/text_matcher"
|
|
13
14
|
require_relative "vehicles/dataset_resolution"
|
|
14
15
|
require_relative "vehicles/dataset"
|
|
16
|
+
require_relative "vehicles/dataset_slice"
|
|
17
|
+
require_relative "vehicles/data_source"
|
|
18
|
+
require_relative "vehicles/refresh_result"
|
|
19
|
+
require_relative "vehicles/release_checks"
|
|
15
20
|
require_relative "vehicles/refresher"
|
|
21
|
+
require_relative "vehicles/hosted_result"
|
|
22
|
+
require_relative "vehicles/labels"
|
|
16
23
|
require_relative "vehicles/providers/local_provider"
|
|
17
24
|
require_relative "vehicles/providers/hosted_provider"
|
|
18
25
|
require_relative "vehicles/other_option"
|
|
26
|
+
require_relative "vehicles/id_lookup"
|
|
19
27
|
require_relative "vehicles/plates"
|
|
20
28
|
require_relative "vehicles/plates/jurisdiction"
|
|
21
29
|
require_relative "vehicles/plates/series"
|
|
@@ -53,7 +61,7 @@ module Vehicles
|
|
|
53
61
|
@configuration = Configuration.new
|
|
54
62
|
@providers = nil
|
|
55
63
|
@data_path = nil
|
|
56
|
-
|
|
64
|
+
reload!
|
|
57
65
|
end
|
|
58
66
|
|
|
59
67
|
# --- data access ---------------------------------------------------------
|
|
@@ -63,35 +71,59 @@ module Vehicles
|
|
|
63
71
|
@data_path || DATA_PATH
|
|
64
72
|
end
|
|
65
73
|
|
|
66
|
-
attr_writer :
|
|
67
|
-
|
|
68
|
-
# The dataset file actually in effect: an explicit override wins; otherwise a
|
|
69
|
-
# refreshed cache (if present and `use_cache`); otherwise the bundled snapshot.
|
|
70
|
-
# This is how a refresh reaches the running app — no gem upgrade needed.
|
|
71
|
-
def active_data_path
|
|
72
|
-
return @data_path if @data_path
|
|
73
|
-
return Refresher.cached_path if configuration.use_cache && Refresher.cached?
|
|
74
|
+
attr_writer :logger
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
# Serve this file instead (tests, a pinned snapshot). Takes effect on the
|
|
77
|
+
# next call; nil goes back to the cache/bundled rules.
|
|
78
|
+
def data_path=(path)
|
|
79
|
+
@data_path = path&.to_s
|
|
80
|
+
reload!
|
|
76
81
|
end
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
end
|
|
83
|
+
# The explicit `data_path=` override, or nil.
|
|
84
|
+
def data_path_override = @data_path
|
|
81
85
|
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
# The dataset file actually in effect: an explicit override wins; otherwise
|
|
87
|
+
# a refreshed cache when usable (`use_cache`, a schema this gem reads, not
|
|
88
|
+
# older than the bundled data); otherwise the bundled snapshot. This is how
|
|
89
|
+
# a refresh reaches the running app — no gem upgrade, no restart.
|
|
90
|
+
def active_data_path = DataSource.active_path
|
|
91
|
+
|
|
92
|
+
# The Vehicles::Dataset in effect. Every process re-checks the file every
|
|
93
|
+
# `config.reload_check_interval` seconds and adopts a newer one on its own.
|
|
94
|
+
def dataset = DataSource.dataset
|
|
95
|
+
|
|
96
|
+
# Pull the latest published dataset into `config.cache_path`, so data
|
|
97
|
+
# fixes and new makes land WITHOUT a gem upgrade, safely: manifest first,
|
|
98
|
+
# never a downgrade, never a schema this gem cannot read, never a release
|
|
99
|
+
# that lost a chunk of your data (see Vehicles::Refresher). Never raises;
|
|
100
|
+
# returns a Vehicles::RefreshResult (`ok?`, `updated?`, `status`,
|
|
101
|
+
# `reason`, `message`). Schedule it daily (`rails g vehicles:install`).
|
|
102
|
+
# result = Vehicles.refresh
|
|
103
|
+
# Rails.logger.warn(result.message) unless result.ok?
|
|
104
|
+
def refresh = Refresher.refresh
|
|
88
105
|
|
|
89
|
-
#
|
|
90
|
-
#
|
|
106
|
+
# `refresh.ok?` — true when the app has the latest release (just written,
|
|
107
|
+
# or already current). Kept for callers of the pre-0.9.2 boolean API.
|
|
108
|
+
def refresh! = Refresher.refresh!
|
|
109
|
+
|
|
110
|
+
# Drop the in-memory dataset so the next access reloads from disk now
|
|
111
|
+
# (processes also notice a changed file on their own; see `dataset`).
|
|
91
112
|
def reload!
|
|
113
|
+
DataSource.reset!
|
|
92
114
|
Dataset.reset!
|
|
93
115
|
end
|
|
94
116
|
|
|
117
|
+
# ActiveSupport::Notifications when present ("refresh.vehicles",
|
|
118
|
+
# "request.vehicles"); a no-op otherwise. Never raises.
|
|
119
|
+
def instrument(name, payload)
|
|
120
|
+
return unless defined?(ActiveSupport::Notifications)
|
|
121
|
+
|
|
122
|
+
ActiveSupport::Notifications.instrument(name, payload)
|
|
123
|
+
rescue StandardError
|
|
124
|
+
nil
|
|
125
|
+
end
|
|
126
|
+
|
|
95
127
|
# Version of the dataset currently in effect (refreshed cache or bundled),
|
|
96
128
|
# e.g. "2026.06.0".
|
|
97
129
|
def data_version
|
|
@@ -108,8 +140,9 @@ module Vehicles
|
|
|
108
140
|
# Make display names. => ["Abarth", "Alfa Romeo", ...]
|
|
109
141
|
# Vehicles.makes(kind: :motorcycle, region: :as) # continent filter
|
|
110
142
|
# Vehicles.makes(include_other: true) # ... + the "Other" escape hatch
|
|
111
|
-
|
|
112
|
-
|
|
143
|
+
# Vehicles.makes(kind: :car, country: :es) # makes with a car evidenced in Spain
|
|
144
|
+
def makes(kind: nil, region: nil, country: nil, include_other: false)
|
|
145
|
+
names = dataset.makes(kind: kind, region: region || configuration.region, country: country).map(&:name)
|
|
113
146
|
append_other_name(names, include_other)
|
|
114
147
|
end
|
|
115
148
|
|
|
@@ -119,10 +152,15 @@ module Vehicles
|
|
|
119
152
|
# With `include_other:`, an unknown make (e.g. the "Other" make itself) yields
|
|
120
153
|
# just `[other_label]`, so a make→model picker never dead-ends.
|
|
121
154
|
# `fuzzy:` is forwarded to `make` (typo-tolerant by default since 0.7.8).
|
|
122
|
-
|
|
123
|
-
|
|
155
|
+
#
|
|
156
|
+
# With `country:` (ISO alpha-2), only models evidenced there, in that
|
|
157
|
+
# country's own popularity order:
|
|
158
|
+
# Vehicles.models("SEAT", kind: :car, country: :es) # => ["Ibiza", "Arona", "León", ...]
|
|
159
|
+
# A name can repeat when the make sells it as several kinds (pass `kind:`).
|
|
160
|
+
def models(make, kind: nil, body_type: nil, region: nil, rarity: nil, max_decile: nil, country: nil,
|
|
161
|
+
include_other: false, fuzzy: true)
|
|
124
162
|
names = make(make, fuzzy: fuzzy)&.models(kind: kind, body_type: body_type,
|
|
125
|
-
region: region || configuration.region,
|
|
163
|
+
region: region || configuration.region, country: country,
|
|
126
164
|
rarity: rarity, max_decile: max_decile)&.map(&:name) || []
|
|
127
165
|
append_other_name(names, include_other)
|
|
128
166
|
end
|
|
@@ -159,8 +197,14 @@ module Vehicles
|
|
|
159
197
|
# data, not guessing: a spelling claimed by two different models answers
|
|
160
198
|
# nil. `fuzzy:` governs the MAKE typo tolerance only, as before. Need to
|
|
161
199
|
# know how a pair matched, or want cleanup/typo tolerance? `match_model`.
|
|
162
|
-
|
|
163
|
-
|
|
200
|
+
#
|
|
201
|
+
# Kind collisions (one nameplate as several kinds) follow the one rule
|
|
202
|
+
# every VehiclesDB surface shares (Vehicles::KindRule): "Iveco Daily"
|
|
203
|
+
# is the van. `kind:` picks a kind; `kinds:` allows only some (nil when
|
|
204
|
+
# none of them has that name).
|
|
205
|
+
def model(make_name, model_name, fuzzy: true, kind: nil, kinds: nil)
|
|
206
|
+
(found = make(make_name, fuzzy: fuzzy)) &&
|
|
207
|
+
ModelMatcher.exact(found, model_name, kind: KindRule.scope(kind, kinds))
|
|
164
208
|
end
|
|
165
209
|
|
|
166
210
|
# Resolve a stored make + model pair and say HOW: a Vehicles::ModelMatch
|
|
@@ -176,8 +220,13 @@ module Vehicles
|
|
|
176
220
|
# Vehicles.match_model("VW", "Sirocco").rung # => :fuzzy
|
|
177
221
|
# Vehicles.match_model("Honda", "Honda") # => nil
|
|
178
222
|
# See Vehicles::ModelMatcher for the rungs and their rules.
|
|
179
|
-
|
|
180
|
-
|
|
223
|
+
#
|
|
224
|
+
# Safe to write back without asking? `match.confident?` (curated rungs
|
|
225
|
+
# only, never a typo guess, never ambiguous). Scope by kind with `kind:`
|
|
226
|
+
# (one kind) or `kinds:` (a hard list: a record of another kind never
|
|
227
|
+
# answers).
|
|
228
|
+
def match_model(make_name, model_name, kind: nil, kinds: nil)
|
|
229
|
+
ModelMatcher.match(make_name, model_name, kind: kind, kinds: kinds)
|
|
181
230
|
end
|
|
182
231
|
|
|
183
232
|
# Resolve ONE free-text string ("vw golf", "MERCEDES-BENZ SPRINTER",
|
|
@@ -188,9 +237,12 @@ module Vehicles
|
|
|
188
237
|
# Nothing resolves without a make except ids and whole-text aliases.
|
|
189
238
|
# Vehicles.identify("vw tuareg").model # => Volkswagen Touareg (:fuzzy)
|
|
190
239
|
# Vehicles.identify("golf") # => nil
|
|
191
|
-
|
|
240
|
+
# `kind:` scopes the name rungs (an explicit id still answers whatever
|
|
241
|
+
# its kind); `kinds:` is a hard filter on every rung, ids included:
|
|
242
|
+
# Vehicles.identify("bus/iveco/daily", kinds: [:car]) # => nil
|
|
243
|
+
def identify(text, kind: nil, kinds: nil) = TextMatcher.identify(text, kind: kind, kinds: kinds)
|
|
192
244
|
|
|
193
|
-
# License plates (the bundled
|
|
245
|
+
# License plates (the bundled plates dataset). No argument: every
|
|
194
246
|
# jurisdiction. With a code: that jurisdiction (or nil — forgiving).
|
|
195
247
|
# Vehicles.plates # => [#<Jurisdiction nl …>, …]
|
|
196
248
|
# Vehicles.plates(:nl) # => the Netherlands entry (33 series)
|
|
@@ -223,6 +275,7 @@ module Vehicles
|
|
|
223
275
|
# Vehicles.top_models(kind: :car, country: :nl, limit: 10).map(&:name)
|
|
224
276
|
# Vehicles.top_models(kind: :motorcycle, region: :as, limit: 10)
|
|
225
277
|
# Vehicles.top_models(kind: :car, by: :mass_decile) # by registration mass (data 2026.10.1+)
|
|
278
|
+
# `limit: nil` returns every match (for a per-country picker, prefer `catalog(country:)`).
|
|
226
279
|
def top_models(kind: nil, country: nil, region: nil, limit: 20, by: :global_decile)
|
|
227
280
|
dataset.top_models(kind: kind, country: country, region: region, limit: limit, by: by)
|
|
228
281
|
end
|
|
@@ -238,43 +291,22 @@ module Vehicles
|
|
|
238
291
|
|
|
239
292
|
# Continent codes present in the dataset. => [:af, :as, :eu, :na, :oc, :sa]
|
|
240
293
|
def regions
|
|
241
|
-
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
# --- colors (canonical reference palette) --------------------------------
|
|
245
|
-
|
|
246
|
-
# The canonical color palette, frequency-ordered. => [Vehicles::Color, ...]
|
|
247
|
-
def colors
|
|
248
|
-
Colors::ALL
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
# [[name, slug], ...] for a Rails `select`. Names are English — localize the
|
|
252
|
-
# labels in your app; the slug is the stable value you store.
|
|
253
|
-
def color_options
|
|
254
|
-
Colors::ALL.map { |c| [c.name, c.slug] }
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
# Resolve a color by slug or name (forgiving: case, diacritics, synonyms like
|
|
258
|
-
# "gray"→grey, "navy"→blue). Returns a Vehicles::Color, or nil.
|
|
259
|
-
def color(query)
|
|
260
|
-
q = normalize(query)
|
|
261
|
-
return nil if q.empty?
|
|
262
|
-
|
|
263
|
-
Colors::BY_SLUG[q] || Colors::BY_NAME[q] || Colors::BY_SLUG[Colors::SYNONYMS[q]]
|
|
294
|
+
dataset.regions
|
|
264
295
|
end
|
|
265
296
|
|
|
266
297
|
# [[label, value], ...] of makes for a Rails `select`.
|
|
267
298
|
# Vehicles.make_options(include_other: true) # ... + [other_label, "other"]
|
|
268
|
-
def make_options(kind: nil, region: nil, include_other: false)
|
|
269
|
-
opts = dataset.makes(kind: kind, region: region || configuration.region
|
|
299
|
+
def make_options(kind: nil, region: nil, country: nil, include_other: false)
|
|
300
|
+
opts = dataset.makes(kind: kind, region: region || configuration.region, country: country)
|
|
301
|
+
.map { |m| [m.name, m.slug] }
|
|
270
302
|
append_other_option(opts, include_other)
|
|
271
303
|
end
|
|
272
304
|
|
|
273
305
|
# [[label, value], ...] of a make's models for a Rails `select`. Unknown => [].
|
|
274
306
|
# Vehicles.model_options("audi", include_other: true) # ... + [other_label, "other"]
|
|
275
|
-
def model_options(make, kind: nil, body_type: nil, include_other: false, fuzzy: true)
|
|
307
|
+
def model_options(make, kind: nil, body_type: nil, country: nil, include_other: false, fuzzy: true)
|
|
276
308
|
found = make(make, fuzzy: fuzzy)
|
|
277
|
-
opts = found ? found.model_options(kind: kind, body_type: body_type) : []
|
|
309
|
+
opts = found ? found.model_options(kind: kind, body_type: body_type, country: country) : []
|
|
278
310
|
append_other_option(opts, include_other)
|
|
279
311
|
end
|
|
280
312
|
|
|
@@ -284,14 +316,27 @@ module Vehicles
|
|
|
284
316
|
|
|
285
317
|
# A { make => [model names] } map for the given filters — everything you need
|
|
286
318
|
# to build a dependent make → model picker entirely on the client: embed it
|
|
287
|
-
# once
|
|
288
|
-
#
|
|
289
|
-
#
|
|
290
|
-
#
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
319
|
+
# once and switch the model list in a few lines of JS (or hand it to the
|
|
320
|
+
# make-model-dropdown widget). No endpoint, no extra request, instant.
|
|
321
|
+
# Makes are alphabetical.
|
|
322
|
+
#
|
|
323
|
+
# Scope it to your market with `country:` (ISO alpha-2): only models with
|
|
324
|
+
# official evidence there, each make's models in that country's popularity
|
|
325
|
+
# order. That is the picker list a one-country app wants — Spain's cars are
|
|
326
|
+
# ~140 makes / ~1,300 models, ~14 KB of JSON — where the whole global car
|
|
327
|
+
# catalog is ~330 makes / ~6,000 models (~63 KB).
|
|
328
|
+
# Vehicles.catalog(kind: :car, country: :es)["SEAT"] # => ["Ibiza", "Arona", "León", ...]
|
|
329
|
+
# Vehicles.catalog(kind: :car) # => every car, worldwide
|
|
330
|
+
# Pass `kind:` for a picker: without it a nameplate sold as a car and a
|
|
331
|
+
# van appears twice.
|
|
332
|
+
# `kinds:` allows several kinds (a car-and-van picker); `[]` is empty.
|
|
333
|
+
def catalog(kind: nil, region: nil, country: nil, kinds: nil)
|
|
334
|
+
scope = KindRule.scope(kind, kinds)
|
|
335
|
+
region ||= configuration.region
|
|
336
|
+
dataset.makes(kind: kind, region: region, country: country).filter_map do |make|
|
|
337
|
+
names = make.models(region: region, country: country).select { |m| KindRule.allows?(scope, m) }.map(&:name)
|
|
338
|
+
[make.name, names.uniq] unless names.empty?
|
|
339
|
+
end.to_h
|
|
295
340
|
end
|
|
296
341
|
|
|
297
342
|
# --- provider resolution (hosted enrichment, optional) -------------------
|
|
@@ -363,6 +408,13 @@ module Vehicles
|
|
|
363
408
|
# they become Vehicles singleton methods with access to `configuration` /
|
|
364
409
|
# `normalize`.
|
|
365
410
|
extend OtherOption
|
|
411
|
+
# Canonical-id lookups (`by_id`, `successor`, `live?`) and the en/es label
|
|
412
|
+
# helpers (`kind_label`, `kind_options`, `body_type_label`,
|
|
413
|
+
# `body_type_options`), extended the same way.
|
|
414
|
+
extend IdLookup
|
|
415
|
+
extend LabelOptions
|
|
416
|
+
# `Vehicles.dataset_slice` (lib/vehicles/dataset_slice.rb).
|
|
417
|
+
extend DatasetSliceExport
|
|
366
418
|
end
|
|
367
419
|
|
|
368
420
|
# Loaded after the module so its lookup tables can use Vehicles.normalize.
|
data/rakelib/data_refresh.rake
CHANGED
|
@@ -14,7 +14,7 @@ require "json"
|
|
|
14
14
|
# are copied from the SAME tag's catalog/<kind>/models.json
|
|
15
15
|
# (popularity.by_country[cc].rank — open CC-BY data), keyed by the full
|
|
16
16
|
# kind/make/model id, so the merged file is what the pipeline's own projection
|
|
17
|
-
# emits once it ships the key (
|
|
17
|
+
# emits once it ships the key (the VehiclesDB data pipeline,
|
|
18
18
|
# 2026-10-10). When the projection DOES carry the key, the task asserts every
|
|
19
19
|
# rank equals the catalog's and aborts on any mismatch. Output formatting is
|
|
20
20
|
# the pipeline's (JSON.pretty_generate + "\n"); stripping `country_ranks`
|
data/rakelib/plates_refresh.rake
CHANGED
|
@@ -13,7 +13,7 @@ namespace :plates do
|
|
|
13
13
|
rm_rf "#{root}/data/plates/_art"
|
|
14
14
|
commit = `git -C #{ds} rev-parse origin/main`.strip
|
|
15
15
|
File.write("#{root}/data/plates/PROVENANCE.md", <<~MD)
|
|
16
|
-
# Bundled plates dataset (
|
|
16
|
+
# Bundled plates dataset (full corpus)
|
|
17
17
|
|
|
18
18
|
Copy of `plates/` from the public data repo (EXCLUDING `_art/`),
|
|
19
19
|
refreshed on gem releases:
|
data/sig/vehicles.rbs
CHANGED
|
@@ -13,26 +13,35 @@ module Vehicles
|
|
|
13
13
|
def self.data_version: () -> String?
|
|
14
14
|
def self.region: () -> Symbol
|
|
15
15
|
|
|
16
|
-
def self.makes: (?kind: Symbol?, ?region: Symbol?) -> Array[String]
|
|
17
|
-
def self.models: (untyped make, ?kind: Symbol?, ?body_type: Symbol?, ?region: Symbol?) -> Array[String]
|
|
16
|
+
def self.makes: (?kind: Symbol?, ?region: Symbol?, ?country: untyped, ?include_other: bool) -> Array[String]
|
|
17
|
+
def self.models: (untyped make, ?kind: Symbol?, ?body_type: Symbol?, ?region: Symbol?, ?country: untyped) -> Array[String]
|
|
18
18
|
def self.make: (untyped query) -> Make?
|
|
19
19
|
def self.find: (untyped query) -> Model?
|
|
20
|
-
def self.model: (untyped make_name, untyped model_name, ?fuzzy: bool, ?kind: Symbol?) -> Model?
|
|
21
|
-
def self.match_model: (untyped make_name, untyped model_name, ?kind: Symbol?) -> ModelMatch?
|
|
22
|
-
def self.identify: (untyped text, ?kind: Symbol?) -> ModelMatch?
|
|
20
|
+
def self.model: (untyped make_name, untyped model_name, ?fuzzy: bool, ?kind: Symbol?, ?kinds: Array[Symbol]?) -> Model?
|
|
21
|
+
def self.match_model: (untyped make_name, untyped model_name, ?kind: Symbol?, ?kinds: Array[Symbol]?) -> ModelMatch?
|
|
22
|
+
def self.identify: (untyped text, ?kind: Symbol?, ?kinds: Array[Symbol]?) -> ModelMatch?
|
|
23
|
+
def self.by_id: (untyped id, ?follow: bool, ?kinds: Array[Symbol]?) -> Model?
|
|
24
|
+
def self.successor: (untyped id) -> String?
|
|
25
|
+
def self.live?: (untyped id) -> bool
|
|
23
26
|
def self.search: (untyped query) -> Array[Model]
|
|
24
|
-
def self.make_options: (?kind: Symbol?, ?region: Symbol?) -> Array[[String, String]]
|
|
25
|
-
def self.model_options: (untyped make, ?kind: Symbol?, ?body_type: Symbol?) -> Array[[String, String]]
|
|
26
|
-
def self.catalog: (?kind: Symbol?, ?region: Symbol?) -> Hash[String, Array[String]]
|
|
27
|
+
def self.make_options: (?kind: Symbol?, ?region: Symbol?, ?country: untyped) -> Array[[String, String]]
|
|
28
|
+
def self.model_options: (untyped make, ?kind: Symbol?, ?body_type: Symbol?, ?country: untyped) -> Array[[String, String]]
|
|
29
|
+
def self.catalog: (?kind: Symbol?, ?region: Symbol?, ?country: untyped, ?kinds: Array[Symbol]?) -> Hash[String, Array[String]]
|
|
30
|
+
def self.dataset_slice: (?kind: Symbol?, ?region: Symbol?, ?country: untyped, ?kinds: Array[Symbol]?) -> Hash[String, untyped]
|
|
27
31
|
|
|
28
|
-
def self.colors: () -> Array[Color]
|
|
29
|
-
def self.color: (untyped query) -> Color?
|
|
30
|
-
def self.color_options: () -> Array[[String, String]]
|
|
32
|
+
def self.colors: (?locale: untyped) -> Array[Color]
|
|
33
|
+
def self.color: (untyped query, ?locale: untyped) -> Color?
|
|
34
|
+
def self.color_options: (?locale: untyped) -> Array[[String, String]]
|
|
35
|
+
def self.kind_label: (untyped kind, ?locale: untyped) -> String?
|
|
36
|
+
def self.kind_options: (?locale: untyped, ?kinds: Array[Symbol]) -> Array[[String, String]]
|
|
37
|
+
def self.body_type_label: (untyped body_type, ?locale: untyped) -> String?
|
|
38
|
+
def self.body_type_options: (?locale: untyped) -> Array[[String, String]]
|
|
31
39
|
|
|
32
40
|
def self.dataset: () -> Dataset
|
|
33
41
|
def self.data_path: () -> String
|
|
34
42
|
def self.data_path=: (String) -> void
|
|
35
43
|
def self.active_data_path: () -> String
|
|
44
|
+
def self.refresh: () -> RefreshResult
|
|
36
45
|
def self.refresh!: () -> bool
|
|
37
46
|
def self.reload!: () -> void
|
|
38
47
|
def self.normalize: (untyped str) -> String
|
|
@@ -53,6 +62,43 @@ module Vehicles
|
|
|
53
62
|
attr_accessor refresh_timeout: Integer
|
|
54
63
|
attr_accessor plate_template_cache: untyped
|
|
55
64
|
attr_accessor plate_template_ttl: Integer
|
|
65
|
+
attr_accessor manifest_url: String?
|
|
66
|
+
attr_accessor cache_dir: String
|
|
67
|
+
attr_accessor reload_check_interval: Numeric?
|
|
68
|
+
attr_accessor refresh_min_ratio: Float?
|
|
69
|
+
attr_accessor refresh_guard: (^(Dataset, Dataset) -> boolish)?
|
|
70
|
+
attr_accessor on_hosted_error: (^(HostedResult) -> void)?
|
|
71
|
+
attr_accessor image_cache: untyped
|
|
72
|
+
attr_accessor image_cache_ttl: Integer
|
|
73
|
+
attr_accessor image_miss_ttl: Integer
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class RefreshResult
|
|
77
|
+
attr_reader status: Symbol
|
|
78
|
+
attr_reader reason: Symbol?
|
|
79
|
+
attr_reader version: String?
|
|
80
|
+
attr_reader previous_version: String?
|
|
81
|
+
attr_reader detail: String?
|
|
82
|
+
def ok?: () -> bool
|
|
83
|
+
def updated?: () -> bool
|
|
84
|
+
def message: () -> String
|
|
85
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class HostedResult
|
|
89
|
+
attr_reader reason: Symbol
|
|
90
|
+
attr_reader status: Integer?
|
|
91
|
+
attr_reader error: String?
|
|
92
|
+
attr_reader payload: Hash[String, untyped]?
|
|
93
|
+
attr_reader path: String?
|
|
94
|
+
def ok?: () -> bool
|
|
95
|
+
def cached?: () -> bool
|
|
96
|
+
def url: (?Symbol size) -> String?
|
|
97
|
+
def to_error: () -> HostedError
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class HostedError < StandardError
|
|
101
|
+
attr_accessor result: HostedResult
|
|
56
102
|
end
|
|
57
103
|
|
|
58
104
|
module Plates
|
|
@@ -77,7 +123,8 @@ module Vehicles
|
|
|
77
123
|
attr_reader aliases: Array[String]
|
|
78
124
|
attr_reader kinds: Array[Symbol]
|
|
79
125
|
def models: (?kind: Symbol?, ?body_type: Symbol?) -> Array[Model]
|
|
80
|
-
|
|
126
|
+
attr_reader former_slugs: Array[String]
|
|
127
|
+
def model: (untyped query, ?kind: Symbol?, ?kinds: Array[Symbol]?) -> Model?
|
|
81
128
|
def model_names: (?kind: Symbol?, ?body_type: Symbol?) -> Array[String]
|
|
82
129
|
def model_options: (?kind: Symbol?, ?body_type: Symbol?) -> Array[[String, String]]
|
|
83
130
|
def to_h: () -> Hash[Symbol, untyped]
|
|
@@ -91,6 +138,7 @@ module Vehicles
|
|
|
91
138
|
attr_reader via: Array[String]
|
|
92
139
|
attr_reader candidates: Array[Model]
|
|
93
140
|
def ambiguous?: () -> bool
|
|
141
|
+
def confident?: () -> bool
|
|
94
142
|
def to_h: () -> Hash[Symbol, untyped]
|
|
95
143
|
end
|
|
96
144
|
|
|
@@ -98,6 +146,7 @@ module Vehicles
|
|
|
98
146
|
attr_reader slug: String
|
|
99
147
|
attr_reader name: String
|
|
100
148
|
attr_reader hex: String
|
|
149
|
+
def label: (?untyped locale) -> String
|
|
101
150
|
def to_h: () -> Hash[Symbol, String]
|
|
102
151
|
end
|
|
103
152
|
|
|
@@ -111,6 +160,9 @@ module Vehicles
|
|
|
111
160
|
attr_reader body_type: Symbol
|
|
112
161
|
attr_reader aliases: Array[String]
|
|
113
162
|
attr_reader forms: Array[String]
|
|
163
|
+
attr_reader id: String
|
|
164
|
+
attr_reader former_ids: Array[String]
|
|
165
|
+
attr_reader registration_years: { first: Integer, last: Integer }?
|
|
114
166
|
def full_name: () -> String
|
|
115
167
|
def to_s: () -> String
|
|
116
168
|
def slug: () -> String
|
|
@@ -122,6 +174,8 @@ module Vehicles
|
|
|
122
174
|
def hatchback?: () -> bool
|
|
123
175
|
def years: () -> Range[Integer]?
|
|
124
176
|
def segment: () -> Symbol?
|
|
125
|
-
def image: (?year: Integer?, ?color: untyped) -> String?
|
|
177
|
+
def image: (?year: Integer?, ?color: untyped, ?size: Symbol, ?exact_color: bool) -> String?
|
|
178
|
+
def images: (?color: untyped, ?year: Integer?, ?exact_color: bool) -> Hash[String, untyped]?
|
|
179
|
+
def images_result: (?color: untyped, ?year: Integer?, ?exact_color: bool) -> HostedResult
|
|
126
180
|
end
|
|
127
181
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vehicles
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-11-
|
|
10
|
+
date: 2026-11-11 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: vehicles ships a curated, bundled dataset of car makes and models (with
|
|
13
13
|
kind and body type) and a delightful, Rails-friendly API for vehicle dropdowns,
|
|
@@ -96,6 +96,7 @@ files:
|
|
|
96
96
|
- data/plates/de.yml
|
|
97
97
|
- data/plates/dk.yml
|
|
98
98
|
- data/plates/ee.yml
|
|
99
|
+
- data/plates/eg.yml
|
|
99
100
|
- data/plates/es.yml
|
|
100
101
|
- data/plates/fi.yml
|
|
101
102
|
- data/plates/fr.yml
|
|
@@ -117,6 +118,7 @@ files:
|
|
|
117
118
|
- data/plates/mt.yml
|
|
118
119
|
- data/plates/mx.yml
|
|
119
120
|
- data/plates/my.yml
|
|
121
|
+
- data/plates/ng.yml
|
|
120
122
|
- data/plates/nl.yml
|
|
121
123
|
- data/plates/no.yml
|
|
122
124
|
- data/plates/nz.yml
|
|
@@ -128,6 +130,7 @@ files:
|
|
|
128
130
|
- data/plates/sk.yml
|
|
129
131
|
- data/plates/sm.yml
|
|
130
132
|
- data/plates/th.yml
|
|
133
|
+
- data/plates/tr.yml
|
|
131
134
|
- data/plates/ua.yml
|
|
132
135
|
- data/plates/us-ak.yml
|
|
133
136
|
- data/plates/us-al.yml
|
|
@@ -196,10 +199,16 @@ files:
|
|
|
196
199
|
- lib/vehicles/color.rb
|
|
197
200
|
- lib/vehicles/colors.rb
|
|
198
201
|
- lib/vehicles/configuration.rb
|
|
202
|
+
- lib/vehicles/data_source.rb
|
|
199
203
|
- lib/vehicles/dataset.rb
|
|
200
204
|
- lib/vehicles/dataset_resolution.rb
|
|
205
|
+
- lib/vehicles/dataset_slice.rb
|
|
201
206
|
- lib/vehicles/fuzzy.rb
|
|
207
|
+
- lib/vehicles/hosted_result.rb
|
|
202
208
|
- lib/vehicles/http.rb
|
|
209
|
+
- lib/vehicles/id_lookup.rb
|
|
210
|
+
- lib/vehicles/kind_rule.rb
|
|
211
|
+
- lib/vehicles/labels.rb
|
|
203
212
|
- lib/vehicles/make.rb
|
|
204
213
|
- lib/vehicles/mcp_server.rb
|
|
205
214
|
- lib/vehicles/model.rb
|
|
@@ -214,7 +223,9 @@ files:
|
|
|
214
223
|
- lib/vehicles/providers/hosted_provider.rb
|
|
215
224
|
- lib/vehicles/providers/local_provider.rb
|
|
216
225
|
- lib/vehicles/railtie.rb
|
|
226
|
+
- lib/vehicles/refresh_result.rb
|
|
217
227
|
- lib/vehicles/refresher.rb
|
|
228
|
+
- lib/vehicles/release_checks.rb
|
|
218
229
|
- lib/vehicles/text_matcher.rb
|
|
219
230
|
- lib/vehicles/validators/vehicle_make_validator.rb
|
|
220
231
|
- lib/vehicles/validators/vehicle_model_validator.rb
|