hh-shipments 0.3.14 → 0.3.16

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/app.js +30 -13
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "test": "node test/run-tests.js && node test/run-carriers-tests.js"
19
19
  },
20
20
  "type": "commonjs",
21
- "version": "0.3.14",
21
+ "version": "0.3.16",
22
22
  "bin": {
23
23
  "hh-shipments": "server.js"
24
24
  },
package/public/app.js CHANGED
@@ -175,12 +175,13 @@ async function ocrFile(file, statusEl) {
175
175
  async function handlePhotoFile(f) {
176
176
  const target = $('photo-result');
177
177
  try {
178
+ const orig = f;
178
179
  f = await shrinkImage(f);
179
180
  const url = URL.createObjectURL(f);
180
181
  target.innerHTML = `<div class="review"><img src="${url}" style="max-width:100%;border-radius:10px"><p class="muted" id="ocr-live-photo">📷 Scanning barcode…</p></div>`;
181
182
  const live = $('ocr-live-photo');
182
183
  const barcode = await decodeBarcode(f);
183
- let text, preset;
184
+ let text, preset, topText = null;
184
185
  if (barcode) {
185
186
  text = barcode;
186
187
  if (live) live.textContent = '📷 Barcode read — reading sender…';
@@ -188,7 +189,7 @@ async function handlePhotoFile(f) {
188
189
  fd.append('image', f);
189
190
  const upPromise = fetchTimeout('/api/intake/image', { method: 'POST', body: fd }, 60000)
190
191
  .catch(() => { throw new Error('upload timed out — check your connection and try again'); });
191
- const topText = await ocrTopStrip(f, $('ocr-status'));
192
+ topText = await ocrTopStrip(orig, $('ocr-status'));
192
193
  const up = await upPromise;
193
194
  if (!up.ok) throw new Error('upload failed');
194
195
  preset = topText ? { labelText: topText } : undefined;
@@ -204,6 +205,7 @@ async function handlePhotoFile(f) {
204
205
  if (!up.ok) throw new Error('upload failed');
205
206
  }
206
207
  const r = await runQuick(text, $('photo-result'), preset);
208
+ if (looksLikeAppScreenshot(topText || text)) $('photo-result').insertAdjacentHTML('afterbegin', '<p class="warn">⚠️ This looks like a screenshot of the app itself — choose the label photo directly for best results.</p>');
207
209
  if (barcode && r && r.added.length) $('photo-result').insertAdjacentHTML('afterbegin', '<p class="muted">📷 Read from the barcode — checksum verified.</p>');
208
210
  if (r && !r.added.length && !r.needs_review.length && !r.duplicates.length) showOcrRead($('photo-result'), text);
209
211
  loadPending();
@@ -274,17 +276,21 @@ function loadZxing() {
274
276
  }
275
277
 
276
278
  // OCR just the top strip of a label (sender block) for the seller name.
277
- // Small crop = fast. Returns text or null.
278
- async function ocrTopStrip(file, statusEl) {
279
+ // Works from the ORIGINAL full-resolution image, upscaled — small text
280
+ // reads far better than on the shrunk copy. Returns text or null.
281
+ async function ocrTopStrip(origFile, statusEl) {
279
282
  try {
280
- const bmp = await createImageBitmap(file);
281
- const sh = Math.max(60, Math.round(bmp.height * 0.30));
283
+ const bmp = await createImageBitmap(origFile);
284
+ const sw = bmp.width, sh = Math.max(60, Math.round(bmp.height * 0.35));
285
+ const targetW = 1400, scale = targetW / sw;
282
286
  const canvas = document.createElement('canvas');
283
- canvas.width = bmp.width;
284
- canvas.height = sh;
285
- canvas.getContext('2d').drawImage(bmp, 0, 0, bmp.width, sh, 0, 0, bmp.width, sh);
287
+ canvas.width = targetW;
288
+ canvas.height = Math.max(60, Math.round(sh * scale));
289
+ const ctx = canvas.getContext('2d');
290
+ ctx.imageSmoothingEnabled = true;
291
+ ctx.drawImage(bmp, 0, 0, sw, sh, 0, 0, targetW, canvas.height);
286
292
  bmp.close();
287
- const blob = await new Promise((res) => canvas.toBlob(res, 'image/jpeg', 0.9));
293
+ const blob = await new Promise((res) => canvas.toBlob(res, 'image/jpeg', 0.92));
288
294
  if (!blob) return null;
289
295
  const p = ocrFile(new File([blob], 'top.jpg', { type: 'image/jpeg' }), statusEl);
290
296
  return await Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error('top-strip timeout')), 25000))]);
@@ -342,10 +348,19 @@ async function decodeBarcode(file) {
342
348
  } finally { bmp.close(); }
343
349
  return null;
344
350
  }
351
+ // Detect when the pasted image is a screenshot OF THE APP itself (its own
352
+ // header/buttons/tab bar get OCR'd) — the label inside is too small to read
353
+ // well, and UI words pollute the results.
354
+ function looksLikeAppScreenshot(text) {
355
+ const t = String(text || '').toLowerCase();
356
+ return /paste a screenshot|extract from text|finding tracking number|reading screenshot|mark received|open carrier tracking|hh shipments/.test(t);
357
+ }
358
+
345
359
  async function handlePastedImageFile(f) {
346
360
  const target = $('quick-result');
347
361
  try {
348
362
  // Instant feedback: show a thumbnail + progress the moment the image lands
363
+ const orig = f;
349
364
  f = await shrinkImage(f);
350
365
  const url = URL.createObjectURL(f);
351
366
  target.innerHTML = `<div class="review"><img src="${url}" style="max-width:100%;border-radius:10px"><p class="muted" id="ocr-live">📷 Scanning barcode…</p></div>`;
@@ -356,17 +371,18 @@ async function handlePastedImageFile(f) {
356
371
  if (barcode) {
357
372
  text = barcode;
358
373
  if (live) live.textContent = '📷 Barcode read — reading sender…';
359
- // Number is verified; OCR just the top strip (sender block) for the
360
- // name, in parallel with the upload.
374
+ // Number is verified; OCR the top strip of the ORIGINAL full-res
375
+ // image (sender block) for the name, in parallel with the upload.
361
376
  const fd = new FormData();
362
377
  fd.append('image', f);
363
378
  const upPromise = fetchTimeout('/api/intake/image', { method: 'POST', body: fd }, 60000)
364
379
  .catch(() => { throw new Error('upload timed out — check your connection and try again'); });
365
- const topText = await ocrTopStrip(f, $('ocr-status-paste'));
380
+ const topText = await ocrTopStrip(orig, $('ocr-status-paste'));
366
381
  const up = await upPromise;
367
382
  if (!up.ok) throw new Error('upload failed');
368
383
  if (live) live.textContent = '🔍 Finding tracking number…';
369
384
  const r = await runQuick(text, target, topText ? { labelText: topText } : undefined);
385
+ if (looksLikeAppScreenshot(topText)) target.insertAdjacentHTML('afterbegin', '<p class="warn">⚠️ This looks like a screenshot of the app itself — paste the label photo directly for best results.</p>');
370
386
  if (r && r.added.length) target.insertAdjacentHTML('afterbegin', '<p class="muted">📷 Read from the barcode — checksum verified.</p>');
371
387
  if (r && !r.added.length && !r.needs_review.length && !r.duplicates.length) showOcrRead(target, text);
372
388
  return;
@@ -383,6 +399,7 @@ async function handlePastedImageFile(f) {
383
399
  if (!up.ok) throw new Error('upload failed');
384
400
  if (live) live.textContent = '🔍 Finding tracking number…';
385
401
  const r = await runQuick(text, target);
402
+ 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>');
386
403
  if (r && !r.added.length && !r.needs_review.length && !r.duplicates.length) showOcrRead(target, text);
387
404
  } catch (err) { $('ocr-status-paste').textContent = ''; target.innerHTML = `<p class="error">${esc(err.message)}</p>`; }
388
405
  }