hh-shipments 0.3.26 → 0.3.27
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 +13 -6
- package/src/extract.js +36 -17
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -400,13 +400,15 @@ function loadZxing() {
|
|
|
400
400
|
return zxingPromise;
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
-
// OCR
|
|
403
|
+
// OCR the top strip of a label (sender block) for the seller name.
|
|
404
404
|
// Works from the ORIGINAL full-resolution image, upscaled — small text
|
|
405
|
-
// reads far better than on the shrunk copy.
|
|
405
|
+
// reads far better than on the shrunk copy. Top 45%: covers the sender
|
|
406
|
+
// block on USPS labels (~35-40%) as well as UPS/FedEx (top). Returns text
|
|
407
|
+
// or null.
|
|
406
408
|
async function ocrTopStrip(origFile, statusEl) {
|
|
407
409
|
try {
|
|
408
410
|
const bmp = await createImageBitmap(origFile);
|
|
409
|
-
const sw = bmp.width, sh = Math.max(60, Math.round(bmp.height * 0.
|
|
411
|
+
const sw = bmp.width, sh = Math.max(60, Math.round(bmp.height * 0.45));
|
|
410
412
|
const targetW = 1400, scale = targetW / sw;
|
|
411
413
|
const canvas = document.createElement('canvas');
|
|
412
414
|
canvas.width = targetW;
|
|
@@ -537,9 +539,14 @@ async function handlePastedImageFile(f) {
|
|
|
537
539
|
if (r && !r.added.length && !r.duplicates.length && !r.needs_review.some((c) => c.carrier && c.carrier !== 'unknown')) showOcrRead(target, text);
|
|
538
540
|
return;
|
|
539
541
|
}
|
|
540
|
-
// 2) Fallback: OCR the printed text
|
|
542
|
+
// 2) Fallback: OCR the printed text (+ the sender strip for the name,
|
|
543
|
+
// in parallel — the number crop usually excludes the sender block)
|
|
541
544
|
if (live) live.textContent = '📖 Reading screenshot…';
|
|
542
|
-
|
|
545
|
+
const [ocrText, topTextFb] = await Promise.all([
|
|
546
|
+
ocrFile(f, $('ocr-status-paste')),
|
|
547
|
+
ocrTopStrip(orig, $('ocr-status-paste')),
|
|
548
|
+
]);
|
|
549
|
+
text = ocrText;
|
|
543
550
|
if (live) live.textContent = '⬆️ Uploading…';
|
|
544
551
|
const fd = new FormData();
|
|
545
552
|
fd.append('image', f);
|
|
@@ -549,7 +556,7 @@ async function handlePastedImageFile(f) {
|
|
|
549
556
|
if (!up.ok) throw new Error('upload failed');
|
|
550
557
|
const upId2 = await up.json().then((j) => j.id).catch(() => null);
|
|
551
558
|
if (live) live.textContent = '🔍 Finding tracking number…';
|
|
552
|
-
const r = await runQuick(text, target);
|
|
559
|
+
const r = await runQuick(text, target, topTextFb ? { labelText: topTextFb } : undefined);
|
|
553
560
|
// Paste flow already handled this image — don't leave it nagging in the review queue.
|
|
554
561
|
if (upId2) api.patch('/api/intake/' + upId2, { state: 'reviewed' }).catch(() => {});
|
|
555
562
|
if (looksLikeAppScreenshot(text)) target.insertAdjacentHTML('afterbegin', '<p class="warn">⚠️ This looks like a screenshot of the app itself — paste the label photo directly for best results.</p>');
|
package/src/extract.js
CHANGED
|
@@ -66,24 +66,43 @@
|
|
|
66
66
|
var m2 = text.match(/(?:from|seller)\s*:\s*([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)/);
|
|
67
67
|
if (m2) out.seller_name = m2[1].trim();
|
|
68
68
|
}
|
|
69
|
-
// Shipping
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
// Shipping-label name test, shared by the address-anchor rule and the
|
|
70
|
+
// first-two-lines fallback below.
|
|
71
|
+
var labelWords = function (s) { return String(s).trim().split(/\s+/); };
|
|
72
|
+
var looksLikeLabelName = function (s) {
|
|
73
|
+
var words = labelWords(s);
|
|
74
|
+
if (words.length < 2 || words.length > 3) return false;
|
|
75
|
+
// Real shipping labels print names in ALL CAPS; app UI chrome
|
|
76
|
+
// ("Back Shipment") and OCR fragments ("Eg NTT") don't.
|
|
77
|
+
if (s !== s.toUpperCase()) return false;
|
|
78
|
+
// First word 4+ letters (a real first name — rejects "EE SS");
|
|
79
|
+
// later words 4+ letters or a single initial ("MOHAMED A").
|
|
80
|
+
if (!/^[A-Z]{4,}$/.test(words[0])) return false;
|
|
81
|
+
for (var wi = 1; wi < words.length; wi++) if (!/^([A-Z]{4,}|[A-Z])$/.test(words[wi])) return false;
|
|
82
|
+
if (/\b(UPS|FEDEX|USPS|DHL|GROUND|EXPRESS|PRIORITY|BILLING|SHIP\b|FROM|TRACKING|POSTAGE|LABEL|BACK|SHIPMENT|HOME|ARCHIVE|SETTINGS|SCREENSHOT|READING|FINDING|UPLOADING|EXTRACT|PASTE|DETAILS|SAVE|TAP|CLIPBOARD|NUMBER|HUNTER|HILLS)\b/.test(s)) return false;
|
|
83
|
+
return true;
|
|
84
|
+
};
|
|
85
|
+
// Shipping labels: the sender name sits on the line directly above the
|
|
86
|
+
// street-address line ("MOHAMED A" above "2507 GLACIER ST"). Find that
|
|
87
|
+
// anchor anywhere in the text — far more reliable than line position,
|
|
88
|
+
// since OCR crops don't always start at the label's first line. The
|
|
89
|
+
// first such block wins (sender prints above recipient).
|
|
90
|
+
if (!out.seller_name) {
|
|
91
|
+
var alines = String(text).split(/\r?\n/);
|
|
92
|
+
for (var ai = 0; ai < alines.length; ai++) {
|
|
93
|
+
var addr = alines[ai].trim();
|
|
94
|
+
if (!/^\d{1,6}\s+[A-Z0-9][A-Z0-9 .'-]*$/.test(addr)) continue;
|
|
95
|
+
if (!/\b(ST|STREET|AVE|AVENUE|RD|ROAD|DR|DRIVE|LN|LANE|BLVD|PL|PLACE|CT|COURT|WAY|TER|TERRACE|PKWY|PARKWAY)\b/.test(addr)) continue;
|
|
96
|
+
if (ai === 0) continue;
|
|
97
|
+
var cand = alines[ai - 1].replace(/[^A-Za-z ]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
98
|
+
if (looksLikeLabelName(cand)) { out.seller_name = cand; break; }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Fallback: sender name is the first or second line ("MOHAMMED SALEM").
|
|
102
|
+
// Only take it when it's clearly a name — never digits, never address
|
|
103
|
+
// fragments, never OCR garbage like "EE SS", and never the app's own UI
|
|
104
|
+
// words when a screenshot of the app itself gets pasted ("Back Shipment").
|
|
74
105
|
if (!out.seller_name) {
|
|
75
|
-
var labelWords = function (s) { return String(s).trim().split(/\s+/); };
|
|
76
|
-
var looksLikeLabelName = function (s) {
|
|
77
|
-
var words = labelWords(s);
|
|
78
|
-
if (words.length < 2 || words.length > 3) return false;
|
|
79
|
-
// Real shipping labels print names in ALL CAPS; app UI chrome
|
|
80
|
-
// ("Back Shipment") and OCR fragments ("Eg NTT") don't.
|
|
81
|
-
if (s !== s.toUpperCase()) return false;
|
|
82
|
-
// every word 4+ letters: OCR garbage ("EE SS") never qualifies.
|
|
83
|
-
for (var i = 0; i < words.length; i++) if (!/^[A-Z]{4,}$/.test(words[i])) return false;
|
|
84
|
-
if (/\b(UPS|FEDEX|USPS|DHL|GROUND|EXPRESS|PRIORITY|BILLING|SHIP\b|FROM|TRACKING|POSTAGE|LABEL|BACK|SHIPMENT|HOME|ARCHIVE|SETTINGS|SCREENSHOT|READING|FINDING|UPLOADING|EXTRACT|PASTE|DETAILS|SAVE|TAP|CLIPBOARD|NUMBER)\b/.test(s)) return false;
|
|
85
|
-
return true;
|
|
86
|
-
};
|
|
87
106
|
var lines = String(text).split(/\r?\n/);
|
|
88
107
|
for (var li = 0; li < Math.min(2, lines.length); li++) {
|
|
89
108
|
var cleaned = lines[li].replace(/[^A-Za-z ]/g, ' ').replace(/\s+/g, ' ').trim();
|