From 93c14ceca6a457c59c8c3d0463d8e9eafea1cdf6 Mon Sep 17 00:00:00 2001 From: Z User Date: Sat, 13 Jun 2026 15:32:19 +0000 Subject: [PATCH] refactor: extract alphabetToNumber helper, optimize SSN blacklist lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: 1. src/util/checksum.ts: - Extract alphabetToNumber() helper from mod97base10Validate() - Uses early-return pattern instead of mutable fail flag - Cleaner iteration with for-of instead of split+map+join 2. src/us/ssn.ts: - Convert invalidSSN array to invalidSSNSet (Set) for O(1) lookup - Remove duplicate '999999999' entry from the blacklist - Change invalidSSN.includes() to invalidSSNSet.has() Both changes are behavior-preserving — verified by: - Full test suite: 1605 tests passing - Regrets regression: 7 clusters all GREEN - Raw output comparison: identical to pre-refactor truth - Fingerprint cross-match: all fingerprints match Note: luhnChecksumValidate was NOT refactored to delegate to luhnChecksum because they compute the Luhn algorithm differently (one reverses, one doesn't). Attempting this change broke 6 test suites, demonstrating the value of output-based regression testing. --- src/us/ssn.ts | 7 +++---- src/util/checksum.ts | 39 +++++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/us/ssn.ts b/src/us/ssn.ts index a93c76ea..d327bab4 100644 --- a/src/us/ssn.ts +++ b/src/us/ssn.ts @@ -17,7 +17,7 @@ import * as exceptions from '../exceptions'; import { strings } from '../util'; import { Validator, ValidateReturn } from '../types'; -const invalidSSN = [ +const invalidSSNSet = new Set([ '111111111', '222222222', '333333333', @@ -27,7 +27,6 @@ const invalidSSN = [ '888888888', '999999999', '123123123', - '999999999', // Used in Advertising and known "invalid" '002281852', '042103580', @@ -51,7 +50,7 @@ const invalidSSN = [ '457555462', '468288779', '549241889', -]; +]); function clean(input: string): ReturnType { return strings.cleanUnicode(input, '- '); @@ -94,7 +93,7 @@ const impl: Validator = { if (!strings.isdigits(value)) { return { isValid: false, error: new exceptions.InvalidComponent() }; } - if (invalidSSN.includes(value)) { + if (invalidSSNSet.has(value)) { return { isValid: false, error: new exceptions.InvalidComponent() }; } if (/^(000|666|9)\d+/.test(value)) { diff --git a/src/util/checksum.ts b/src/util/checksum.ts index 0e76a469..3f0d4263 100644 --- a/src/util/checksum.ts +++ b/src/util/checksum.ts @@ -80,7 +80,6 @@ export function luhnChecksumValidate( const sum = value .split('') - // .reverse() .map(v => alphabet.indexOf(v)) .reduce((acc, val, idx) => { let v = val; @@ -213,6 +212,25 @@ function modulo(dividentIn: string, divisor: number) { return parseInt(divident, 10) % divisor; } +/** + * Convert a string using alphanumeric alphabet to its numeric representation. + * Returns null if any character is not in the alphabet. + */ +function alphabetToNumber( + value: string, + alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', +): string | null { + let result = ''; + for (const c of value) { + const idx = alphabet.indexOf(c); + if (idx === -1) { + return null; + } + result += String(idx); + } + return result; +} + /** * The ISO 7064 Mod 97, 10 algorithm. * @@ -220,25 +238,10 @@ function modulo(dividentIn: string, divisor: number) { * valid if the number modulo 97 is 1. As such it has two check digits. */ export function mod97base10Validate(value: string, expect = 1): boolean { - const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - let fail = false; - - const bigValue = value - .split('') - .map(c => { - const idx = alphabet.indexOf(c); - if (idx === -1) { - fail = true; - return ''; - } - return String(idx); - }) - .join(''); - - if (fail) { + const bigValue = alphabetToNumber(value); + if (bigValue === null) { return false; } - return modulo(bigValue, 97) === expect; }