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>
136 lines
7.2 KiB
Clojure
136 lines
7.2 KiB
Clojure
(ns auto-ap.parse.templates-test
|
|
(:require [auto-ap.parse :as sut]
|
|
[clojure.test :refer [deftest is testing]]
|
|
[clojure.java.io :as io]
|
|
[clojure.string :as str]
|
|
[clj-time.core :as time]))
|
|
|
|
(deftest parse-bonanza-produce-invoice-03881260
|
|
(testing "Should parse Bonanza Produce invoice 03881260 with customer identifier including address"
|
|
(let [pdf-file (io/file "dev-resources/INVOICE - 03881260.pdf")
|
|
;; Extract text same way parse-file does
|
|
pdf-text (:out (clojure.java.shell/sh "pdftotext" "-layout" (str pdf-file) "-"))
|
|
results (sut/parse pdf-text)
|
|
result (first results)]
|
|
(is (some? results) "parse should return a result")
|
|
(is (some? result) "Template should match and return a result")
|
|
(when result
|
|
(println "DEBUG: customer-identifier =" (pr-str (:customer-identifier result)))
|
|
(println "DEBUG: account-number =" (pr-str (:account-number result)))
|
|
(is (= "Bonanza Produce" (:vendor-code result)))
|
|
(is (= "03881260" (:invoice-number result)))
|
|
;; Date is parsed as org.joda.time.DateTime - compare year/month/day
|
|
(let [d (:date result)]
|
|
(is (= 2026 (time/year d)))
|
|
(is (= 1 (time/month d)))
|
|
(is (= 20 (time/day d))))
|
|
;; Customer identifier includes name, account-number includes street address
|
|
;; Together they form the full customer identification
|
|
(is (= "NICK THE GREEK" (:customer-identifier result)))
|
|
(is (= "600 VISTA WAY" (str/trim (:account-number result))))
|
|
(is (= "NICK THE GREEK 600 VISTA WAY"
|
|
(str (:customer-identifier result) " " (str/trim (:account-number result)))))
|
|
;; Total is parsed as string, not number (per current behavior)
|
|
(is (= "23.22" (:total result)))))))
|
|
|
|
(deftest parse-bonanza-produce-statement-13595522
|
|
(testing "Should parse Bonanza Produce statement 13595522 with multiple invoices"
|
|
(let [pdf-file (io/file "dev-resources/13595522.pdf")
|
|
pdf-text (:out (clojure.java.shell/sh "pdftotext" "-layout" (str pdf-file) "-"))
|
|
results (sut/parse pdf-text)]
|
|
(is (some? results) "parse should return results")
|
|
(is (= 4 (count results)) "Should parse 4 invoices from statement")
|
|
(doseq [result results]
|
|
(is (= "Bonanza Produce" (:vendor-code result)))
|
|
(is (= "600 VISTA WAY" (:customer-identifier result))))
|
|
(is (= "03876838" (:invoice-number (nth results 0))))
|
|
(is (= "03877314" (:invoice-number (nth results 1))))
|
|
(is (= "03878619" (:invoice-number (nth results 2))))
|
|
(is (= "03879035" (:invoice-number (nth results 3))))
|
|
(is (= "891.65" (:total (nth results 0))))
|
|
(is (= "720.33" (:total (nth results 1))))
|
|
(is (= "853.16" (:total (nth results 2))))
|
|
(is (= "1066.60" (:total (nth results 3)))))))
|
|
|
|
(deftest parse-bonanza-produce-invoice-03882095
|
|
(testing "Should parse Bonanza Produce invoice 03882095 with customer identifier including address"
|
|
(let [pdf-file (io/file "dev-resources/INVOICE - 03882095.pdf")
|
|
pdf-text (:out (clojure.java.shell/sh "pdftotext" "-layout" (str pdf-file) "-"))
|
|
results (sut/parse pdf-text)
|
|
result (first results)]
|
|
(is (some? results) "parse should return a result")
|
|
(is (some? result) "Template should match and return a result")
|
|
(when result
|
|
(is (= "Bonanza Produce" (:vendor-code result)))
|
|
(is (= "03882095" (:invoice-number result)))
|
|
(let [d (:date result)]
|
|
(is (= 2026 (time/year d)))
|
|
(is (= 1 (time/month d)))
|
|
(is (= 23 (time/day d))))
|
|
(is (= "NICK THE GREEK" (:customer-identifier result)))
|
|
(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")
|
|
pdf-text (:out (clojure.java.shell/sh "pdftotext" "-layout" (str pdf-file) "-"))
|
|
results (sut/parse pdf-text)]
|
|
(is (seq results) "Template should match and return results")
|
|
(is (= 7 (count results)) "Should parse 7 invoices from statement")
|
|
(doseq [result results]
|
|
(is (= "Reel Produce" (:vendor-code result)))
|
|
(is (= "Sushi Confidential - San Jose" (:customer-identifier result))))
|
|
(is (= ["454379" "454826" "455120" "455683" "456654" "456774" "457171"]
|
|
(mapv :invoice-number results)))
|
|
(is (= ["1003.10" "530.85" "605.00" "1187.40" "164.00" "675.60" "265.75"]
|
|
(mapv :total results)))
|
|
;; totals add up to the statement's $4,431.70 amount due
|
|
(is (= 4431.70 (->> results (map #(Double/parseDouble (:total %))) (reduce +))))
|
|
(let [d (:date (first results))]
|
|
(is (= 2026 (time/year d)))
|
|
(is (= 6 (time/month d)))
|
|
(is (= 23 (time/day d)))))))
|