amortizy 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Amortization Schedule Generator
2
- #
4
+ #
3
5
  # Generates comprehensive loan amortization schedules with support for:
4
6
  # - Grace periods with capitalized interest
5
7
  # - Interest-only payment periods
@@ -12,14 +14,16 @@ require 'date'
12
14
  require 'csv'
13
15
  require 'holidays'
14
16
 
15
- module Amortizy
17
+ module Amortizy
16
18
  class AmortizationSchedule
17
- attr_reader :start_date, :principal, :term_months, :annual_rate, :frequency
19
+ attr_reader :start_date, :principal, :term_months, :annual_rate, :frequency, :additional_fee_label
18
20
 
19
- def initialize(start_date:, principal:, term_months:, annual_rate:, frequency:, origination_fee: 0, additional_fee: 0, additional_fee_label: 'Additional Fee', additional_fee_treatment: :distributed, bank_days_only: false, interest_only_periods: 0, grace_period_days: 0, interest_method: :simple)
21
+ def initialize(start_date:, principal:, annual_rate:, frequency:, term_months: nil, num_payments: nil,
22
+ origination_fee: 0, additional_fee: 0, additional_fee_label: 'Additional Fee',
23
+ additional_fee_treatment: :distributed, bank_days_only: false, interest_only_periods: 0,
24
+ grace_period_days: 0, interest_method: :simple)
20
25
  @start_date = Date.parse(start_date.to_s)
21
26
  @principal = principal.to_f
22
- @term_months = term_months.to_i
23
27
  @annual_rate = annual_rate.to_f / 100.0
24
28
  @frequency = frequency.to_sym
25
29
  @origination_fee = origination_fee.to_f
@@ -30,9 +34,10 @@ module Amortizy
30
34
  @interest_only_periods = interest_only_periods.to_i
31
35
  @grace_period_days = grace_period_days.to_i
32
36
  @interest_method = interest_method.to_sym
33
-
34
- validate_term_months!
37
+
38
+ validate_principal!
35
39
  validate_frequency!
40
+ validate_term_input!(term_months, num_payments)
36
41
  validate_fee_treatment!
37
42
  validate_interest_only_periods!
38
43
  validate_interest_method!
@@ -41,86 +46,162 @@ module Amortizy
41
46
  def generate(output: :console, csv_path: nil)
42
47
  case output
43
48
  when :console
44
- generate_console_output
49
+ ConsoleFormatter.new(self).render
45
50
  when :csv
46
- raise ArgumentError, "csv_path required for CSV output" unless csv_path
47
- generate_csv_output(csv_path)
51
+ raise ArgumentError, 'csv_path required for CSV output' unless csv_path
52
+
53
+ CsvFormatter.new(self).render(csv_path)
48
54
  else
49
- raise ArgumentError, "Output must be :console or :csv"
55
+ raise ArgumentError, 'Output must be :console or :csv'
50
56
  end
51
57
  end
52
58
 
59
+ def schedule
60
+ @schedule ||= generate_schedule_data.each(&:freeze).freeze
61
+ end
62
+
63
+ def summary
64
+ schedule
65
+ {
66
+ start_date: @start_date,
67
+ end_date: end_date,
68
+ principal: @principal,
69
+ total_payments: total_payments,
70
+ frequency: @frequency,
71
+ annual_rate: @annual_rate * 100.0,
72
+ payment_amount: payment_amount,
73
+ total_interest: total_interest,
74
+ total_paid: total_paid
75
+ }
76
+ end
77
+
78
+ def end_date
79
+ schedule.last[:date]
80
+ end
81
+
82
+ def total_interest
83
+ schedule.sum { |row| row[:interest_payment] || 0 }
84
+ end
85
+
86
+ def total_paid
87
+ schedule.sum { |row| row[:total_payment] || 0 }
88
+ end
89
+
90
+ def payment_amount
91
+ calculate_payment
92
+ end
93
+
53
94
  private
54
95
 
96
+ def validate_principal!
97
+ raise ArgumentError, 'principal must be positive' unless @principal.positive?
98
+ end
99
+
55
100
  def validate_frequency!
56
- unless [:daily, :weekly].include?(@frequency)
57
- raise ArgumentError, "Frequency must be :daily or :weekly"
58
- end
101
+ return if %i[daily weekly monthly biweekly].include?(@frequency)
102
+
103
+ raise ArgumentError, 'Frequency must be :daily, :weekly, :biweekly, or :monthly'
59
104
  end
60
105
 
61
- def validate_term_months!
62
- unless [6, 9, 12, 15, 18].include?(@term_months)
63
- raise ArgumentError, "Term must be 6, 9, 12, 15, or 18 months"
106
+ def validate_term_input!(term_months, num_payments)
107
+ raise ArgumentError, 'Cannot specify both term_months and num_payments' if term_months && num_payments
108
+
109
+ raise ArgumentError, 'Must specify either term_months or num_payments' if term_months.nil? && num_payments.nil?
110
+
111
+ if term_months
112
+ @term_months = term_months.to_i
113
+ raise ArgumentError, 'term_months must be a positive integer' unless @term_months.positive?
114
+
115
+ @num_payments = calculate_payments_from_term
116
+ else
117
+ @num_payments = num_payments.to_i
118
+ raise ArgumentError, 'num_payments must be a positive integer' unless @num_payments.positive?
119
+
120
+ @term_months = nil
64
121
  end
65
122
  end
66
123
 
67
124
  def validate_fee_treatment!
68
- unless [:distributed, :add_to_principal, :separate_payment].include?(@additional_fee_treatment)
69
- raise ArgumentError, "Additional fee treatment must be :distributed, :add_to_principal, or :separate_payment"
70
- end
125
+ return if %i[distributed add_to_principal separate_payment].include?(@additional_fee_treatment)
126
+
127
+ raise ArgumentError, 'Additional fee treatment must be :distributed, :add_to_principal, or :separate_payment'
71
128
  end
72
129
 
73
130
  def validate_interest_only_periods!
74
- total_payments = calculate_total_payments
75
- if @interest_only_periods >= total_payments
76
- raise ArgumentError, "Interest-only periods (#{@interest_only_periods}) must be less than total payments (#{total_payments})"
77
- end
131
+ num = total_payments
132
+ return unless @interest_only_periods >= num
133
+
134
+ raise ArgumentError,
135
+ "Interest-only periods (#{@interest_only_periods}) must be less than total payments (#{num})"
78
136
  end
79
137
 
80
138
  def validate_interest_method!
81
- unless [:simple, :precomputed].include?(@interest_method)
82
- raise ArgumentError, "Interest method must be :simple or :precomputed"
139
+ return if %i[simple precomputed].include?(@interest_method)
140
+
141
+ raise ArgumentError, 'Interest method must be :simple or :precomputed'
142
+ end
143
+
144
+ def total_payments
145
+ @num_payments
146
+ end
147
+
148
+ def calculate_payments_from_term
149
+ end_date = @start_date >> @term_months
150
+
151
+ case @frequency
152
+ when :monthly
153
+ @term_months
154
+ when :biweekly
155
+ ((end_date - @start_date) / 14.0).round
156
+ when :weekly
157
+ ((end_date - @start_date) / 7.0).round
158
+ when :daily
159
+ if @bank_days_only
160
+ count_business_days(@start_date, end_date)
161
+ else
162
+ (end_date - @start_date).to_i
163
+ end
83
164
  end
84
165
  end
85
166
 
86
- def calculate_total_payments
87
- payment_schedule = {
88
- 6 => { daily: 124, weekly: 27 },
89
- 9 => { daily: 185, weekly: 39 },
90
- 12 => { daily: 248, weekly: 53 },
91
- 15 => { daily: 312, weekly: 65 },
92
- 18 => { daily: 370, weekly: 79 }
93
- }
94
-
95
- payment_schedule[@term_months][@frequency]
167
+ def count_business_days(from_date, to_date)
168
+ count = 0
169
+ current = from_date
170
+ while current < to_date
171
+ count += 1 if bank_day?(current)
172
+ current += 1
173
+ end
174
+ count
96
175
  end
97
176
 
98
177
  def calculate_average_days_per_period
99
- return 1 if @frequency == :daily && !@bank_days_only
100
- return 7 if @frequency == :weekly && !@bank_days_only
101
-
102
- if @bank_days_only
103
- total_payments = calculate_total_payments
104
- current_date = first_payment_date
105
- total_days = 0
106
- sample_size = [30, total_payments].min
107
-
108
- (1..sample_size).each do |i|
109
- next_date = next_payment_date(current_date)
110
- total_days += calculate_days_between(current_date, next_date)
111
- current_date = next_date
112
- end
113
-
114
- return total_days.to_f / sample_size
178
+ base_days = case @frequency
179
+ when :daily then 1
180
+ when :weekly then 7
181
+ when :biweekly then 14
182
+ when :monthly then 30.4375 # 365.25 / 12
183
+ end
184
+
185
+ return base_days unless @bank_days_only
186
+
187
+ num = total_payments
188
+ current_date = first_payment_date
189
+ total_days = 0
190
+ sample_size = [30, num].min
191
+
192
+ (1..sample_size).each do |_i|
193
+ next_date = next_payment_date(current_date)
194
+ total_days += calculate_days_between(current_date, next_date)
195
+ current_date = next_date
115
196
  end
116
-
117
- @frequency == :daily ? 1 : 7
197
+
198
+ total_days.to_f / sample_size
118
199
  end
119
200
 
120
201
  def estimate_total_loan_days
121
- total_payments = calculate_total_payments
202
+ num = total_payments
122
203
  avg_days_per_period = calculate_average_days_per_period
123
- total_payments * avg_days_per_period
204
+ num * avg_days_per_period
124
205
  end
125
206
 
126
207
  def calculate_precomputed_total_interest
@@ -130,7 +211,6 @@ module Amortizy
130
211
  end
131
212
 
132
213
  def precomputed_interest_per_payment
133
- total_payments = calculate_total_payments
134
214
  calculate_precomputed_total_interest / total_payments
135
215
  end
136
216
 
@@ -139,14 +219,15 @@ module Amortizy
139
219
  end
140
220
 
141
221
  def grace_period_interest
142
- return 0.0 if @grace_period_days == 0
222
+ return 0.0 if @grace_period_days.zero?
223
+
143
224
  grace_rate = (@annual_rate / 365.0) * @grace_period_days
144
225
  initial_principal_with_origination * grace_rate
145
226
  end
146
227
 
147
228
  def effective_principal
148
229
  base_principal = initial_principal_with_origination + grace_period_interest
149
-
230
+
150
231
  case @additional_fee_treatment
151
232
  when :add_to_principal
152
233
  base_principal + @additional_fee
@@ -156,7 +237,7 @@ module Amortizy
156
237
  end
157
238
 
158
239
  def first_payment_date
159
- if @grace_period_days > 0
240
+ if @grace_period_days.positive?
160
241
  grace_end_date = @start_date + @grace_period_days
161
242
  next_bank_day(grace_end_date)
162
243
  else
@@ -165,31 +246,31 @@ module Amortizy
165
246
  end
166
247
 
167
248
  def calculate_payment
168
- total_payments = calculate_total_payments
169
- principal_payments = total_payments - @interest_only_periods
170
-
249
+ num = total_payments
250
+ principal_payments = num - @interest_only_periods
251
+
171
252
  if @interest_method == :precomputed
172
253
  principal_payment_portion = effective_principal / principal_payments
173
254
  interest_portion = precomputed_interest_per_payment
174
-
255
+
175
256
  if @additional_fee_treatment == :distributed
176
- principal_payment_portion + interest_portion + (@additional_fee / total_payments)
257
+ principal_payment_portion + interest_portion + (@additional_fee / num)
177
258
  else
178
259
  principal_payment_portion + interest_portion
179
260
  end
180
261
  else
181
262
  days_per_period = calculate_average_days_per_period
182
263
  period_rate = (@annual_rate / 365.0) * days_per_period
183
-
184
- base_payment = if period_rate == 0
185
- effective_principal / principal_payments
186
- else
187
- effective_principal * (period_rate * (1 + period_rate)**principal_payments) /
188
- ((1 + period_rate)**principal_payments - 1)
189
- end
264
+
265
+ base_payment = if period_rate.zero?
266
+ effective_principal / principal_payments
267
+ else
268
+ effective_principal * (period_rate * ((1 + period_rate)**principal_payments)) /
269
+ (((1 + period_rate)**principal_payments) - 1)
270
+ end
190
271
 
191
272
  if @additional_fee_treatment == :distributed
192
- base_payment + (@additional_fee / total_payments)
273
+ base_payment + (@additional_fee / num)
193
274
  else
194
275
  base_payment
195
276
  end
@@ -205,30 +286,41 @@ module Amortizy
205
286
  return true unless @bank_days_only
206
287
  return false if date.saturday? || date.sunday?
207
288
  return false if federal_holiday?(date)
289
+
208
290
  true
209
291
  end
210
292
 
211
293
  def next_bank_day(date)
212
294
  return date unless @bank_days_only
213
-
295
+
214
296
  current = date
215
- until bank_day?(current)
216
- current += 1
217
- end
297
+ current += 1 until bank_day?(current)
218
298
  current
219
299
  end
220
300
 
221
301
  def next_payment_date(current_date)
222
- case @frequency
223
- when :daily
224
- next_date = current_date + 1
225
- when :weekly
226
- next_date = current_date + 7
227
- end
228
-
302
+ next_date = case @frequency
303
+ when :daily
304
+ current_date + 1
305
+ when :weekly
306
+ current_date + 7
307
+ when :biweekly
308
+ current_date + 14
309
+ when :monthly
310
+ advance_by_month(current_date)
311
+ end
312
+
229
313
  next_bank_day(next_date)
230
314
  end
231
315
 
316
+ def advance_by_month(date)
317
+ target_year = date.month == 12 ? date.year + 1 : date.year
318
+ target_month = date.month == 12 ? 1 : date.month + 1
319
+ last_day = Date.new(target_year, target_month, -1).day
320
+ target_day = [date.day, last_day].min
321
+ Date.new(target_year, target_month, target_day)
322
+ end
323
+
232
324
  def calculate_days_between(start_date, end_date)
233
325
  (end_date - start_date).to_i
234
326
  end
@@ -240,10 +332,10 @@ module Amortizy
240
332
  payment_date = first_payment_date
241
333
  previous_payment_date = first_payment_date
242
334
  payment_number = 0
243
- total_payments = calculate_total_payments
335
+ num = total_payments
244
336
  schedule_data = []
245
337
 
246
- if @grace_period_days > 0
338
+ if @grace_period_days.positive?
247
339
  grace_interest = grace_period_interest
248
340
  schedule_data << {
249
341
  payment_number: 'Grace',
@@ -261,7 +353,7 @@ module Amortizy
261
353
  }
262
354
  end
263
355
 
264
- if @additional_fee_treatment == :separate_payment && @additional_fee > 0
356
+ if @additional_fee_treatment == :separate_payment && @additional_fee.positive?
265
357
  payment_date = next_payment_date(payment_date)
266
358
  schedule_data << {
267
359
  payment_number: 0,
@@ -279,38 +371,49 @@ module Amortizy
279
371
  previous_payment_date = payment_date
280
372
  end
281
373
 
282
- additional_fee_per_payment = @additional_fee_treatment == :distributed ? (@additional_fee / total_payments) : 0.0
374
+ additional_fee_per_payment = @additional_fee_treatment == :distributed ? (@additional_fee / num) : 0.0
283
375
  precomputed_interest = @interest_method == :precomputed ? precomputed_interest_per_payment : 0.0
284
376
 
285
- while payment_number < total_payments && balance > 0.01
377
+ # For monthly frequency, track the target date separately from the actual
378
+ # payment date to prevent bank-day adjustments from compounding drift.
379
+ monthly_target = @frequency == :monthly ? first_payment_date : nil
380
+
381
+ while payment_number < num && balance > 0.01
286
382
  payment_number += 1
287
- payment_date = next_payment_date(previous_payment_date)
383
+
384
+ if @frequency == :monthly && monthly_target
385
+ monthly_target = advance_by_month(monthly_target)
386
+ payment_date = next_bank_day(monthly_target)
387
+ else
388
+ payment_date = next_payment_date(previous_payment_date)
389
+ end
390
+
288
391
  days_in_period = calculate_days_between(previous_payment_date, payment_date)
289
-
392
+
290
393
  if @interest_method == :precomputed
291
394
  interest_payment = precomputed_interest
292
395
  else
293
396
  period_rate = (@annual_rate / 365.0) * days_in_period
294
397
  interest_payment = balance * period_rate
295
398
  end
296
-
399
+
297
400
  accrued_interest += interest_payment
298
401
  is_interest_only = payment_number <= @interest_only_periods
299
-
402
+
300
403
  if is_interest_only
301
404
  principal_payment = 0.0
302
405
  payment_type = 'Interest Only'
303
406
  else
304
407
  principal_payment = [payment_amount - interest_payment - additional_fee_per_payment, balance].min
305
- principal_payment = balance if payment_number == total_payments
408
+ principal_payment = balance if payment_number == num
306
409
  payment_type = 'Regular Payment'
307
410
  end
308
-
411
+
309
412
  total_payment = principal_payment + interest_payment + additional_fee_per_payment
310
413
  balance -= principal_payment
311
414
  balance = 0 if balance < 0.01
312
415
  total_balance = balance + accrued_interest
313
-
416
+
314
417
  schedule_data << {
315
418
  payment_number: payment_number,
316
419
  date: payment_date,
@@ -324,193 +427,12 @@ module Amortizy
324
427
  payment_type: payment_type,
325
428
  days_in_period: days_in_period
326
429
  }
327
-
430
+
328
431
  accrued_interest = 0.0
329
432
  previous_payment_date = payment_date
330
433
  end
331
434
 
332
435
  schedule_data
333
436
  end
334
-
335
- def generate_console_output
336
- puts format_header
337
- puts "-" * 195
338
-
339
- generate_schedule_data.each do |row|
340
- if row[:payment_type] == 'Grace Period'
341
- puts format_grace_row(
342
- row[:payment_number],
343
- row[:date],
344
- row[:grace_interest_capitalized],
345
- row[:principal_balance],
346
- row[:days_in_period]
347
- )
348
- else
349
- puts format_row(
350
- row[:payment_number],
351
- row[:date],
352
- row[:principal_payment],
353
- row[:interest_payment],
354
- row[:additional_fee_payment],
355
- row[:total_payment],
356
- row[:principal_balance],
357
- row[:accrued_interest],
358
- row[:total_balance],
359
- row[:payment_type],
360
- row[:days_in_period]
361
- )
362
- end
363
- end
364
-
365
- print_summary
366
- end
367
-
368
- def generate_csv_output(csv_path)
369
- CSV.open(csv_path, 'w') do |csv|
370
- csv << [
371
- 'Payment Number',
372
- 'Date',
373
- 'Days in Period',
374
- 'Principal Payment',
375
- 'Interest Payment',
376
- 'Additional Fee Payment',
377
- 'Total Payment',
378
- 'Principal Balance Remaining',
379
- 'Accrued Interest',
380
- 'Total Balance',
381
- 'Payment Type',
382
- 'Grace Interest Capitalized'
383
- ]
384
-
385
- generate_schedule_data.each do |row|
386
- csv << [
387
- row[:payment_number],
388
- row[:date].strftime("%Y-%m-%d"),
389
- row[:days_in_period],
390
- format("%.2f", row[:principal_payment] || 0),
391
- format("%.2f", row[:interest_payment] || 0),
392
- format("%.2f", row[:additional_fee_payment] || 0),
393
- format("%.2f", row[:total_payment] || 0),
394
- format("%.2f", row[:principal_balance]),
395
- format("%.2f", row[:accrued_interest] || 0),
396
- format("%.2f", row[:total_balance]),
397
- row[:payment_type],
398
- row[:grace_interest_capitalized] ? format("%.2f", row[:grace_interest_capitalized]) : ''
399
- ]
400
- end
401
- end
402
-
403
- puts "CSV file generated: #{csv_path}"
404
- end
405
-
406
- def print_summary
407
- puts "\n" + "=" * 195
408
- puts "LOAN SUMMARY"
409
- puts "=" * 195
410
- puts "Loan Start Date: #{@start_date.strftime('%Y-%m-%d')}"
411
- puts "First Payment Date: #{first_payment_date.strftime('%Y-%m-%d')}"
412
- puts "Term: #{@term_months} months"
413
- puts "Payment Frequency: #{@frequency.to_s.capitalize}"
414
- puts "Grace Period: #{@grace_period_days} days"
415
-
416
- if @grace_period_days > 0
417
- puts "Grace Period Interest (Capitalized): $#{format('%.2f', grace_period_interest)}"
418
- end
419
-
420
- puts "\nOriginal Principal: $#{format('%.2f', @principal)}"
421
- puts "Origination Fee: $#{format('%.2f', @origination_fee)} (added to principal)"
422
- puts "Additional Fee: $#{format('%.2f', @additional_fee)}"
423
- puts "#{@additional_fee_label} Treatment: #{@additional_fee_treatment.to_s.split('_').map(&:capitalize).join(' ')}"
424
- puts "Bank Days Only: #{@bank_days_only}"
425
- puts "Interest-Only Periods: #{@interest_only_periods}"
426
- puts "Interest Method: #{@interest_method.to_s.capitalize}"
427
-
428
- puts "\nPrincipal after Origination Fee: $#{format('%.2f', initial_principal_with_origination)}"
429
-
430
- if @grace_period_days > 0
431
- puts "Principal after Grace Period (with capitalized interest): $#{format('%.2f', initial_principal_with_origination + grace_period_interest)}"
432
- end
433
-
434
- case @additional_fee_treatment
435
- when :add_to_principal
436
- puts "Total Principal (with all fees): $#{format('%.2f', effective_principal)}"
437
- when :distributed
438
- puts "#{@additional_fee_label} per payment: $#{format('%.2f', @additional_fee / calculate_total_payments)}"
439
- when :separate_payment
440
- puts "#{@additional_fee_label} collected as separate payment"
441
- end
442
-
443
- if @interest_method == :precomputed
444
- puts "\nPrecomputed Interest Calculation:"
445
- puts " Estimated Total Loan Days: #{format('%.0f', estimate_total_loan_days)}"
446
- puts " Total Precomputed Interest: $#{format('%.2f', calculate_precomputed_total_interest)}"
447
- puts " Interest per Payment: $#{format('%.2f', precomputed_interest_per_payment)}"
448
- end
449
-
450
- schedule_data = generate_schedule_data
451
- total_interest = schedule_data.sum { |row| row[:interest_payment] || 0 }
452
- total_additional_fees = schedule_data.sum { |row| row[:additional_fee_payment] || 0 }
453
- total_paid = effective_principal + total_interest + (@additional_fee_treatment == :separate_payment ? @additional_fee : 0)
454
-
455
- puts "\nTotal Interest Paid (during payments): $#{format('%.2f', total_interest)}"
456
- if @grace_period_days > 0
457
- puts "Total Interest Including Grace Period: $#{format('%.2f', total_interest + grace_period_interest)}"
458
- end
459
- puts "Total #{@additional_fee_label} Paid: $#{format('%.2f', total_additional_fees)}"
460
- puts "Total Amount Paid: $#{format('%.2f', total_paid)}"
461
- puts "=" * 195
462
- end
463
-
464
- def format_header
465
- format(
466
- "%-8s %-12s %-8s %15s %15s %18s %18s %20s %18s %18s %20s",
467
- "Payment",
468
- "Date",
469
- "Days",
470
- "Principal Pmt",
471
- "Interest Pmt",
472
- @additional_fee_label,
473
- "Total Payment",
474
- "Principal Balance",
475
- "Accrued Interest",
476
- "Total Balance",
477
- "Payment Type"
478
- )
479
- end
480
-
481
- def format_grace_row(payment_num, date, grace_interest, principal_balance, days)
482
- format(
483
- "%-8s %-12s %-8d %15s %15s %18s %18s %20.2f %18s %18.2f %20s",
484
- "Grace",
485
- date.strftime("%Y-%m-%d"),
486
- days,
487
- "---",
488
- "+#{format('%.2f', grace_interest)}",
489
- "---",
490
- "0.00",
491
- principal_balance,
492
- "---",
493
- principal_balance,
494
- "Grace Period"
495
- )
496
- end
497
-
498
- def format_row(payment_num, date, principal_pmt, interest_pmt, additional_fee_pmt, total_pmt, principal_balance, accrued_interest, total_balance, payment_type, days)
499
- format(
500
- "%-8s %-12s %-8d %15.2f %15.2f %18.2f %18.2f %20.2f %18.2f %18.2f %20s",
501
- payment_num == 0 ? "Fee" : payment_num.to_s,
502
- date.strftime("%Y-%m-%d"),
503
- days,
504
- principal_pmt,
505
- interest_pmt,
506
- additional_fee_pmt,
507
- total_pmt,
508
- principal_balance,
509
- accrued_interest,
510
- total_balance,
511
- payment_type
512
- )
513
- end
514
437
  end
515
438
  end
516
-
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Amortizy
4
- VERSION = "1.0.0"
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/amortizy.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "amortizy/version"
4
- require_relative "amortizy/engine"
3
+ require_relative 'amortizy/version'
4
+ require_relative 'amortizy/engine'
5
+ require_relative 'amortizy/console_formatter'
6
+ require_relative 'amortizy/csv_formatter'
5
7
 
6
8
  module Amortizy
7
9
  class Error < StandardError; end