hh-shipments 0.3.25 → 0.3.26
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 +3 -2
- package/public/app.js +12 -5
- package/public/extract.js +74 -9
- package/public/sw.js +1 -1
- package/public/vendor/zxing.min.js +1 -0
- package/src/extract.js +74 -8
package/src/extract.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
// UPS: 1Z + 16 alphanumerics
|
|
11
11
|
{ carrier: 'ups', confidence: 'high', re: /\b(1Z[0-9A-Z]{16})\b/gi },
|
|
12
12
|
// USPS: 20-22 digits starting 92-95 (9400..., 9205..., etc.)
|
|
13
|
-
|
|
13
|
+
// USPS: 20-22 digits starting 91-95 (9400..., 9305..., 9205..., 9101..., etc.)
|
|
14
|
+
{ carrier: 'usps', confidence: 'high', re: /\b(9[1-5]\d{18,20})\b/g },
|
|
14
15
|
// USPS S10 international: 2 letters + 9 digits + 2 letters (ends US = USPS)
|
|
15
16
|
{ carrier: 'usps', confidence: 'medium', re: /\b([A-Z]{2}\d{9}US)\b/g },
|
|
16
17
|
// FedEx SmartPost / Ground 22-digit starting 96
|
|
@@ -247,9 +248,14 @@
|
|
|
247
248
|
runPatterns(z.text, found, seen, z.carrier);
|
|
248
249
|
spacedUpsIn(z.text, found, seen, 'high');
|
|
249
250
|
looseUpsIn(z.text, found, seen, 'high');
|
|
251
|
+
spacedDigitGroupsIn(z.text, found, seen, 'high');
|
|
250
252
|
});
|
|
251
253
|
// 1) Whole-text strict patterns
|
|
252
254
|
runPatterns(text, found, seen, false);
|
|
255
|
+
// 1b) Spaced digit groups: labels print "9305 5110 4390 0011 2062 42"
|
|
256
|
+
// with spaces, which strict PATTERNS cannot match. Without this pass a
|
|
257
|
+
// perfectly-read number is silently dropped.
|
|
258
|
+
spacedDigitGroupsIn(text, found, seen, 'medium');
|
|
253
259
|
// 2) OCR-tolerant recovery pass
|
|
254
260
|
extractOcrTolerant(text, found, seen);
|
|
255
261
|
var rank = { high: 0, medium: 1, low: 2 };
|
|
@@ -294,6 +300,9 @@
|
|
|
294
300
|
var num = normalize(m[1]);
|
|
295
301
|
if (seen[num]) continue;
|
|
296
302
|
seen[num] = true;
|
|
303
|
+
// Never accept an IMpb routing prefix (420 + 9 digits) as a FedEx
|
|
304
|
+
// number — it is postal routing, not a shipment.
|
|
305
|
+
if (p.carrier === 'fedex' && /^420\d{9}$/.test(num)) continue;
|
|
297
306
|
// Context boost: carrier word near the number, or a hot-zone anchor
|
|
298
307
|
// naming this carrier (a label's "UPS GROUND" line means the number
|
|
299
308
|
// under it really is UPS).
|
|
@@ -314,12 +323,61 @@
|
|
|
314
323
|
function isValidFormat(number, carrier) {
|
|
315
324
|
var n = normalize(number);
|
|
316
325
|
if (carrier === 'ups') return /^1Z[0-9A-Z]{16}$/.test(n);
|
|
317
|
-
if (carrier === 'usps') return /^9[
|
|
318
|
-
|
|
326
|
+
if (carrier === 'usps') return /^9[1-5]\d{18,20}$/.test(n) || /^[A-Z]{2}\d{9}US$/.test(n);
|
|
327
|
+
// FedEx: never accept an IMpb routing prefix (420 + 9 digits) — it is
|
|
328
|
+
// postal routing, not a shipment number.
|
|
329
|
+
if (carrier === 'fedex') return /^(96\d{20}|\d{12}|\d{15}|\d{20})$/.test(n) && !/^420\d{9}$/.test(n);
|
|
319
330
|
if (carrier === 'dhl') return /^\d{10,11}$/.test(n) || /^[A-Z]{2,4}\d{10,20}$/.test(n);
|
|
320
331
|
return false;
|
|
321
332
|
}
|
|
322
333
|
|
|
334
|
+
// Intelligent Mail package barcode: 420 + routing digits + USPS tracking,
|
|
335
|
+
// e.g. 420078742956 + 9305511043900011206242 (34 digits). The full barcode
|
|
336
|
+
// is NOT the tracking number — return the trailing tracking number. The
|
|
337
|
+
// routing portion must never pass as a shipment number.
|
|
338
|
+
function parseImpb(s) {
|
|
339
|
+
var num = normalize(s);
|
|
340
|
+
if (!/^420\d{23,31}$/.test(num)) return null; // 26–34 digits total
|
|
341
|
+
var m = /(9[1-5]\d{18,20})$/.exec(num);
|
|
342
|
+
return m ? m[1] : null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// A spaced/dashed digit run is only plausible as a tracking number when
|
|
346
|
+
// every group has 2+ digits — this rejects ZIP+4 glued to a nearby lone
|
|
347
|
+
// digit ("07874-2956 1 lb"), phone fragments ("1-800-555-1234"), etc.
|
|
348
|
+
function validSpacedGroups(raw) {
|
|
349
|
+
var groups = String(raw).split(/[\s-]+/);
|
|
350
|
+
if (groups.length < 2) return true; // contiguous — decided elsewhere
|
|
351
|
+
for (var i = 0; i < groups.length; i++) if (groups[i].length < 2) return false;
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Digit groups split by spaces/dashes: "9305 5110 4390 0011 2062 42".
|
|
356
|
+
// Strict PATTERNS only match contiguous runs, so this pass exists for the
|
|
357
|
+
// standard label printing format. Only fires when the raw match actually
|
|
358
|
+
// contains a separator (contiguous runs are already covered by PATTERNS).
|
|
359
|
+
// Joined candidates must validate against a carrier format; IMpb barcodes
|
|
360
|
+
// yield their tracking number, never the routing.
|
|
361
|
+
function spacedDigitGroupsIn(text, found, seen, conf) {
|
|
362
|
+
var re = /\b\d(?:[\s-]?\d){9,33}\b/g, m;
|
|
363
|
+
while ((m = re.exec(text)) !== null) {
|
|
364
|
+
if (!/[\s-]/.test(m[0])) continue;
|
|
365
|
+
if (!validSpacedGroups(m[0])) continue;
|
|
366
|
+
var num = normalize(m[0]);
|
|
367
|
+
if (!num || seen[num]) continue;
|
|
368
|
+
var impb = parseImpb(num);
|
|
369
|
+
if (impb) {
|
|
370
|
+
seen[num] = true;
|
|
371
|
+
if (!seen[impb]) { seen[impb] = true; found.push({ number: impb, carrier: 'usps', confidence: conf }); }
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
var carrier = guessCarrier(num);
|
|
375
|
+
if (carrier === 'unknown') { seen[num] = true; continue; }
|
|
376
|
+
seen[num] = true;
|
|
377
|
+
found.push({ number: num, carrier: carrier, confidence: conf });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
323
381
|
function guessCarrier(num) {
|
|
324
382
|
var carriers = ['ups', 'usps', 'fedex', 'dhl'];
|
|
325
383
|
for (var i = 0; i < carriers.length; i++) if (isValidFormat(num, carriers[i])) return carriers[i];
|
|
@@ -349,13 +407,20 @@
|
|
|
349
407
|
spacedUpsIn(text, found, seen, 'high');
|
|
350
408
|
expandPunctHotZone(text, found, seen);
|
|
351
409
|
looseUpsIn(text, found, seen, 'high');
|
|
352
|
-
// b) long digit-like runs with OCR confusions -> fix -> re-match digit formats
|
|
353
|
-
|
|
410
|
+
// b) long digit-like runs with OCR confusions -> fix -> re-match digit formats.
|
|
411
|
+
// Space-aware: OCR often reads "9305 51O0 ..." with both mangled chars
|
|
412
|
+
// AND spaces, so runs may contain separators. Runs up to 34 chars so a
|
|
413
|
+
// full 34-digit IMpb barcode is tested whole (parseImpb) instead of
|
|
414
|
+
// being split into chunks that false-positive as DHL/FedEx.
|
|
415
|
+
var runRe = /[0-9OILSBoilszb](?:[\s-]?[0-9OILSBoilszb]){9,33}/g, run;
|
|
354
416
|
while ((run = runRe.exec(text)) !== null) {
|
|
417
|
+
if (/[\s-]/.test(run[0]) && !validSpacedGroups(run[0])) continue;
|
|
355
418
|
var fixed = normalize(ocrDigitFix(run[0]));
|
|
356
|
-
|
|
419
|
+
var impbFixed = parseImpb(fixed);
|
|
420
|
+
if (impbFixed) { push(impbFixed, 'usps', 'medium'); continue; }
|
|
421
|
+
if (/^9[1-5]\d{18,20}$/.test(fixed)) push(fixed, 'usps', 'medium');
|
|
357
422
|
else if (/^96\d{20}$/.test(fixed)) push(fixed, 'fedex', 'medium');
|
|
358
|
-
else if (/^(\d{12}|\d{15}|\d{20})$/.test(fixed)) push(fixed, 'fedex', 'medium');
|
|
423
|
+
else if (/^(\d{12}|\d{15}|\d{20})$/.test(fixed) && !/^420\d{9}$/.test(fixed)) push(fixed, 'fedex', 'medium');
|
|
359
424
|
else if (/^\d{11}$/.test(fixed)) push(fixed, 'dhl', 'medium');
|
|
360
425
|
else if (/^\d{10}$/.test(fixed)) push(fixed, 'dhl', 'low');
|
|
361
426
|
}
|
|
@@ -369,6 +434,7 @@
|
|
|
369
434
|
});
|
|
370
435
|
tokens.sort(function (a, b) { return b.length - a.length; });
|
|
371
436
|
tokens.slice(0, 3).forEach(function (t) {
|
|
437
|
+
if (/^420\d{9}$/.test(t)) return; // IMpb routing — never a shipment
|
|
372
438
|
// UPS "1Z" misread as "12"/"IZ" in a joined (space-free) token
|
|
373
439
|
var fixed = repairUpsZ(t);
|
|
374
440
|
if (fixed) { push(fixed, 'ups', 'high'); return; }
|
|
@@ -393,7 +459,7 @@
|
|
|
393
459
|
});
|
|
394
460
|
}
|
|
395
461
|
|
|
396
|
-
var api = { extractTrackingNumbers: extractTrackingNumbers, extractContext: extractContext, normalize: normalize, isValidFormat: isValidFormat };
|
|
462
|
+
var api = { extractTrackingNumbers: extractTrackingNumbers, extractContext: extractContext, normalize: normalize, isValidFormat: isValidFormat, parseImpb: parseImpb };
|
|
397
463
|
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
398
464
|
globalThis.HHExtract = api;
|
|
399
465
|
})();
|