Skip to content

Instantly share code, notes, and snippets.

@unRob
Last active October 20, 2017 04:27
Show Gist options
  • Save unRob/040fa132b27a0700f49d71ec19a324af to your computer and use it in GitHub Desktop.
Save unRob/040fa132b27a0700f49d71ec19a324af to your computer and use it in GitHub Desktop.
AMEX vs Uber
require 'csv'
require 'json'
if ARGV.count == 0
puts <<-USAGE
Finds uber phantom charges on amex CSV's by comparing them to your manually-scraped uber trips
\e[1mUsage\e[0m:
ruby #{$0} CSV TXT
CSV: is the one provided by AMEX, clean of non-uber transactions (search for "uber" transactions on amex's website, limit by date)
TXT: is what you get when you paste your collapsed trip list from https://riders.uber.com/trips and remove all cancelled trips. lines look like
30/02/42 DriverName $37.93 uberY GhostTown •••• 0420
\e[1mOutput\e[0m:
Outputs the amount of matching transactions, the amount of phantom transactions and the charge ids of them.
Then you go to uber's help website and search for \"My account has an unrnecognized charge\" and hope for the best.
https://help.uber.com/h/fe547761-4384-42d4-8531-4cfb0e0e523e
USAGE
exit 99
end
class PrintableDate < Date
def around? other
(to_time - other.to_time).abs <= 864000
end
end
Trip = Struct.new(:date, :amount, :id, :usd, :recognized?)
known = open(ARGV[1]).read.split("\n").map { |d|
# AA$0,000.00
amount = d.match(/[A-Z]*\$[\d,.]+/)[0]
# Converts poor date format choices to sane, parseable ones
m,d,y = d.split(' ').first.split('/').map(&:to_i)
[PrintableDate.new(2000+y, m, d), amount]
}.sort {|a, b| a[0] <=> b[0]}
trips = CSV.foreach(ARGV[0]).map do |row|
next if row[2].include?('*EATS')
desc = row[10]
id = desc.split(' ', 2).first
# There's many ways of fixing poor date formats, here's another one
date_components = row[0][0,10].split('/', 3).map(&:to_i)
date = PrintableDate.new(date_components.pop, *date_components)
amount = "MX$"+desc.match(/Spend Amount:([\d.,]+)/)[1] rescue '$'+row[7].to_s
# get the first known transaction to match
needle = known.select { |(d,a)| amount == a && date.around?(d) }.first
if needle
date = needle[0]
known.delete needle
end
Trip.new(date, amount, id, row[7].to_f, !needle.nil?)
end.reject(&:nil?).sort { |a,b| a.date <=> b.date }
unless known.empty?
puts "Could not find charges for known trips:"
puts known.map {|(date, amount)| "#{date} - #{amount}"}.join("\n")
exit 3
end
if ENV['CSV']
puts %w{date id usd ✓?}.join(',')
puts trips
.map {|t| [t.date, t.id, t.usd, t.recognized? ? 1 : 0].join(',') }
.join("\n")
else
total_usd = -> (txns) { txns.reduce(0.0) { |sum, tx| sum + tx.usd }.round(2) }
unrecognized = trips.reject(&:recognized?)
puts "✓: $#{total_usd.(trips.select(&:recognized?))}"
puts "𐄂: $#{total_usd.(unrecognized)}"
puts "?: #{unrecognized.map(&:id).join(',')}"
exit unrecognized.count
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment