# Billing Reconciliation Platform  
## Backend Findings & Improvement Proposal

**Prepared for:** Client review  
**Scope:** Backend only (Django · MySQL · Celery · Redis · Statement API)  
**Out of scope:** Mobile / React Native statement UI  
**Document purpose:** Summarize backend defects, recommended fixes, and optional improvements so priorities and scope can be agreed before implementation.

---

## 1. Executive summary

The billing reconciliation backend manages issued invoices, post-issue corrections (adjustments), settlement (“statement”) runs, durable correction records, and the HTTP APIs that expose account statement data.

Review of the codebase, architecture, pricing, and settlement contracts shows gaps in:

- **Money correctness** (pricing, promos, caps, tax, settlement policies)
- **Audit integrity** (issued invoice immutability, withdrawals)
- **Delivery durability** (idempotency, incomplete work recovery)
- **Settlement consistency** (statement runs, projections)
- **Operations resilience** (Redis cache vs MySQL as system of record)

This document groups work into:

| Priority | Meaning |
|----------|---------|
| **P0 — Critical** | Money accuracy, audit integrity, or production recovery |
| **P1 — High** | Release-blocking for finance/ops, or high correctness impact |
| **P2 — Medium** | Feature rollout, performance, maintainability |
| **P3 — Optional** | Nice-to-have; defer unless capacity allows |

**Recommendation:** Deliver in focused phases. Depth on P0/P1 beats a shallow pass across everything. Preserve existing database tables and field names used by finance export and ops tooling (new fields/tables are fine).

---

## 2. Backend system overview

```
Partner / ops delivery
        ↓
  Django (web)     →  MySQL (system of record)
        ↓
  Celery worker    →  Redis (broker + cache only)
        ↓
  Statement API    →  (consumed by client apps; UI not in this proposal)
```

| Component | Role |
|-----------|------|
| `billing/` | Django project settings, URLs, Celery app |
| `adjustments/` | Durable models, intake (`deliver`), resolution tasks, settlement runs, ops seams |
| `pricing/` | Pure pricing math over in-memory value objects |
| `statement/` | Builds statement payload for the API |
| `api/` | HTTP endpoints (`statement`, `periods`) |

- **MySQL** is the source of truth for accounts, invoices, adjustments, correction records, and statement runs.
- **Redis** is cache/broker only and may be flushed without data loss.
- The service is internal; authentication is expected at a gateway outside this repository.
- This proposal does **not** cover changes to the mobile statement screen.

---

## 3. Critical fixes (P0)

### 3.1 Pricing and money correctness

**Problem:** Totals are incorrect for some combinations of promotions, caps, and tax. Results must be exact to the cent for both settlement policies (`credit_forward` and `restate`).

**Impact:** Wrong bills, support load, audit risk.

**Proposed work:**
- Align `pricing/` engine behavior with the pricing contract (`docs/PRICING_MATH.md`).
- Add regression tests for known promo/cap/tax edge cases.
- Verify both settlement policies; fixing only one family is incomplete.

**Primary packages:** `pricing/`, `adjustments/` (intake / apply path)

---

### 3.2 Issued invoices must not change

**Problem:** Retroactive corrections have been observed mutating invoices that were already sent.

**Impact:** Regulatory / audit failure. Once issued, invoice economics must remain fixed; later corrections should appear as separate correction records and projections, not rewrites of the original invoice.

**Proposed work:**
- Enforce immutability of issued invoice fields in persistence and reprice paths.
- Represent post-issue changes only via adjustment / correction records and statement runs.
- Document and test the representation of “correction after send.”

**Primary packages:** `adjustments/models.py`, `pricing/engine.py`, resolution / persist path

---

### 3.3 Idempotent correction delivery

**Problem:** The same correction can apply twice, or final amounts can depend on delivery order. Partners retry aggressively.

**Impact:** Double credits, inconsistent customer balances, unreproducible support cases.

**Proposed work:**
- Treat accepted `adjustment_id` as the stable identity of a correction.
- Same ID with conflicting payload: keep the accepted economics (do not silently replace with a different amount).
- Distinct IDs remain distinct even when fields look identical.
- Attribute economics by effective order `(effective_at, adjustment_id)`, not arrival order.
- Cover with tests for redelivery and out-of-order arrival.

**Primary packages:** `adjustments/services.py`, `idempotency.py`, `tasks.py`, `intake.py`

---

### 3.4 Cross-period usage transfers

**Problem:** Moving usage between billing periods can fix one month and break another.

**Impact:** Wrong amounts on both the source and destination period statements / projections.

**Proposed work:**
- Ensure both affected periods re-resolve when a transfer is accepted.
- Add fixtures that assert both periods stay consistent after a transfer.

**Primary packages:** `pricing/`, `adjustments/` intake and hydrate paths

---

### 3.5 Statement run membership consistency

**Problem:** Different statement runs can disagree about which corrections they settled.

**Impact:** Finance cannot reconcile “what did run N settle?”; receivable / amount-due projections become unreliable.

**Proposed work:**
- Make run cutoff, membership, and recorded demand deterministic and immutable once issued.
- Align billing and receivable projections with the settlement contract (`docs/SETTLEMENT_RUNS.md`).
- Add tests for run issue, retry (same operation ID → same run), and membership.

**Primary packages:** `adjustments/services.py` (`issue_statement_run`), related models

---

## 4. High-priority improvements (P1)

### 4.1 Withdrawals that are audit-matchable

**Problem:** When a correction is withdrawn, finance cannot always find a clear equal-and-opposite row in the export. Missing rows look like concealment.

**Requirement:**
- Write a withdrawal as the opposite of the referenced adjustment’s recorded delta (e.g. original −10.00 → withdrawal +10.00).
- Emit a row even when net effect is `0.00`.
- Work even if the withdrawn version was already superseded.
- Do not reprice the entire historical chain.

**Impact:** Audit sign-off; release gating for accounting ops.

**Primary packages:** `adjustments/` resolution / persist, `AdjustmentRecord` (and related export-facing fields)

---

### 4.2 Incomplete delivery reconciliation (ops recovery)

**Problem:** After incidents (e.g. Redis OOM), ops cannot list which accepted adjustments never finished the lifecycle (`accepted` → … → `posted`).

**Requirement (fixed public API names — do not rename):**

| Callable | Purpose |
|----------|---------|
| `adjustments.services.reconciliation_report(account_id)` | Return accepted-but-not-posted `adjustment_id`s in acceptance order |
| `adjustments.services.redrive_reconciliation(account_id)` | Safely advance that set to posted (idempotent / safe to repeat) |

**Impact:** Account repair after outages; nightly ops jobs and runbooks.

**Primary packages:** `adjustments/services.py`, lifecycle state on adjustments, worker tasks

---

### 4.3 Statement API payload correctness

**Problem:** Downstream clients (including the statement screen) depend on a correct semantic tree from the backend. Gaps in roles, pending vs settled, withdrawals, superseded versions, and forwarded in/out facts cause incorrect or incomplete bills regardless of UI quality.

**Proposed work:**
- Ensure `statement.build_statement(...)` and `GET /api/accounts/<id>/statement/<period>` emit the contracted node roles and values.
- Amount due reflects the latest **issued** statement run; corrections after cutoff appear as pending, not as live changes to issued demand.
- Include audit-visible facts in the payload: correction ID, net effect (including `0.00`), withdrawals, superseded links, cross-period forwards.
- Keep `GET /api/accounts/<id>/periods` consistent with issued invoice / period data.

**Primary packages:** `statement/`, `api/views.py`, `api/serializers.py`

**Note:** Rendering, layout, and mobile UX are out of scope for this document.

---

## 5. Medium-priority enhancements (P2)

### 5.1 Per-invoice reconciliation summary on statement runs

**Goal:** Let finance answer “what did run N do to invoice X?” without manually summing correction records.

**Recommended scope:**
- On issuance, record an immutable per-invoice rollup for in-scope runs (member count + sum of member deltas).
- Add `adopt_run_summaries(account, adoption_id)` (or equivalent) so the feature rolls out **account by account**.
- Runs started at or before the adoption ordinal stay unchanged (`reconciliation_summary` remains empty / unset).
- Run-operation retry returns the same summary unchanged.

**Out of recommended scope (unless separately approved):**
- Rewriting historical run summaries when later corrections arrive (conflicts with issued-run immutability).
- Mandatory full historical backfill that mutates pre-adoption runs.
- Large speculative storage refactors or config-flag sprawl without a clear ops need.

**Primary packages:** `adjustments/` models & services; optional additive tables/fields

---

### 5.2 Cache correctness and post-purge performance

**Problem:** Nightly Redis flush is safe for durability but can leave hot paths slow afterward if they depend on cache contents instead of MySQL. Memory also trends upward between purges when keys lack TTLs (follow-up from the March OOM incident).

**Proposed work:**
- Ensure intake, resolution, statement build, and amount-due paths rebuild from MySQL after cache miss.
- Identify and TTL (or stop writing) non-expiring cache keys.
- Keep `adjustments.maintenance.purge_cache` (nightly); do not remove it without a proven replacement.
- Confirm `FLUSHALL` / purge never drops system-of-record data.

**Primary packages:** `adjustments/hydrate.py`, cache usage in services, `maintenance.py`, Celery beat schedule

---

### 5.3 Backend documentation & contract hygiene

**Problem:** Standing contracts, stakeholder proposals, and code disagree in places. Ops and finance need a single clear source of truth.

**Proposed work:**
- Update `ARCHITECTURE.md`, `PRICING_MATH.md`, and `SETTLEMENT_RUNS.md` where behavior intentionally changes; note rationale in the release note.
- Keep stakeholder chat asks as input; standing contracts govern unless explicitly adopted.
- Leave UI/paper presentation contracts out of this backend workstream unless they define API payload facts.

---

## 6. Optional / later (P3)

| Item | Notes |
|------|--------|
| Collapse “identical partner payloads within 24h” into one correction | Conflicts with “distinct IDs are distinct corrections”; not recommended |
| Replace accepted payload when the same ID arrives with a different amount | Conflicts with accepted-ID stability; not recommended |
| Always apply the single largest promotion when offers compete | Likely conflicts with stacking/exclusive promotion rules; prefer pricing contract |
| Vendor remittance “one line per subject = current position” | May conflict with marginal/immutable run records; design with finance before building |
| Autopay or customer-notification hooks | Product/integration work; not core reconciliation |
| Authentication, authorization, rate limiting | Gateway responsibility; not in this service |

---

## 7. Suggested delivery phases (backend)

### Phase A — Correctness & audit
1. Pricing math + both settlement policies  
2. Issued-invoice immutability  
3. Delivery idempotency & ordering  
4. Usage transfer across periods  
5. Statement run membership / demand  
6. Withdrawal equal-and-opposite records  
7. `reconciliation_report` / `redrive_reconciliation`

**Exit criteria:** Cent-accurate fixtures; issued invoices unchanged; safe redrive after simulated crash; finance can match withdrawals in export.

### Phase B — Statement API & settlement polish
1. Statement payload roles and pending vs settled semantics  
2. Audit facts present in API response (`0.00`, withdrawals, superseded, forwards)  
3. Projection / amount-due alignment with issued runs  
4. Contract updates for intentional behavior changes  

**Exit criteria:** API responses match settlement and statement *data* contracts; demo account (`acct-demo` / `2026-06`) validates via curl or automated tests without requiring UI work.

### Phase C — Run summaries & ops resilience
1. Per-account adoption of run summaries  
2. Cache miss / post-purge correctness and performance  
3. Redis key TTL cleanup  
4. Architecture doc updates for ops seams  

**Exit criteria:** Pilot accounts have per-invoice run rollups; pre-adoption runs untouched; no Sev1-class Redis regression.

---

## 8. Constraints & non-goals

**Do:**
- Change implementation freely behind stable model field names and ops entry points.
- Add fields, tables, tests, and internal refactors as needed.
- Keep money results exact to the cent.
- Document decisions when stakeholder asks conflict with contracts.

**Do not (without explicit change control):**
- Drop or rename existing tables/fields consumed by finance export or ops.
- Rename `reconciliation_report` / `redrive_reconciliation`.
- Add authentication/rate limiting inside this service.
- Include mobile/UI redesign in this backend workstream.
- Depend on packages beyond the pinned `requirements.txt` allowlist unless the client expands it.

---

## 9. Risks & open decisions (for client input)

| Decision | Options | Recommendation |
|----------|---------|----------------|
| Correction after invoice sent | Mutate invoice vs append correction records | Append records; never mutate issued invoices |
| Conflicting redelivery same ID | Replace payload vs keep accepted meaning | Keep accepted meaning |
| Run summary scope | Marginal members only vs “current subject position” | Marginal members for immutability; discuss vendor format separately |
| Historical summary backfill | Backfill all old runs vs leave pre-adoption untouched | Leave pre-adoption untouched for pilot safety |
| Promo competition | Marketing “max % only” vs contract stacking rules | Follow pricing contract |

---

## 10. Success metrics (backend)

- **Accuracy:** Pricing and settlement fixtures pass to the cent for both settlement policies.  
- **Audit:** Withdrawals appear as matchable pairs; issued invoices and issued runs do not change after the fact.  
- **Ops:** Incomplete deliveries are listable and redrivable per account without double-application.  
- **API:** Statement and periods endpoints return contracted facts, including `0.00` and pending vs settled.  
- **Stability:** Redis flush does not corrupt balances; hot paths recover from MySQL after cache clear.

---

## 11. Next steps

1. Confirm Phase A as the first backend delivery slice.  
2. Confirm open decisions in §9.  
3. Agree acceptance tests (visible suite + additional fixtures for promo/cap/tax, withdrawals, redrive).  
4. Implement, verify with automated tests and demo seed (`make demo` / `acct-demo`), and ship with a release note covering what changed, what was deferred, and residual risk.

---

*This proposal is based on review of the billing reconciliation backend (models, intake, pricing, settlement, statement API, and architecture contracts). Mobile UI work is intentionally excluded and can be scoped in a separate document if needed. Timelines are indicative until scope is confirmed.*
