hh-shipments 0.3.27 → 0.3.28
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/package.json +1 -1
- package/public/app.js +23 -2
- package/public/extract.js +7 -5
- package/src/extract.js +34 -5
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -450,6 +450,18 @@ async function decodeOne(reader, url, ms = 9000) {
|
|
|
450
450
|
} catch (e) { return null; }
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
// Rank barcode candidates by format specificity: a 1Z UPS read or a
|
|
454
|
+
// 22-digit USPS read is unambiguous, while a 10-digit "DHL" read off a
|
|
455
|
+
// label is often a fragment of another carrier's number (or a routing
|
|
456
|
+
// barcode). Weak hits never beat strong ones.
|
|
457
|
+
function barcodeRank(num) {
|
|
458
|
+
if (/^1Z/i.test(num)) return 0; // UPS
|
|
459
|
+
if (/^9[1-5]\d{18,20}$/.test(num)) return 1; // USPS
|
|
460
|
+
if (/^96\d{20}$/.test(num) || /^\d{15}$/.test(num) || /^\d{20}$/.test(num)) return 2; // FedEx long
|
|
461
|
+
if (/^\d{12}$/.test(num)) return 3; // FedEx 12 (weak: may be routing)
|
|
462
|
+
return 4; // DHL 10/11 (weakest)
|
|
463
|
+
}
|
|
464
|
+
|
|
453
465
|
// Whole-label decode usually fails (barcodes too small in frame), so try
|
|
454
466
|
// horizontal region strips, upscaled — the 1Z barcode lives low on UPS labels.
|
|
455
467
|
async function decodeBarcode(file) {
|
|
@@ -467,6 +479,11 @@ async function decodeBarcode(file) {
|
|
|
467
479
|
[0, 0.60, 1, 0.35],
|
|
468
480
|
[0.05, 0.55, 0.90, 0.40],
|
|
469
481
|
];
|
|
482
|
+
// Collect every candidate across the sweep and take the strongest —
|
|
483
|
+
// never let the first weak hit (e.g. a 10-digit routing barcode read as
|
|
484
|
+
// DHL) win over the real tracking barcode. Unambiguous reads (UPS/USPS)
|
|
485
|
+
// still return immediately on the fast path.
|
|
486
|
+
let best = null, bestRank = 99;
|
|
470
487
|
try {
|
|
471
488
|
for (const [fx, fy, fw, fh] of regions) {
|
|
472
489
|
for (const scale of [1, 1.5, 2]) {
|
|
@@ -491,11 +508,15 @@ async function decodeBarcode(file) {
|
|
|
491
508
|
const url = canvas.toDataURL('image/png');
|
|
492
509
|
const text = await decodeOne(reader, url);
|
|
493
510
|
const num = strictTracking(text);
|
|
494
|
-
if (num)
|
|
511
|
+
if (num) {
|
|
512
|
+
const r = barcodeRank(num);
|
|
513
|
+
if (r < bestRank) { best = num; bestRank = r; }
|
|
514
|
+
if (r <= 1) return num; // UPS or USPS: unambiguous, fast path
|
|
515
|
+
}
|
|
495
516
|
}
|
|
496
517
|
}
|
|
497
518
|
} finally { bmp.close(); }
|
|
498
|
-
return
|
|
519
|
+
return best;
|
|
499
520
|
}
|
|
500
521
|
// Detect when the pasted image is a screenshot OF THE APP itself (its own
|
|
501
522
|
// header/buttons/tab bar get OCR'd) — the label inside is too small to read
|
package/public/extract.js
CHANGED
|
@@ -59,13 +59,14 @@
|
|
|
59
59
|
// Strict UPS structure: 1Z + 6-char shipper number + 2-digit service + 8-digit serial.
|
|
60
60
|
var UPS_STRICT = /^1Z[0-9A-Z]{6}[0-9]{2}[0-9]{8}$/;
|
|
61
61
|
function repairUpsZ(ups) {
|
|
62
|
-
// Tesseract often reads the "1Z" anchor as "12" (or "IZ"
|
|
63
|
-
// only when the rest has strict UPS structure
|
|
64
|
-
// contains a letter — real UPS shipper numbers
|
|
65
|
-
// don't. Never invent numbers from thin air.
|
|
62
|
+
// Tesseract often reads the "1Z" anchor as "12" (or "IZ", or "17" —
|
|
63
|
+
// 7→Z confusion). Repair it only when the rest has strict UPS structure
|
|
64
|
+
// AND the shipper segment contains a letter — real UPS shipper numbers
|
|
65
|
+
// do, random digit runs don't. Never invent numbers from thin air.
|
|
66
66
|
var cand = null;
|
|
67
67
|
if (/^12[0-9A-Z]{16}$/.test(ups)) cand = '1Z' + ups.slice(2);
|
|
68
68
|
else if (/^IZ[0-9A-Z]{16}$/i.test(ups)) cand = '1Z' + ups.slice(2);
|
|
69
|
+
else if (/^17[0-9A-Z]{16}$/.test(ups)) cand = '1Z' + ups.slice(2);
|
|
69
70
|
if (!cand || !UPS_STRICT.test(cand)) return null;
|
|
70
71
|
if (!/[A-Z]/.test(cand.slice(2, 8))) return null;
|
|
71
72
|
return cand;
|
|
@@ -73,7 +74,8 @@
|
|
|
73
74
|
|
|
74
75
|
// UPS number split by spaces/dashes: "1Z 9999 99 ..." (or "12 ..."/"IZ ..." misreads)
|
|
75
76
|
function spacedUpsIn(text, found, seen, conf) {
|
|
76
|
-
|
|
77
|
+
// Anchor covers common 1Z misreads: 12, IZ, 17 (7→Z confusion)
|
|
78
|
+
var m, re = /\b(?:1Z|12|IZ|17)[\s-]?(?:[0-9A-Z][\s-]?){16}\b/gi;
|
|
77
79
|
while ((m = re.exec(text)) !== null) {
|
|
78
80
|
var ups = normalize(m[0]);
|
|
79
81
|
var num = /^1Z[0-9A-Z]{16}$/.test(ups) ? ups : repairUpsZ(ups);
|
package/src/extract.js
CHANGED
|
@@ -267,7 +267,8 @@
|
|
|
267
267
|
runPatterns(z.text, found, seen, z.carrier);
|
|
268
268
|
spacedUpsIn(z.text, found, seen, 'high');
|
|
269
269
|
looseUpsIn(z.text, found, seen, 'high');
|
|
270
|
-
spacedDigitGroupsIn(z.text, found, seen, 'high');
|
|
270
|
+
spacedDigitGroupsIn(z.text, found, seen, 'high', z.carrier);
|
|
271
|
+
if (z.carrier === 'ups') upsAnchorlessRecovery(z.text, found, seen);
|
|
271
272
|
});
|
|
272
273
|
// 1) Whole-text strict patterns
|
|
273
274
|
runPatterns(text, found, seen, false);
|
|
@@ -305,7 +306,10 @@
|
|
|
305
306
|
// anchor line + the two lines under it (the number is printed below)
|
|
306
307
|
var slice = lines.slice(i, Math.min(lines.length, i + 3));
|
|
307
308
|
var carrier = null;
|
|
308
|
-
slice
|
|
309
|
+
// Scan the slice AND the line above it: "UPS GROUND" typically sits
|
|
310
|
+
// directly above the "TRACKING #:" line, and the zone needs to know.
|
|
311
|
+
var scan = slice.concat(i > 0 ? [lines[i - 1]] : []);
|
|
312
|
+
scan.forEach(function (sl) { carrier = carrier || anchorCarrier(sl); });
|
|
309
313
|
zones.push({ text: slice.join('\n'), carrier: carrier });
|
|
310
314
|
}
|
|
311
315
|
return zones;
|
|
@@ -377,7 +381,11 @@
|
|
|
377
381
|
// contains a separator (contiguous runs are already covered by PATTERNS).
|
|
378
382
|
// Joined candidates must validate against a carrier format; IMpb barcodes
|
|
379
383
|
// yield their tracking number, never the routing.
|
|
380
|
-
|
|
384
|
+
// zoneCarrier: when the anchor zone names a carrier ("UPS GROUND"), a
|
|
385
|
+
// candidate of any OTHER carrier is a fragment of the real number
|
|
386
|
+
// ("03 3985 0416" off a UPS label is not a DHL shipment) — demote it to
|
|
387
|
+
// low so it can only ever surface for review, never auto-add.
|
|
388
|
+
function spacedDigitGroupsIn(text, found, seen, conf, zoneCarrier) {
|
|
381
389
|
var re = /\b\d(?:[\s-]?\d){9,33}\b/g, m;
|
|
382
390
|
while ((m = re.exec(text)) !== null) {
|
|
383
391
|
if (!/[\s-]/.test(m[0])) continue;
|
|
@@ -387,13 +395,34 @@
|
|
|
387
395
|
var impb = parseImpb(num);
|
|
388
396
|
if (impb) {
|
|
389
397
|
seen[num] = true;
|
|
390
|
-
|
|
398
|
+
var ic = (zoneCarrier && zoneCarrier !== 'usps') ? 'low' : conf;
|
|
399
|
+
if (!seen[impb]) { seen[impb] = true; found.push({ number: impb, carrier: 'usps', confidence: ic }); }
|
|
391
400
|
continue;
|
|
392
401
|
}
|
|
393
402
|
var carrier = guessCarrier(num);
|
|
394
403
|
if (carrier === 'unknown') { seen[num] = true; continue; }
|
|
395
404
|
seen[num] = true;
|
|
396
|
-
|
|
405
|
+
var c = (zoneCarrier && carrier !== zoneCarrier) ? 'low' : conf;
|
|
406
|
+
found.push({ number: num, carrier: carrier, confidence: c });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Last resort for UPS zones: the "1Z" anchor itself was unreadable, so the
|
|
411
|
+
// number survives only as a 16-char alphanumeric tail ("X01 48H 03 3985
|
|
412
|
+
// 0416"). If it validates as strict UPS tail structure, offer 1Z+tail at
|
|
413
|
+
// medium (review-only, never auto-add) — far better than a DHL fragment.
|
|
414
|
+
var UPS_TAIL_STRICT = /^[0-9A-Z]{6}[0-9]{2}[0-9]{8}$/;
|
|
415
|
+
function upsAnchorlessRecovery(text, found, seen) {
|
|
416
|
+
var re = /\b([0-9A-Z](?:[\s-]?[0-9A-Z]){15})\b/gi, m;
|
|
417
|
+
while ((m = re.exec(text)) !== null) {
|
|
418
|
+
var token = normalize(m[1]);
|
|
419
|
+
if (/^(1Z|12|IZ|17)/i.test(token)) continue; // anchored — handled elsewhere
|
|
420
|
+
if (!UPS_TAIL_STRICT.test(token)) continue;
|
|
421
|
+
if (!/[A-Z]/.test(token.slice(0, 6))) continue; // real shipper codes have a letter
|
|
422
|
+
var num = '1Z' + token;
|
|
423
|
+
if (seen[num]) continue;
|
|
424
|
+
seen[num] = true;
|
|
425
|
+
found.push({ number: num, carrier: 'ups', confidence: 'medium', ocr_fixed: true });
|
|
397
426
|
}
|
|
398
427
|
}
|
|
399
428
|
|