Skip to content

Commit

Permalink
add missing comments
Browse files Browse the repository at this point in the history
  • Loading branch information
simontreanor committed Oct 16, 2024
1 parent ac381ea commit b24a776
Show file tree
Hide file tree
Showing 15 changed files with 46 additions and 35 deletions.
1 change: 1 addition & 0 deletions src/Amortisation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ module Amortisation =
FeesRefundIfSettled: int64<Cent>
}

/// amortisation schedule item showing apportionment of payments to principal, fees, interest and charges
module ScheduleItem =
let initial = {
Window = 0
Expand Down
1 change: 1 addition & 0 deletions src/Apr.fs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ module Apr =
|> ValueSome
| _ -> ValueNone

/// calculates the APR rate for the specified unit-period as per UK regulation
let ukUnitPeriodRate unitPeriod apr =
apr
|> Percent.toDecimal
Expand Down
1 change: 1 addition & 0 deletions src/Array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module ArrayExtension =
Max: int
}

/// lower and upper bounds, as well as a step value, for tolerance when using the solver
module ToleranceSteps =
/// no tolerance steps
let zero =
Expand Down
2 changes: 2 additions & 0 deletions src/Calculation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ module Calculation =
/// round up or down to the specified precision based on the given midpoint rounding rules
| RoundWith of MidpointRounding

/// the type of rounding, specifying midpoint-rounding where necessary
module Rounding =
/// derive a rounded value from a decimal according to the specified rounding method
let round rounding (m: decimal) =
Expand Down Expand Up @@ -90,6 +91,7 @@ module Calculation =
| Zero
| Simple of Numerator: int * Denominator: int

/// a fraction expressed as a numerator and denominator
module Fraction =
let toDecimal = function
| Fraction.Zero ->
Expand Down
2 changes: 2 additions & 0 deletions src/Charge.fs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ module Charge =
LatePaymentGracePeriod: int<DurationDay>
}

/// options specifying the types of charges, their amounts, and any restrictions on these
module Config =
/// a default config value, with no charges but recommended settings
let initialRecommended = {
Expand Down Expand Up @@ -73,6 +74,7 @@ module Charge =
|> ValueOption.defaultValue chargeConfig.ChargeTypes
|> Array.sumBy (total chargeConfig baseValue)

/// generates a date range during which charges are not incurred
let holidayDates chargeConfig startDate =
chargeConfig.ChargeHolidays
|> Array.collect(fun (ih: DateRange) ->
Expand Down
13 changes: 7 additions & 6 deletions src/DateDay.fs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@ module DateDay =
/// create a date from a year, month, and tracking day
let toDate y m td = Date(y, m, min (Date.DaysInMonth(y, m)) td)

#if DATEONLY
module DateOnly =
let FromDate (d:Date) =
DateOnly(d.Year, d.Month, d.Day)

#if DATEONLY
/// wrapper for DateOnly support
type DateOnly with
member this.ToDate () =
Date(this.Year, this.Month, this.Day)

#endif
/// wrapper for DateOnly support
module DateOnly =
let FromDate (d:Date) =
DateOnly(d.Year, d.Month, d.Day)
#endif
1 change: 1 addition & 0 deletions src/Fee.fs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ module Fee =
SettlementRefund: SettlementRefund
}

/// options specifying the types of fees, their amounts, and any restrictions on these
module Config =
/// a default config value, with no fees but recommended settings
let initialRecommended = {
Expand Down
11 changes: 6 additions & 5 deletions src/Formatting.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module Formatting =
open Currency

/// filter out hidden fields
let filterColumns hideProperties =
let internal filterColumns hideProperties =
match hideProperties with
| [||] -> id
| columns -> Array.filter(fun (pi: PropertyInfo) -> columns |> Array.exists(( = ) pi.Name) |> not)
Expand Down Expand Up @@ -68,7 +68,7 @@ module Formatting =
let internal formatCell index className style value = $"""<td class="ci{index} {className}" style="{style}">{value}</td>"""

/// writes a table cell, formatting the value for legibility (optimised for amortisation schedules)
let formatHtmlTableCell index value =
let internal formatHtmlTableCell index value =
value
|> sprintf "%A"
|> fun s -> regexPascaleCase.Replace(s, fun (m: Match) -> $" {m.Groups[1].Value |> (_.ToLower())}")
Expand All @@ -91,7 +91,7 @@ module Formatting =
else s |> formatCell index "" ""

/// writes table rows from an array
let formatHtmlTableRows hideProperties propertyInfos items =
let internal formatHtmlTableRows hideProperties propertyInfos items =
items
|> Array.map(fun li ->
propertyInfos
Expand All @@ -112,8 +112,9 @@ module Formatting =
/// creates HTML files from an array
let outputArrayToHtml fileName append data =
data |> generateHtmlFromArray [||] |> outputToFile' fileName append
/// writes table rows from an array
let formatHtmlTableRowsFromMap hideProperties propertyInfos data =

/// writes table rows from a map
let internal formatHtmlTableRowsFromMap hideProperties propertyInfos data =
data
|> Map.map(fun itemIndex li ->
propertyInfos
Expand Down
19 changes: 10 additions & 9 deletions src/Interest.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ open System
/// methods for calculating interest and unambiguously expressing interest rates, as well as enforcing regulatory caps on interest chargeable
module Interest =

open Util
open Calculation
open Currency
open DateDay
Expand Down Expand Up @@ -49,7 +48,7 @@ module Interest =
InterestRate: Rate
}

/// the interest cap options
/// caps on the total interest accruable
[<RequireQualifiedAccess; Struct>]
type Cap = {
/// a cap on the total amount of interest chargeable over the lifetime of a product
Expand All @@ -58,6 +57,7 @@ module Interest =
DailyAmount: Amount voption
}

/// caps on the total interest accruable
module Cap =
/// no cap
let zero = {
Expand All @@ -76,6 +76,7 @@ module Interest =
Rate: Rate
}

/// a promotional interest rate valid during the specified date range
module PromotionalRate =
/// creates a map of offset days and promotional interest rates
let toMap (startDate: Date) (promotionalRates: PromotionalRate array) =
Expand Down Expand Up @@ -130,7 +131,7 @@ module Interest =
| None -> { RateDay = offsetDay; InterestRate = standardRate }
)

/// calculate the interest accrued on a balance at a particular interest rate over a number of days, optionally capped by a daily amount
/// calculates the interest accrued on a balance at a particular interest rate over a number of days, optionally capped by a daily amount
let calculate (balance: int64<Cent>) (dailyInterestCap: Amount voption) interestRounding (dailyRates: DailyRate array) =
let dailyCap = Cap.total balance dailyInterestCap

Expand All @@ -144,7 +145,7 @@ module Interest =
)
|> Cent.roundTo interestRounding 8

/// calculate the settlement figure based on Consumer Credit (Early Settlement) Regulations 2004 regulation 4(1)
/// calculates the settlement figure based on Consumer Credit (Early Settlement) Regulations 2004 regulation 4(1)
let internal ``CCA 2004 regulation 4(1) formula`` (A: Map<int, decimal<Cent>>) (B: Map<int, decimal<Cent>>) (r: decimal) (m: int) (n: int) (a: Map<int, int>) (b: Map<int, int>) =
if A.Count < m || B.Count < n || a.Count < m || b.Count < n then
0m<Cent>
Expand All @@ -158,24 +159,24 @@ module Interest =
|> List.sumBy(fun j -> B[j] * ((1m + r) |> powi b[j] |> decimal) |> round)
sum1 - sum2

/// calculate the amount of rebate due following an early settlement
/// calculates the amount of rebate due following an early settlement
let calculateRebate (principal: int64<Cent>) (payments: (int * int64<Cent>) array) (apr: Percent) (settlementPeriod: int) (settlementPartPeriod: Fraction) unitPeriod paymentRounding =
if payments |> Array.isEmpty then
0L<Cent>
else
let advanceMap = Map [ (1, decimal principal * 1m<Cent>) ]
let paymentMap = payments |> Array.map(fun (i, p) -> (i, decimal p * 1m<Cent>)) |> Map.ofArray
let aprDailyRate = apr |> Apr.ukUnitPeriodRate unitPeriod |> Percent.toDecimal |> Rounding.roundTo (RoundWith MidpointRounding.AwayFromZero) 6
let aprUnitPeriodRate = apr |> Apr.ukUnitPeriodRate unitPeriod |> Percent.toDecimal |> Rounding.roundTo (RoundWith MidpointRounding.AwayFromZero) 6
let advanceCount = 1
let paymentCount = payments |> Array.length |> min settlementPeriod
let advanceIntervalMap = Map [ (1, settlementPeriod) ]
let paymentIntervalMap = payments |> Array.take paymentCount |> Array.scan(fun state p -> (fst state + 1), (int settlementPeriod - int (fst p))) (0, settlementPeriod) |> Array.tail |> Map.ofArray
let addPartPeriodTotal (sf: decimal<Cent>) = settlementPartPeriod |> fun spp -> sf * ((1m + aprDailyRate) |> powm (Fraction.toDecimal spp) |> decimal)
let wholePeriodTotal = ``CCA 2004 regulation 4(1) formula`` advanceMap paymentMap aprDailyRate advanceCount paymentCount advanceIntervalMap paymentIntervalMap
let addPartPeriodTotal (sf: decimal<Cent>) = settlementPartPeriod |> fun spp -> sf * ((1m + aprUnitPeriodRate) |> powm (Fraction.toDecimal spp) |> decimal)
let wholePeriodTotal = ``CCA 2004 regulation 4(1) formula`` advanceMap paymentMap aprUnitPeriodRate advanceCount paymentCount advanceIntervalMap paymentIntervalMap
let settlementFigure = wholePeriodTotal |> addPartPeriodTotal |> Cent.fromDecimalCent paymentRounding
let remainingPaymentTotal = payments |> Array.filter (fun (i, _) -> i > settlementPeriod) |> Array.sumBy snd
remainingPaymentTotal - settlementFigure

/// if there is less than one cent remaining, discard any fraction
/// if there is less than one cent remaining, discards any fraction
let ignoreFractionalCent i =
if abs i < 1m<Cent> then 0m<Cent> else i
9 changes: 2 additions & 7 deletions src/Map.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@ namespace FSharp.Finance.Personal
/// extension to the array module of a solve function, allowing an unknown value to be calculated through iteration
module MapExtension =

/// Contains operations for working with F# map values
module Map =
/// creates a map from an array of key-value tuples with simple values
let ofArrayWithMerge (array: ('a * 'b) array) =
array
|> Array.groupBy fst
|> Array.map(fun (k, v) -> k, Array.map snd v)
|> Map.ofArray

/// creates a map from an array of key-value tuples with array values
let ofArrayWithArrayMerge (array: ('a * 'b array) array) =
let ofArrayWithMerge (array: ('a * 'b array) array) =
array
|> Array.groupBy fst
|> Array.map(fun (k, v) -> k, Array.collect snd v)
Expand Down
9 changes: 6 additions & 3 deletions src/PaymentSchedule.fs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ module PaymentSchedule =
/// the payment has been failed, with optional charges (e.g. due to insufficient-funds penalties)
| Failed of Failed: int64<Cent> * ChargeTypes: Charge.ChargeType array

/// the status of the payment, allowing for delays due to payment-provider processing times
module ActualPaymentStatus =
/// the total amount of the payment
let total = function
Expand All @@ -138,6 +139,7 @@ module PaymentSchedule =
Metadata: Map<string, obj>
}

/// an actual payment made by the customer, optionally including metadata such as bank references etc.
module ActualPayment =
/// the total amount of the payment
let total =
Expand Down Expand Up @@ -266,6 +268,7 @@ module PaymentSchedule =
TotalPrincipal: int64<Cent>
}

/// a scheduled payment item, with running calculations of interest and principal balance
module SimpleItem =
/// a default value with no data
let initial =
Expand Down Expand Up @@ -604,6 +607,7 @@ module PaymentSchedule =
| _ ->
failwith "Unable to calculate simple schedule"

/// merges scheduled payments, determining the currently valid original and rescheduled payments, and preserving a record of any previous payments that have been superseded
let mergeScheduledPayments (scheduledPayments: (int<OffsetDay> * ScheduledPayment) array) =
let rescheduleDays = scheduledPayments |> Array.map snd |> Array.choose(fun sp -> if sp.Rescheduled.IsSome then Some sp.Rescheduled.Value.RescheduleDay else None) |> Array.distinct |> Array.sort
let mutable previousRescheduleDay = ValueNone
Expand Down Expand Up @@ -661,9 +665,8 @@ module PaymentSchedule =
ChargesPortion: int64<Cent>
}

/// functions relating to apportionments
/// a breakdown of how an actual payment is apportioned to principal, fees, interest and charges
module Apportionment =

/// add principal, fees, interest and charges to an existing apportionment
let Add principal fees interest charges apportionment =
{ apportionment with
Expand Down Expand Up @@ -703,8 +706,8 @@ module PaymentSchedule =
| GeneratedValue gv ->
formatCent gv

/// a generated payment, where applicable
module GeneratedPayment =

/// the total value of the generated payment
let Total = function
| GeneratedValue gv -> gv
Expand Down
2 changes: 2 additions & 0 deletions src/Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module Util =
/// constrain values to within a range
| WithinRange of MinValue:int64<Cent> * MaxValue:int64<Cent>

/// the type of restriction placed on a possible value
module Restriction =
/// calculate a permitted value based on a restriction
let calculate restriction value =
Expand All @@ -52,6 +53,7 @@ module Util =
/// a fixed fee
| Simple of Simple:int64<Cent>

/// an amount specified either as a simple amount or as a percentage of another amount, optionally restricted to lower and/or upper limits
module Amount =
/// calculates the total amount based on any restrictions
let total (baseValue: int64<Cent>) amount =
Expand Down
6 changes: 3 additions & 3 deletions tests/EdgeCaseTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ module EdgeCaseTests =
}
}

let actualPayments = Map.ofArrayWithArrayMerge [|
let actualPayments = Map.ofArrayWithMerge [|
8<OffsetDay>, [| ActualPayment.quickFailed 22500L<Cent> [||] |]
8<OffsetDay>, [| ActualPayment.quickFailed 22500L<Cent> [||] |]
8<OffsetDay>, [| ActualPayment.quickFailed 22500L<Cent> [||] |]
Expand Down Expand Up @@ -379,7 +379,7 @@ module EdgeCaseTests =
}
}

let actualPayments = Map.ofArrayWithArrayMerge [|
let actualPayments = Map.ofArrayWithMerge [|
8<OffsetDay>, [| ActualPayment.quickFailed 22500L<Cent> [| Charge.InsufficientFunds (Amount.Simple 10_00L<Cent>) |] |]
8<OffsetDay>, [| ActualPayment.quickFailed 22500L<Cent> [| Charge.InsufficientFunds (Amount.Simple 10_00L<Cent>) |] |]
8<OffsetDay>, [| ActualPayment.quickFailed 22500L<Cent> [| Charge.InsufficientFunds (Amount.Simple 10_00L<Cent>) |] |]
Expand Down Expand Up @@ -501,7 +501,7 @@ module EdgeCaseTests =
}
}

let actualPayments = Map.ofArrayWithArrayMerge [|
let actualPayments = Map.ofArrayWithMerge [|
23<OffsetDay>, [| ActualPayment.quickFailed 166_67L<Cent> [||] |]
23<OffsetDay>, [| ActualPayment.quickFailed 66_67L<Cent> [||] |]
23<OffsetDay>, [| ActualPayment.quickFailed 66_67L<Cent> [||] |]
Expand Down
2 changes: 1 addition & 1 deletion tests/InterestFirstTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ module InterestFirstTests =
314<OffsetDay>, [| ActualPayment.quickConfirmed 25000L<Cent> |]
315<OffsetDay>, [| ActualPayment.quickConfirmed 1L<Cent> |]
|]
|> Map.ofArrayWithArrayMerge
|> Map.ofArrayWithMerge

let schedule =
actualPayments
Expand Down
2 changes: 1 addition & 1 deletion tests/UnitPeriodConfigTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ module UnitPeriodConfigTests =
201<OffsetDay>, [| ActualPayment.quickConfirmed 12489L<Cent> |]
234<OffsetDay>, [| ActualPayment.quickConfirmed 12489L<Cent> |]
|]
|> Map.ofArrayWithArrayMerge
|> Map.ofArrayWithMerge

let actual =
let originalFinalPaymentDay = originalScheduledPayments |> Map.maxKeyValue |> fst
Expand Down

0 comments on commit b24a776

Please sign in to comment.