///|
pub(all) struct Payment {
origin : Int32
age : Int32
calendar : Int32
amount : Double
is_tail : Bool
} derive(Debug)
///|
pub extend Payment with @debug.Debug::{to_repr}
///|
/// Allocates each method's reserve by remaining pattern weights.
/// Tail is a separate final period (age=order), not observed development.
pub fn Estimate::payments(self : Estimate) -> Array[Payment] raise RunoffError {
let out : Array[Payment] = []
let p = self.pattern
for row in self.origins {
let reported = 1.0 / p.cdf[row.age]
let remaining = 1.0 - reported
if remaining == 0.0 {
if row.reserve != 0.0 {
raise Numerical("nonzero reserve with fully reported pattern")
}
continue
}
let mut allocated = 0.0
let mut previous = reported
for age in (row.age + 1).. 1.0 {
let amount = checked(
row.reserve - allocated,
"tail reconciliation overflow",
)
if amount < -1.0e-10 * (1.0 + row.reserve) {
raise Numerical("payment rounding exceeds reserve")
}
out.push({
origin: row.origin,
age: p.order(),
calendar: row.origin + p.order(),
amount: if amount < 0.0 {
0.0
} else {
amount
},
is_tail: true,
})
} else if out.length() > 0 {
// Reconcile the last development payment to eliminate summation drift.
let last = out.length() - 1
let old = out[last]
if old.origin == row.origin {
let amount = old.amount + (row.reserve - allocated)
if amount < 0.0 {
raise Numerical("negative reconciled payment")
}
out[last] = { ..old, amount, }
}
}
}
out
}
///|
pub fn Estimate::calendar_payments(
self : Estimate,
) -> Array[Double] raise RunoffError {
let total = Array::make(self.pattern.order() * 2, 0.0)
for p in self.payments() {
total[p.calendar] = checked(
total[p.calendar] + p.amount,
"calendar payment overflow",
)
}
total
}