# Three-Way Django Analysis Engine — Coop ↔ Uniconta ↔ Bank Session-specific implementation notes for building a maintainable three-source reconciliation layer in a Django-based internal finance app. ## When to use this pattern Use when the task has three sources with different semantic roles: - ERP/accounting ledger, e.g. Uniconta = invoiced/booked postings - Counterparty/customer statement, e.g. Coop kontoudtog = settlement/detail source - Bank CSV = actual cash movement / reality check Do **not** treat the result as a legal claim automatically. The engine should produce review flags and explainable evidence. ## Recommended architecture Keep the analysis engine separate from import parsing and HTTP views: ```text apps/core/parsers.py # source-specific import/normalization apps/core/three_way.py # pure-ish analysis functions apps/core/management/commands/*.py # import/analyze CLI commands apps/reconciliation/views.py # dashboard/report presentation only apps/reconciliation/templates/... # HTML tables/cards ``` Good public functions in the analysis module: ```python analyze_three_way_year(year) build_monthly_summary(year) build_invoice_statuses(year) find_unexplained_bank_payments(year) build_bonus_adjustment_summary(year) ``` Views should call these functions and render results; avoid embedding reconciliation logic in templates or views. ## Batch import command For folder-based source projects, add a deterministic command that imports all configured source directories: ```bash python manage.py import_all_sources --base-dir /app/source_data ``` Expected source layout: ```text Fra Coop/ Fra Uniconta/ Fra bank/ ``` The command should: 1. Walk each known directory. 2. Infer source from directory, not filename alone. 3. Preserve original file, sheet and row identifiers. 4. Store row-level import errors. 5. Print counts per file and total imported/error counts. 6. Be safe to rerun or clearly document duplicate behavior. ## Three-way reporting outputs A useful first report page should show: - possible amounts the counterparty may owe (`Mulige beløb Coop skylder`) - unexplained bank postings (`Uforklarede bankposter`) - invoice statuses analyzed - months with material differences - month-level totals across all three sources - bonus/credit/offset summary by period For each proposed issue, include evidence: - amount difference - relevant date range - invoice/reference, if present - source counts - which fields matched and which did not - score/status and whether manual review is required ## Interpretation pitfall Large totals such as “possible amount owed” are **analysis flags**, not final claims. Reasons: - One bank payment can cover many Coop/Uniconta postings. - One Coop settlement can cover many invoices. - Dates may differ between invoice date, statement date, settlement date and bank date. - Bonus/credit notes may be non-cash accruals or period-based offsets. - Open-post lists are status snapshots and should be treated separately from ledger/history. The UI and README should label these as review candidates until group matching and manual approval confirm them. ## Matching strategy after basic import Start with conservative layers: 1. Invoice/reference + absolute amount + date tolerance. 2. Remaining amount + date tolerance. 3. Month-level totals across all three sources. 4. Many-to-one and one-to-many group matching. 5. Bonus/credit period reconciliation. 6. Manual approval/rejection with audit log. For bank matching, prioritize group matching: ```text bank payment = sum of many Coop settlement lines = sum of many Uniconta invoices ``` Use the bank as cash-flow validation, not necessarily as a row-level join target. ## Tests to include Minimum tests for this class of feature: - invoice is paid when Uniconta, Coop and bank agree - invoice is flagged when Coop/bank evidence indicates a residual amount - bank payment without Coop/Uniconta support is flagged as unexplained - monthly summary calculates Bank-Coop and Bank-Uniconta differences correctly For Django, add a lightweight test settings module if the normal dev database requires services/permissions: ```text config/settings/test.py # SQLite, fast local tests ``` Then run: ```bash DJANGO_SETTINGS_MODULE=config.settings.test python manage.py test ``` ## Documentation to update When this pattern is implemented, update: - README.md: installation, Docker, import flow, analysis commands, limitations - ANALYSE.md or ANALYSE_TREVEJS.md: source findings and interpretation - HANDOFF.md: current status and next recommended steps - TODO.md/ROADMAP.md: group matching, manual review, bonus expansion