///|
/// Verify a supplied breakdown against trusted plan and usage snapshots.
/// Does not invoke rate(): checks a contiguous partition, per-tier products,
/// and conservation of the exact quantity and amount independently.
pub fn verify_calculation(
plan : Plan,
usage : Usage,
calculation : Calculation,
) -> Unit raise PricingError {
let quantity = validate_usage(plan, usage)
if calculation.plan_id != plan.id ||
calculation.plan_version != plan.version ||
calculation.currency != plan.currency ||
calculation.unit != plan.unit ||
calculation.usage != usage {
invalid(
"identity", "calculation", "plan, version, currency, unit and usage must match trusted inputs",
)
}
if plan.mode == Volume {
verify_volume(plan, quantity, calculation)
return
}
if calculation.lines.length() > plan.tiers.length() {
invalid("partition", "calculation.lines", "more lines than price tiers")
}
let mut covered = 0N
let mut sum = 0N
for i, line in calculation.lines {
let path = "calculation.lines[\{i}]"
let tier = plan.tiers[i]
let lower = natural(line.from_exclusive, path + ".from_exclusive")
let upper = natural(line.to_inclusive, path + ".to_inclusive")
let count = natural(line.quantity, path + ".quantity")
if line.tier != i ||
lower != covered ||
count == 0N ||
upper - lower != count ||
upper > quantity {
invalid(
"partition", path, "lines must be ordered, contiguous, positive and cover the trusted quantity exactly once",
)
}
match tier.up_to {
Some(bound) =>
if upper > bound ||
(i + 1 < calculation.lines.length() && upper != bound) {
invalid(
"boundary", path, "finish the current tier before entering the next tier",
)
}
None =>
if i + 1 < calculation.lines.length() {
invalid("boundary", path, "no lines may follow an unbounded tier")
}
}
if line.unit_price != format_atoms(tier.price) {
invalid(
"price",
path + ".unit_price",
"price must match the canonical price from the trusted plan",
)
}
let product = count * tier.price
if line.subtotal != format_atoms(product) {
invalid(
"subtotal",
path + ".subtotal",
"expected exact quantity multiplied by unit price",
)
}
covered = upper
sum += product
}
if covered != quantity {
invalid(
"coverage", "calculation.lines", "missing usage units or extra lines for zero usage",
)
}
if calculation.total != format_atoms(sum) {
invalid(
"total", "calculation.total", "expected the exact sum of all tier subtotals",
)
}
}
///|
/// Verify arithmetic and an explicitly trusted settlement policy.
/// This checker uses rounding error intervals and tie parity, not settle().
pub fn verify_settlement(
calculation : Calculation,
settlement : Settlement,
scale~ : Int32,
rounding~ : Rounding,
) -> Unit raise PricingError {
verify_total_settlement(
calculation.currency,
calculation.total,
settlement,
scale~,
rounding~,
)
}
///|
fn verify_total_settlement(
currency : String,
exact : String,
settlement : Settlement,
scale~ : Int32,
rounding~ : Rounding,
) -> Unit raise PricingError {
if scale < 0 || scale > 18 {
invalid(
"scale", "settlement.scale", "choose a trusted scale between 0 and 18",
)
}
if settlement.currency != currency ||
settlement.scale != scale ||
settlement.rounding != rounding {
invalid(
"policy", "settlement", "currency, scale and rounding must match the trusted policy",
)
}
let units = wide_natural(settlement.minor_units, 78, "settlement.minor_units")
let step = BigInt::from_string("1" + "0".repeat(18 - scale))
let total = amount_atoms(exact, "calculation.total")
let paid = units * step
let delta = paid - total
let valid = match rounding {
Down => delta <= 0N && delta > -step
Up => delta >= 0N && delta < step
HalfUp => delta * 2N > -step && delta * 2N <= step
HalfEven => {
let twice = delta * 2N
twice >= -step &&
twice <= step &&
((twice != step && twice != -step) || units % 2N == 0N)
}
}
if !valid {
invalid(
"rounding", "settlement.minor_units", "rounded amount violates the requested rounding interval or tie parity",
)
}
if amount_atoms(settlement.amount, "settlement.amount") != paid ||
settlement.amount != fixed_amount(units, scale) {
invalid(
"amount", "settlement.amount", "amount must equal minor_units at the explicit fixed decimal scale",
)
}
if settlement.delta != format_atoms(delta) {
invalid(
"delta", "settlement.delta", "expected settled amount minus exact total",
)
}
}
///|
fn wide_natural(
text : String,
max_digits : Int32,
path : String,
) -> BigInt raise PricingError {
if text.length() == 0 ||
text.length() > max_digits ||
(text.length() > 1 && text[0] == '0') {
invalid(
"integer",
path,
"expected a canonical nonnegative integer of at most \{max_digits} digits",
)
}
for c in text {
if c < '0' || c > '9' {
invalid("integer", path, "expected ASCII decimal digits")
}
}
BigInt::from_string(text)
}
///|
fn fixed_amount(units : BigInt, scale : Int32) -> String {
let digits = units.to_string()
if scale == 0 {
return digits
}
let padded = "0".repeat(
if digits.length() <= scale {
scale + 1 - digits.length()
} else {
0
},
) +
digits
let split = padded.length() - scale
padded[:split].to_owned() + "." + padded[split:].to_owned()
}