Engineering case study · October 2026
A small, open-source component we built so a prospective partner can evaluate our engineering without an NDA, a sales call or a reference check. Everything below is measured on the published code; nothing is a client figure.
What it is. A library that reconciles the three documents behind a sea shipment — commercial invoice, packing list, bill of lading — against 21 rules, and explains every finding.
Why this problem. It is the shape of work we have done for a customs-document comparison system for an Egyptian broker (figures and material not disclosed). We chose a problem we understand rather than a generic to-do app.
Where. github.com/dalteno/docrecon (MIT) · live demo on dalteno.com.
| Metric | Value | How measured |
|---|---|---|
| Rules | 21, each with a stable id, severity and one-line contract | RULES in web/docrecon.js; RuleCatalogue in C#; both counted by a test |
| Shared cases | 52 (15 adversarial) | cases/generate.mjs; expectations hand-written as RULE|key sets |
| JS tests | 62 passing | node --test web/tests/cases.test.mjs |
| .NET tests | 136 defined (52 expectation + 52 golden-parity + 32 unit) | dotnet test, xUnit; parity compares every field of every finding to the JS golden report — see the CI badge in the repo for the current run |
| Mutation checks | 3 of 3 caught | Disabling R05, flipping the rounding mode, and changing the tolerance comparison each turned the suite red |
| Throughput | 38 µs per bundle | 1,000 bundles in 38 ms, Node 22, after warm-up, mixed cases |
| Engine size | 276 lines JS · 389 lines C# core | wc -l on docrecon.js, Reconciler.cs + Numeric.cs |
| Floating-point operations | 0 | All arithmetic in integer minor units (BigInt / BigInteger) — ADR-0002 |
| Runtime dependencies | 0 | Neither implementation imports anything outside the platform |
| Effort | one engineering day, AI-assisted | Design, both implementations, cases, CI, this page. We use AI tooling the same way we would on your project: it drafts, the tests decide. |
Money is parsed to cents, quantities and weights to thousandths, counts to integers. 3 × 0.10 is 0.30, not 0.30000000000000004. Tolerances are basis points, tested as |a − b| × 10000 > bp × max(|a|, |b|) — no division, so the C# and JS results cannot drift by a rounding step. Case C15_float_trap exists to keep it that way.
One consistent synthetic bundle plus a named mutation per case, with the expected finding set written by hand. The generator never calls the engine. Separately, the JS engine's full reports are frozen as golden files the .NET engine must reproduce field by field. A rule change therefore shows up three times in review: the generator diff, the golden diff, and both engines.
A quantity of "ten" is a finding (R21), not an exception and not a silent zero. A missing document is reported once and every rule that needs it steps aside. Nothing is inferred from free text; a consignee name is compared only after a documented normalisation (case, punctuation, one trailing legal suffix).
The first mutation check exposed a gap: flipping the rounding mode from half-away-from-zero to truncation did not fail any shared case, because the ±0.01 amount tolerance absorbed the one-cent difference. Only a unit test caught it. We added C17b_half_cent_zero_tolerance — the same half-cent input with tolerance set to zero — so the behaviour is pinned at the integration level too. The lesson generalises: a tolerance that makes a rule forgiving also makes its tests forgiving; test the boundary with the tolerance off.
The same discipline applied to the process the documents live in: a dossier of seven customs documents routed to six roles, with SLAs, a reconciliation gate that reuses docrecon unchanged, an ERP outbox with idempotency keys, notifications on every hand-off, and closure only when every required document is approved and integrated. Built as an event-sourced pure function — every command carries its date, there is no clock — so a visitor can drive it in any order and replay is byte-identical. Try it at dalteno.com/lab/docflow/.
| Metric | Value | How measured |
|---|---|---|
| Rules | 16 (F01–F16) | RULES in web/docflow.js; FlowRules in C#; counted by a test |
| Document types / roles | 7 / 6 | DEFAULT_DEFINITION — data, not code; the roles are the real roles of the process |
| Shared cases | 41 (9 adversarial) | cases-docflow/generate.mjs; expectations hand-written as status + document states + rule hits |
| JS tests | 45 passing | node --test web/tests/docflow.test.mjs — includes the F13 invariant (one notification per hand-off) on every case |
| .NET tests | 87 defined (41 expectation + 41 golden-parity + 5 unit) | dotnet test; parity compares the whole report, key-order-insensitive |
| Mutation checks | 5 of 5 caught | Disabled F08, flipped SLA boundary, removed integration cancel on rejection, froze reconciliation re-run, disabled F02 — each turned the suite red |
| Throughput | 73 µs per dossier replay | 1,000 replays in 73 ms, ~20 events each, Node 22 |
| Engine size | 247 lines JS | wc -l web/docflow.js |
| Red line | no writes to operational ERP tables | Integration is an outbox to DOCFLOW_STG.STG_<TYPE> staging tables; the ERP team's job/procedure moves data in. ADR-0004. |
Two things the second day taught us. First, a rule that is tested through a tolerance is only half tested — the same lesson as day one, now on dates: the SLA boundary (exactly N days = no breach) needed its own case. Second, hand-writing expectations for a stateful engine forces you to think about every date in a scenario: two of our first drafts accidentally crossed an SLA boundary and would have "expected" a breach we did not intend. The generator caught the author, not the engine — which is the point.
We would start exactly here: one page of scope with acceptance criteria, a fixed-price pilot of two to four weeks, the shared-cases approach from day one so you can read our tests before our code, and the right to stop after week one. Write to hello@dalteno.com.
© 2026 Dalteno, Cairo · All sample data in docrecon is synthetic. Client names and figures are withheld under NDA.