Skip to content

Instantly share code, notes, and snippets.

@tlhunter
Created April 12, 2024 23:24
Show Gist options
  • Save tlhunter/a702cf859b34df60aaf99c9e7158b6eb to your computer and use it in GitHub Desktop.
Save tlhunter/a702cf859b34df60aaf99c9e7158b6eb to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
// this script looks through chase credit card transactions looking for repeat description/amount combinations
// login to Chase, click credit card, set a time range, click download account activity (down arrow), choose CSV
// usage: node ./chase-subscription-finder.js Chase*Activity*.CSV
const fs = require('fs');
const BLACKLIST = [
'PEETZ', // coffee
'PHILZ', // coffee
'CLIPPER', // public trans
];
const filename = process.argv[2];
const lines = fs.readFileSync(filename).toString().trim().split('\n');
lines.shift(); // discard header row
const cases = new Map();
iterator: for (let line of lines) {
let [date, _post, vendor, _cat, _type, amount, _memo] = line.split(',');
let [month, day, year] = date.split('/');
amount = Number(amount);
const cents = Math.round(amount * 100) * -1;
if (cents < 0) continue; // ignore credit card payments and returns
vendor = vendor.replaceAll('&amp;', ' ').toUpperCase();
date = `${year}-${month}-${day}`;
const normalized = vendor.replaceAll(/[^A-Z]+/g, '_'); // replace non letters with underscores
let skip = false;
for (const blacklist of BLACKLIST) { // inefficiency is OK
if (vendor.includes(blacklist)) {
skip = true;
break;
}
}
console.log(`${skip ? '\x1b[2m' : '\x1b[1m'}${date} ${normalized.padStart(30, ' ')} ${String(cents).padStart(8)}¢\x1b[0m`);
if (skip) continue;
const name = normalized + ':' + cents;
const c = cases.get(name);
if (!c) {
const obj = {
dates: [date],
vendor,
amount
}
cases.set(name, obj);
} else {
c.dates.push(date);
}
}
console.log();
console.log('DISCOVERED THE FOLLOWING RECURRING PAYMENTS');
console.log();
let sum = 0;
for (const c of cases.values()) {
if (c.dates.length > 1) {
const amount = '$' + (c.amount * -1).toFixed(2);
console.log(`${c.vendor.padEnd(30, ' ')} ${String(amount).padStart(8)} ${c.dates.join(', ')}`);
sum += c.amount * -1;
}
}
console.log();
console.log(`TOTAL MONTHLY: \$${sum.toFixed(2)}`);
@tlhunter
Copy link
Author

output:

... a bunch of raw data with ignored entries faded out...
2024-01-01               DIGITALOCEAN_COM     2700¢
2023-12-31                          PHILZ     756¢
2023-12-27          KINOKUNIYA_BOOKSTORES     2167¢

DISCOVERED THE FOLLOWING RECURRING PAYMENTS

NETFLIX                          $15.49   2024-04-06, 2024-02-06, 2024-01-06
SOMA FM COM RADIO                 $4.20   2024-04-03, 2024-03-03, 2024-02-03
DIGITALOCEAN.COM                 $27.00   2024-04-01, 2024-03-01, 2024-02-01
GITHUB                            $2.00   2024-03-10, 2024-02-10, 2024-01-10
GITHUB  INC.                      $4.00   2024-03-10, 2024-02-10, 2024-01-10

TOTAL MONTHLY: $123.45

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment