# Amazon A/R Aging Dashboard — System Guide
Complete reference for how the month-end Amazon Accounts Receivable system works: the flow,
every module, every screen, every calculation, and every rule.
> **Benchmark:** the engine reproduces the Finance team's manual workbook exactly —
> **USA January 2026 = 11,110,433** (`Detail!D11`), and all **13 marketplaces reconcile to the penny**.
---
## 1. What this system does
Finance downloads Amazon **Custom Unified Transaction** reports each month (one or more files per
marketplace). Previously they hand-built a 283 MB Excel workbook to answer one question:
> **How much does Amazon still owe us at month-end?**
This system automates that end to end: upload the files → it parses, classifies, calculates,
reconciles → you get a dashboard and a downloadable audit workbook.
**Scope:** Amazon only (no Walmart/TikTok/Temu/Shein/eBay/Shopify). 13 marketplaces supported.
---
## 2. Quick start
```bash
# both servers (from the project root)
python3 -m uvicorn app.api.main:app --app-dir ar-aging-app/backend --reload --port 8000
npm --prefix ar-aging-app/frontend run dev # http://localhost:5173
```
Or use the preview launcher, which starts both from `.claude/launch.json`.
```bash
# headless engine run
cd ar-aging-app/backend
python cli.py "/path/file1.xlsx" "/path/file2.xlsx" --month-end 2026-01-31 --lag 2
# tests
python -m pytest tests -m "not integration" # 64 fast tests
python -m pytest tests -m integration # 12 tests against the real sample files
```
---
## 3. End-to-end flow
```mermaid
flowchart TD
A["Amazon reports
(.xlsx per marketplace)"] --> B[Upload & validate]
B --> C[Parse & normalize]
C --> D[Aggregate by settlement]
D --> E[Classify paid vs receivable]
E --> F[Compute receivable]
F --> G[Journal decomposition]
G --> H[(SQLite
persisted results)]
H --> I[Dashboard tabs]
H --> J[Excel exports]
K[Finance inputs
opening balance · FX · clearing-lag
reserve · control amounts] --> E
K --> F
K --> I
style H fill:#ECEAFC,stroke:#6D5DE8
style I fill:#E7E5FB,stroke:#6D5DE8
style J fill:#E7E5FB,stroke:#6D5DE8
```
### The nine processing stages
Each stage reports live progress (rows done / total + ETA) to the dashboard.
```mermaid
flowchart LR
S1[1 Reading
workbook] --> S2[2 Validating
columns]
S2 --> S3[3 Combining
transactions]
S3 --> S4[4 Detecting
duplicates]
S4 --> S5[5 Calculating
settlements]
S5 --> S6[6 Creating
receivable]
S6 --> S7[7 Reconciling
totals]
S7 --> S8[8 Persisting
transactions]
S8 --> S9[9 Building
journal entry]
```
| # | Stage | What happens |
|---|---|---|
| 1 | **Reading workbook** | Opens each file, auto-detects the raw data sheet (ignores user-made pivot tabs) |
| 2 | **Validating columns** | Maps localized headers to canonical fields; flags unmapped/missing |
| 3 | **Combining transactions** | Streams all files into one normalized record set |
| 4 | **Detecting duplicates** | Fingerprints rows; counts repeats (never deletes them) |
| 5 | **Calculating settlements** | Groups by settlement id; marks each paid or receivable |
| 6 | **Creating receivable** | Sums open settlements; applies reserve, rounding and FX |
| 7 | **Reconciling totals** | Checks the identity `uploaded = receivable + paid + transfers` |
| 8 | **Persisting transactions** | Bulk-inserts every row with full source traceability |
| 9 | **Building journal entry** | Decomposes activity into GL accounts, per marketplace |
---
## 4. The calculation — four rules
Everything the dashboard shows derives from these. **`total` (column AD) is the net amount of each
row** and is the figure that gets summed.
### Rule 1 — Settlement classification (which money is still owed)
Amazon pays in settlement batches. When a batch closes, the next opens and Amazon disburses the
prior one via a **`Transfer`** row *tagged with the next batch's id*.
> A settlement stays **receivable** until the payout that clears it has actually **reached the bank**
> by month-end.
```mermaid
flowchart TD
T["Transfer row
(a bank payout)"] --> D{"payout date ≤
month_end − clearing_lag?"}
D -->|Yes| R["RECEIVED
→ sets the paid boundary"]
D -->|No| I["IN TRANSIT
→ its settlement stays receivable"]
R --> B["paid_boundary = max settlement_id
over received payouts"]
B --> C{"settlement_id ≥ boundary?"}
C -->|Yes| RECV[RECEIVABLE]
C -->|No| PAID[PAID]
style RECV fill:#DCFCE7,stroke:#2FA875
style PAID fill:#F1F1F6,stroke:#868AA0
style I fill:#FAF0DA,stroke:#B37A1B
```
`clearing_lag` defaults to **2 days** and is adjustable per closing. Every payout is listed on the
**Settlement Reconciliation** tab where you can override its received/in-transit status.
### Rule 2 — The receivable base
```
additional_sales = Σ total
WHERE settlement is receivable
AND account_type ∈ {Standard Orders, Invoiced Orders, All Orders}
AND type ≠ Transfer
```
### Rule 3 — Closing receivable
Two independent methods, which must agree:
```
# Settlement method (the workbook's method — the benchmark)
receivable_local = ROUND(reserve + additional_sales, 0)
receivable_usd = receivable_local × fx_rate
# Roll-forward method (the AR Ledger)
closing = opening_balance + net_revenue − payouts_received
```
#### Opening balance — three modes
Chosen when the closing is created (and changeable any time on the AR Ledger tab):
| Mode | Behaviour |
|---|---|
| **Zero** *(default)* | Every marketplace opens at 0 — correct for a first-ever closing |
| **Carry forward** | Copies each marketplace's closing receivable from a chosen prior closing. Falls back to zero if no processed prior exists |
| **Manual** | Opens at 0; you type each marketplace's balance on the AR Ledger tab after processing |
### Rule 4 — Reconciliation identity
Every uploaded row lands in exactly one bucket, so this must always hold:
```
uploaded_total = receivable_orders + paid_orders + transfers_total
```
Status shows **Reconciled** when the difference is within tolerance (default **$0.01**).
---
## 5. Architecture
```mermaid
flowchart TB
subgraph FE["Frontend — React + TypeScript + Vite"]
P1[Dashboard] --- P2[New Closing] --- P3[Closing tabs] --- P4[Settings]
end
subgraph API["Backend — FastAPI"]
R1[sessions/files/processing] --- R2[results/ar/control] --- R3[analytics] --- R4[export/settings]
end
subgraph CORE["Engine — pure Python, no web deps"]
C1[readers] --> C2[column_map + i18n]
C2 --> C3[pipeline]
C3 --> C4[settlements]
C4 --> C5[receivable]
C5 --> C6[movement + journal]
C6 --> C7[reconciliation + validation]
C7 --> C8[excel_export + summary_export]
end
DB[(SQLite / Postgres)]
FE -->|"/api (Vite proxy)"| API
API --> CORE
API --> DB
CORE --> DB
style CORE fill:#ECEAFC,stroke:#6D5DE8
```
**Key principle:** the engine (`app/core/`) has no web or database dependencies. It can run headless
via `cli.py`, which is what the integration tests exercise.
---
## 6. Backend modules — each one defined
### `app/core/` — the engine
| Module | Purpose |
|---|---|
| **`readers.py`** | Factory choosing the parser: `python-calamine` (fast, Rust) by default, streaming fallback via `AR_USE_CALAMINE=0` |
| **`calamine_reader.py`** | Fast parser. Detects the data sheet, skips pivot/helper rows, yields normalized records |
| **`xlsx_reader.py`** | Low-memory streaming parser (`xml.etree.iterparse`) + `FileMeta`. Used for very large files |
| **`column_map.py`** | Canonical schema and header aliases for 8 languages. Maps by **normalized name, never position** |
| **`i18n.py`** | Marketplace config: transaction-type translations, currencies, default FX, storage keywords |
| **`dates.py`** | Multilingual date parsing (11 formats) with a prefix cache — turns millions of parses into dozens |
| **`regions.py`** | Maps `amazon.de` → `Germany` etc. |
| **`pipeline.py`** | Orchestrates parse → enrich → aggregate → classify → receivable → reconcile, with progress callbacks |
| **`settlements.py`** | Groups rows by settlement; owner attribution; the paid/receivable classification (Rule 1) |
| **`receivable.py`** | Rules 2 and 3: additional sales, reserve, rounding, FX, aging bands |
| **`movement.py`** | Roll-forward: opening + net revenue − payouts, and the AR ledger rows |
| **`journal.py`** | GL decomposition into 9 journal lines + 17 granular components, per marketplace |
| **`reconciliation.py`** | Rule 4 identity check and status |
| **`validation.py`** | File coverage: wrong month, overlapping ranges, date gaps |
| **`excel_export.py`** | Full audit workbook (streaming `write_only`, live formulas) |
| **`summary_export.py`** | Compact Finance pack (5 sheets, generates in seconds) |
| **`compare.py`** | Diffs a generated workbook against the sample for the comparison test |
### `app/api/routes/` — the web layer
| Route module | Responsibility |
|---|---|
| **`sessions.py`** | Create/list/update/delete month-end closings; seeds opening balances from the prior month |
| **`files.py`** | Chunked upload, filename sanitization, per-file parse validation |
| **`processing.py`** | Starts the background job; reports status, stage, rows done, ETA |
| **`results.py`** | Summary, settlements, transactions (filterable), aging, exceptions, journal |
| **`ar.py`** | Opening balances, `ar-movement` (roll-forward), `finance-summary` |
| **`control.py`** | Reconciliation control: dashboard vs Finance amounts, sign-off, completion gate |
| **`analytics.py`** | Date-filtered ledger, daily FX, sign-explained reconciliation detail, all-markets roll-up |
| **`export.py`** | Starts Summary/Full exports; lists and downloads them |
| **`settings.py`** | Reserves, FX rates, and global header-mapping rules |
### `app/services/`
| Module | Responsibility |
|---|---|
| **`jobs.py`** | Background processing and export jobs; owns its DB session; writes progress + ETA |
| **`store.py`** | Bulk transaction inserts (batched raw DBAPI), persists aggregates, builds exceptions |
---
## 7. Database tables
| Table | What it holds |
|---|---|
| `sessions` | One month-end closing: month, month-end date, clearing-lag, tolerance, status, progress |
| `session_files` | Uploaded files: name, size, SHA-256, sheet, rows, date range, currency, status |
| `transactions` | Every parsed row with `source_file`/`source_row` traceability, canonical type, flags |
| `settlements` | Per (marketplace, account, settlement id): order total, transfers, dates, paid/receivable |
| `receivable_results` | Per marketplace and account: additional sales, reserve, local + USD receivable |
| `reconciliation` | Identity check figures and status |
| `market_payouts` | Per-marketplace received / total payouts, attributed to each settlement's **owner** |
| `opening_balances` | Opening AR per marketplace, with source (manual / carried-forward) and reason |
| `fx_rates` | Month FX rate + currency per marketplace |
| `fx_rates_daily` | Optional per-date FX override |
| `reserves` | Net Closing Balance per marketplace and account |
| `journal_entries` | The GL decomposition JSON (primary + `per_marketplace`) and entry number |
| `finance_control` | Finance's control-sheet amounts, tolerance, sign-off and comments |
| `exceptions` | Every validation finding: category, severity, detail, source |
| `mapping_rules` | Admin header aliases (global, applied to all future parses) |
| `exports` | Generated workbooks: kind, path, SHA-256, size, timestamp |
---
## 8. The dashboard — each tab defined
```mermaid
flowchart LR
OV[Overview] --> UP[Upload & Mapping]
UP --> VE[Validation & Exceptions]
VE --> FS[Finance Summary]
FS --> AL[AR Ledger]
AL --> SR[Settlement Reconciliation]
SR --> AG[A/R Aging]
AG --> TD[Transaction Details]
TD --> RE[Reconciliation]
RE --> JE[Journal Entry]
JE --> EX[Excel Export]
```
| Tab | What it shows | Key controls |
|---|---|---|
| **Overview** | KPI cards (closing receivable, reconciliation status, settlement counts, exceptions) and receivable by marketplace | **View details →** per marketplace; **All markets →** |
| **Dashboard** (home) | All closings with status and receivable | **Delete** removes the closing and every row/file it owns |
| **Upload & Mapping** | Drag-drop upload; per-file rows, date coverage, marketplace, status | **Header mapping** panel to classify unknown columns and save the rule |
| **Validation & Exceptions** | Every finding grouped by severity | — |
| **Finance Summary** | The Finance report: opening → revenue components → gross → fees → net revenue → closing, plus the ledger | Market switcher · **All Markets** sub-tab · Summary Excel |
| **AR Ledger** | Opening balance (**Adjust · Carry forward · Set to zero**), roll-forward statement, ledger movement, cross-check vs settlement method, **movement by date**, **daily FX table** | Daily/Weekly/Monthly + custom range · market switcher · All Markets |
| **Settlement Reconciliation** | Every settlement with orders, transfers, rows, period and status; the disbursement list | **Clearing-lag** control + re-process |
| **A/R Aging** | Aging matrix (Current / 1-30 / 31-60 / 61-90 / 91-Over) and chart | — |
| **Transaction Details** | Virtualized grid of every row with source file + row | Filters: settlement, type, marketplace, receivable, storage, search |
| **Reconciliation** | Sign-explained metrics (Revenue / Fees / Memo / Settlements), closing & variance, plus the **Reconciliation Control** | Finance amounts, tolerance, **Verify**, **Mark month complete** |
| **Journal Entry** | GL decomposition per period with account names | Journal Entry # |
| **Excel Export** | Generate/download Summary or Full workbook, with history | Two generate buttons |
### The All Markets sub-tab
Present on Finance Summary, AR Ledger and Reconciliation. Shows every marketplace in **its own
currency** with its FX rate, plus the **combined USD total**, and a **View details** button per row.
The selected market lives in the URL (`?market=Germany`, or `?market=__all__`), so links are shareable.
---
## 9. Data model — the source columns
Amazon's report has a 7-row preamble, **header on row 8**, data from row 9. Column counts vary by
marketplace (USA 30, Canada 29, UK 27, AU/IE 23, localized EU 28/24).
**The five columns that drive everything:**
| Col | Header | Role |
|---|---|---|
| A | `date/time` | Aging, date filters, payout clearing |
| B | `settlement id` | Grouping; the paid/receivable boundary (monotonic — higher = newer) |
| C | `type` | Order / Refund / Adjustment / fees / **Transfer** |
| I | `account type` | `Standard Orders` / `Invoiced Orders` (USA only; others use one `All Orders` stream) |
| **AD** | **`total`** | **The net amount summed for every figure** |
The other 25 columns (product sales, taxes, shipping, gift wrap, promos, selling fees, FBA fees,
storage, other) feed the **revenue/fee breakdown** in the Finance Summary and Journal Entry.
---
## 10. Multi-marketplace handling
| Aspect | How it works |
|---|---|
| **Languages** | Headers, transaction types and dates are localized in FR, DE, IT, ES, NL, PL, SV, TR. All normalized to canonical English |
| **Currencies** | USD, CAD, GBP, EUR, PLN, SEK, AUD, TRY — each with a default FX-to-USD rate, editable per closing |
| **Account streams** | Only the USA report has `account type`. Other marketplaces run a single `All Orders` stream |
| **Settlement owner** | A settlement belongs to the marketplace holding most of its rows. Payouts attach to their settlement's **owner**, not the file they appear in (Belgium's payouts sit inside the Germany report) |
| **Blank marketplace rows** | Inherit the file's **dominant** marketplace — never a filename guess (critical for combined files like "Belgium Germany") |
| **Cross-market rows** | Rows in a different marketplace than their settlement's owner are excluded and flagged |
| **Helper rows** | EU files carry a Finance-added translation row under the real header — detected and skipped |
---
## 11. Validation & exception categories
No amount is ever silently excluded.
| Category | Severity | Meaning |
|---|---|---|
| `missing_column` | error | A required field is absent — must be fixed |
| `unmapped_amounts` | **error** | An unmapped column contains money. **Caught Australia's renamed FBA-fee column (−2,427.43)** |
| `unmapped_column` | warning | An unrecognized header — classify it in Upload & Mapping |
| `wrong_month` | error/warning | File dates fall outside the reporting month |
| `overlapping_files` | warning | Two files of the same marketplace cover the same dates |
| `missing_dates` | warning | A gap in coverage within the month |
| `duplicate_rows` | warning | Identical row fingerprints — surfaced, never deleted |
| `undated_rows` | warning | Rows with no usable date (e.g. a manually appended total row) |
| `potential_storage_fee` | warning | Storage-like description booked under another type |
| `missing_storage_fees` | warning | A marketplace with no storage fees at all this month |
| `cross_market_settlement` | warning | Rows belonging to another marketplace's settlement |
| `in_transit_disbursement` | info | A payout not yet cleared — explains why a settlement stays receivable |
| `helper_header_row` | info | Translation rows skipped |
| `empty_file` | warning | Header only, no data rows |
---
## 12. Excel exports
| | Contents | Size / speed |
|---|---|---|
| **Summary Excel** | Finance Summary · AR Ledger · Reconciliation Control · Category Totals · Audit Trail | ~11 KB, seconds |
| **Full Excel** | Summary · Detail · COA · AR Ledger · Journal Entry · per-marketplace transaction tabs · Reconciliation · Exceptions · Processing Audit Trail — with **live formulas** (`SUMIFS`, `ROUND`, cross-sheet refs) | ~240 MB, a few minutes |
The Full workbook mirrors the manual file's structure so an auditor can trace any total back to the
transaction rows. The **Processing Audit Trail** sheet records generation time, inputs, filenames with
**SHA-256 hashes**, row counts, FX rates, reserves and the app version.
---
## 13. Testing
| Suite | Covers |
|---|---|
| `test_engine_unit.py` | Dates, column mapping, aging bands, settlement classification |
| `test_multimarket.py` | 11 localized date formats, 22 type translations, storage detection, header mapping, **13-market benchmark** |
| `test_per_market.py` | Journal resolution, payout owner attribution, per-market API |
| `test_reconciliation_jan2026.py` | **USA = 11,110,433 to the penny**, settlement split, reconciliation |
| `test_excel_export.py` / `test_summary_export.py` | Workbook structure, formulas, sheet splitting |
| `test_workbook_comparison.py` | Generated workbook vs the sample |
| `test_api.py` / `test_robustness.py` | Full API flow; corrupt/empty/bad-header files |
**64 fast + 12 integration tests.** Run integration with the sample files present:
```bash
AR_SAMPLE_DIR="/path/to/samples" python -m pytest tests -m integration
```
---
## 14. Known data issues & assumptions
These came out of reviewing the real January and February files:
1. **Australia renamed a column** (Feb): `fba fees` → `fulfilment by amazon fees`. Aliased; the
`unmapped_amounts` check caught the −2,427.43 before it reached a report.
2. **Canada has a manual footer row** (Feb): "Cost of advertising", −362,715.55, with no date, type
or settlement id. Excluded from the receivable, flagged as `undated_rows` — **needs a Finance decision**.
3. **New Amazon columns** (Feb): `Transaction Status`, `Transaction Release Date`, `low value goods`. All mapped.
4. **Duplicates are usually legitimate** — components sum to totals and the receivable reconciles
exactly with them included. Surfaced for review, never auto-removed.
5. **Liquidations are a memo line** — their amounts stay in the sales/fee buckets; re-bucketing would
change gross revenue.
6. **Settlement ids are assumed unique per Amazon account** — true across all real data.
7. **Month-boundary payouts**: a payout dated Jan 30 clears the bank in early February but lives in
the January file, so February never records receiving it. Explains USA/Canada roll-forward variances.
---
## 15. Where to look
| I want to… | Go to |
|---|---|
| Understand the receivable maths | [`accounts-receivable-logic.md`](accounts-receivable-logic.md) |
| See where each dashboard number comes from | `dashboard-guide.html` |
| Change the settlement cutoff | Settlement Reconciliation tab → clearing-lag |
| Map a new/renamed Amazon column | Upload & Mapping tab → Header mapping |
| Set the opening balance | AR Ledger tab → Adjust |
| Sign off the month | Reconciliation tab → Reconciliation Control → Verify → Mark month complete |
| Get the audit file | Excel Export tab → Generate full workbook |