@mleonard9/vin-scanner 1.2.0 → 1.2.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.
- package/README.md +3 -3
- package/lib/commonjs/index.js +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/useVinScanner.js +1 -1
- package/lib/commonjs/useVinScanner.js.map +1 -1
- package/lib/commonjs/vinUtils.js +42 -119
- package/lib/commonjs/vinUtils.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/useVinScanner.js +2 -2
- package/lib/module/useVinScanner.js.map +1 -1
- package/lib/module/vinUtils.js +40 -117
- package/lib/module/vinUtils.js.map +1 -1
- package/lib/typescript/src/types.d.ts +1 -3
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/vinUtils.d.ts +1 -1
- package/lib/typescript/src/vinUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +2 -2
- package/src/types.ts +1 -3
- package/src/useVinScanner.ts +2 -2
- package/src/vinUtils.ts +45 -139
package/src/vinUtils.ts
CHANGED
|
@@ -54,9 +54,8 @@ const DEFAULT_RESOLVED_OPTIONS: ResolvedVinScannerOptions = {
|
|
|
54
54
|
language: 'latin',
|
|
55
55
|
},
|
|
56
56
|
detection: {
|
|
57
|
-
resultMode: '
|
|
57
|
+
resultMode: 'first',
|
|
58
58
|
preferBarcode: true,
|
|
59
|
-
validateChecksum: true,
|
|
60
59
|
emitDuplicates: false,
|
|
61
60
|
maxFrameRate: 30,
|
|
62
61
|
forceOrientation: null,
|
|
@@ -94,9 +93,6 @@ export const resolveOptions = (
|
|
|
94
93
|
preferBarcode:
|
|
95
94
|
options?.detection?.preferBarcode ??
|
|
96
95
|
DEFAULT_RESOLVED_OPTIONS.detection.preferBarcode,
|
|
97
|
-
validateChecksum:
|
|
98
|
-
options?.detection?.validateChecksum ??
|
|
99
|
-
DEFAULT_RESOLVED_OPTIONS.detection.validateChecksum,
|
|
100
96
|
emitDuplicates,
|
|
101
97
|
maxFrameRate:
|
|
102
98
|
options?.detection?.maxFrameRate ??
|
|
@@ -320,12 +316,16 @@ const isValidNorthAmericaWmi = (value: string): boolean => {
|
|
|
320
316
|
return false;
|
|
321
317
|
}
|
|
322
318
|
const firstChar = value[0];
|
|
323
|
-
// North America: 1, 2 (Canada), 4, 5 (United States)
|
|
319
|
+
// North America: 1, 2 (Canada), 3, 4, 5 (United States)
|
|
320
|
+
// Japan : 'J' Germany : 'W'
|
|
324
321
|
return (
|
|
325
322
|
firstChar === '1' ||
|
|
326
323
|
firstChar === '2' ||
|
|
324
|
+
firstChar === '3' ||
|
|
327
325
|
firstChar === '4' ||
|
|
328
|
-
firstChar === '5'
|
|
326
|
+
firstChar === '5' ||
|
|
327
|
+
firstChar === 'J' ||
|
|
328
|
+
firstChar === 'W'
|
|
329
329
|
);
|
|
330
330
|
};
|
|
331
331
|
|
|
@@ -466,34 +466,7 @@ const toBoundingBox = (
|
|
|
466
466
|
return blockBox || lineBox || elementBox;
|
|
467
467
|
};
|
|
468
468
|
|
|
469
|
-
|
|
470
|
-
* Calculate confidence score for a barcode-based VIN candidate
|
|
471
|
-
* Barcodes/QR codes are hard values, so they should always return 100% confidence
|
|
472
|
-
*/
|
|
473
|
-
const calculateBarcodeConfidence = (
|
|
474
|
-
value: string,
|
|
475
|
-
validateChecksum: boolean
|
|
476
|
-
): number => {
|
|
477
|
-
'worklet';
|
|
478
|
-
// Barcodes/QR codes are hard values - always 100% confidence for valid VINs
|
|
479
|
-
const checksumValid = isValidVin(value);
|
|
480
|
-
if (checksumValid) {
|
|
481
|
-
return 1.0; // 100% confidence for valid VINs from barcodes
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// If validation is disabled, still return high confidence
|
|
485
|
-
if (!validateChecksum) {
|
|
486
|
-
return 0.95;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Invalid VINs should not be returned (handled by caller)
|
|
490
|
-
return 0;
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
const candidateFromBarcode = (
|
|
494
|
-
detection: BarcodeDetection,
|
|
495
|
-
validateChecksum: boolean
|
|
496
|
-
): VinCandidate[] => {
|
|
469
|
+
const candidateFromBarcode = (detection: BarcodeDetection): VinCandidate[] => {
|
|
497
470
|
'worklet';
|
|
498
471
|
const values = new Set<string>();
|
|
499
472
|
tokenizeCandidate(detection.rawValue ?? '').forEach((value) =>
|
|
@@ -504,73 +477,17 @@ const candidateFromBarcode = (
|
|
|
504
477
|
);
|
|
505
478
|
const boundingBox = toBoundingBox(detection, 'barcode');
|
|
506
479
|
return Array.from(values)
|
|
507
|
-
.filter((value) =>
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
})
|
|
515
|
-
.map((value) => {
|
|
516
|
-
const confidence = calculateBarcodeConfidence(value, validateChecksum);
|
|
517
|
-
return {
|
|
518
|
-
value,
|
|
519
|
-
source: 'barcode' as const,
|
|
520
|
-
confidence,
|
|
521
|
-
boundingBox,
|
|
522
|
-
origin: detection.displayValue ? 'displayValue' : 'rawValue',
|
|
523
|
-
rawPayload: detection,
|
|
524
|
-
};
|
|
525
|
-
});
|
|
480
|
+
.filter((value) => isValidVin(value))
|
|
481
|
+
.map((value) => ({
|
|
482
|
+
value,
|
|
483
|
+
source: 'barcode' as const,
|
|
484
|
+
boundingBox,
|
|
485
|
+
origin: detection.displayValue ? 'displayValue' : 'rawValue',
|
|
486
|
+
rawPayload: detection,
|
|
487
|
+
}));
|
|
526
488
|
};
|
|
527
489
|
|
|
528
|
-
|
|
529
|
-
* Calculate confidence score for a text-based VIN candidate
|
|
530
|
-
* Higher confidence for:
|
|
531
|
-
* - Valid check digit
|
|
532
|
-
* - Found with VIN prefix context
|
|
533
|
-
* - Element/line level detection (vs block level)
|
|
534
|
-
*/
|
|
535
|
-
const calculateTextConfidence = (
|
|
536
|
-
value: string,
|
|
537
|
-
rawText: string,
|
|
538
|
-
origin: string,
|
|
539
|
-
validateChecksum: boolean
|
|
540
|
-
): number => {
|
|
541
|
-
'worklet';
|
|
542
|
-
let confidence = 0.5; // Base confidence for text recognition
|
|
543
|
-
|
|
544
|
-
// Check digit validation significantly increases confidence
|
|
545
|
-
const checksumValid = isValidVin(value);
|
|
546
|
-
if (checksumValid) {
|
|
547
|
-
confidence += 0.35; // Boost to 0.85
|
|
548
|
-
} else if (!validateChecksum) {
|
|
549
|
-
confidence += 0.2; // Still boost if validation is disabled
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Context-aware detection (VIN prefix found) increases confidence
|
|
553
|
-
const hasVinPrefix =
|
|
554
|
-
/VIN|Vehicle\s+(?:Identification|ID|Id)|Chassis|Serial/i.test(rawText);
|
|
555
|
-
if (hasVinPrefix) {
|
|
556
|
-
confidence += 0.1; // Boost to 0.95 if checksum valid, 0.6 if not
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Element-level text is more accurate than block-level
|
|
560
|
-
if (origin === 'element') {
|
|
561
|
-
confidence += 0.05;
|
|
562
|
-
} else if (origin === 'line') {
|
|
563
|
-
confidence += 0.03;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// Cap at 0.98 (never 1.0 for text recognition)
|
|
567
|
-
return Math.min(confidence, 0.98);
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
const candidateFromText = (
|
|
571
|
-
detection: TextDetection,
|
|
572
|
-
validateChecksum: boolean
|
|
573
|
-
): VinCandidate[] => {
|
|
490
|
+
const candidateFromText = (detection: TextDetection): VinCandidate[] => {
|
|
574
491
|
'worklet';
|
|
575
492
|
const valueMap = new Map<string, { rawText: string; order: number }>();
|
|
576
493
|
|
|
@@ -596,32 +513,18 @@ const candidateFromText = (
|
|
|
596
513
|
toBoundingBox(detection, 'block');
|
|
597
514
|
|
|
598
515
|
return Array.from(valueMap.entries())
|
|
599
|
-
.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
return isValidVin(value);
|
|
603
|
-
}
|
|
604
|
-
// If validation is disabled, still only return 17-character VINs
|
|
605
|
-
return value.length === 17;
|
|
606
|
-
})
|
|
607
|
-
.map(([value, { rawText }]) => {
|
|
516
|
+
.map(([value]) => value)
|
|
517
|
+
.filter((value) => isValidVin(value))
|
|
518
|
+
.map((value) => {
|
|
608
519
|
const origin = detection.elementText?.includes(value)
|
|
609
520
|
? 'element'
|
|
610
521
|
: detection.lineText?.includes(value)
|
|
611
522
|
? 'line'
|
|
612
523
|
: 'block';
|
|
613
524
|
|
|
614
|
-
const confidence = calculateTextConfidence(
|
|
615
|
-
value,
|
|
616
|
-
rawText,
|
|
617
|
-
origin,
|
|
618
|
-
validateChecksum
|
|
619
|
-
);
|
|
620
|
-
|
|
621
525
|
return {
|
|
622
526
|
value,
|
|
623
527
|
source: 'text' as const,
|
|
624
|
-
confidence,
|
|
625
528
|
boundingBox,
|
|
626
529
|
origin,
|
|
627
530
|
rawPayload: detection,
|
|
@@ -639,8 +542,7 @@ const dedupeCandidates = (candidates: VinCandidate[]): VinCandidate[] => {
|
|
|
639
542
|
candidate.boundingBox?.top ?? 'x',
|
|
640
543
|
candidate.boundingBox?.left ?? 'y',
|
|
641
544
|
].join(':');
|
|
642
|
-
|
|
643
|
-
if (!existing || existing.confidence < candidate.confidence) {
|
|
545
|
+
if (!map.has(key)) {
|
|
644
546
|
map.set(key, candidate);
|
|
645
547
|
}
|
|
646
548
|
});
|
|
@@ -654,39 +556,43 @@ export const buildVinCandidates = (
|
|
|
654
556
|
'worklet';
|
|
655
557
|
const list: VinCandidate[] = [];
|
|
656
558
|
|
|
657
|
-
|
|
559
|
+
const addBarcodeCandidates = () => {
|
|
560
|
+
if (!options.barcode.enabled) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
658
563
|
(payload.barcodes ?? []).forEach((barcode) => {
|
|
659
|
-
list.push(
|
|
660
|
-
...candidateFromBarcode(barcode, options.detection.validateChecksum)
|
|
661
|
-
);
|
|
564
|
+
list.push(...candidateFromBarcode(barcode));
|
|
662
565
|
});
|
|
663
|
-
}
|
|
664
|
-
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const addTextCandidates = () => {
|
|
569
|
+
if (!options.text.enabled) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
665
572
|
(payload.textBlocks ?? []).forEach((textBlock) => {
|
|
666
|
-
list.push(
|
|
667
|
-
...candidateFromText(textBlock, options.detection.validateChecksum)
|
|
668
|
-
);
|
|
573
|
+
list.push(...candidateFromText(textBlock));
|
|
669
574
|
});
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
if (options.detection.preferBarcode) {
|
|
578
|
+
addBarcodeCandidates();
|
|
579
|
+
addTextCandidates();
|
|
580
|
+
} else {
|
|
581
|
+
addTextCandidates();
|
|
582
|
+
addBarcodeCandidates();
|
|
670
583
|
}
|
|
671
584
|
return dedupeCandidates(list);
|
|
672
585
|
};
|
|
673
586
|
|
|
674
|
-
export const
|
|
587
|
+
export const pickFirstCandidate = (
|
|
675
588
|
candidates: VinCandidate[],
|
|
676
589
|
options: ResolvedVinScannerOptions
|
|
677
590
|
): VinCandidate | null => {
|
|
678
591
|
'worklet';
|
|
592
|
+
// eslint-disable-next-line no-void
|
|
593
|
+
void options;
|
|
679
594
|
if (!candidates.length) {
|
|
680
595
|
return null;
|
|
681
596
|
}
|
|
682
|
-
|
|
683
|
-
if (a.confidence !== b.confidence) {
|
|
684
|
-
return b.confidence - a.confidence;
|
|
685
|
-
}
|
|
686
|
-
if (options.detection.preferBarcode && a.source !== b.source) {
|
|
687
|
-
return a.source === 'barcode' ? -1 : 1;
|
|
688
|
-
}
|
|
689
|
-
return 0;
|
|
690
|
-
});
|
|
691
|
-
return sorted[0] ?? null;
|
|
597
|
+
return candidates[0] ?? null;
|
|
692
598
|
};
|