Skip to content

Commit

Permalink
distinguish between late and missed payments
Browse files Browse the repository at this point in the history
  • Loading branch information
simontreanor committed Jul 3, 2024
1 parent 9160999 commit 1ee28a6
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 3 deletions.
40 changes: 40 additions & 0 deletions src/Amortisation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ module Amortisation =
Advances: int64<Cent> array
/// any payment scheduled on the current day
ScheduledPayment: ScheduledPaymentType
/// the window during which a scheduled payment can be made; if the date is missed, the payment is late, but if the window is missed, the payment is missed
Window: int
/// any payment scheduled on the current day
PaymentDue: int64<Cent>
/// any payments actually made on the current day
Expand Down Expand Up @@ -75,6 +77,7 @@ module Amortisation =
}
with
static member Default = {
Window = 0
OffsetDate = Unchecked.defaultof<Date>
OffsetDay = 0<OffsetDay>
Advances = [||]
Expand Down Expand Up @@ -169,8 +172,42 @@ module Amortisation =

let dailyInterestRates fromDay toDay = Interest.dailyRates sp.StartDate isSettledWithinGracePeriod sp.Interest.StandardRate sp.Interest.PromotionalRates fromDay toDay

let markMissedPaymentsAsLate schedule =
schedule
|> Array.groupBy _.Window
|> Array.map snd
|> Array.filter (Array.isEmpty >> not)
|> Array.map(fun v ->
{|
OffsetDay = v |> Array.head |> _.OffsetDay
PaymentDueTotal = v |> Array.sumBy _.PaymentDue
ActualPaymentTotal = v |> Array.sumBy (_.ActualPayments >> Array.sumBy ActualPaymentStatus.total)
PaymentStatus = v |> Array.head |> _.PaymentStatus
|}
)
|> Array.filter(fun a -> a.PaymentStatus = MissedPayment)
|> Array.choose(fun a -> if a.ActualPaymentTotal >= a.PaymentDueTotal then Some a.OffsetDay else None)
|> fun a ->
if a |> Array.isEmpty then
schedule
else
schedule
|> Array.map(fun si ->
match a |> Array.tryFind ((=) si.OffsetDay) with
| Some _ -> { si with PaymentStatus = PaidLater }
| None -> si
)

appliedPayments
|> Array.scan(fun ((si: ScheduleItem), (a: Accumulator)) ap ->
let window =
match ap.ScheduledPayment with
| ScheduledPaymentType.Original _
| ScheduledPaymentType.Rescheduled _
-> si.Window + 1
| ScheduledPaymentType.None
-> si.Window

let advances = if ap.AppliedPaymentDay = 0<OffsetDay> then [| sp.Principal |] else [||] // note: assumes single advance on day 0L<Cent>

let newInterest =
Expand Down Expand Up @@ -372,6 +409,7 @@ module Amortisation =
let generatedSettlementPayment' = generatedSettlementPayment - netEffect'

{
Window = window
OffsetDate = offsetDate
OffsetDay = ap.AppliedPaymentDay
Advances = advances
Expand Down Expand Up @@ -437,6 +475,7 @@ module Amortisation =
let interestPortion' = roundedInterestPortion - roundedCarriedInterest

{
Window = window
OffsetDate = offsetDate
OffsetDay = ap.AppliedPaymentDay
Advances = advances
Expand Down Expand Up @@ -492,6 +531,7 @@ module Amortisation =
|> fun a -> if (a |> Array.filter(fun (si, _) -> si.OffsetDay = 0<OffsetDay>) |> Array.length = 2) then a |> Array.tail else a
|> Array.unzip
|> fst
|> markMissedPaymentsAsLate

/// wraps the amortisation schedule in some statistics, and optionally calculate the final APR (optional because it can be processor-intensive)
let calculateStats sp intendedPurpose items =
Expand Down
4 changes: 3 additions & 1 deletion src/CustomerPayments.fs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ module CustomerPayments =
| PaymentMade
/// no payment is due on the specified day because of earlier extra-/overpayments
| NothingDue
/// a scheduled payment was missed completely
/// a scheduled payment is paid late but within the window
| PaidLater
/// a scheduled payment was missed completely, i.e. not paid within the window
| MissedPayment
/// a scheduled payment was made on time but not in the full amount
| Underpayment
Expand Down
2 changes: 1 addition & 1 deletion src/FSharp.Finance.Personal.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>FSharp.Finance.Personal</PackageId>
<Version>1.0.2</Version>
<Version>1.1.0</Version>
<Authors>Simon Treanor</Authors>
<PackageDescription>F# Personal Finance Library</PackageDescription>
<RepositoryUrl>https://github.com/simontreanor/FSharp.Finance.Personal</RepositoryUrl>
Expand Down
2 changes: 1 addition & 1 deletion src/Formatting.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module Formatting =
/// an array of properties relating to quotes
let quoteProperties hide = if hide then [| "GeneratedPayment" |] else [||]
/// an array of properties representing extra information
let extraProperties hide = if hide then [| "FeesRefundIfSettled"; "SettlementFigure" |] else [||]
let extraProperties hide = if hide then [| "FeesRefundIfSettled"; "SettlementFigure"; "Window" |] else [||]

/// filter out hidden fields
let filterColumns hideProperties =
Expand Down
10 changes: 10 additions & 0 deletions tests/ActualPaymentTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module ActualPaymentTests =
OffsetDay = offsetDay
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original paymentAmount
Window = 5
PaymentDue = paymentAmount
ActualPayments = [| ActualPaymentStatus.Confirmed paymentAmount |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -249,6 +250,7 @@ module ActualPaymentTests =
OffsetDay = 140<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.None
Window = 5
PaymentDue = 0L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed 1193_95L<Cent> |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -320,6 +322,7 @@ module ActualPaymentTests =
OffsetDay = 140<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.None
Window = 5
PaymentDue = 0L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed 1474_59L<Cent> |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -396,6 +399,7 @@ module ActualPaymentTests =
OffsetDay = 143<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.None
Window = 5
PaymentDue = 0L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed -280_64L<Cent> |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -469,6 +473,7 @@ module ActualPaymentTests =
OffsetDay = 0<OffsetDay>
Advances = [| 1500_00L<Cent> |]
ScheduledPayment = ScheduledPaymentType.None
Window = 0
PaymentDue = 0L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed 1500_00L<Cent> |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -545,6 +550,7 @@ module ActualPaymentTests =
OffsetDay = 154<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 243_66L<Cent> // to-do: this should be less than the level payment
Window = 11
PaymentDue = 243_66L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -621,6 +627,7 @@ module ActualPaymentTests =
OffsetDay = 143<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.None
Window = 5
PaymentDue = 0L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed -280_83L<Cent> |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -696,6 +703,7 @@ module ActualPaymentTests =
OffsetDay = 134<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 491_53L<Cent>
Window = 5
PaymentDue = 491_53L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -771,6 +779,7 @@ module ActualPaymentTests =
OffsetDay = 134<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 491_53L<Cent>
Window = 5
PaymentDue = 491_53L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -848,6 +857,7 @@ module ActualPaymentTests =
OffsetDay = 134<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 491_53L<Cent>
Window = 5
PaymentDue = 457_65L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed 500_00L<Cent> |]
GeneratedPayment = ValueNone
Expand Down
8 changes: 8 additions & 0 deletions tests/ActualPaymentTestsExtra.fs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ module ActualPaymentTestsExtra =
OffsetDay = 131<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 407_64L<Cent>
Window = 5
PaymentDue = 407_64L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed 407_64L<Cent> |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -452,6 +453,7 @@ module ActualPaymentTestsExtra =
OffsetDay = 172<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 170_90L<Cent>
Window = 12
PaymentDue = 170_04L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -537,6 +539,7 @@ module ActualPaymentTestsExtra =
OffsetDay = 1969<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Rescheduled 20_00L<Cent>
Window = 141
PaymentDue = 9_80L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -613,6 +616,7 @@ module ActualPaymentTestsExtra =
OffsetDay = 1025<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 137_36L<Cent>
Window = 19
PaymentDue = 137_36L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed 137_36L<Cent> |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -689,6 +693,7 @@ module ActualPaymentTestsExtra =
OffsetDay = 185<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 51_53L<Cent>
Window = 7
PaymentDue = 51_53L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed 51_53L<Cent> |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -767,6 +772,7 @@ module ActualPaymentTestsExtra =
OffsetDay = 144<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 171_02L<Cent>
Window = 10
PaymentDue = 142_40L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -851,6 +857,7 @@ module ActualPaymentTestsExtra =
OffsetDay = 1793<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 20_00L<Cent>
Window = 129
PaymentDue = 18_71L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -935,6 +942,7 @@ module ActualPaymentTestsExtra =
OffsetDay = 1793<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 20_00L<Cent>
Window = 129
PaymentDue = 18_71L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueNone
Expand Down
3 changes: 3 additions & 0 deletions tests/EdgeCaseTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ module EdgeCaseTests =
OffsetDay = 88<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.None
Window = 2
PaymentDue = 0L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueSome 83_74L<Cent>
Expand Down Expand Up @@ -734,6 +735,7 @@ module EdgeCaseTests =
OffsetDay = 88<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.None
Window = 2
PaymentDue = 0L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueSome 10_19L<Cent>
Expand Down Expand Up @@ -804,6 +806,7 @@ module EdgeCaseTests =
OffsetDay = 97<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 87_67L<Cent>
Window = 4
PaymentDue = 0L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueNone
Expand Down
3 changes: 3 additions & 0 deletions tests/FeesAndChargesTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ module FeesAndChargesTests =
OffsetDay = 125<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 456_84L<Cent>
Window = 5
PaymentDue = 456_84L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed 456_84L<Cent> |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -164,6 +165,7 @@ module FeesAndChargesTests =
OffsetDay = 125<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 456_84L<Cent>
Window = 5
PaymentDue = 456_84L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed 456_84L<Cent> |]
GeneratedPayment = ValueNone
Expand Down Expand Up @@ -245,6 +247,7 @@ module FeesAndChargesTests =
OffsetDay = 125<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 456_84L<Cent>
Window = 5
PaymentDue = 456_84L<Cent>
ActualPayments = [| ActualPaymentStatus.Confirmed 456_84L<Cent> |]
GeneratedPayment = ValueNone
Expand Down
1 change: 1 addition & 0 deletions tests/InterestTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ module InterestTests =
OffsetDay = 7305<OffsetDay>
Advances = [||]
ScheduledPayment = ScheduledPaymentType.Original 1525_12L<Cent>
Window = 240
PaymentDue = 1523_25L<Cent>
ActualPayments = [||]
GeneratedPayment = ValueNone
Expand Down
Loading

0 comments on commit 1ee28a6

Please sign in to comment.