nuban-predictor
Version:
Predicts Nigerian banks with a confidence score based on NUBAN account number.
139 lines (119 loc) • 6.85 kB
JavaScript
/**
* 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;