impl/typescript.ts
2,514 bytes · the TypeScript implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { epochDayFromIso } from "./dates_add_days.ts"; ← from dates.add-days ^1.0.0 · built alongside by fune
import { assertSameCurrency, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { type ClaimTransaction, type ReservePosition } from "./insurance_claim_reserve_types.ts";
/**
* A claim's position after each transaction: paid, recovered, the outstanding
* case reserve, and incurred = paid - recovered + outstanding.
*
* A payment erodes the outstanding reserve by what it pays, never below zero:
* a payment larger than the reserve raises incurred by the excess, which is
* how an under-reserved claim shows up. A reserve transaction sets the
* outstanding estimate outright. The movement is what each transaction did to
* incurred, the figure that goes through the accounts.
*/
export function claimReserveHistory(transactions: readonly ClaimTransaction[], currency: string): readonly ReservePosition[] {
const zero = money(0, currency);
let paid = 0;
let recovered = 0;
let outstanding = 0;
let incurred = 0;
let previousDay: number | null = null;
let previousDate = "";
const out: ReservePosition[] = [];
for (const t of transactions) {
const day = epochDayFromIso(t.date);
if (previousDay !== null && day < previousDay) {
throw new RangeError(`transactions must be in date order: ${t.date} comes after ${previousDate}`);
}
previousDay = day;
previousDate = t.date;
assertSameCurrency(zero, t.amount);
if (t.kind === "reserve") {
if (t.amount.minor < 0) {
throw new RangeError(`a reserve must not be negative, received ${t.amount.minor} on ${t.date}`);
}
outstanding = t.amount.minor;
} else if (t.kind === "payment" || t.kind === "recovery") {
if (t.amount.minor <= 0) {
throw new RangeError(`a ${t.kind} must be more than zero, received ${t.amount.minor} on ${t.date}`);
}
if (t.kind === "payment") {
paid += t.amount.minor;
outstanding = Math.max(outstanding - t.amount.minor, 0);
} else {
recovered += t.amount.minor;
}
} else {
throw new RangeError(`unknown transaction kind "${t.kind}": use reserve, payment or recovery`);
}
const now = paid - recovered + outstanding;
out.push({
date: t.date,
kind: t.kind,
paid: money(paid, currency),
recovered: money(recovered, currency),
outstanding: money(outstanding, currency),
incurred: money(now, currency),
movement: money(now - incurred, currency),
});
incurred = now;
}
return out;
}