enhancement: add BPER Italy bank parser (BPER_RETAIL_BPMOIT22) (#5741)

* Refine BPER retail parser with anonymized fixtures

* style: streamline bper parser comments

* chore: add release note for BPER Italy parser
This commit is contained in:
dirk-apers
2025-09-19 18:31:04 +02:00
committed by GitHub
parent f4419b96de
commit 988bc21818
3 changed files with 222 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
/**
* Normalize BPER Retail BPMOIT22 transactions by extracting a friendly payee
* while keeping the raw description in the notes field.
*/
import Fallback from './integration-bank.js';
const CARD_PAYMENT_PREFIX = 'PAGAMENTO SU CIRCUITO INTERNAZIONALE';
const CARD_PAYMENT_SUFFIX = 'Operazione carta';
const BONIFICO_PREFIX = 'BONIFICO';
const BONIFICO_ESTERI_PREFIX = 'BONIFICI ESTERI';
const SDD_PREFIX = 'ADDEBITO SDD';
const BOLLETTINO_MARKER = 'CREDITORE:';
const BONIFICO_ORIGINATOR_REGEX =
/o\/c:\s*([A-Z0-9\s.'/&-]+?)(?:ABI|BIC|IBAN|a favore di|Num|EUR|$)/i;
const SDD_PAYEE_REGEX = /ADDEBITO SDD\s+([A-Z0-9\s.'/&-]+?)(?:N:|ID:|$)/i;
const BOLLETTINO_PAYEE_REGEX = /CREDITORE:\s*([A-Z0-9\s.'/&-]+)/i;
// Extract payee for card transactions
function parseCardPayee(description) {
const [beforeSuffix] = description.split(CARD_PAYMENT_SUFFIX);
return beforeSuffix.replace(CARD_PAYMENT_PREFIX, '').trim();
}
// Extract originator for bonifico (domestic/foreign transfers)
function parseBonificoOriginator(description) {
const match = description.match(BONIFICO_ORIGINATOR_REGEX);
return match ? match[1].trim() : '';
}
// Extract creditor for SDD direct debits
function parseSddPayee(description) {
const match = description.match(SDD_PAYEE_REGEX);
return match ? match[1].trim() : '';
}
// Extract creditor for bollettini / utilities
function parseBollettinoPayee(description) {
const match = description.match(BOLLETTINO_PAYEE_REGEX);
return match ? match[1].trim() : '';
}
function setPayee(editedTransaction, payee) {
if (!payee) {
return;
}
editedTransaction.creditorName = payee;
editedTransaction.debtorName = payee;
}
/** @type {import('./bank.interface.js').IBank} */
const BperRetailBank = {
...Fallback,
institutionIds: ['BPER_RETAIL_BPMOIT22'],
normalizeTransaction(transaction, booked) {
const editedTrans = { ...transaction };
const description = (
transaction.remittanceInformationUnstructured || ''
).trim();
if (description) {
editedTrans.remittanceInformationUnstructured = description;
}
let payee = '';
if (description.startsWith(CARD_PAYMENT_PREFIX)) {
payee = parseCardPayee(description);
} else if (
description.startsWith(BONIFICO_PREFIX) ||
description.startsWith(BONIFICO_ESTERI_PREFIX)
) {
payee = parseBonificoOriginator(description);
} else if (description.startsWith(SDD_PREFIX)) {
payee = parseSddPayee(description);
} else if (description.includes(BOLLETTINO_MARKER)) {
payee = parseBollettinoPayee(description);
}
setPayee(editedTrans, payee);
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
},
};
export default BperRetailBank;

View File

@@ -0,0 +1,122 @@
import BperRetail from '../bper_retail_bpmoit22.js';
const bookingDate = '2025-09-17';
describe('BPER Retail BPMOIT22', () => {
describe('#normalizeTransaction', () => {
it('extracts card merchant between the circuit prefix and operation suffix', () => {
const transaction = {
bookingDate,
remittanceInformationUnstructured:
'PAGAMENTO SU CIRCUITO INTERNAZIONALE HEALTHCARE DISTRICT ZX042 METROPOLIS ITA Operazione carta ****8005 del 15.09.2025',
transactionAmount: { amount: '-17.80', currency: 'EUR' },
};
const normalizedTransaction = BperRetail.normalizeTransaction(
transaction,
true,
);
expect(normalizedTransaction.payeeName).toEqual(
'Healthcare District Zx042 Metropolis Ita',
);
expect(normalizedTransaction.notes).toEqual(
'PAGAMENTO SU CIRCUITO INTERNAZIONALE HEALTHCARE DISTRICT ZX042 METROPOLIS ITA Operazione carta ****8005 del 15.09.2025',
);
});
it('extracts bonifico originator appearing after o/c:', () => {
const transaction = {
bookingDate,
remittanceInformationUnstructured:
'BONIFICO o/c: ACME CONSULTING SRL ABI-CAB: 03015-03200 a favore di Example Recipient Num. Bon.Sepa 252531000195141 Note di cortesia',
transactionAmount: { amount: '1000.00', currency: 'EUR' },
};
const normalizedTransaction = BperRetail.normalizeTransaction(
transaction,
true,
);
expect(normalizedTransaction.payeeName).toEqual('Acme Consulting Srl');
expect(normalizedTransaction.notes).toEqual(
'BONIFICO o/c: ACME CONSULTING SRL ABI-CAB: 03015-03200 a favore di Example Recipient Num. Bon.Sepa 252531000195141 Note di cortesia',
);
});
it('extracts foreign bonifico originator before the BIC marker', () => {
const transaction = {
bookingDate,
remittanceInformationUnstructured:
'BONIFICI ESTERI o/c: GLOBAL PARTNERS LTD BIC: EXAMPGB2L a favore di Example Recipient (BPER) Num. Bon.Sepa 252131000238275BE Memo casuale 1.000,00 EUR',
transactionAmount: { amount: '1000.00', currency: 'EUR' },
};
const normalizedTransaction = BperRetail.normalizeTransaction(
transaction,
true,
);
expect(normalizedTransaction.payeeName).toEqual('Global Partners Ltd');
expect(normalizedTransaction.notes).toEqual(
'BONIFICI ESTERI o/c: GLOBAL PARTNERS LTD BIC: EXAMPGB2L a favore di Example Recipient (BPER) Num. Bon.Sepa 252131000238275BE Memo casuale 1.000,00 EUR',
);
});
it('extracts creditor for SDD direct debits', () => {
const transaction = {
bookingDate,
remittanceInformationUnstructured:
'ADDEBITO SDD CLOUD HOSTING LTD N: 1057087621/48 ID:0210000049513 Cod.Cl. K309846700/ Fatt. 109993626070 Deb: Example Account Owner',
transactionAmount: { amount: '-1.22', currency: 'EUR' },
};
const normalizedTransaction = BperRetail.normalizeTransaction(
transaction,
true,
);
expect(normalizedTransaction.payeeName).toEqual('Cloud Hosting Ltd');
expect(normalizedTransaction.notes).toEqual(
'ADDEBITO SDD CLOUD HOSTING LTD N: 1057087621/48 ID:0210000049513 Cod.Cl. K309846700/ Fatt. 109993626070 Deb: Example Account Owner',
);
});
it('captures bollettino creditor after CREDITORE:', () => {
const transaction = {
bookingDate,
remittanceInformationUnstructured:
'PAGAMENTI DIVERSI DA INTERNET BANKING E CSA PAGAMENTO BOLLETTINO POSTALE 420251388002409360 DEL 22/06/2025 TRAMITE I.B. / CSA TIPO : 896 CCPOST : 000000000000 CREDITORE: UTILITY COMPANY S.P.A.',
transactionAmount: { amount: '-171.34', currency: 'EUR' },
};
const normalizedTransaction = BperRetail.normalizeTransaction(
transaction,
true,
);
expect(normalizedTransaction.payeeName).toEqual('Utility Company S.P.A.');
expect(normalizedTransaction.notes).toEqual(
'PAGAMENTI DIVERSI DA INTERNET BANKING E CSA PAGAMENTO BOLLETTINO POSTALE 420251388002409360 DEL 22/06/2025 TRAMITE I.B. / CSA TIPO : 896 CCPOST : 000000000000 CREDITORE: UTILITY COMPANY S.P.A.',
);
});
it('falls back to the original description when no pattern matches', () => {
const transaction = {
bookingDate,
remittanceInformationUnstructured: 'COMPETENZE SPESE ED ONERI',
transactionAmount: { amount: '-4.90', currency: 'EUR' },
};
const normalizedTransaction = BperRetail.normalizeTransaction(
transaction,
true,
);
expect(normalizedTransaction.payeeName).toEqual(
'Competenze Spese Ed Oneri',
);
expect(normalizedTransaction.notes).toEqual('COMPETENZE SPESE ED ONERI');
});
});
});

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [dirk-apers]
---
Add BPER Italy bank parser (BPER_RETAIL_BPMOIT22)