@meterapp/vehicle-db 2.5.0 → 2.7.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.
- package/README.md +131 -4
- package/dist/data.d.ts +14 -0
- package/dist/data.js +1 -1
- package/dist/index.d.ts +75 -2
- package/dist/index.js +138 -3
- package/dist/search.d.ts +47 -0
- package/dist/search.js +482 -0
- package/package.json +26 -2
package/dist/index.d.ts
CHANGED
|
@@ -39,8 +39,61 @@ export interface GetModelsOptions {
|
|
|
39
39
|
year?: number;
|
|
40
40
|
vehicleTypeId?: number;
|
|
41
41
|
makeId?: number;
|
|
42
|
+
modelId?: number;
|
|
42
43
|
sourceId?: string;
|
|
43
44
|
}
|
|
45
|
+
export interface GetAvailableYearsOptions {
|
|
46
|
+
vehicleTypeId?: number;
|
|
47
|
+
makeId?: number;
|
|
48
|
+
modelId?: number;
|
|
49
|
+
sourceId?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface VehicleYearRange {
|
|
52
|
+
yearFrom: number;
|
|
53
|
+
yearTo: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A sourced period in which a model shares one exterior design. Ranges may
|
|
57
|
+
* overlap when a facelift, body variant, or regional model changed during a
|
|
58
|
+
* calendar year; callers must not treat an overlap as render-equivalent.
|
|
59
|
+
*/
|
|
60
|
+
export interface VehicleAppearanceRange {
|
|
61
|
+
appearanceId: string;
|
|
62
|
+
makeId: number;
|
|
63
|
+
makeName: string;
|
|
64
|
+
modelName: string;
|
|
65
|
+
label: string;
|
|
66
|
+
bodyStyle: string;
|
|
67
|
+
yearFrom: number;
|
|
68
|
+
yearTo: number;
|
|
69
|
+
representativeYear: number;
|
|
70
|
+
regions: string[];
|
|
71
|
+
sourceName: string;
|
|
72
|
+
sourceUrl: string;
|
|
73
|
+
}
|
|
74
|
+
export interface GetModelAppearanceRangesOptions {
|
|
75
|
+
makeId: number;
|
|
76
|
+
modelName: string;
|
|
77
|
+
year?: number;
|
|
78
|
+
}
|
|
79
|
+
export interface GetModelRenderGroupsOptions extends GetModelAppearanceRangesOptions {
|
|
80
|
+
vehicleTypeId?: number;
|
|
81
|
+
sourceId?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Safe cache groups for every available year. Sourced, unambiguous appearance
|
|
85
|
+
* periods are grouped; unknown and overlapping years deliberately stay as
|
|
86
|
+
* singleton exact-year groups.
|
|
87
|
+
*/
|
|
88
|
+
export interface VehicleRenderGroup {
|
|
89
|
+
groupId: string;
|
|
90
|
+
kind: "appearance" | "year";
|
|
91
|
+
years: number[];
|
|
92
|
+
yearFrom: number;
|
|
93
|
+
yearTo: number;
|
|
94
|
+
representativeYear: number;
|
|
95
|
+
appearance?: VehicleAppearanceRange;
|
|
96
|
+
}
|
|
44
97
|
/**
|
|
45
98
|
* Returns all vehicle types in the database.
|
|
46
99
|
*/
|
|
@@ -58,6 +111,26 @@ export declare function getMakes(options?: GetMakesOptions): Make[];
|
|
|
58
111
|
*/
|
|
59
112
|
export declare function getModels(options?: GetModelsOptions): Model[];
|
|
60
113
|
/**
|
|
61
|
-
*
|
|
114
|
+
* Gets available years, optionally filtered by vehicle identity and source.
|
|
115
|
+
*/
|
|
116
|
+
export declare function getAvailableYears(options?: GetAvailableYearsOptions): number[];
|
|
117
|
+
/**
|
|
118
|
+
* Compresses available catalog years into contiguous ranges. These ranges
|
|
119
|
+
* describe availability only; use getModelAppearanceRanges when exterior
|
|
120
|
+
* equivalence matters.
|
|
121
|
+
*/
|
|
122
|
+
export declare function getAvailableYearRanges(options?: GetAvailableYearsOptions): VehicleYearRange[];
|
|
123
|
+
/** Returns the curated, source-backed exterior-design periods for a model. */
|
|
124
|
+
export declare function getModelAppearanceRanges(options: GetModelAppearanceRangesOptions): VehicleAppearanceRange[];
|
|
125
|
+
/**
|
|
126
|
+
* Returns a complete, non-overlapping render plan for a model. A year is only
|
|
127
|
+
* grouped when exactly one verified appearance range contains it.
|
|
128
|
+
*/
|
|
129
|
+
export declare function getModelRenderGroups(options: GetModelRenderGroupsOptions): VehicleRenderGroup[];
|
|
130
|
+
/**
|
|
131
|
+
* Canonical year for cache/storage identity. Unknown, unavailable, and
|
|
132
|
+
* ambiguous years return themselves, which is the fail-closed behavior.
|
|
62
133
|
*/
|
|
63
|
-
export declare function
|
|
134
|
+
export declare function getRepresentativeYear(options: GetModelRenderGroupsOptions & {
|
|
135
|
+
year: number;
|
|
136
|
+
}): number;
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,10 @@ exports.getDataSources = getDataSources;
|
|
|
8
8
|
exports.getMakes = getMakes;
|
|
9
9
|
exports.getModels = getModels;
|
|
10
10
|
exports.getAvailableYears = getAvailableYears;
|
|
11
|
+
exports.getAvailableYearRanges = getAvailableYearRanges;
|
|
12
|
+
exports.getModelAppearanceRanges = getModelAppearanceRanges;
|
|
13
|
+
exports.getModelRenderGroups = getModelRenderGroups;
|
|
14
|
+
exports.getRepresentativeYear = getRepresentativeYear;
|
|
11
15
|
const data_1 = __importDefault(require("./data"));
|
|
12
16
|
// ---------------------------------------------------------------------------
|
|
13
17
|
// Internal lookup maps (lazy)
|
|
@@ -41,6 +45,56 @@ function getSourceIds(sourceMask) {
|
|
|
41
45
|
}
|
|
42
46
|
return sourceIds;
|
|
43
47
|
}
|
|
48
|
+
function normalizeLookupName(value) {
|
|
49
|
+
return value.trim().toLocaleUpperCase("en-US").replace(/\s+/g, " ");
|
|
50
|
+
}
|
|
51
|
+
function compactYears(years) {
|
|
52
|
+
const sorted = [...new Set(years)].sort((left, right) => left - right);
|
|
53
|
+
const ranges = [];
|
|
54
|
+
for (const year of sorted) {
|
|
55
|
+
const previous = ranges[ranges.length - 1];
|
|
56
|
+
if (previous && previous.yearTo + 1 === year)
|
|
57
|
+
previous.yearTo = year;
|
|
58
|
+
else
|
|
59
|
+
ranges.push({ yearFrom: year, yearTo: year });
|
|
60
|
+
}
|
|
61
|
+
return ranges;
|
|
62
|
+
}
|
|
63
|
+
function modelNameYears(options) {
|
|
64
|
+
const sourceMask = options.sourceId == null ? undefined : getSourceMaskMap().get(options.sourceId);
|
|
65
|
+
if (options.sourceId != null && sourceMask == null)
|
|
66
|
+
return [];
|
|
67
|
+
const wantedModel = normalizeLookupName(options.modelName);
|
|
68
|
+
const years = new Set();
|
|
69
|
+
for (const model of data_1.default.models) {
|
|
70
|
+
if (model[1] !== options.makeId)
|
|
71
|
+
continue;
|
|
72
|
+
if (options.vehicleTypeId != null && model[4] !== options.vehicleTypeId)
|
|
73
|
+
continue;
|
|
74
|
+
if (sourceMask != null && (model[5] & sourceMask) === 0)
|
|
75
|
+
continue;
|
|
76
|
+
if (normalizeLookupName(data_1.default.modelNames[model[3]]) !== wantedModel)
|
|
77
|
+
continue;
|
|
78
|
+
years.add(model[0]);
|
|
79
|
+
}
|
|
80
|
+
return [...years].sort((left, right) => left - right);
|
|
81
|
+
}
|
|
82
|
+
function toAppearanceRange(range) {
|
|
83
|
+
return {
|
|
84
|
+
appearanceId: range.id,
|
|
85
|
+
makeId: range.make_id,
|
|
86
|
+
makeName: range.make_name,
|
|
87
|
+
modelName: range.model_name,
|
|
88
|
+
label: range.label,
|
|
89
|
+
bodyStyle: range.body_style,
|
|
90
|
+
yearFrom: range.year_from,
|
|
91
|
+
yearTo: range.year_to,
|
|
92
|
+
representativeYear: range.representative_year,
|
|
93
|
+
regions: [...range.regions],
|
|
94
|
+
sourceName: range.source_name,
|
|
95
|
+
sourceUrl: range.source_url,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
44
98
|
// ---------------------------------------------------------------------------
|
|
45
99
|
// Public API
|
|
46
100
|
// ---------------------------------------------------------------------------
|
|
@@ -105,7 +159,7 @@ function getMakes(options = {}) {
|
|
|
105
159
|
function getModels(options = {}) {
|
|
106
160
|
const makeMap = getMakeMap();
|
|
107
161
|
const typeMap = getTypeMap();
|
|
108
|
-
const { year, vehicleTypeId, makeId, sourceId } = options;
|
|
162
|
+
const { year, vehicleTypeId, makeId, modelId, sourceId } = options;
|
|
109
163
|
const sourceMask = sourceId == null ? undefined : getSourceMaskMap().get(sourceId);
|
|
110
164
|
if (sourceId != null && sourceMask == null)
|
|
111
165
|
return [];
|
|
@@ -115,6 +169,8 @@ function getModels(options = {}) {
|
|
|
115
169
|
continue;
|
|
116
170
|
if (makeId != null && m[1] !== makeId)
|
|
117
171
|
continue;
|
|
172
|
+
if (modelId != null && m[2] !== modelId)
|
|
173
|
+
continue;
|
|
118
174
|
if (vehicleTypeId != null && m[4] !== vehicleTypeId)
|
|
119
175
|
continue;
|
|
120
176
|
if (sourceMask != null && (m[5] & sourceMask) === 0)
|
|
@@ -133,12 +189,91 @@ function getModels(options = {}) {
|
|
|
133
189
|
a.vehicleTypeId - b.vehicleTypeId);
|
|
134
190
|
}
|
|
135
191
|
/**
|
|
136
|
-
*
|
|
192
|
+
* Gets available years, optionally filtered by vehicle identity and source.
|
|
137
193
|
*/
|
|
138
|
-
function getAvailableYears() {
|
|
194
|
+
function getAvailableYears(options = {}) {
|
|
195
|
+
const { vehicleTypeId, makeId, modelId, sourceId } = options;
|
|
196
|
+
const sourceMask = sourceId == null ? undefined : getSourceMaskMap().get(sourceId);
|
|
197
|
+
if (sourceId != null && sourceMask == null)
|
|
198
|
+
return [];
|
|
139
199
|
const years = new Set();
|
|
140
200
|
for (const m of data_1.default.models) {
|
|
201
|
+
if (makeId != null && m[1] !== makeId)
|
|
202
|
+
continue;
|
|
203
|
+
if (modelId != null && m[2] !== modelId)
|
|
204
|
+
continue;
|
|
205
|
+
if (vehicleTypeId != null && m[4] !== vehicleTypeId)
|
|
206
|
+
continue;
|
|
207
|
+
if (sourceMask != null && (m[5] & sourceMask) === 0)
|
|
208
|
+
continue;
|
|
141
209
|
years.add(m[0]);
|
|
142
210
|
}
|
|
143
211
|
return [...years].sort((a, b) => a - b);
|
|
144
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Compresses available catalog years into contiguous ranges. These ranges
|
|
215
|
+
* describe availability only; use getModelAppearanceRanges when exterior
|
|
216
|
+
* equivalence matters.
|
|
217
|
+
*/
|
|
218
|
+
function getAvailableYearRanges(options = {}) {
|
|
219
|
+
return compactYears(getAvailableYears(options));
|
|
220
|
+
}
|
|
221
|
+
/** Returns the curated, source-backed exterior-design periods for a model. */
|
|
222
|
+
function getModelAppearanceRanges(options) {
|
|
223
|
+
const wantedModel = normalizeLookupName(options.modelName);
|
|
224
|
+
return data_1.default.appearanceRanges
|
|
225
|
+
.filter((range) => range.make_id === options.makeId &&
|
|
226
|
+
normalizeLookupName(range.model_name) === wantedModel &&
|
|
227
|
+
(options.year == null ||
|
|
228
|
+
(range.year_from <= options.year && range.year_to >= options.year)))
|
|
229
|
+
.map(toAppearanceRange);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Returns a complete, non-overlapping render plan for a model. A year is only
|
|
233
|
+
* grouped when exactly one verified appearance range contains it.
|
|
234
|
+
*/
|
|
235
|
+
function getModelRenderGroups(options) {
|
|
236
|
+
const years = modelNameYears(options);
|
|
237
|
+
const appearances = getModelAppearanceRanges(options);
|
|
238
|
+
const groupYears = new Map();
|
|
239
|
+
const appearanceByGroup = new Map();
|
|
240
|
+
for (const year of years) {
|
|
241
|
+
const candidates = appearances.filter((appearance) => appearance.yearFrom <= year && appearance.yearTo >= year);
|
|
242
|
+
const groupId = candidates.length === 1 ? candidates[0].appearanceId : `year:${year}`;
|
|
243
|
+
const existing = groupYears.get(groupId);
|
|
244
|
+
if (existing)
|
|
245
|
+
existing.push(year);
|
|
246
|
+
else
|
|
247
|
+
groupYears.set(groupId, [year]);
|
|
248
|
+
if (candidates.length === 1)
|
|
249
|
+
appearanceByGroup.set(groupId, candidates[0]);
|
|
250
|
+
}
|
|
251
|
+
return [...groupYears.entries()]
|
|
252
|
+
.map(([groupId, groupedYears]) => {
|
|
253
|
+
const appearance = appearanceByGroup.get(groupId);
|
|
254
|
+
const preferred = appearance?.representativeYear;
|
|
255
|
+
const representativeYear = preferred != null && groupedYears.includes(preferred)
|
|
256
|
+
? preferred
|
|
257
|
+
: groupedYears.reduce((best, year) => preferred != null && Math.abs(year - preferred) < Math.abs(best - preferred)
|
|
258
|
+
? year
|
|
259
|
+
: best);
|
|
260
|
+
return {
|
|
261
|
+
groupId,
|
|
262
|
+
kind: appearance ? "appearance" : "year",
|
|
263
|
+
years: [...groupedYears],
|
|
264
|
+
yearFrom: groupedYears[0],
|
|
265
|
+
yearTo: groupedYears[groupedYears.length - 1],
|
|
266
|
+
representativeYear,
|
|
267
|
+
...(appearance ? { appearance } : {}),
|
|
268
|
+
};
|
|
269
|
+
})
|
|
270
|
+
.sort((left, right) => left.yearFrom - right.yearFrom || left.yearTo - right.yearTo);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Canonical year for cache/storage identity. Unknown, unavailable, and
|
|
274
|
+
* ambiguous years return themselves, which is the fail-closed behavior.
|
|
275
|
+
*/
|
|
276
|
+
function getRepresentativeYear(options) {
|
|
277
|
+
const group = getModelRenderGroups(options).find((candidate) => candidate.years.includes(options.year));
|
|
278
|
+
return group?.representativeYear ?? options.year;
|
|
279
|
+
}
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type VehicleSearchMatchKind = "exact" | "prefix" | "token" | "contains" | "fuzzy";
|
|
2
|
+
export interface SearchVehiclesOptions {
|
|
3
|
+
/** Restrict results to one catalog year. A year in the query is detected automatically. */
|
|
4
|
+
year?: number;
|
|
5
|
+
vehicleTypeId?: number;
|
|
6
|
+
sourceId?: string;
|
|
7
|
+
/** Maximum number of results. Defaults to 10 and is capped at 100. */
|
|
8
|
+
limit?: number;
|
|
9
|
+
/** Enables conservative typo matching. Defaults to true. */
|
|
10
|
+
fuzzy?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface VehicleSearchResultBase {
|
|
13
|
+
makeId: number;
|
|
14
|
+
makeName: string;
|
|
15
|
+
/** Available years after applying the supplied filters, sorted ascending. */
|
|
16
|
+
years: number[];
|
|
17
|
+
sourceIds: string[];
|
|
18
|
+
matchKind: VehicleSearchMatchKind;
|
|
19
|
+
}
|
|
20
|
+
export interface VehicleMakeSearchResult extends VehicleSearchResultBase {
|
|
21
|
+
kind: "make";
|
|
22
|
+
}
|
|
23
|
+
export interface VehicleSearchVariant {
|
|
24
|
+
year: number;
|
|
25
|
+
/** The preferred catalog model ID for this canonical make/model/type/year. */
|
|
26
|
+
modelId: number;
|
|
27
|
+
sourceIds: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface VehicleModelSearchResult extends VehicleSearchResultBase {
|
|
30
|
+
kind: "model";
|
|
31
|
+
modelName: string;
|
|
32
|
+
vehicleTypeId: number;
|
|
33
|
+
vehicleTypeName: string;
|
|
34
|
+
/** One directly selectable catalog identity per available year. */
|
|
35
|
+
variants: VehicleSearchVariant[];
|
|
36
|
+
}
|
|
37
|
+
export type VehicleSearchResult = VehicleMakeSearchResult | VehicleModelSearchResult;
|
|
38
|
+
/**
|
|
39
|
+
* Finds canonical make and model candidates for a driver-entered query.
|
|
40
|
+
*
|
|
41
|
+
* Matching is deterministic and popularity-neutral: exact, prefix, token,
|
|
42
|
+
* substring, then conservative typo matching. Applications can request a
|
|
43
|
+
* larger candidate set and rerank within the returned `matchKind` using local
|
|
44
|
+
* recents or regional telemetry.
|
|
45
|
+
*/
|
|
46
|
+
export declare function searchVehicles(query: string, options?: SearchVehiclesOptions): VehicleSearchResult[];
|
|
47
|
+
export {};
|