import { Budget } from "./budget"; import Currency from "./currency/currency"; class BalanceToBudgetRatio { toString(): string { return this._ratio?.toFixed(2) ?? ""; } public readonly recommendedAction: "arrears" | "save" | "spend" | null; public readonly normalisedRatio: number | null; private constructor( private readonly _ratio: number | null, private readonly _normalisationFactor: number, ) { if (this._ratio === null) { this.normalisedRatio = null; this.recommendedAction = null; return; } this.normalisedRatio = this._ratio * this._normalisationFactor; if (this._ratio < 0) { this.recommendedAction = "arrears"; } else if (this._ratio >= 1) { this.recommendedAction = "spend"; } else { this.recommendedAction = "save"; } } static new(balance: Currency, budget: Budget | null) { if (budget === null || budget.quantity === 0 || budget.quantity === -0) { return null; } return new BalanceToBudgetRatio( balance.valueOf() / budget.quantity.valueOf(), 1, ); } static makeNormaliser( ...balanceToBudgetRatios: (BalanceToBudgetRatio | null)[] ): (r: BalanceToBudgetRatio | null) => BalanceToBudgetRatio { const validRatios = balanceToBudgetRatios.filter( (r) => r && r._ratio && !isNaN(r._ratio) && Infinity > r._ratio && -Infinity < r._ratio, ); const maxAbsRatio = Math.max( ...validRatios.map((r) => Math.abs(r?._ratio ?? 0)), ); const normalisationFactor = 1 / maxAbsRatio; return (r) => new BalanceToBudgetRatio(r?._ratio ?? null, normalisationFactor); } static compare( lhs: BalanceToBudgetRatio | null, rhs: BalanceToBudgetRatio | null, ): number { if (lhs === null || lhs._ratio === null) { if (rhs === null || rhs._ratio === null) { return 0; } return 1; } if (rhs === null || rhs._ratio === null) { return -1; } return rhs._ratio - lhs._ratio; } } export { BalanceToBudgetRatio };