Skip to content

Add Salary Advance Zero-Interest Modeling Module#29

Open
Thorium wants to merge 6 commits into
simontreanor:mainfrom
Thorium:salary-addvance
Open

Add Salary Advance Zero-Interest Modeling Module#29
Thorium wants to merge 6 commits into
simontreanor:mainfrom
Thorium:salary-addvance

Conversation

@Thorium
Copy link
Copy Markdown
Contributor

@Thorium Thorium commented Sep 10, 2025

Summary

Introduces zero-interest Salary Advance / Earned Wage Access modeling under:
FSharp.Finance.Personal.SalaryAdvance

Focus: construct repayment schedules, handle fee treatments, expose provider-perspective cashflows.


Module Overview

src/SalaryAdvance.fs

Types:

  • RepaymentMode = LumpOnFirstPayroll | EvenlyProrated | Custom of int64<Cent> list
  • Fee = NoFee | Flat of int64<Cent> | Percentage of decimal (0 < p < 1)
  • FeeTreatment = NettedFromProceeds | AddedOnTop
  • BuildConfig
  • SalaryAdvanceResult

Primary function:

  • build : BuildConfig -> SalaryAdvanceResult

Helper:

  • borrowerCashflows (sign inversion for borrower perspective analysis)

Fee & Principal Logic

FeeTreatment Net Disbursed Total Repayable Principal Stored
NettedFromProceeds Advance - Fee Advance Advance
AddedOnTop Advance Advance + Fee Advance + Fee

Principal equals the sum of scheduled repayments so amortisation alignment is preserved.
Fee rounding: percentage fee → multiply advance amount, round up fractional cent.


Validations

  • Payroll dates: non-empty, strictly increasing, all > AdvanceDate
  • Percentage fee: 0 < p < 1
  • Custom list length = payroll dates length AND sums to total repayable
  • AdvanceAmount > 0

Repayment Allocation

  • Lump: single payment on first payroll date
  • EvenlyProrated: integer division across all dates; final repayment absorbs remainder
  • Custom: explicit amounts—must match total & count

Cashflow Convention

Provider perspective:

  • (AdvanceDate, -netDisbursed)
  • Each payroll date: positive repayment

Borrower-facing analytics: use borrowerCashflows to invert signs.


APR Handling

Reuses Apr.CalculationMethod.UnitedKingdom 2 placeholder (no interest).
Potential future addition: AprMethod.Disabled for clarity on zero-interest products.


Tests

Planned / to be ensured:

  • Test_Lump_Netted_NoFee
  • Test_Prorated_PercentageFee_AddedOnTop
  • Test_CustomMismatch_ShouldFail

All validate principal, fee handling, sum consistency, and failure modes.


Documentation

docs/exampleSalaryAdvance.fsx:

  • Demonstrates both fee treatments
  • Prints cashflows & totals
  • Notes that XIRR analytics reside in separate B2B PR
  • Serves as a user onboarding example for fee semantics

Checklist

  • Module scaffold
  • Tests added & green
  • README bullet added (verify after push)
  • Build verification
  • Coordinate version bump after B2B PR merges

Open Questions

  1. Introduce Apr.CalculationMethod.Disabled later?
  2. Unify fee abstraction with future factoring / trade credit?
  3. Add early/partial repayment support?

Non-Goals (This PR)

  • Interest accrual mechanics
  • Multi-advance aggregation
  • XIRR calculation
  • Regulatory or compliance classification

Disclaimer

Analytical only; validate independently for production, compliance, or disclosure usage.

Copilot AI and others added 5 commits September 10, 2025 09:51
…nality

Co-authored-by: Thorium <229355+Thorium@users.noreply.github.com>
Co-authored-by: Thorium <229355+Thorium@users.noreply.github.com>
…e4be

# Conflicts:
#	src/FSharp.Finance.Personal.fsproj
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new FSharp.Finance.Personal.SalaryAdvance module intended to model zero-interest salary advances by generating repayment schedules, computing fees, and exporting cashflows, plus accompanying tests and an FsDocs example.

Changes:

  • Introduces src/SalaryAdvance.fs with schedule construction, fee calculation, cashflow export, and basic validation.
  • Adds tests/SalaryAdvanceTests.fs and wires it into the test project.
  • Adds docs/exampleSalaryAdvance.fsx usage example (and a standalone tests/SalaryAdvanceTest.fsx script).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/SalaryAdvance.fs New salary advance modeling module (repayment modes, fees, schedule/cashflows, validation).
src/FSharp.Finance.Personal.fsproj Includes SalaryAdvance.fs in the library build.
tests/SalaryAdvanceTests.fs New Xunit tests for salary advance schedule/fees/cashflows/validation/summary.
tests/FSharp.Finance.Personal.Tests.fsproj Includes the new salary advance test file.
tests/SalaryAdvanceTest.fsx Adds a manual FSI script to exercise the module.
docs/exampleSalaryAdvance.fsx Adds an FsDocs example showing basic usage and output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +16 to +21
- **RepaymentMode**: Discriminated union supporting multiple repayment strategies
- **Schedule Construction**: Generate repayment schedules based on payroll dates
- **Fee Handling**: Support for flat fees, percentage fees, or no fees
- **Cashflow Export**: Export cashflows for analytical use (compatible with XIRR analysis)
- **Self-contained**: No dependencies on B2B modules

Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example claims exported cashflows are "compatible with XIRR analysis". In this PR, exportCashflows emits a positive initial disbursement and negative repayments, which is the opposite of the provider-perspective convention described in the PR text (and the most common convention for IRR/XIRR inputs). Clarify the sign convention here (or update the underlying function) so consumers don’t compute inverted results.

Copilot uses AI. Check for mistakes.
Comment thread src/SalaryAdvance.fs
Comment on lines +41 to +46
type SalaryAdvanceFee =
/// flat fee amount
| FlatFee of Amount: int64<Cent>
/// percentage-based fee
| PercentageFee of Percentage: decimal
/// no fee
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elsewhere in the codebase, percentage values are represented with Calculation.Percent (e.g., Interest.Rate.Annual of Percent and Amount.Percentage(Percent ...)) rather than raw decimal. Using decimal here makes it ambiguous whether the value is 0–1 or 0–100 and complicates formatting/validation. Consider changing PercentageFee to take a Percent to align with existing conventions and reduce misuse.

Copilot uses AI. Check for mistakes.
Comment thread src/SalaryAdvance.fs
Comment on lines +11 to +20
/// represents different repayment modes for salary advances
[<Struct; StructuredFormatDisplay("{Html}")>]
type RepaymentMode =
/// repayment in full on the first payroll after advance
| LumpOnFirstPayroll
/// repayment evenly distributed across multiple payrolls
| EvenlyProrated
/// custom repayment over specified number of days
| Custom of Days: int64

Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public API implemented here (e.g., RepaymentMode.Custom of Days:int64, SalaryAdvanceFee, ScheduleConfig.createSchedule) doesn’t match the PR description’s proposed API (RepaymentMode.Custom of int64<Cent> list, Fee/FeeTreatment, BuildConfig + build : BuildConfig -> SalaryAdvanceResult, and borrowerCashflows). Either update the implementation to the documented API or update the PR description/docs so consumers aren’t misled about available functionality and semantics.

Copilot uses AI. Check for mistakes.
Comment thread src/SalaryAdvance.fs Outdated
Comment thread src/SalaryAdvance.fs Outdated
Comment thread src/SalaryAdvance.fs Outdated
Comment thread src/SalaryAdvance.fs Outdated
Comment thread tests/SalaryAdvanceTests.fs Outdated
assert (schedule2.[1].RepaymentAmount = 25000L<Cent>)

printfn "✓ Schedule construction works for EvenlyProrated"

Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvenlyProrated is only tested with an amount that divides evenly across payroll dates, so it won’t catch rounding/remainder behavior (and related balance correctness). Add a test where AdvanceAmount + Fee is not divisible by the number of payroll dates and assert that (a) the sum of repayments equals the total repayable and (b) remaining balances step down correctly.

Suggested change
// Test evenly prorated with a remainder to validate rounding and balance step-down
let payrollDatesWithRemainder =
[| Date(2024, 1, 31); Date(2024, 2, 15); Date(2024, 2, 29) |]
let config3 =
SalaryAdvance.ScheduleConfig.create
advanceDate
10000L<Cent> // $100.00
EvenlyProrated
payrollDatesWithRemainder
|> SalaryAdvance.ScheduleConfig.withFee (FlatFee 1L<Cent>) // total repayable = 10001 cents
let schedule3 = SalaryAdvance.createSchedule config3
let totalRepayable = 10001L<Cent>
let totalRepayments = schedule3 |> Array.sumBy (fun payment -> payment.RepaymentAmount)
assert (schedule3.Length = 3)
assert (totalRepayments = totalRepayable)
let runningRepayments =
schedule3
|> Array.scan (fun acc payment -> acc + payment.RepaymentAmount) 0L<Cent>
|> Array.tail
schedule3
|> Array.iteri (fun i payment ->
let expectedRemainingBalance = totalRepayable - runningRepayments.[i]
assert (payment.RemainingBalance = expectedRemainingBalance))
assert (schedule3.[0].RemainingBalance > schedule3.[1].RemainingBalance)
assert (schedule3.[1].RemainingBalance > schedule3.[2].RemainingBalance)
assert (schedule3.[2].RemainingBalance = 0L<Cent>)
printfn "✓ EvenlyProrated handles remainders and remaining balances correctly"

Copilot uses AI. Check for mistakes.
Comment thread tests/SalaryAdvanceTest.fsx Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants