UNPKG

nuban-predictor

Version:

Predicts Nigerian banks with a confidence score based on NUBAN account number.

139 lines (119 loc) 6.85 kB
/** * NUBAN Predictor & Validator (Smart Ranking Edition) * Author: Kenneth Saint H. Faust * Description: Predicts Nigerian banks with a confidence score to handle collisions. */ class NubanPredictor { constructor() { // Bank List with known prefixes for higher precision ranking this.banks = [ // --- Tier 1 Commercial Banks (DMBs) --- { name: "Guaranty Trust Bank (GTB)", code: "058", type: "DMB", prefixes: ['00', '01', '02', '04', '05', '06'] }, { name: "United Bank for Africa (UBA)", code: "033", type: "DMB", prefixes: ['20', '21', '22'] }, { name: "Zenith Bank", code: "057", type: "DMB", prefixes: ['20', '22', '23'] }, { name: "First Bank of Nigeria", code: "011", type: "DMB", prefixes: ['30', '31', '20'] }, { name: "Access Bank", code: "044", type: "DMB", prefixes: ['00', '06', '07', '1'] }, { name: "Access Bank (Diamond)", code: "063", type: "DMB", prefixes: ['00'] }, { name: "Fidelity Bank", code: "070", type: "DMB", prefixes: ['4', '5', '6'] }, { name: "Ecobank Nigeria", code: "050", type: "DMB", prefixes: ['2', '3', '4', '5'] }, { name: "FCMB", code: "214", type: "DMB", prefixes: ['0', '1', '2'] }, { name: "Sterling Bank", code: "232", type: "DMB", prefixes: ['00'] }, { name: "Union Bank", code: "032", type: "DMB", prefixes: ['00'] }, { name: "Stanbic IBTC", code: "221", type: "DMB", prefixes: ['00'] }, { name: "Wema Bank (ALAT)", code: "035", type: "DMB", prefixes: ['01', '02'] }, { name: "Keystone Bank", code: "082", type: "DMB", prefixes: ['1', '2', '6'] }, { name: "Polaris Bank", code: "076", type: "DMB", prefixes: ['1', '3'] }, { name: "Titan Trust Bank", code: "102", type: "DMB", prefixes: ['00'] }, { name: "Globus Bank", code: "00103", type: "DMB", prefixes: ['10'] }, // --- Fintechs & PSBs (High Priority for 7/8/9 prefixes) --- { name: "OPay (Paycom)", code: "999992", type: "OFI", prefixes: ['70', '80', '81', '90', '91'] }, { name: "OPay (DMB Route)", code: "305", type: "DMB", prefixes: ['70', '80', '81', '90', '91'] }, { name: "Moniepoint MFB", code: "50515", type: "OFI", prefixes: ['6', '8', '5'] }, { name: "PalmPay", code: "100033", type: "OFI", prefixes: ['70', '80', '81', '90', '91'] }, { name: "Kuda MFB", code: "50211", type: "OFI", prefixes: ['1', '2', '3'] }, { name: "VFD Microfinance Bank", code: "566", type: "DMB", prefixes: ['4', '5'] }, { name: "Rubies MFB", code: "125", type: "DMB", prefixes: ['00'] }, { name: "Carbon", code: "565", type: "DMB", prefixes: ['0'] }, // --- Other MFBs (Lower Priority unless math is perfect) --- // Adding a few common ones for demonstration. // In a full app, you'd load the full 500+ list here. { name: "Lagos Building Investment Company", code: "90052", type: "OFI" }, { name: "LAPO Microfinance Bank", code: "50547", type: "OFI" }, { name: "Hasal Microfinance Bank", code: "50383", type: "OFI" }, { name: "Petra Microfinance Bank", code: "50746", type: "OFI" }, { name: "Safe Haven MFB", code: "51113", type: "OFI" }, { name: "Sparkle MFB", code: "51310", type: "OFI" } ]; } /** * Predicts and ranks banks for a given NUBAN. * @param {string} accountNumber - The 10-digit NUBAN. * @returns {Array} - Sorted array of banks (Highest confidence first). */ predict(accountNumber) { if (!/^\d{10}$/.test(accountNumber)) { throw new Error("Invalid NUBAN: Must be 10 digits."); } const matches = []; const uniqueCodes = new Set(); for (const bank of this.banks) { if (this.validate(accountNumber, bank.code, bank.type)) { // --- CONFIDENCE SCORING ENGINE --- let score = 10; // Base score for passing the math // Check Prefixes (Boost score if it matches known patterns) if (bank.prefixes) { // Check 2-digit prefix (e.g., "00", "20", "80") const prefix2 = accountNumber.substring(0, 2); // Check 1-digit prefix (e.g., "0", "2") const prefix1 = accountNumber.substring(0, 1); if (bank.prefixes.includes(prefix2)) { score += 50; // Strong match } else if (bank.prefixes.includes(prefix1)) { score += 20; // Weak match } } // Boost Commercial Banks slightly over generic MFBs if no prefix match // (Because statistically, a user is more likely to have a GTB account than a random MFB) if (bank.type === 'DMB' && score === 10) { score += 5; } const uniqueKey = `${bank.name}-${bank.code}`; if (!uniqueCodes.has(uniqueKey)) { matches.push({ ...bank, confidence: score }); uniqueCodes.add(uniqueKey); } } } // Sort by Confidence (High to Low) return matches.sort((a, b) => b.confidence - a.confidence); } validate(accountNumber, bankCode, type) { const serialNumber = accountNumber.substring(0, 9); const checkDigit = parseInt(accountNumber.charAt(9)); let cipher; if (type === 'DMB') { if (bankCode.length > 3) { const cleanCode = bankCode.slice(-3); cipher = cleanCode + serialNumber; } else { cipher = bankCode.padStart(3, '0') + serialNumber; } } else if (type === 'OFI') { let cleanCode = bankCode; if (bankCode.length > 5) cleanCode = bankCode.slice(-5); if (bankCode.length < 5) cleanCode = bankCode.padStart(5, '0'); cipher = '9' + cleanCode + serialNumber; } else { return false; } let sum = 0; const weights = [3, 7, 3, 3, 7, 3, 3, 7, 3, 3, 7, 3, 3, 7, 3]; for (let i = 0; i < cipher.length; i++) { sum += parseInt(cipher.charAt(i)) * weights[i]; } let calculatedCheck = 10 - (sum % 10); if (calculatedCheck === 10) calculatedCheck = 0; return calculatedCheck === checkDigit; } } module.exports = NubanPredictor;