Enable Banking poller
Source: agent/src/accounting_agent/reconcile/enablebanking_poller.py.
Runs as a background asyncio task inside the webhooks container.
What it does, in order
For each active Enable Banking session (one per linked bank account):
GET /sessions/{id}to verify the session is still valid.- For each account in the session:
iter_transactionsfrom(last_polled_at - OVERLAP_DAYS)to now. - For each transaction:
- Convert to a
NewReconciliation(one of debit/credit, withsource_kind='wise',source_reference=<txn id>, plus the amount/date/currency/counterparty/description). - Look up the document store for candidate matches in the date+currency window.
- Score the top candidate via the same matcher used elsewhere.
- If score ≥ HITL_THRESHOLD → insert with
status='proposed'anddocument_idset. Otherwise →status='unmatched'. - For existing rows with
status='unmatched': re-score (the "re-match path") and promote toproposedif the score now clears. - Update
last_polled_aton the session.
Why polling, not webhooks
Enable Banking offers webhooks but only for paid tiers. The free PSD2 dev tier is poll-only. 4 polls/day × 30 days = 120 calls/month per account, well under the ~100 AIS calls/account limit at our overlap of 2 days.
Idempotency
(source_kind, source_reference) is UNIQUE on the
reconciliations table. Re-running the poller produces the same
end state. If the poller crashes mid-run, the next start re-fetches
the same window and the UNIQUE constraint silently dedups.
The card-debit shape
Wise via PSD2 splits the card-debit description across two remittance lines:
["CARD-3976488961",
"Card transaction of 45.61 EUR issued by Hetzner Online Gmbh HETZNER.COM/"]
The poller's _counterparty_name falls back to the joined
remittance string (when creditor_account.name is empty) so the
matcher's counterparty signal has something to score on.
Otherwise card debits would score below HITL forever — see the
matcher card-debit boost.
What it does NOT do
- It doesn't book to e-Fin. That's the booker daemon.
- It doesn't talk to Discord. The bot handles HITL prompts on the rows the poller writes.
- It doesn't run
vat_decision. VAT happens at book time, not pull time.