Before I moved into QA, I ran transport logistics for a delivery service: 50-60 drivers and 2,500-3,000 deliveries a day. Every order had to match in four places: the waybill a driver loaded from in the morning, the status the driver set in the mobile app, the record in our system, and the returns that a warehouse employee checked against the waybill in the evening. If an item was in one place and missing in another, the driver paid a fine.
We did that reconciliation by hand, every day.
Later, as a QA engineer in FinTech and advertising analytics, I saw the same problem in software. The same order, payment or report lives in several places at once: the user interface, the API, the accounting export and the database. The expensive bugs are rarely inside one endpoint. They hide between systems.
Four bugs that live between systems
- Rounding. On one FinTech project, our accrual and income statistics did not match the accountant's numbers. The formulas rounded intermediate values with ordinary half-up rounding, and the code applied a fixed percentage per client tariff while the accountant adjusted it to the business terms. Each number looked plausible on its own. The gap only showed up when two sources were compared.
- Timezones. On every project I have worked on, "which timezone is this timestamp in?" was a question for the product owner or the customer. An export writes local time without an offset, the API returns UTC, and a delivery suddenly happened six hours earlier.
- A row lost at a page boundary. An export pages through 200 rows at a time and drops the last row of every page. The endpoint test passes. Fourteen of 2,750 orders are missing, and nobody notices.
- A stale status. The backend knows the order was delivered, but the dispatcher screen still shows "in transit" from a cache.
Postman checks that an API answers correctly. Playwright checks that a page works. Contract tests check the shape of a response. None of them takes the same shipment from the API, the screen and the Excel export and tells you: here it is 101 cents, there it is 100.
What LogiParity does
LogiParity is an MIT-licensed CLI that automates that reconciliation. It reads records from REST APIs, CSV and XLSX files, UI tables (through Playwright) and PostgreSQL (read only), matches them by key, and reports what is missing, what is extra and which field differs.
Everything is described in YAML. This is a shortened version of the demo configuration:
version: 1
environment: test
entities:
- name: shipment
key: shipment_id
sources:
api:
type: rest
label: API
baseUrl: http://127.0.0.1:3010
path: /api/shipments
items: $.data[*]
pagination: {type: page, totalPath: $.pagination.total, sizeParam: limit, size: 200}
fields: {shipment_id: $.id, status: $.status, payout: $.driver_payout, delivered_at: $.delivered_at}
export:
type: xlsx
label: Excel export
file: ./shipments.xlsx
sheet: Shipments
fields: {shipment_id: 'Shipment ID', status: Status, payout: 'Driver payout', delivered_at: 'Delivered at'}
compare:
status: {type: enum}
payout: {type: money, currency: USD}
delivered_at: {type: datetime, sourceTimezones: {export: UTC}, toleranceSeconds: 60}
A few decisions come straight from that logistics experience:
- The first source is the reference. At the delivery service, the waybill was the reference that returns were checked against, not the other way round. Which system is authoritative is a business decision, so the configuration states it.
-
Money is compared in exact cents.
1.01becomes101, so there is no floating-point drift. By default, values with more decimals than the currency allows are rejected instead of rounded. Rounding is an explicit, configurable rule, because after that FinTech project I treat rounding as business logic, not formatting. - No guessing timezones. A timestamp without an offset is an error until the configuration says which timezone the source uses. Ambiguous times around daylight saving changes are not guessed either.
From thousands of findings to a few causes
The package includes a synthetic delivery service with 55 drivers and 2,750 orders, with optional bugs. Without bugs, the check finds zero differences and exits with code 0. With bugs enabled, comparing the API with the Excel and CSV exports produces 3,741 findings. Nobody reads 3,741 rows, so the report starts with causes:
14 missing rows in Excel export follow page boundaries. Likely a pagination boundary defect.
1,833 delivery time values in CSV export and 1,823 delivery time values in Excel export differ by −6 h. Likely local time written without an offset.
34 driver payout values in CSV export and 34 driver payout values in Excel export differ by −$0.01. Likely a different rounding rule than the reference.
Add the dispatcher screen through Playwright and a fourth cause appears:
57 of 1,833 Delivered orders show In transit in Dispatcher; 1,776 match. Likely a stale or lagging status.
Totals are traced to causes too. The Excel export's payout total is off by −$409.55: −$409.21 comes from 14 missing rows and −$0.34 from rounding, with a $0.00 unexplained remainder. That is the kind of breakdown you can take straight to the developers.
Reports come as HTML, Markdown, JSON and JUnit. Exit code 1 means data findings and exit code 2 means a configuration or source error, so a flaky staging server does not look like a data defect in CI.
Disruption scenarios
The second half of the tool is about what happens when delivery goes wrong. A scenario describes an event, such as a driver no-show or a vehicle breakdown with orders on board, and the checks after replanning: no order lost, exactly one assignee per active order, capacity respected, ETAs updated, customers notified. This is the breakdown scenario on the buggy demo:
Scenario: Vehicle breakdown with orders on board (demo/test)
6 checks need attention
50 affected orders checked against the baseline.
Needs review Orders lost after the breakdown: 1 ... Keys: S000801
Needs review Orders with incorrect assignments: 1 ... Drivers: S000802: D17 → D01, D55
Passed Orders exceeding available capacity: 0
At the delivery service, we used to find exactly these problems in the warehouse in the evening.
Freight: many lines per load
Carrier settlement usually has several lines per load (linehaul, fuel surcharge, detention), while the TMS has one total. Version 1.1.0 adds composite keys, filters and rollups that sum those lines per load in exact cents before the comparison. The repository includes a synthetic example with 120 loads and 358 confirmed charges; its report lists 16 findings.
How it was built
I built LogiParity with AI coding agents, and the README says so. I defined the problem, the requirements and the design decisions above. AI agents generated the code, tests and documentation in milestones, and I reviewed each milestone before the next one began.
Then I ran several review rounds with simulated users: AI agents playing a QA engineer at a delivery company, a team lead deciding what goes into CI, a developer integrating an API, a freight QA lead, a DevOps engineer and a security reviewer. They are simulations, not human testers, so I do not present their scores as user feedback.
That process still caught real defects. When a server silently capped page size, an early version read one page and blamed the CSV for "extra" records. In the last round, a lagging status was being diagnosed as a vocabulary mismatch; the tool now checks how many records with that status already match (1,776 of 1,833) before it chooses a cause. Both are fixed and covered by tests.
Limitations
- It is a young project with one maintainer.
- A source is limited to 100,000 rows by default. You can raise the limit together with Node's heap: in my test, two 1,000,000-row, six-column CSV files did not fit in Node's default heap and passed with
NODE_OPTIONS=--max-old-space-size=6144. - PostgreSQL is the only database for now.
- The AI commands (
logiparity ai ...) only prepare drafts. They preview locally and send nothing unless you configure a provider and pass--send.
Try it
npm install --save-dev --save-exact logiparity
npx logiparity demo --bugs
The demo prints the commands for a second terminal: download two exports and run npx logiparity check --config parity.yaml. The report is written to logiparity-demo/report/report.html. Container images are on GHCR as ghcr.io/nutts5796/logiparity:1.1.0.
Repository: https://github.com/Nutts5796/logiparity
If your orders, payments or reports live in more than one system, I would love to know whether LogiParity finds anything in yours. Issues and comments are very welcome. Real data and real feedback matter most right now.

Top comments (0)