Validate container numbers from shipping documents.
A container number like CSQU 305438 3 ends in a check digit, defined by ISO 6346. After OCR, one wrong character (an 8 read as a 0) makes the check fail, so you can catch misreads before a wrong number reaches your TMS. Here's how, in Python.
01 / INSTALL AND GET A KEY
One dependency. Your first 100 pages are free, no card, at pennyocr.com/dashboard.
pip install pennyocr
export PENNYOCR_API_KEY=pk_live_...02 / READ THE DOCUMENT
from pennyocr import PennyOCR
result = PennyOCR().ocr("bill-of-lading.pdf", format="text")
text = result.text03 / WHAT YOU GET BACK
The page in reading order. Container numbers appear with or without spaces and hyphens:
**BILL OF LADING** B/L No: MAEU240117653
Container / Seal: CSQU 305438 3 / SL-88213, MSKU-123456504 / FIND THE CONTAINER NUMBERS
Four letters (owner code plus U, J or Z), six digits and a check digit, with optional separators:
import re
CONTAINER_RE = re.compile(r"\b([A-Z]{3}[UJZ])[\s-]?(\d{6})[\s-]?(\d)\b")
found = ["".join(m) for m in CONTAINER_RE.findall(text)]
print(found)05 / VALIDATE THE CHECK DIGIT
Each character gets a value (letters skip multiples of 11), weighted by powers of 2. The sum mod 11, mod 10, must equal the last digit.
# ISO 6346 letter values skip multiples of 11 (11, 22, 33).
LETTERS = {c: v for c, v in zip("ABCDEFGHIJKLMNOPQRSTUVWXYZ",
[n for n in range(10, 39) if n % 11])}
def container_is_valid(number: str) -> bool:
"""The 11th character is a check digit over the first 10."""
total = sum((LETTERS[c] if c.isalpha() else int(c)) * 2 ** i
for i, c in enumerate(number[:10]))
return total % 11 % 10 == int(number[10])
for n in found:
print(n, "ok" if container_is_valid(n) else "CHECK FAILED - send for review")The same idea works for other identifiers with built-in checks: IBANs, GSTINs (see the GST invoice guide) and ISBNs. Cost: $0.00075 per page, so a 20-page shipment packet costs $0.015.