You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

14 KiB

Reconciliation Checkpoints Implementation Plan

For Codex: Use the superpowers:executing-plans skill 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.py

    Expected: FAIL because the model and service do not exist.

  • Step 2: Add the checkpoint model and migration.

    In finance/models/reconciliation.py, define ReconciliationCheckpoint(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 named 0003_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_checkpoint must be decorated with the repository's @write mechanism, lock the active account using the existing lock(Account, ..., active=True) helper, calculate a decimal ledger total from LedgerEntry rows where posting_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 check
    

    Stage 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.00 none reconciled
    non-zero none difference
    0.00 backdated activity changed_since_reconciliation

    Also 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.py

    Expected: FAIL because no read model exists.

  • Step 2: Extract the inclusive ledger-total helper.

    Put the shared query in finance/reconciliation.py. It returns Decimal("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 CheckpointStatus dataclass 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_statuses selects one checkpoint per active account in the requested currency, ordered by as_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.py

    Stage 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 preview submission renders ledger balance and difference but creates no checkpoint;
    • a confirm submission 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.py

    Expected: 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 named reconciliation-index and reconciliation-history in config/urls.py, and ReconciliationForm in core/forms.py with active Account choices, 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 check
    

    Stage 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.py

    Expected: FAIL until the completed slices are integrated.

  • Step 2: Document recording and correcting a checkpoint.

    In docs/local-workflows.md and README.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 --short
    

    Confirm 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"