fix(parse): extract Bonanza bill-to lines with mixed case and punctuation
The Bonanza Produce invoice template captured the bill-to name and street with [A-Z\s] / [A-Z0-9\s] classes, so any store name or address containing a lowercase letter or punctuation failed to match and came back nil. On the McCarran invoice that meant both :customer-identifier and :account-number were empty, leaving the import with nothing to look a client up by. Anchor instead on the B/I/L/L letters printed down the left margin at the start of a line (the ship-to block on the right reuses the same letters mid-line) and take the whole column up to the next column gap, without restricting the character set. The leading \d on the account-number capture is what selects the street L line over the name L line. Every value the template already extracted is unchanged; only the nils moved. Adds regression tests for both Reno locations, covering the address that used to drop out and the sibling one that already worked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
BIN
dev-resources/Bonanza Sample Reno 2.pdf
Executable file
BIN
dev-resources/Bonanza Sample Reno 2.pdf
Executable file
Binary file not shown.
BIN
dev-resources/Bonanza Sample Reno.pdf
Executable file
BIN
dev-resources/Bonanza Sample Reno.pdf
Executable file
Binary file not shown.
@@ -771,8 +771,22 @@
|
||||
:keywords [#"530-544-4136"]
|
||||
:extract {:invoice-number #"NO\s+(\d{8,})\s+\d{2}/\d{2}/\d{2}"
|
||||
:date #"NO\s+\d{8,}\s+(\d{2}/\d{2}/\d{2})"
|
||||
:customer-identifier #"(?s)I\s+([A-Z][A-Z\s]+?)\s{2,}.*?L\s+([0-9][A-Z0-9\s]+?)(?=\s{2,}|\n)"
|
||||
:account-number #"(?s)L\s+([0-9][A-Z0-9\s]+?)(?=\s{2,}|\n)"
|
||||
;; The bill-to block spells BILL TO down the left margin, one letter
|
||||
;; per line, with the customer's details in the column to its right:
|
||||
;; B NICKGK
|
||||
;; I NICK THE GREEK <<McCARRAN>>
|
||||
;; L NICK THE GREEK <<McCARRAN>>
|
||||
;; L 10310 N McCARRAN BLVD STE.400
|
||||
;; RENO, NV 89503
|
||||
;; Anchor on those margin letters at the start of a line -- the ship-to
|
||||
;; block to the right reuses the same letters mid-line -- and take the
|
||||
;; whole column up to the next column gap. That column carries mixed
|
||||
;; case and punctuation, so it must not be restricted to [A-Z0-9\s];
|
||||
;; doing so dropped store names like McCARRAN entirely.
|
||||
:customer-identifier #"(?m)^\s+I\s{2,}(\S.*?)(?:\s{2,}|$)"
|
||||
;; Both the name and the street sit on an L line; the street is the
|
||||
;; one that starts with a house number.
|
||||
:account-number #"(?m)^\s+L\s{2,}(\d\S*.*?)(?:\s{2,}|$)"
|
||||
:total #"SHIPPED\s+[\d\.]+\s+TOTAL\s+([\d\.]+)"}
|
||||
:parser {:date [:clj-time "MM/dd/yy"]
|
||||
:total [:trim-commas nil]}}
|
||||
|
||||
@@ -71,6 +71,48 @@
|
||||
(is (= "600 VISTA WAY" (str/trim (:account-number result))))
|
||||
(is (= "946.24" (:total result)))))))
|
||||
|
||||
(deftest parse-bonanza-produce-invoice-03932070
|
||||
(testing "Should parse a Bonanza invoice whose bill-to block carries a mixed-case, punctuated street address"
|
||||
(let [pdf-file (io/file "dev-resources/Bonanza Sample Reno 2.pdf")
|
||||
pdf-text (:out (clojure.java.shell/sh "pdftotext" "-layout" (str pdf-file) "-"))
|
||||
results (sut/parse pdf-text)
|
||||
result (first results)]
|
||||
(is (some? result) "Template should match and return a result")
|
||||
(when result
|
||||
(is (= "Bonanza Produce" (:vendor-code result)))
|
||||
(is (= "03932070" (:invoice-number result)))
|
||||
(let [d (:date result)]
|
||||
(is (= 2026 (time/year d)))
|
||||
(is (= 8 (time/month d)))
|
||||
(is (= 4 (time/day d))))
|
||||
;; "10310 N McCARRAN BLVD STE.400" has both lowercase letters and a
|
||||
;; period; an uppercase/digit-only capture used to drop it entirely,
|
||||
;; leaving the invoice with no identifier to match a client against.
|
||||
(is (= "10310 N McCARRAN BLVD STE.400" (str/trim (:account-number result))))
|
||||
(is (str/starts-with? (:customer-identifier result) "NICK THE GREEK"))
|
||||
(is (str/includes? (:customer-identifier result) "McCARRAN"))
|
||||
(is (= "524.17" (:total result)))))))
|
||||
|
||||
(deftest parse-bonanza-produce-invoice-03933054
|
||||
(testing "Should keep parsing the sibling Reno location whose address was already extractable"
|
||||
(let [pdf-file (io/file "dev-resources/Bonanza Sample Reno.pdf")
|
||||
pdf-text (:out (clojure.java.shell/sh "pdftotext" "-layout" (str pdf-file) "-"))
|
||||
results (sut/parse pdf-text)
|
||||
result (first results)]
|
||||
(is (some? result) "Template should match and return a result")
|
||||
(when result
|
||||
(is (= "Bonanza Produce" (:vendor-code result)))
|
||||
(is (= "03933054" (:invoice-number result)))
|
||||
(let [d (:date result)]
|
||||
(is (= 2026 (time/year d)))
|
||||
(is (= 8 (time/month d)))
|
||||
(is (= 7 (time/day d))))
|
||||
(is (= "5140 KIETZKE" (str/trim (:account-number result))))
|
||||
(is (str/starts-with? (:customer-identifier result) "NICK THE GREEK"))
|
||||
(is (str/includes? (:customer-identifier result) "KIETZKE"))
|
||||
;; Two-page invoice: the totals only appear on the final page.
|
||||
(is (= "955.75" (:total result)))))))
|
||||
|
||||
(deftest parse-reel-produce-statement-28676
|
||||
(testing "Should parse the Reel Produce statement layout that no longer prints 'Reel Produce' on the page"
|
||||
(let [pdf-file (io/file "dev-resources/Statement1_from_REEL_Produce_Inc.28676.pdf")
|
||||
|
||||
Reference in New Issue
Block a user