commit d03bebf7ce20a4390d84c47819b202633b9ab799
parent 275827002624bdb1a269bb95c0d9c0431f135198
Author: triesap <tyson@radroots.org>
Date: Mon, 21 Sep 2026 13:05:19 +0000
provider: parse ordered score answers
Diffstat:
2 files changed, 74 insertions(+), 0 deletions(-)
diff --git a/src/hyf_provider/jev_answers.mojo b/src/hyf_provider/jev_answers.mojo
@@ -58,3 +58,49 @@ def parse_choice_answer(
if total < 0.999 or total > 1.001:
raise Error("provider_answer_bad_distribution_sum")
return typed_choice(question_id, selected, confidence)
+
+
+def parse_score_answer(
+ question_id: String, value: Value, rubric: List[String]
+) raises -> TypedAnswer:
+ if not value.is_object() or not _has_key(value, "type"):
+ raise Error("provider_answer_invalid")
+ if value["type"].string_value() != "score":
+ raise Error("provider_answer_wrong_type")
+ if not _has_key(value, "score"):
+ raise Error("provider_answer_invalid")
+ var score_value = value["score"]
+ var raw_score = 0.0
+ if score_value.is_int():
+ raw_score = Float64(score_value.int_value())
+ elif score_value.is_float():
+ raw_score = score_value.float_value()
+ else:
+ raise Error("provider_answer_invalid")
+ var score = Int(raw_score)
+ if Float64(score) != raw_score:
+ raise Error("provider_answer_invalid")
+ if score < 0 or score >= len(rubric):
+ raise Error("provider_answer_score_out_of_range")
+ if not _has_key(value, "confidence") or not value["confidence"].is_float():
+ raise Error("provider_answer_invalid")
+ var confidence = value["confidence"].float_value()
+ if confidence < 0.0 or confidence > 1.0:
+ raise Error("provider_answer_confidence_out_of_range")
+ if _has_key(value, "legend"):
+ var legend = value["legend"]
+ if not legend.is_object() or len(legend.object_keys()) != len(rubric):
+ raise Error("provider_answer_wrong_legend")
+ if _has_key(value, "probabilities"):
+ var probabilities = value["probabilities"]
+ if not probabilities.is_object() or len(probabilities.object_keys()) != len(rubric):
+ raise Error("provider_answer_missing_level")
+ var total = 0.0
+ for level in range(len(rubric)):
+ var key = String(level)
+ if not _has_key(probabilities, key):
+ raise Error("provider_answer_missing_level")
+ total += probabilities[key].float_value()
+ if total < 0.999 or total > 1.001:
+ raise Error("provider_answer_bad_distribution_sum")
+ return typed_score(question_id, score, len(rubric), confidence)
diff --git a/tests/test_jev.mojo b/tests/test_jev.mojo
@@ -80,3 +80,31 @@ def test_parse_choice_and_noul_answers() raises:
_ = parse_choice_answer(
"q", loads('{"type":"noul","noul":0.5}'), choices
)
+
+
+from hyf_provider.jev_answers import parse_score_answer
+
+
+def test_parse_score_answer_validates_rubric_and_levels() raises:
+ var rubric = List[String]()
+ rubric.append("unsuitable")
+ rubric.append("limited")
+ rubric.append("suitable")
+ var answer = parse_score_answer(
+ "culinary_fit",
+ loads('{"type":"score","score":2,"legend":{"0":"unsuitable","1":"limited","2":"suitable"},"probabilities":{"0":0.0,"1":0.0,"2":1.0},"confidence":1.0}'),
+ rubric,
+ )
+ assert_equal(answer.score, 2)
+ with assert_raises():
+ _ = parse_score_answer(
+ "q",
+ loads('{"type":"score","score":5,"legend":{"0":"a","1":"b","2":"c"},"confidence":1.0}'),
+ rubric,
+ )
+ with assert_raises():
+ _ = parse_score_answer(
+ "q",
+ loads('{"type":"score","score":1,"legend":{"0":"a"},"confidence":1.0}'),
+ rubric,
+ )