amortizy 1.0.0 → 2.1.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,17 @@ 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,
20
+ :origination_fee, :additional_fee, :additional_fee_treatment
18
21
 
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)
22
+ def initialize(start_date:, principal:, annual_rate:, frequency:, term_months: nil, num_payments: nil,
23
+ origination_fee: 0, additional_fee: 0, additional_fee_label: 'Additional Fee',
24
+ additional_fee_treatment: :distributed, bank_days_only: false, interest_only_periods: 0,
25
+ grace_period_days: 0, interest_method: :simple)
20
26
  @start_date = Date.parse(start_date.to_s)
21
27
  @principal = principal.to_f
22
- @term_months = term_months.to_i
23
28
  @annual_rate = annual_rate.to_f / 100.0
24
29
  @frequency = frequency.to_sym
25
30
  @origination_fee = origination_fee.to_f
@@ -30,9 +35,10 @@ module Amortizy
30
35
  @interest_only_periods = interest_only_periods.to_i
31
36
  @grace_period_days = grace_period_days.to_i
32
37
  @interest_method = interest_method.to_sym
33
-
34
- validate_term_months!
38
+
39
+ validate_principal!
35
40
  validate_frequency!
41
+ validate_term_input!(term_months, num_payments)
36
42
  validate_fee_treatment!
37
43
  validate_interest_only_periods!
38
44
  validate_interest_method!
@@ -41,86 +47,162 @@ module Amortizy
41
47
  def generate(output: :console, csv_path: nil)
42
48
  case output
43
49
  when :console
44
- generate_console_output
50
+ ConsoleFormatter.new(self).render
45
51
  when :csv
46
- raise ArgumentError, "csv_path required for CSV output" unless csv_path
47
- generate_csv_output(csv_path)
52
+ raise ArgumentError, 'csv_path required for CSV output' unless csv_path
53
+
54
+ CsvFormatter.new(self).render(csv_path)
48
55
  else
49
- raise ArgumentError, "Output must be :console or :csv"
56
+ raise ArgumentError, 'Output must be :console or :csv'
50
57
  end
51
58
  end
52
59
 
60
+ def schedule
61
+ @schedule ||= generate_schedule_data.each(&:freeze).freeze
62
+ end
63
+
64
+ def summary
65
+ schedule
66
+ {
67
+ start_date: @start_date,
68
+ end_date: end_date,
69
+ principal: @principal,
70
+ total_payments: total_payments,
71
+ frequency: @frequency,
72
+ annual_rate: @annual_rate * 100.0,
73
+ payment_amount: payment_amount,
74
+ total_interest: total_interest,
75
+ total_paid: total_paid
76
+ }
77
+ end
78
+
79
+ def end_date
80
+ schedule.last[:date]
81
+ end
82
+
83
+ def total_interest
84
+ schedule.sum { |row| row[:interest_payment] || 0 }
85
+ end
86
+
87
+ def total_paid
88
+ schedule.sum { |row| row[:total_payment] || 0 }
89
+ end
90
+
91
+ def payment_amount
92
+ calculate_payment
93
+ end
94
+
53
95
  private
54
96
 
97
+ def validate_principal!
98
+ raise ArgumentError, 'principal must be positive' unless @principal.positive?
99
+ end
100
+
55
101
  def validate_frequency!
56
- unless [:daily, :weekly].include?(@frequency)
57
- raise ArgumentError, "Frequency must be :daily or :weekly"
58
- end
102
+ return if %i[daily weekly monthly biweekly].include?(@frequency)
103
+
104
+ raise ArgumentError, 'Frequency must be :daily, :weekly, :biweekly, or :monthly'
59
105
  end
60
106
 
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"
107
+ def validate_term_input!(term_months, num_payments)
108
+ raise ArgumentError, 'Cannot specify both term_months and num_payments' if term_months && num_payments
109
+
110
+ raise ArgumentError, 'Must specify either term_months or num_payments' if term_months.nil? && num_payments.nil?
111
+
112
+ if term_months
113
+ @term_months = term_months.to_i
114
+ raise ArgumentError, 'term_months must be a positive integer' unless @term_months.positive?
115
+
116
+ @num_payments = calculate_payments_from_term
117
+ else
118
+ @num_payments = num_payments.to_i
119
+ raise ArgumentError, 'num_payments must be a positive integer' unless @num_payments.positive?
120
+
121
+ @term_months = nil
64
122
  end
65
123
  end
66
124
 
67
125
  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
126
+ return if %i[distributed add_to_principal separate_payment].include?(@additional_fee_treatment)
127
+
128
+ raise ArgumentError, 'Additional fee treatment must be :distributed, :add_to_principal, or :separate_payment'
71
129
  end
72
130
 
73
131
  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
132
+ num = total_payments
133
+ return unless @interest_only_periods >= num
134
+
135
+ raise ArgumentError,
136
+ "Interest-only periods (#{@interest_only_periods}) must be less than total payments (#{num})"
78
137
  end
79
138
 
80
139
  def validate_interest_method!
81
- unless [:simple, :precomputed].include?(@interest_method)
82
- raise ArgumentError, "Interest method must be :simple or :precomputed"
140
+ return if %i[simple precomputed].include?(@interest_method)
141
+
142
+ raise ArgumentError, 'Interest method must be :simple or :precomputed'
143
+ end
144
+
145
+ def total_payments
146
+ @num_payments
147
+ end
148
+
149
+ def calculate_payments_from_term
150
+ end_date = @start_date >> @term_months
151
+
152
+ case @frequency
153
+ when :monthly
154
+ @term_months
155
+ when :biweekly
156
+ ((end_date - @start_date) / 14.0).round
157
+ when :weekly
158
+ ((end_date - @start_date) / 7.0).round
159
+ when :daily
160
+ if @bank_days_only
161
+ count_business_days(@start_date, end_date)
162
+ else
163
+ (end_date - @start_date).to_i
164
+ end
83
165
  end
84
166
  end
85
167
 
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]
168
+ def count_business_days(from_date, to_date)
169
+ count = 0
170
+ current = from_date
171
+ while current < to_date
172
+ count += 1 if bank_day?(current)
173
+ current += 1
174
+ end
175
+ count
96
176
  end
97
177
 
98
178
  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
179
+ base_days = case @frequency
180
+ when :daily then 1
181
+ when :weekly then 7
182
+ when :biweekly then 14
183
+ when :monthly then 30.4375 # 365.25 / 12
184
+ end
185
+
186
+ return base_days unless @bank_days_only
187
+
188
+ num = total_payments
189
+ current_date = first_payment_date
190
+ total_days = 0
191
+ sample_size = [30, num].min
192
+
193
+ (1..sample_size).each do |_i|
194
+ next_date = next_payment_date(current_date)
195
+ total_days += calculate_days_between(current_date, next_date)
196
+ current_date = next_date
115
197
  end
116
-
117
- @frequency == :daily ? 1 : 7
198
+
199
+ total_days.to_f / sample_size
118
200
  end
119
201
 
120
202
  def estimate_total_loan_days
121
- total_payments = calculate_total_payments
203
+ num = total_payments
122
204
  avg_days_per_period = calculate_average_days_per_period
123
- total_payments * avg_days_per_period
205
+ num * avg_days_per_period
124
206
  end
125
207
 
126
208
  def calculate_precomputed_total_interest
@@ -130,7 +212,6 @@ module Amortizy
130
212
  end
131
213
 
132
214
  def precomputed_interest_per_payment
133
- total_payments = calculate_total_payments
134
215
  calculate_precomputed_total_interest / total_payments
135
216
  end
136
217
 
@@ -139,14 +220,15 @@ module Amortizy
139
220
  end
140
221
 
141
222
  def grace_period_interest
142
- return 0.0 if @grace_period_days == 0
223
+ return 0.0 if @grace_period_days.zero?
224
+
143
225
  grace_rate = (@annual_rate / 365.0) * @grace_period_days
144
226
  initial_principal_with_origination * grace_rate
145
227
  end
146
228
 
147
229
  def effective_principal
148
230
  base_principal = initial_principal_with_origination + grace_period_interest
149
-
231
+
150
232
  case @additional_fee_treatment
151
233
  when :add_to_principal
152
234
  base_principal + @additional_fee
@@ -156,7 +238,7 @@ module Amortizy
156
238
  end
157
239
 
158
240
  def first_payment_date
159
- if @grace_period_days > 0
241
+ if @grace_period_days.positive?
160
242
  grace_end_date = @start_date + @grace_period_days
161
243
  next_bank_day(grace_end_date)
162
244
  else
@@ -165,31 +247,31 @@ module Amortizy
165
247
  end
166
248
 
167
249
  def calculate_payment
168
- total_payments = calculate_total_payments
169
- principal_payments = total_payments - @interest_only_periods
170
-
250
+ num = total_payments
251
+ principal_payments = num - @interest_only_periods
252
+
171
253
  if @interest_method == :precomputed
172
254
  principal_payment_portion = effective_principal / principal_payments
173
255
  interest_portion = precomputed_interest_per_payment
174
-
256
+
175
257
  if @additional_fee_treatment == :distributed
176
- principal_payment_portion + interest_portion + (@additional_fee / total_payments)
258
+ principal_payment_portion + interest_portion + (@additional_fee / num)
177
259
  else
178
260
  principal_payment_portion + interest_portion
179
261
  end
180
262
  else
181
263
  days_per_period = calculate_average_days_per_period
182
264
  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
265
+
266
+ base_payment = if period_rate.zero?
267
+ effective_principal / principal_payments
268
+ else
269
+ effective_principal * (period_rate * ((1 + period_rate)**principal_payments)) /
270
+ (((1 + period_rate)**principal_payments) - 1)
271
+ end
190
272
 
191
273
  if @additional_fee_treatment == :distributed
192
- base_payment + (@additional_fee / total_payments)
274
+ base_payment + (@additional_fee / num)
193
275
  else
194
276
  base_payment
195
277
  end
@@ -205,30 +287,41 @@ module Amortizy
205
287
  return true unless @bank_days_only
206
288
  return false if date.saturday? || date.sunday?
207
289
  return false if federal_holiday?(date)
290
+
208
291
  true
209
292
  end
210
293
 
211
294
  def next_bank_day(date)
212
295
  return date unless @bank_days_only
213
-
296
+
214
297
  current = date
215
- until bank_day?(current)
216
- current += 1
217
- end
298
+ current += 1 until bank_day?(current)
218
299
  current
219
300
  end
220
301
 
221
302
  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
-
303
+ next_date = case @frequency
304
+ when :daily
305
+ current_date + 1
306
+ when :weekly
307
+ current_date + 7
308
+ when :biweekly
309
+ current_date + 14
310
+ when :monthly
311
+ advance_by_month(current_date)
312
+ end
313
+
229
314
  next_bank_day(next_date)
230
315
  end
231
316
 
317
+ def advance_by_month(date)
318
+ target_year = date.month == 12 ? date.year + 1 : date.year
319
+ target_month = date.month == 12 ? 1 : date.month + 1
320
+ last_day = Date.new(target_year, target_month, -1).day
321
+ target_day = [date.day, last_day].min
322
+ Date.new(target_year, target_month, target_day)
323
+ end
324
+
232
325
  def calculate_days_between(start_date, end_date)
233
326
  (end_date - start_date).to_i
234
327
  end
@@ -240,10 +333,10 @@ module Amortizy
240
333
  payment_date = first_payment_date
241
334
  previous_payment_date = first_payment_date
242
335
  payment_number = 0
243
- total_payments = calculate_total_payments
336
+ num = total_payments
244
337
  schedule_data = []
245
338
 
246
- if @grace_period_days > 0
339
+ if @grace_period_days.positive?
247
340
  grace_interest = grace_period_interest
248
341
  schedule_data << {
249
342
  payment_number: 'Grace',
@@ -261,7 +354,7 @@ module Amortizy
261
354
  }
262
355
  end
263
356
 
264
- if @additional_fee_treatment == :separate_payment && @additional_fee > 0
357
+ if @additional_fee_treatment == :separate_payment && @additional_fee.positive?
265
358
  payment_date = next_payment_date(payment_date)
266
359
  schedule_data << {
267
360
  payment_number: 0,
@@ -279,38 +372,49 @@ module Amortizy
279
372
  previous_payment_date = payment_date
280
373
  end
281
374
 
282
- additional_fee_per_payment = @additional_fee_treatment == :distributed ? (@additional_fee / total_payments) : 0.0
375
+ additional_fee_per_payment = @additional_fee_treatment == :distributed ? (@additional_fee / num) : 0.0
283
376
  precomputed_interest = @interest_method == :precomputed ? precomputed_interest_per_payment : 0.0
284
377
 
285
- while payment_number < total_payments && balance > 0.01
378
+ # For monthly frequency, track the target date separately from the actual
379
+ # payment date to prevent bank-day adjustments from compounding drift.
380
+ monthly_target = @frequency == :monthly ? first_payment_date : nil
381
+
382
+ while payment_number < num && balance > 0.01
286
383
  payment_number += 1
287
- payment_date = next_payment_date(previous_payment_date)
384
+
385
+ if @frequency == :monthly && monthly_target
386
+ monthly_target = advance_by_month(monthly_target)
387
+ payment_date = next_bank_day(monthly_target)
388
+ else
389
+ payment_date = next_payment_date(previous_payment_date)
390
+ end
391
+
288
392
  days_in_period = calculate_days_between(previous_payment_date, payment_date)
289
-
393
+
290
394
  if @interest_method == :precomputed
291
395
  interest_payment = precomputed_interest
292
396
  else
293
397
  period_rate = (@annual_rate / 365.0) * days_in_period
294
398
  interest_payment = balance * period_rate
295
399
  end
296
-
400
+
297
401
  accrued_interest += interest_payment
298
402
  is_interest_only = payment_number <= @interest_only_periods
299
-
403
+
300
404
  if is_interest_only
301
405
  principal_payment = 0.0
302
406
  payment_type = 'Interest Only'
303
407
  else
304
408
  principal_payment = [payment_amount - interest_payment - additional_fee_per_payment, balance].min
305
- principal_payment = balance if payment_number == total_payments
409
+ principal_payment = balance if payment_number == num
306
410
  payment_type = 'Regular Payment'
307
411
  end
308
-
412
+
309
413
  total_payment = principal_payment + interest_payment + additional_fee_per_payment
310
414
  balance -= principal_payment
311
415
  balance = 0 if balance < 0.01
312
416
  total_balance = balance + accrued_interest
313
-
417
+
314
418
  schedule_data << {
315
419
  payment_number: payment_number,
316
420
  date: payment_date,
@@ -324,193 +428,12 @@ module Amortizy
324
428
  payment_type: payment_type,
325
429
  days_in_period: days_in_period
326
430
  }
327
-
431
+
328
432
  accrued_interest = 0.0
329
433
  previous_payment_date = payment_date
330
434
  end
331
435
 
332
436
  schedule_data
333
437
  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
438
  end
515
439
  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.1.0'
5
5
  end
data/lib/amortizy.rb CHANGED
@@ -1,7 +1,10 @@
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'
7
+ require_relative 'amortizy/disclosure'
5
8
 
6
9
  module Amortizy
7
10
  class Error < StandardError; end