root@construct:~/projects/cte$
<-- back to /projects

CT-e Reconciliation

Freight invoice checks for a large logistics operation

PythonPlaywright

>Context

An accounting office checks the CT-e, Brazil's electronic freight invoices, of its trucking clients every month. One of the sources is the portal of a large logistics operation. Today the check is manual: download the XML files, import them into the accounting system and compare the reports to see whether the total matches.

This proof of concept automates the check. It compares the portal's report with the XML files for the same period and answers, document by document, OK, DIVERGENT or NOT CHECKED. A read-only connector logs into the portal, downloads the report and the XML batch and runs the check. It has run against the real portal.

I built it between September 30 and October 20, 2026, in 62 commits. The proof of concept then became an end-to-end pilot, still in progress: capture from three sources, import into the accounting system and a three-way check.

>High-level design

Everything runs on the office's Windows PC as a local agent: Python 3.13 in its own environment, with Playwright's Chromium. A PowerShell script installs it all with pinned versions and checks the SHA-256 of every download; running it again duplicates nothing.

A run starts from the panel button or from Task Scheduler, which catches up on a missed date if the PC was off. First the updater checks for a new version. Then the connector signs in to the portal read-only: it builds the report, downloads the XML batch and compares the downloaded count with the counter on the screen. The files go to one folder per run and client. The readers bring report and XMLs to one model, and the comparator returns OK, DIVERGENT or NOT CHECKED for each document. The result comes out as a summary, CSV, JSON and a spreadsheet and goes out by e-mail over SMTP. In the pilot, the same run also imports the folder into the accounting system, which runs in the cloud.

State lives on the PC's disk: run folders, history and logs with no secrets. Passwords live in Windows Credential Manager, and the client registry only on the machine, never in the repository. Apart from the schedule, nothing is asynchronous: no queue and no server in between. Updates ship as a zip package and a signed manifest on a static host. The robot refuses the package if the signature or the SHA-256 doesn't match, replaces only the program files and rolls back to the previous version if the new one fails a smoke check.

Hover or focus a piece: it lights up with its links.
who uses itOffice PC (Windows)external services2 One-button panel1 Office staff5 Reconciliation run3 Task Scheduler7 Portal connector4 Credential vault10 Logistics portal8 XML folder6 Updater12 Result e-mail11 Accounting system9 Result13 Static host
13 pieces13 links3 areas

Components

  • Panel and schedule :: A one-button window on the office PC and a Windows Task Scheduler task that runs on the agreed dates and catches up on a missed one.
  • Portal connector :: Playwright, read-only: sign-in, report, XML batch, retries on a stuck modal, the month split into weeks and a count read from the screen.
  • Pluggable readers :: Bring binary and HTML Excel, xlsx, CSV, PDF and XMLs in a folder or zip to one document model.
  • Comparator :: Applies the rules document by document and returns OK, DIVERGENT or NOT CHECKED, always with the reason.
  • Output and e-mail :: A Portuguese summary, CSV, JSON, a spreadsheet and exit code 0, 1 or 2; the e-mail goes out over SMTP with the spreadsheet attached and no XML.
  • Credential vault :: Portal passwords live in Windows Credential Manager; error messages name the secret, never its value.
  • Signed updater :: Before each run it fetches the manifest from a static host, checks signature and SHA-256, replaces only the code and rolls back if the smoke check fails.

>Trade-offs

Three states, with NOT CHECKED for anything that could not be verified.

Alternative rejected: Only OK or DIVERGENT.

Why: For an accountant, automation that says OK without checking is worse than the manual process. The first version returned OK in at least 4 wrong cases, including an empty capture and a CT-e with a cancellation event.

A read-only connector that refuses to click buttons that change the portal.

Alternative rejected: Free screen automation, including saving the report layout.

Why: It is a fixed project rule: on the portals, only reading and downloading.

Compare the downloaded count with the counter on the portal's screen and regenerate the report if it does not match.

Alternative rejected: Trust the downloaded file.

Why: In one run the report came back with more rows than valid CT-e. If the count still differs, the result is NOT CHECKED.

Supplementary CT-e in their own category, with count and sum.

Alternative rejected: Mixing them with the other divergences.

Why: The report's default filter does not list supplementary CT-e. Over the full month, the extra CT-e were all supplementary; kept apart, the divergence explains itself.

Run as a local agent on the office computer.

Alternative rejected: A cloud app that receives the files by upload.

Why: The XML folder and the accounting system live on the office machine, and a month of XML is heavy: 15,000 minimal synthetic XML files already take 5.9 MB zipped.

>Highlights

  • >Ran on the real portal, read-only: OK on every CT-e for Sep 1-4, 2026
  • >All of September 2026: OK on every CT-e, supplementary ones included
  • >15,000 XML files matched against 15,000 report rows in 0.8 s, on synthetic data
  • >504 tests, with a local fake portal that mimics the real one's labels and modal
  • >62 commits between Sep 30 and Oct 20, 2026

>Fun facts

  • *pdftotext 25 drops a character when two values touch in the PDF. The reader rebuilds the value, and the footer total is still checked, so a wrong rebuild never turns into OK.
  • *One test injects 65 extra rows into the real September report, and the result does not change.
  • *In 11 real runs there were 3 intermittent failures: 2 clicks blocked by a stuck 'Processing, please wait...' modal and 1 download that took longer than 180 s.
  • *Project rule: no new file goes over 400 lines.
CT-e Reconciliation | The Broad Way | Kinho.dev