Skip to content
PR
04Active project

ReturnProof

Deterministic evidence reconciliation for conflicting returns data.

What it solves
Decides which of two disagreeing sources to trust for a returned shipment, field by field, so stock and supplier-credit decisions are defensible.
Engineering decision
Evidence is resolved per claim field, never per source, and every decision keeps the ID of the rule that produced it, so a wrong outcome can be traced back to a rule rather than argued about.
Proof
pytest across the reconciliation engine and the Django API. Vitest for frontend units. Playwright for the end-to-end claim workflow.

Python · Pydantic · Typer · Django · Django Ninja · React · TypeScript · TanStack Query · pytest · Vitest · Playwright

Scope note: Not deployed in a regulated production environment.

Animated diagram: conflicting warehouse and supplier streams meet a resolver and leave as ordered per-field lanes into an append-only ledger.

Two contradictory streams collide and are separated into field-level evidence ribbons before joining an auditable ledger.

(Case study)

How ReturnProof is built

Constraints, decisions and limits, in the order they shaped the system.

Use case

  • Returns and finance operations that must decide, per claim, whether stock is restocked, scrapped or quarantined, and whether a supplier credit is owed.

What I built

  • Built a deterministic reconciliation engine that resolves evidence per claim rather than per source.
  • Created eight documented decision rules.
  • Resolves condition, batch identity, expiry, quantity, and credit eligibility independently.
  • Orders supplier events by business timestamp instead of arrival order.
  • Records accepted, rejected, superseded, corrupted, and unresolved evidence.
  • Preserves the rule ID behind each decision.
  • Enforces quantity conservation across restock, scrap, and quarantine.
  • Tests the engine, Django API, and React frontend.
  • Reconciles multi-item returns and surfaces each decision in an Operator Console.

Constraints

  • Two sources of truth that legitimately disagree, with no authority to declare one globally correct.
  • Supplier events can arrive late, out of order, or corrupted.
  • Quantities must balance: nothing can be restocked and scrapped at the same time.

Approach

  • Resolve each claim field independently rather than picking a winning source.
  • Encode the resolution logic as eight documented rules with stable IDs.
  • Treat ordering as a data problem: sort supplier events by business timestamp.
  • Persist every evidence outcome, including the ones that were rejected.

Architecture

  • A pure Python reconciliation core with Pydantic models and a Typer CLI for replay.
  • A Django + Django Ninja API layer exposing claims, evidence and decisions.
  • A React + TypeScript client using TanStack Query for server state.

Key decisions

  • Per-field resolution over per-source trust, because source-level trust silently corrupts unrelated fields.
  • Rule IDs stored with decisions so audits do not depend on re-running the engine.
  • Explicit evidence states (accepted, rejected, superseded, corrupted, unresolved) instead of dropping data.
  • Quantity conservation enforced across restock, scrap and quarantine.

Limitations

  • Not deployed in a regulated production environment.
  • Rules encode a modelled returns policy, not any specific company's legal policy.

What I learned

  • Determinism is a product feature: an operator will trust a decision they can retrace.
  • Preserving rejected evidence costs little and answers most later questions.