class CapitalOneStatement
CapitalOneStatement
object, with data parsed from a PDF monthly statement.
@!attribute start_date
[r]
@return [Date] the first day of the monthly statement
@!attribute end_date
[r]
@return [Date] the final day of the monthly statement
@!attribute previous_balance
[r]
@return [Float] the "Previous Balance" field listed in the statement
@!attribute total_payments
[r]
@return [Float] the "Payments and Credits" field listed in the statement
@!attribute total_fees
[r]
@return [Float] the "Fees and Interest Charged" field listed in the statement
@!attribute total_transactions
[r]
@return [Float] the "Transactions" field listed in the statement
@!attribute new_balance
[r]
@return [Float] the New Balance listed in the statement
@!attribute payments [r]
@return [Array<CapitalOneStatement::Transaction>] array of payment transactions
@!attribute transactions [r]
@return [Array<CapitalOneStatement::Transaction>] array of charge transactions
@!attribute fees [r]
@return [Array<CapitalOneStatement::Transaction>] array of fee transactions
Constants
- AMOUNT_ONLY_REGEX
- AMOUNT_REGEX
- CARD_NUMBER_REGEX
- DATE_REGEX
- FEES_REGEX
- INTEREST_REGEX
- TRANSACTION_REGEX
- VERSION
Attributes
end_date[R]
new_balance[R]
payments[R]
previous_balance[R]
start_date[R]
total_fees[R]
total_payments[R]
total_transactions[R]
transactions[R]
Public Class Methods
new(pdf_path)
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 59 def initialize(pdf_path) @card_number = nil @dec_from_prev_year = nil @year = nil @start_date = nil @end_date = nil @previous_balance = nil @new_balance = nil @total_payments = nil @total_transactions = nil @total_fees = nil @payments = [] @transactions = [] @fees = [] parse_from_pdf pdf_path %w{payments transactions fees}.each do |type| trxs = "@#{type}" total = "@total_#{type}" instance_variable_set(trxs, instance_variable_get(trxs).sort_by {|trx| trx[:id]}) check_total( type, instance_variable_get(total), instance_variable_get(trxs).inject(0) {|sum, trx| sum += trx[:amount]} ) end end
Public Instance Methods
to_json(*args)
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 90 def to_json(*args) { :start_date => @start_date, :end_date => @end_date, :previous_balance => @previous_balance, :total_payments => @total_payments, :total_fees => @total_fees, :total_transactions => @total_transactions, :new_balance => @new_balance, :payments => @payments, :transactions => @transactions, :fees => @fees }.to_json(*args) end
Private Instance Methods
check_parse_state()
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 227 def check_parse_state raise "Failed to determine billing cycle dates" if @year.nil? raise "Failed to determine card number" if @card_number.nil? end
check_total(type, expected, actual)
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 232 def check_total(type, expected, actual) return if actual.round(2) == expected.round(2) raise "Calculated %s mismatch %.2f != %.2f" % [ type, actual, expected ] end
method_missing(name, *args)
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 115 def method_missing(name, *args) return unless name =~ /show_text/ if args.any? {|str| str.to_s =~ DATE_REGEX} self.offset = ($1.upcase == 'DEC' && $3.upcase == 'JAN') ? 1 : 0 self.year = $5.to_i self.start_date = Date.parse('%s-%s-%s' % [year - offset, $1, $2]) self.end_date = Date.parse('%s-%s-%s' % [year, $3, $4]) end end
parse_amount(amount)
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 222 def parse_amount(amount) num = amount.gsub(/[^\d.]/, '').to_f amount.start_with?(?() ? -num : num end
parse_from_pdf(pdf_path)
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 107 def parse_from_pdf(pdf_path) PDF::Reader.new(pdf_path).pages.each_with_index do |page, page_num| if @year.nil? walker = Struct.new(:year, :offset, :start_date, :end_date) do def respond_to?(_) true end def method_missing(name, *args) return unless name =~ /show_text/ if args.any? {|str| str.to_s =~ DATE_REGEX} self.offset = ($1.upcase == 'DEC' && $3.upcase == 'JAN') ? 1 : 0 self.year = $5.to_i self.start_date = Date.parse('%s-%s-%s' % [year - offset, $1, $2]) self.end_date = Date.parse('%s-%s-%s' % [year, $3, $4]) end end end.new page.walk walker @dec_from_prev_year = walker.offset == 1 @year = walker.year @start_date = walker.start_date @end_date = walker.end_date end enum = page.text.split("\n").each loop do current_line = enum.next enum.next until (enum.peek rescue nil) != '' next_line = (enum.peek rescue nil) parse_pdf_line page_num, current_line, next_line end end end
parse_pdf_line(page_num, line, next_line)
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 147 def parse_pdf_line(page_num, line, next_line) if line =~ CARD_NUMBER_REGEX @card_number = $1.to_i end if @previous_balance.nil? amount_strs = line.scan AMOUNT_REGEX if amount_strs.size == 5 @previous_balance, @total_payments, @total_fees, @total_transactions, @new_balance = amount_strs.map {|amount| parse_amount(amount)} @total_payments = -@total_payments end end if line =~ FEES_REGEX && $1 != '$0.00' check_parse_state @fees << Transaction.new( @fees.size + 1, @card_number, @end_date, "CAPITAL ONE MEMBER FEE", $1, parse_amount($1) ) end if line =~ INTEREST_REGEX && $1 != '$0.00' check_parse_state @fees << Transaction.new( @fees.size + 1, @card_number, @end_date, "INTEREST CHARGE:PURCHASES", $1, parse_amount($1) ) end transactions, payments = [(0..78), (80..-1)].map do |index| str = line .to_s[index].to_s next_str = next_line.to_s[index].to_s repair_transaction_line str, next_str end.map do |str| parse_transaction(str) end.compact.partition do |trx| trx[:amount] >= 0 end @transactions += transactions @payments += payments end
parse_transaction(line)
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 210 def parse_transaction(line) return nil unless line =~ TRANSACTION_REGEX && $4 != "CAPITAL ONE MEMBER FEE" check_parse_state year = ($3.upcase == 'DEC' && @dec_from_prev_year) ? @year - 1 : @year date = Date.parse('%s-%s-%s' % [year, $3, $2]) Transaction.new($1.to_i, @card_number, date, $4, $5, parse_amount($5)) end
repair_transaction_line(line, next_line)
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 202 def repair_transaction_line(line, next_line) if next_line =~ AMOUNT_ONLY_REGEX && !(line =~ AMOUNT_REGEX) line += " #{next_line.strip}" else line end end
respond_to?(_)
click to toggle source
# File lib/parse_capitalone_pdf_statement.rb, line 111 def respond_to?(_) true end