///|
pub(all) struct PlanRef {
id : String
version : String
} derive(Debug, Eq)
///|
pub extend PlanRef with Debug::{to_repr}
///|
pub extend PlanRef with Eq::{equal, not_equal}
///|
/// Inclusive integer interval. For q in [from_quantity,to_quantity], each exact
/// total equals its value at from_quantity plus step * (q-from_quantity).
pub(all) struct PriceSpan {
from_quantity : String
to_quantity : String
baseline_total : String
candidate_total : String
baseline_step : String
candidate_step : String
} derive(Debug, Eq)
///|
pub extend PriceSpan with Debug::{to_repr}
///|
pub extend PriceSpan with Eq::{equal, not_equal}
///|
/// Candidate minus baseline over every integer quantity from zero to limit.
/// Extreme ties select the smallest quantity. This is exact, unsettled pricing.
pub(all) struct PriceAnalysis {
baseline : PlanRef
candidate : PlanRef
currency : String
metric : String
unit : String
limit : String
spans : Array[PriceSpan]
minimum_delta : String
minimum_at : String
maximum_delta : String
maximum_at : String
} derive(Debug, Eq)
///|
pub extend PriceAnalysis with Debug::{to_repr}
///|
pub extend PriceAnalysis with Eq::{equal, not_equal}
///|
fn curve_piece(
plan : Plan,
q : BigInt,
limit : BigInt,
) -> (BigInt, BigInt, BigInt) {
let mut lower = 0N
let mut prefix = 0N
for tier in plan.tiers {
let bound = match tier.up_to {
Some(n) => n
None => limit
}
if q <= bound {
let end = if bound < limit { bound } else { limit }
let total = match plan.mode {
Graduated => prefix + (q - lower) * tier.price
Volume => q * tier.price
}
return (end, total, tier.price)
}
prefix += (bound - lower) * tier.price
lower = bound
}
abort("validated plan always ends in an unbounded tier")
}
///|
fn analysis_extremes(
spans : ArrayView[PriceSpan],
) -> (String, String, String, String) raise PricingError {
let mut minimum = 0N
let mut maximum = 0N
let mut min_at = 0N
let mut max_at = 0N
for span in spans {
let from = natural(span.from_quantity, "analysis.span.from")
let to = natural(span.to_quantity, "analysis.span.to")
let start = amount_atoms(span.candidate_total, "analysis.candidate_total") -
amount_atoms(span.baseline_total, "analysis.baseline_total")
let slope = decimal(span.candidate_step, "analysis.candidate_step") -
decimal(span.baseline_step, "analysis.baseline_step")
for (q, delta) in [(from, start), (to, start + slope * (to - from))] {
if delta < minimum {
minimum = delta
min_at = q
}
if delta > maximum {
maximum = delta
max_at = q
}
}
}
(
format_atoms(minimum),
min_at.to_string(),
format_atoms(maximum),
max_at.to_string(),
)
}
///|
/// Partition the entire bounded integer domain at both plans' tier transitions.
/// Affine extrema occur at endpoints; volume cliffs remain separate spans.
///
/// ```mbt check
/// test {
/// let a = @jieheng.compile_plan(
/// id="api",
/// version="v1",
/// metric="calls",
/// unit="call",
/// currency="USD",
/// tiers=[{ up_to: None, unit_price: "1", }],
/// )
/// let r = @jieheng.analyze_prices(a, a, "999999999999999999999999999999")
/// inspect(r.spans.length(), content="1")
/// inspect(r.maximum_delta, content="0")
/// }
/// ```
pub fn analyze_prices(
baseline : Plan,
candidate : Plan,
limit : String,
) -> PriceAnalysis raise PricingError {
comparable(baseline, candidate)
let cap = natural(limit, "analysis.limit")
let spans : Array[PriceSpan] = []
let mut from = 0N
while from <= cap {
let (be, bt, bs) = curve_piece(baseline, from, cap)
let (ce, ct, cs) = curve_piece(candidate, from, cap)
let to = if be < ce { be } else { ce }
spans.push({
from_quantity: from.to_string(),
to_quantity: to.to_string(),
baseline_total: format_atoms(bt),
candidate_total: format_atoms(ct),
baseline_step: format_atoms(bs),
candidate_step: format_atoms(cs),
})
from = to + 1N
}
let (minimum_delta, minimum_at, maximum_delta, maximum_at) = analysis_extremes(
spans[:],
)
let report = {
baseline: { id: baseline.id, version: baseline.version, },
candidate: { id: candidate.id, version: candidate.version, },
currency: baseline.currency,
metric: baseline.metric,
unit: baseline.unit,
limit,
spans,
minimum_delta,
minimum_at,
maximum_delta,
maximum_at,
}
verify_analysis(baseline, candidate, limit, report)
report
}
///|
/// Independent truncated-range sum and selected-tier slope. Does not call the
/// producer's prefix recurrence, rate, or curve_piece.
fn check_curve_value(plan : Plan, q : BigInt) -> (BigInt, BigInt) {
let mut sum = 0N
let mut previous = 0N
let mut slope = 0N
let mut selected = false
for tier in plan.tiers {
let upper = match tier.up_to {
Some(n) => if q < n { q } else { n }
None => q
}
if upper > previous {
sum += (upper - previous) * tier.price
}
if !selected && (tier.up_to is None || (tier.up_to is Some(n) && q <= n)) {
slope = tier.price
selected = true
}
match tier.up_to {
Some(n) => previous = n
None => ()
}
}
(
match plan.mode {
Graduated => sum
Volume => q * slope
},
slope,
)
}
///|
/// Prove complete canonical coverage, both affine functions and endpoint extrema
/// against trusted plans. No sampled-quantity assumption or solver reexecution.
pub fn verify_analysis(
baseline : Plan,
candidate : Plan,
limit : String,
report : PriceAnalysis,
) -> Unit raise PricingError {
comparable(baseline, candidate)
let cap = natural(limit, "analysis.limit")
if report.baseline != { id: baseline.id, version: baseline.version, } ||
report.candidate != { id: candidate.id, version: candidate.version, } ||
report.currency != baseline.currency ||
report.metric != baseline.metric ||
report.unit != baseline.unit ||
report.limit != limit {
invalid(
"identity", "analysis", "plan references, currency, metric, unit and limit must match trusted inputs",
)
}
if report.spans.length() == 0 || report.spans.length() > 2047 {
invalid("size", "analysis.spans", "expected 1 to 2047 spans")
}
let mut expected_from = 0N
for i, span in report.spans {
let path = "analysis.spans[\{i}]"
let from = natural(span.from_quantity, path + ".from_quantity")
let to = natural(span.to_quantity, path + ".to_quantity")
if from != expected_from || from > to || to > cap {
invalid(
"coverage", path, "spans must cover zero through the trusted limit exactly once, in order",
)
}
let mut expected_to = cap
for plan in [baseline, candidate] {
for tier in plan.tiers {
if tier.up_to is Some(n) && n >= from && n < expected_to {
expected_to = n
}
}
}
if to != expected_to {
invalid(
"boundary", path, "split at exactly the next tier transition of either trusted plan",
)
}
let (bt, bs) = check_curve_value(baseline, from)
let (ct, cs) = check_curve_value(candidate, from)
if span.baseline_total != format_atoms(bt) ||
span.candidate_total != format_atoms(ct) ||
span.baseline_step != format_atoms(bs) ||
span.candidate_step != format_atoms(cs) {
invalid(
"curve", path, "exact values and per-unit slopes must match the trusted plans",
)
}
expected_from = to + 1N
}
if expected_from != cap + 1N {
invalid(
"coverage", "analysis.spans", "missing the end of the trusted quantity domain",
)
}
let (min_delta, min_at, max_delta, max_at) = analysis_extremes(
report.spans[:],
)
if report.minimum_delta != min_delta ||
report.minimum_at != min_at ||
report.maximum_delta != max_delta ||
report.maximum_at != max_at {
invalid(
"extrema", "analysis", "extrema must cover all span endpoints and choose the smallest quantity on ties",
)
}
}