commit 275827002624bdb1a269bb95c0d9c0431f135198
parent df47222d6a7c5aa8528d4f759860661f733d6002
Author: triesap <tyson@radroots.org>
Date: Mon, 21 Sep 2026 13:04:47 +0000
provider: parse typed choice and noul answers
Diffstat:
2 files changed, 97 insertions(+), 0 deletions(-)
diff --git a/src/hyf_provider/jev_answers.mojo b/src/hyf_provider/jev_answers.mojo
@@ -0,0 +1,60 @@
+from std.collections import List
+
+from json import Value
+
+from hyf_assist.evaluator import TypedAnswer, typed_choice, typed_noul, typed_score
+
+
+def _has_key(value: Value, key: String) -> Bool:
+ for candidate in value.object_keys():
+ if candidate == key:
+ return True
+ return False
+
+
+def parse_noul_answer(question_id: String, value: Value) raises -> TypedAnswer:
+ if not value.is_object() or not _has_key(value, "type"):
+ raise Error("provider_answer_invalid")
+ if value["type"].string_value() != "noul":
+ raise Error("provider_answer_wrong_type")
+ if not _has_key(value, "noul") or not value["noul"].is_float():
+ raise Error("provider_answer_invalid")
+ var probability = value["noul"].float_value()
+ if probability < 0.0 or probability > 1.0:
+ raise Error("provider_answer_noul_out_of_range")
+ return typed_noul(question_id, probability)
+
+
+def parse_choice_answer(
+ question_id: String, value: Value, choices: 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() != "choice":
+ raise Error("provider_answer_wrong_type")
+ if not _has_key(value, "choice"):
+ raise Error("provider_answer_invalid")
+ var selected = value["choice"].string_value()
+ var known = False
+ for choice in choices:
+ if choice == selected:
+ known = True
+ if not known:
+ raise Error("provider_answer_unknown_choice")
+ 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, "probabilities"):
+ var probabilities = value["probabilities"]
+ if not probabilities.is_object():
+ raise Error("provider_answer_invalid")
+ var total = 0.0
+ for choice in choices:
+ if not _has_key(probabilities, choice):
+ raise Error("provider_answer_missing_probability")
+ total += probabilities[choice].float_value()
+ if total < 0.999 or total > 1.001:
+ raise Error("provider_answer_bad_distribution_sum")
+ return typed_choice(question_id, selected, confidence)
diff --git a/tests/test_jev.mojo b/tests/test_jev.mojo
@@ -43,3 +43,40 @@ def test_jev_request_serialization_shape() raises:
def main() raises:
TestSuite.discover_tests[__functions_in_module()]().run()
+
+
+from hyf_provider.jev_answers import parse_choice_answer, parse_noul_answer
+from json import loads
+
+
+def test_parse_choice_and_noul_answers() raises:
+ var noul = parse_noul_answer("seconds_ok", loads('{"type":"noul","noul":0.9}'))
+ assert_equal(noul.kind, "noul")
+ assert_equal(noul.noul, 0.9)
+ var choices = List[String]()
+ choices.append("offered")
+ choices.append("forecast")
+ choices.append("unclear")
+ var choice = parse_choice_answer(
+ "supply_status",
+ loads('{"type":"choice","choice":"offered","probabilities":{"offered":1.0,"forecast":0.0,"unclear":0.0},"confidence":1.0}'),
+ choices,
+ )
+ assert_equal(choice.choice, "offered")
+
+ with assert_raises():
+ _ = parse_noul_answer("q", loads('{"type":"noul","noul":1.5}'))
+ with assert_raises():
+ _ = parse_choice_answer(
+ "q", loads('{"type":"choice","choice":"bogus","confidence":1.0}'), choices
+ )
+ with assert_raises():
+ _ = parse_choice_answer(
+ "q",
+ loads('{"type":"choice","choice":"offered","probabilities":{"offered":0.5,"forecast":0.5,"unclear":0.5},"confidence":1.0}'),
+ choices,
+ )
+ with assert_raises():
+ _ = parse_choice_answer(
+ "q", loads('{"type":"noul","noul":0.5}'), choices
+ )