Task
Suppliers send contracts on their own terms, and invoices come with wrong sums, prices above the agreed ones and sometimes a swapped bank account. A lawyer and an accountant check it all by hand. The task was a checker that compares a contract with the company's reference and an invoice with the contract, the price list and the order, explains every finding and hands the edits over in the usual Word and Excel. The demo had to open without sign-up and cost nothing.
Solution
The code makes the decisions, not a model. The matching only finds what to compare with: a reference clause, a price list item, an order line. A pair counts when the texts are alike in letter sequences and in shared words, and a matching clause number adds confidence, so a renumbered clause finds its reference and a new one stays new. Then the lawyer's rules, control digits and arithmetic do the work, and every finding carries its reason and the right version. In Word the edits are made word by word, as a lawyer would, and the supplier accepts or rejects each.
What was done
- Built matching without a neural model: letter sequences, shared words and the clause number pair a contract clause with its reference, and an invoice line with the price list and the order
- Made the lawyer's rules a separate file: a regular expression, a check and the reason in words
- Checked the whole invoice: line arithmetic, the total, the VAT rate and amount, the sum in words, the basis
- Checked requisites by the control digits of ИНН, ОГРН and the account with the bank's БИК, and against the contract
- Taught it to read scans: Tesseract on the server, with the table borders removed before recognition
- Built Excel and Word reports: the contract with tracked changes and comments, a letter to the supplier about the invoice
- Connected GigaChat and OpenAI-compatible models behind one interface: the model only reads invoice lines the rules could not find
- Limited the demo: 10 own files a day, each check in its own process, files live in memory for an hour, the container stays at about 40 MB
Result
The acceptance test: in three examples, a contract, an invoice and a scanned invoice, all 25 planted problems are found, and the reference checked against itself and a clean invoice get no findings. A scanned invoice is read in about 5 seconds, each check runs in its own process, and the idle container takes about 40 MB of memory. The demo on this site is the same project container: files are not stored, reports can be downloaded for an hour.
Developer's note
What was hard
Scans were the hardest: Tesseract read the table borders as “|” and “[”, losing row numbers and the digits next to them. Removing long lines before recognition fixed it: letters have no strokes that long, so only the grid goes. The second one was pack sizes: as text “25 kg bag” and “50 kg bag” are almost the same, so weight and fat are compared separately.
What came easy
The invoice arithmetic came easy: lines, the total, VAT and the sum in words are checked exactly, without thresholds.
Technologies
PythonFastAPITesseract OCRpypdfpython-docxopenpyxlGigaChatDocker