hyf

Context-aware query service for Radroots
git clone https://radroots.dev/git/hyf.git
Log | Files | Refs | README | LICENSE

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:
Asrc/hyf_provider/jev_answers.mojo | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/test_jev.mojo | 37+++++++++++++++++++++++++++++++++++++
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 + )