Appearance
Bank Adapter Development Checklist
Plane task: MHPAY-27
Practical runbook for adding a new Indonesian bank statement adapter to MyHoreca Pay.
Related documents:
- Bank Statement Parsing — architecture overview and pipeline.
- Normalized Bank Transaction Model — field-level rules for
NormalizedBankTransaction. - Bank Adapter Changelog — version bumps and baseline metadata (
MHPAY-64). - Bank Statement Fixture Policy — no real bank files in repo (
MHPAY-65). - Template starter:
backend/src/MyHoreca.Pay.BankStatements/Adapters/_TemplateBankAdapter.example.cs(not compiled, not registered).
1. Purpose
Use this checklist whenever you add support for a new bank or a new export format from an existing bank.
Goals:
- every adapter implements
IBankAdapterexplicitly; - detection is deterministic and scored;
- parsing produces explainable warnings/errors for operators;
- raw source data is preserved for audit and support;
- tests use sanitized fixtures only;
- real bank exports never enter the repository;
- supported banks metadata stays in sync with registered adapters.
_TemplateBankAdapter.example.cs is the learning template. Production adapters must be stricter and more explicit than a catch-all synthetic format.
2. Minimal workflow
| Step | Action |
|---|---|
| 1 | Obtain a real sample export from the operator or bank portal. Store it locally only — never commit. |
| 2 | Create a sanitized copy: replace names, account numbers, references, balances, and descriptions with fictional values. Keep structure realistic. |
| 3 | Identify the format family: CSV, fixed-width text, MT940-like tags, multi-section corporate summary, etc. |
| 4 | Copy _TemplateBankAdapter.example.cs → {Bank}{Format}Adapter.cs and rename the class. |
| 5 | Fill BankAdapterDescriptor (DescriptorValue). |
| 6 | Implement ScoreDetection with explicit, additive signals. |
| 7 | Implement ParseAsync: dates, amounts, direction, references, raw fields, warnings/errors. |
| 8 | Register in BankAdapterRegistry and DependencyInjection.cs. |
| 9 | Add sanitized fixtures under backend/tests/MyHoreca.Pay.UnitTests/Fixtures/BankStatements/. |
| 10 | Add mandatory unit tests in BankStatementAdapterTests.cs (or a dedicated test class). |
| 11 | Update docs and verify supported banks API exposes the new descriptor. |
| 12 | Run verification commands (see section 14). |
| 13 | Update docs/docs/ai-agents/current-implementation-state.md on handoff. |
3. Adapter contract
Every production adapter must implement IBankAdapter (usually via BankAdapterBase).
| Member | Responsibility |
|---|---|
Descriptor | Stable metadata for API, UI, and support. |
BankCode / FormatCode | Short codes from descriptor; used in normalized transactions. |
ScoreDetection | Returns 0.0–1.0 confidence based on file name + header/preview bytes. |
CanParse | Inherited from BankAdapterBase: ScoreDetection >= 0.70. Override only with a documented reason. |
ParseAsync | Reads stream, returns BankStatementAdapterParseResult. |
BankStatementAdapterParseResult
Must populate at minimum:
Transactions— list ofNormalizedBankTransaction;BankCode,FormatCode,AdapterName;ParseConfidenceviaBankQualityScorer.ComputeParseConfidence;TotalRows,ParsedTransactions,SkippedRows;WarningsandErrorsasBankStatementQualityIssuelists;- optional
OpeningBalance,ClosingBalance,BalanceMatches,Metadata.
Shared helpers (reuse before writing custom logic):
| Helper | Use for |
|---|---|
BankAmountParser | Amount parsing, Indonesian decimal conventions |
BankDateParser | Explicit date format list |
BankCounterpartyExtractor | Heuristic counterparty from description. For BILL PAYMENT … strips TOTAL amount and trailing invoice/document tokens (0030, INV-…, SO-…) so names like DRINK SUPPLIER PT are not reduced to PT 0030 0031 TOTAL 1. |
BankCsvLineReader | Quoted CSV splitting |
BankBalanceValidator | Opening/closing balance check |
BankQualityScorer | Parse confidence and quality status |
Reference implementations:
CorporateExplicitDebitCreditCsvAdapter— corporate CSV with debit/credit columns (CIMB).MultiAccountCrDbCsvAdapter— multi-account CR/DB CSV (BCA).PermataCsvAdapter,PermataTextAdapter,PermataMt940LikeAdapter— format variants.MandiriAccountStatementCsvAdapter— Mandiri semicolon CSV._TemplateBankAdapter.example.cs— copy-paste starting point for new adapters.
4. BankAdapterDescriptor checklist
Fill every field deliberately:
| Field | Guidance |
|---|---|
BankCode | Stable uppercase code, e.g. PERMATA, CIMB, BCA. |
BankDisplayName | Operator-facing bank name. |
FormatCode | Unique per format variant, e.g. permata-csv-v1, cimb-account-statement-summary-csv-v1. |
FormatDisplayName | Human-readable format label for UI. |
AdapterName | C# class name (nameof(YourAdapter)). |
AdapterVersion | Semver for detection/descriptor/capability changes; bump when ScoreDetection or descriptor changes. |
ParserVersion | Semver for parser logic; required on any parse change. |
NormalizerVersion | Semver for counterparty extraction / normalization behavior. |
SupportedExtensions | e.g. [".csv"], [".txt"], [".sta"]. |
Status | Experimental, Beta, ProductionReady, or Disabled (BankAdapterStatuses). |
ConfidenceLevel | Operator hint about adapter maturity; usually matches Status initially. |
ExampleFileNames | Typical export file names (sanitized examples only). |
Capabilities | Tags: csv, debit_credit_columns, mt940, balance_validation, etc. |
Notes | Limitations, encoding assumptions, known gaps. |
All three version fields are mandatory — the compiler enforces them on BankAdapterDescriptor. After any bump, add an entry to bank-adapter-changelog.md.
5. Detection scoring
Build score from additive signals. Return 0 immediately when a hard requirement fails (e.g. wrong extension).
Signals to use:
- file extension match;
- header row text or column names;
- mandatory columns present in preview;
- bank-specific markers (company block, account line, statement period);
- MT940 tags (
:20:,:25:,:61:,:86:) where applicable; - file name patterns when stable (low weight only).
Confidence thresholds
| Score | Meaning | Action |
|---|---|---|
>= 0.90 | Strong match | Safe for automatic selection when no more specific adapter scores higher. |
0.70 – 0.89 | Usable automatic match | CanParse returns true; monitor ambiguity in tests. |
0.40 – 0.69 | Weak match | Manual review; must not auto-select (CanParse false). |
< 0.40 | No match | Must not auto-select. |
Rules:
- prefer the more specific adapter over a generic one when scores are close;
- return
0for formats owned by another adapter (e.g. demo header → corporate adapter returns0); - use
ClampScorefromBankAdapterBaseto cap at1.0; - document scoring weights in adapter
Notesor tests.
6. Date parsing
- List accepted formats explicitly in adapter code or comments.
- Use
BankDateParser.TryParseDatewith supported formats; extendBankDateParseronly when a format is shared across adapters. - Do not guess ambiguous
dd/MMvsMM/ddwithout bank evidence. - Indonesian exports may use
dd/MM/yyyy,dd-MM-yyyy,dd MMM yyyy, localized month names, or year-less dates with period context. - Invalid date → row-level error with
BankQualityWarningCodes.InvalidDateorBankImportErrorCodes.InvalidDate, includingLineNumberand source value inMessage.
7. Amount parsing
- Normalized
Amountis always positive. - Use
BankAmountParser.TryParseAmountorTryParseIndonesianDecimalfor thousand separators (.thousands,,decimal in ID locale). - Strip
Rpprefix when present. - If currency is assumed (not in source), add a warning — do not silently change currency without documenting in
Notes. - Zero or empty amount rows: skip or error based on bank rules; document the choice.
- Invalid amount → row-level error with
BankQualityWarningCodes.InvalidAmount,LineNumber, and raw value.
8. Debit/credit direction
Canonical rules (all adapters):
| Direction | Meaning |
|---|---|
Credit | Money enters the bank account (incoming). |
Debit | Money leaves the bank account (outgoing). |
Implementation rules:
- source negative amounts → positive
Amount+ correctDirection; - separate debit/credit columns → populate one direction only; both populated →
BankQualityWarningCodes.AmbiguousDebitCreditand skip/error the row; - single signed amount column → map sign to direction, store positive amount;
- explicit
DR/CRtext columns → parse toTransactionDirection; - ambiguous direction → warning or error using
AmbiguousDebitCredit; never invent direction silently.
9. Raw fields preservation
Every NormalizedBankTransaction must include RawFields for audit and support.
Recommended keys:
| Key | Content |
|---|---|
LineNumber | 1-based source line or row index |
RawLine | Full source line when practical |
{ColumnName} | Original column header → cell value |
ParserAssumption | e.g. assumed year, inferred currency |
| MT940 tags | e.g. :61:, :86: segment text |
Raw fields support duplicate investigation, operator explanations, and future adapter fixes without re-importing production files.
10. Warnings and errors
Use existing codes from BankQualityWarningCodes, BankImportErrorCodes, and severities from BankQualityIssueSeverities.
Populate issues with operator-facing detail:
csharp
new BankStatementQualityIssue
{
Code = BankQualityWarningCodes.InvalidDate,
Severity = BankQualityIssueSeverities.Blocking, // or Warning
Message = $"Строка {lineNumber}: некорректная дата '{rawDate}'.",
OperatorTitle = "Некорректная дата",
WhatToCheck = "Формат даты в исходном файле",
NextAction = "Проверьте экспорт банка или обратитесь в поддержку",
LineNumber = lineNumber,
ColumnName = "Post Date",
FieldName = "TransactionDate"
}Common codes:
| Situation | Code |
|---|---|
| Missing counterparty | counterparty_not_extracted |
| Invalid date | invalid_date |
| Invalid amount | invalid_amount |
| Both debit and credit | ambiguous_debit_credit |
| Missing columns | missing_required_columns / rows_skipped |
| Low detection | low_detection_confidence |
| Low parse confidence | low_parse_confidence |
Prefer row-level errors over throwing for recoverable bad rows; reserve throw for unrecoverable structure (missing header, wrong file type after detection).
11. Sanitized fixtures policy
Never commit real bank exports.
| Rule | Detail |
|---|---|
| Storage | backend/tests/MyHoreca.Pay.UnitTests/Fixtures/BankStatements/ |
| Naming | Use .sanitized.csv, .sanitized.txt, or clearly synthetic names (sample-bank-statement.csv). |
| Replace | Real names, account numbers, bank refs, customer refs, descriptions, balances. |
| Keep | Column order, headers, quoting, encoding quirks, row structure. |
| Git | Verify .gitignore does not accidentally exclude fixtures; real files must stay untracked. |
| Tests | Optionally assert fixtures contain no known production markers (account numbers, company legal names from samples). |
Workflow: real sample → local sanitization script or manual edit → commit sanitized fixture only.
12. Mandatory unit tests
Add tests in BankStatementAdapterTests.cs (or {Bank}AdapterTests.cs). Fixture root: Fixtures/BankStatements.
| Test | Assert |
|---|---|
| Positive detection | ScoreDetection >= 0.70 on sanitized fixture |
| Negative detection | Score < 0.70 on unrelated format (e.g. demo CSV, wrong bank) |
| Successful parsing | Expected transaction count, amounts, directions |
| Invalid date | Error issue with invalid_date, row skipped or not parsed |
| Invalid amount | Error issue with invalid_amount |
| Ambiguous debit/credit | Warning/error ambiguous_debit_credit |
| Missing mandatory columns | Header/structure failure or rows_skipped |
| Optional counterparty/reference warning | counterparty_not_extracted when expected |
| Raw fields preservation | LineNumber, RawLine, column keys present |
| Sanitized fixture parse | End-to-end parse of committed fixture |
| Parse confidence / quality | ParseConfidence and quality status thresholds |
| Registry inclusion | Descriptor appears in BankAdapterRegistry.AllDescriptors |
| Version metadata | BankAdapterRegistry_AllDescriptors_ShouldExposeRequiredVersionMetadata — all version fields non-empty |
| Parse metadata | BankStatementParser adds AdapterVersion, ParserVersion, NormalizerVersion to Metadata |
Use DeterministicUuidV7TestGenerator in tests. Import IDs in tests may use Guid.NewGuid() for test isolation; adapter code must use IUuidGenerator.NewUuidV7() for transaction IDs.
13. Supported banks update
After implementing a production adapter:
- Add
YourAdapter.DescriptorValuetoBankAdapterRegistry.AllDescriptors. - Register
IBankAdapterimplementation inDependencyInjection.cs. - Confirm
IBankStatementSupportedBanksServicereturns the new descriptor via APIGET /api/bank-statements/supported-banks. - Update
docs/docs/backend/api.mdif response shape or documented banks change. - Frontend supported-banks UI picks up API data automatically; update UI copy only if operator messaging is required.
Do not register template or example adapters.
14. Verification commands
Run from repository root unless noted.
bash
# Guard: no real bank files in repo (run before handoff)
python tools/security/guard-bank-files.py --self-test
python tools/security/guard-bank-files.py .
# Narrow: bank statement unit tests
dotnet test backend/tests/MyHoreca.Pay.UnitTests/MyHoreca.Pay.UnitTests.csproj --filter BankStatement
# After csproj / adapter compile changes
dotnet build
# Docs site (from docs/)
cd docs && npm install && npm run buildPrefer narrow tests first, then broader dotnet test when time allows.
15. Final handoff requirements
Before marking the Plane task done:
- [ ] Checklist items above completed for the new adapter.
- [ ]
docs/docs/ai-agents/current-implementation-state.mdupdated with changed files, commands, results, gaps. - [ ]
codegraph/manifest.yamlupdated if new adapter or architecture doc added. - [ ] No real bank files in the commit (
python tools/security/guard-bank-files.py .). - [ ] Template file remains excluded from compile and registry.
Acceptance criteria (MHPAY-27)
This document and _TemplateBankAdapter.example.cs satisfy the developer-experience baseline for future bank adapter work. Each new bank still requires its own adapter implementation, fixtures, and tests following this checklist.