///|
pub fn encode_analysis(report : PriceAnalysis) -> String raise PricingError {
if report.spans.length() == 0 || report.spans.length() > 2047 {
invalid("size", "analysis.spans", "expected 1 to 2047 spans")
}
let spans : Array[Json] = report.spans.map(s => {
"from_quantity": s.from_quantity,
"to_quantity": s.to_quantity,
"baseline_total": s.baseline_total,
"candidate_total": s.candidate_total,
"baseline_step": s.baseline_step,
"candidate_step": s.candidate_step,
})
let json : Json = {
"schema": "jieheng.price-analysis.v1",
"baseline": { "id": report.baseline.id, "version": report.baseline.version },
"candidate": {
"id": report.candidate.id,
"version": report.candidate.version,
},
"currency": report.currency,
"metric": report.metric,
"unit": report.unit,
"limit": report.limit,
"spans": spans,
"minimum_delta": report.minimum_delta,
"minimum_at": report.minimum_at,
"maximum_delta": report.maximum_delta,
"maximum_at": report.maximum_at,
}
bounded_json(json)
}
///|
fn plan_ref_from_json(
value : Json,
path : String,
) -> PlanRef raise PricingError {
let r = fields(value, ["id", "version"], path)
{
id: text_value(r["id"], path + ".id"),
version: text_value(r["version"], path + ".version"),
}
}
///|
/// Structural decode only; verify_analysis proves semantic coverage and values.
pub fn decode_analysis(text : String) -> PriceAnalysis raise PricingError {
let r = fields(
parse_document(text),
[
"schema", "baseline", "candidate", "currency", "metric", "unit", "limit", "spans",
"minimum_delta", "minimum_at", "maximum_delta", "maximum_at",
],
"analysis",
)
expect_value(r, "schema", "jieheng.price-analysis.v1", "analysis")
let entries = bounded_items(r["spans"], 2047, "analysis.spans")
let spans : Array[PriceSpan] = []
for i, value in entries {
let path = "analysis.spans[\{i}]"
let s = fields(
value,
[
"from_quantity", "to_quantity", "baseline_total", "candidate_total", "baseline_step",
"candidate_step",
],
path,
)
spans.push({
from_quantity: text_value(s["from_quantity"], path + ".from_quantity"),
to_quantity: text_value(s["to_quantity"], path + ".to_quantity"),
baseline_total: text_value(s["baseline_total"], path + ".baseline_total"),
candidate_total: text_value(
s["candidate_total"],
path + ".candidate_total",
),
baseline_step: text_value(s["baseline_step"], path + ".baseline_step"),
candidate_step: text_value(s["candidate_step"], path + ".candidate_step"),
})
}
{
baseline: plan_ref_from_json(r["baseline"], "analysis.baseline"),
candidate: plan_ref_from_json(r["candidate"], "analysis.candidate"),
currency: text_value(r["currency"], "analysis.currency"),
metric: text_value(r["metric"], "analysis.metric"),
unit: text_value(r["unit"], "analysis.unit"),
limit: text_value(r["limit"], "analysis.limit"),
spans,
minimum_delta: text_value(r["minimum_delta"], "analysis.minimum_delta"),
minimum_at: text_value(r["minimum_at"], "analysis.minimum_at"),
maximum_delta: text_value(r["maximum_delta"], "analysis.maximum_delta"),
maximum_at: text_value(r["maximum_at"], "analysis.maximum_at"),
}
}