Last active
October 20, 2017 04:27
-
-
Save unRob/040fa132b27a0700f49d71ec19a324af to your computer and use it in GitHub Desktop.
AMEX vs Uber
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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