14 KiB
Reconciliation Checkpoints Implementation Plan
For Codex: Use the
superpowers:executing-plansskill to carry out this plan task by task.
Goal: Let the owner record immutable institution-balance checkpoints and see whether Gizmoduck's ledger still agrees with them.
Architecture: A reconciliation checkpoint is an audited financial write that snapshots an account's ledger total through an inclusive date. Read-model helpers recalculate the live ledger total, derive a status, and supply the reconciliation page and dashboard. A preview-and-confirm UI ensures the owner sees the calculated difference before the checkpoint is written.
Tech Stack: Python 3.12, Django, PostgreSQL, Django migrations, pytest, Docker Compose, server-rendered Django templates.
Scope constraints: Preserve all existing financial data. A checkpoint never changes transactions, imports, opening balances, or account activation. It is immutable; a correction creates a new row. Use only synthetic test data and never commit data from imports/, databases, backups, logs, or screenshots.
Task 1: Persist audited checkpoint snapshots
Files:
-
Create:
finance/models/reconciliation.py -
Modify:
finance/models/__init__.py -
Create:
finance/migrations/0003_reconciliationcheckpoint.py -
Create:
finance/services/reconciliation.py -
Modify:
finance/services/__init__.py -
Create:
tests/finance/test_reconciliation.py -
Step 1: Write failing domain-service tests with synthetic records.
Cover an inclusive
as_of_date, a zero institution balance, two corrections for the same account/date, and audit metadata. The essential behaviour is:checkpoint = create_reconciliation_checkpoint( account_id=account.id, as_of_date=date(2026, 9, 1), institution_balance=Decimal("0.00"), actor=Actor("HUMAN", "local-owner"), ) assert checkpoint.ledger_balance_at_creation == Decimal("-42.50") assert checkpoint.difference_at_creation == Decimal("42.50") assert AuditEvent.objects.filter( action="reconciliation_checkpoint.created", object_id=checkpoint.id, ).exists()Run:
docker compose run --rm test pytest -q tests/finance/test_reconciliation.pyExpected: FAIL because the model and service do not exist.
-
Step 2: Add the checkpoint model and migration.
In
finance/models/reconciliation.py, defineReconciliationCheckpoint(TimestampedModel)with:account = models.ForeignKey(Account, on_delete=models.PROTECT) as_of_date = models.DateField() institution_balance = models.DecimalField(max_digits=18, decimal_places=2) ledger_balance_at_creation = models.DecimalField(max_digits=18, decimal_places=2) difference_at_creation = models.DecimalField(max_digits=18, decimal_places=2)Export it from
finance/models/__init__.py. Generate and inspect a migration named0003_reconciliationcheckpoint.py; it must not introduce a uniqueness constraint for account/date or a delete/cascade path that removes history with the account. -
Step 3: Implement one transactional, audited write service.
create_reconciliation_checkpointmust be decorated with the repository's@writemechanism, lock the active account using the existinglock(Account, ..., active=True)helper, calculate a decimal ledger total fromLedgerEntryrows whereposting_date__lte=as_of_date, and persist the snapshot values.@write(action="reconciliation_checkpoint.created") def create_reconciliation_checkpoint(*, account_id, as_of_date, institution_balance, actor): account = lock(Account, account_id, active=True) ledger_balance = ledger_balance_through(account.id, as_of_date) return ReconciliationCheckpoint.objects.create( account=account, as_of_date=as_of_date, institution_balance=money(institution_balance, allow_zero=True), ledger_balance_at_creation=ledger_balance, difference_at_creation=money(institution_balance - ledger_balance, allow_zero=True), )Keep the aggregation private to the service at this point; Task 2 will extract the shared read helper. Add a test proving an archived account raises the existing domain error and does not create either a checkpoint or audit event.
-
Step 4: Verify and commit the cohesive domain slice.
Run:
docker compose run --rm test pytest -q tests/finance/test_reconciliation.py docker compose run --rm web python manage.py makemigrations --check --dry-run docker compose run --rm web python manage.py checkStage only the seven Task 1 files. Inspect staged names and full staged content for real financial data or secrets, then commit with repository-local identity:
git -c user.name=Codex -c user.email=codex@localhost commit -m "feat: record reconciliation checkpoints"
Task 2: Add live reconciliation status read models
Files:
-
Create:
finance/reconciliation.py -
Modify:
finance/services/reconciliation.py -
Modify:
tests/finance/test_reconciliation.py -
Step 1: Add failing status tests.
Add synthetic tests for these derived states:
Snapshot difference Later ledger activity through date Expected state 0.00none reconcilednon-zero none difference0.00backdated activity changed_since_reconciliationAlso test that, when several checkpoints exist for one account/date, the newest creation is retained in history and the latest status selection uses the latest
as_of_date, then creation time as its tie-breaker.Run:
docker compose run --rm test pytest -q tests/finance/test_reconciliation.pyExpected: FAIL because no read model exists.
-
Step 2: Extract the inclusive ledger-total helper.
Put the shared query in
finance/reconciliation.py. It returnsDecimal("0.00")when there are no entries and intentionally includes opening entries like every other ledger entry.def ledger_balance_through(account_id: UUID, as_of_date: date) -> Decimal: return money( LedgerEntry.objects.filter( account_id=account_id, posting_date__lte=as_of_date, ).aggregate(total=Coalesce(Sum("amount"), Decimal("0.00")))["total"], allow_zero=True, )Update the write service to use this helper, preserving its transaction and audit behaviour.
-
Step 3: Implement presentation-neutral status values.
Define an immutable
CheckpointStatusdataclass containing checkpoint/account identifiers, account name, currency, as-of date, institution balance, snapshot ledger/difference, live ledger/difference, and state. Implement:def checkpoint_status(checkpoint: ReconciliationCheckpoint) -> CheckpointStatus: ... def checkpoint_history(account_id: UUID) -> list[CheckpointStatus]: ... def latest_checkpoint_statuses(currency: str) -> list[CheckpointStatus]: ...Compute
live_difference = institution_balance - live_ledger_balance. State selection is exactly:if live_difference == Decimal("0.00"): state = "reconciled" elif checkpoint.difference_at_creation == Decimal("0.00"): state = "changed_since_reconciliation" else: state = "difference"latest_checkpoint_statusesselects one checkpoint per active account in the requested currency, ordered byas_of_date DESC,created_at DESC,id DESC. It does not alter the checkpoint history. -
Step 4: Verify and commit the read-model slice.
Run:
docker compose run --rm test pytest -q tests/finance/test_reconciliation.pyStage only Task 2 files, inspect staged content for sensitive data, and commit:
git -c user.name=Codex -c user.email=codex@localhost commit -m "feat: report reconciliation status"
Task 3: Build the reconciliation pages and dashboard summary
Files:
-
Modify:
config/urls.py -
Modify:
core/context.py -
Modify:
core/forms.py -
Modify:
core/views.py -
Modify:
core/templates/core/base.html -
Modify:
core/templates/core/dashboard.html -
Create:
core/templates/core/reconciliations/index.html -
Create:
core/templates/core/reconciliations/history.html -
Create:
tests/test_reconciliation_views.py -
Modify:
tests/test_report_views.py -
Step 1: Write failing end-to-end view tests.
Use synthetic account and ledger data. Cover:
- the navigation link and reconciliation landing page;
- active accounts only in the form choices;
- a
previewsubmission renders ledger balance and difference but creates no checkpoint; - a
confirmsubmission revalidates the values, creates one checkpoint through the service, then redirects to that account's history; - malformed money/date values show field errors and no write;
- CSRF protection rejects a client without the token;
- archived-account confirmation fails safely with no write;
- history displays snapshot and live values for corrected same-date checkpoints; and
- dashboard displays the current status after later backdated activity.
Run:
docker compose run --rm test pytest -q tests/test_reconciliation_views.py tests/test_report_views.pyExpected: FAIL because routes, forms, views, and templates do not exist.
-
Step 2: Wire navigation, routes, and form validation.
Add a Reconciliation navigation item in
core/context.py, routes namedreconciliation-indexandreconciliation-historyinconfig/urls.py, andReconciliationFormincore/forms.pywith activeAccountchoices, date, and money amount. Reuse the repository's reference-choice helpers and decimal validation; zero must remain valid. -
Step 3: Implement a preview-and-confirm write flow.
The index view receives two explicit form actions:
if action == "preview" and form.is_valid(): preview = ReconciliationPreview( account=form.cleaned_data["account"], as_of_date=form.cleaned_data["as_of_date"], institution_balance=form.cleaned_data["institution_balance"], ledger_balance=ledger_balance_through(...), ) elif action == "confirm" and form.is_valid(): checkpoint = create_reconciliation_checkpoint(..., actor=LOCAL_OWNER) return redirect("reconciliation-history", account_id=checkpoint.account_id)Render a confirmation submit only after a valid preview. Confirmation must call the service again rather than trusting hidden snapshot fields, so changes between preview and confirmation receive a fresh ledger calculation. The history view can display an archived account's existing checkpoints but must never offer it as a new checkpoint choice.
-
Step 4: Render readable status and history.
The index template presents the form, preview, latest status per active account, and clear labels for Reconciled, Difference, and Changed since reconciliation. The history template presents every checkpoint in descending as-of/creation order, with institution, snapshot ledger/difference, live ledger/difference, and status. Use plain text and existing currency formatting; do not expose source-file contents or parser errors.
Add
latest_checkpoint_statuses("BRL")to dashboard context and render a compact reconciliation-status table beneath account balances. Preserve existing dashboard and monthly-report behaviour. -
Step 5: Verify and commit the web slice.
Run:
docker compose run --rm test pytest -q tests/test_reconciliation_views.py tests/test_report_views.py tests/finance/test_reconciliation.py docker compose run --rm web python manage.py checkStage only Task 3 files, inspect staged paths and content for sensitive data, then commit:
git -c user.name=Codex -c user.email=codex@localhost commit -m "feat: add reconciliation workflow"
Task 4: Document the owner workflow and run acceptance verification
Files:
-
Modify:
docs/local-workflows.md -
Modify:
README.md -
Modify:
tests/test_mvp_acceptance.py -
Step 1: Add a synthetic acceptance test for the owner path.
Extend the acceptance suite with a user-level flow: create a synthetic account and transaction, preview and confirm a matching checkpoint, then add a synthetic backdated transaction and assert the dashboard reports “Changed since reconciliation.” This validates the cross-layer outcome without importing or inspecting any real local data.
Run:
docker compose run --rm test pytest -q tests/test_mvp_acceptance.pyExpected: FAIL until the completed slices are integrated.
-
Step 2: Document recording and correcting a checkpoint.
In
docs/local-workflows.mdandREADME.md, add concise owner instructions: open Reconciliation, enter an account/date/institution balance, review the preview, confirm, and use a new checkpoint to correct a prior one. Explain that checkpoint dates are inclusive and that newly imported or backdated transactions can change a previously reconciled status. Do not include real account names, balances, paths, or import details. -
Step 3: Run final verification.
Run:
docker compose build docker compose run --rm test pytest -q docker compose run --rm web python manage.py migrate --check docker compose run --rm web python manage.py check docker compose run --rm web python manage.py test_permission_matrix docker compose up -d web Invoke-RestMethod http://127.0.0.1:8000/health/ | ConvertTo-Json -Compress git diff --check git status --shortConfirm the test run has no unexpected failures, migration check is clean, permission matrix passes, health reports
ok, and the working tree contains only the intended documentation and test changes. -
Step 4: Commit the documentation and acceptance coverage.
Stage only the Task 4 files. Inspect staged names and full content for data-policy compliance, then commit:
git -c user.name=Codex -c user.email=codex@localhost commit -m "docs: explain reconciliation workflow"