///|
fn normalization_from_json(value : Json) -> Normalization raise ChoiceError {
match json_string(value) {
"minmax" => MinMax
"vector" => Vector
_ => raise Invalid("normalization must be minmax or vector")
}
}
///|
fn preference_from_json(value : Json) -> Preference raise ChoiceError {
let f = object_fields(value, ["kind", "q", "p", "sigma"])
let kind = json_string(required(f, "kind"))
let allowed = match kind {
"usual" => ["kind"]
"ushape" => ["kind", "q"]
"vshape" => ["kind", "p"]
"level" | "linear" => ["kind", "q", "p"]
"gaussian" => ["kind", "sigma"]
_ => raise Invalid("unsupported preference kind")
}
let _ = object_fields(value, allowed)
let preference = match kind {
"usual" => Usual
"ushape" => UShape(json_number(required(f, "q")))
"vshape" => VShape(json_number(required(f, "p")))
"level" =>
Level(json_number(required(f, "q")), json_number(required(f, "p")))
"linear" =>
Linear(json_number(required(f, "q")), json_number(required(f, "p")))
_ => Gaussian(json_number(required(f, "sigma")))
}
valid_preference(preference)
preference
}
///|
fn method_from_json(value : Json) -> Method raise ChoiceError {
let f = object_fields(value, ["kind", "normalization", "v", "preferences"])
let kind = json_string(required(f, "kind"))
let allowed = match kind {
"saw" | "topsis" => ["kind", "normalization"]
"product" => ["kind"]
"vikor" => ["kind", "v"]
"promethee" => ["kind", "preferences"]
_ => raise Invalid("unsupported algorithm kind")
}
let _ = object_fields(value, allowed)
match kind {
"saw" => Additive(normalization_from_json(required(f, "normalization")))
"topsis" =>
IdealDistance(normalization_from_json(required(f, "normalization")))
"product" => Product
"vikor" => {
let v = json_number(required(f, "v"))
probability(v)
Compromise(v)
}
_ =>
Outranking(
json_array(required(f, "preferences"), 32).map(preference_from_json),
)
}
}
///|
fn scenario_json(report : ScenarioReport) -> Json raise ChoiceError {
Json::object({
"schema": Json("moonchoice.scenarios/1"),
"rankings": Json::array(report.rankings.map(ranking_json)),
"rank_counts": Json(report.rank_counts),
"winner_counts": Json(report.winner_counts),
"mean_ranks": Json(report.mean_ranks),
"best_ranks": Json(report.best_ranks),
"worst_ranks": Json(report.worst_ranks),
"universally_best": Json(report.universally_best),
"pairwise_wins": Json(report.pairwise_wins),
"pairwise_ties": Json(report.pairwise_ties),
"frequency_interpretation": Json(
"finite supplied scenarios, not probabilities",
),
})
}
///|
fn execute_request(value : Json) -> Json raise ChoiceError {
let f = object_fields(value, [
"schema", "operation", "matrix", "weights", "algorithm", "scenarios",
])
if json_string(required(f, "schema")) != "moonchoice.request/1" {
raise Invalid("unsupported request schema")
}
let operation = json_string(required(f, "operation"))
let allowed = if operation == "scenarios" {
["schema", "operation", "matrix", "algorithm", "scenarios"]
} else {
["schema", "operation", "matrix", "algorithm", "weights"]
}
let _ = object_fields(value, allowed)
let m = matrix_from_json(required(f, "matrix"))
let algorithm = method_from_json(required(f, "algorithm"))
match operation {
"rank" =>
ranking_json(
evaluate(m, json_numbers(required(f, "weights"), 32), algorithm),
)
"report" =>
report_json(
decision_report(m, json_numbers(required(f, "weights"), 32), algorithm),
)
"scenarios" => {
let scenarios = json_array(required(f, "scenarios"), 64).map(row => {
json_numbers(row, 32)
})
scenario_json(weight_scenarios(m, scenarios, algorithm))
}
_ => raise Invalid("operation must be rank, report or scenarios")
}
}
///|
/// Portable string-in/string-out dispatcher. No filesystem, network or global state.
pub fn dispatch(request : String) -> String {
let response = try {
let result = execute_request(parse_document(request))
Json::object({
"schema": Json("moonchoice.response/1"),
"ok": Json(true),
"result": result,
})
} catch {
Invalid(message) => error_response("invalid", message)
Budget(message) => error_response("budget", message)
Numerical(message) => error_response("numerical", message)
}
response.stringify()
}
///|
fn error_response(kind : String, message : String) -> Json {
Json::object({
"schema": Json("moonchoice.response/1"),
"ok": Json(false),
"error": Json::object({ "kind": Json(kind), "message": Json(message) }),
})
}