commit 3a33491efce98e7a2510fc7e6abbe0026b335a6d
parent d17f335f02e068ea59429f5de58a4ee5b1b4d96f
Author: triesap <tyson@radroots.org>
Date: Mon, 21 Sep 2026 13:04:07 +0000
core: add versioned semantic question bundles
Diffstat:
2 files changed, 113 insertions(+), 0 deletions(-)
diff --git a/src/hyf_assist/questions.mojo b/src/hyf_assist/questions.mojo
@@ -0,0 +1,76 @@
+from std.collections import List
+
+
+@fieldwise_init
+struct Question(Copyable, Movable):
+ var id: String
+ var kind: String
+ var instructions: String
+ var choices: List[String]
+ var rubric: List[String]
+
+
+@fieldwise_init
+struct QuestionBundle(Copyable, Movable):
+ var bundle_id: String
+ var version: String
+ var model: String
+ var questions: List[Question]
+
+
+def _copy_strings(items: List[String]) -> List[String]:
+ var copied = List[String]()
+ for item in items:
+ copied.append(String(item))
+ return copied^
+
+
+def choice_question(id: String, instructions: String, choices: List[String]) raises -> Question:
+ if id.strip() == "" or instructions.strip() == "":
+ raise Error("choice question requires id and instructions")
+ if len(choices) < 2:
+ raise Error("choice question requires at least two choices")
+ return Question(
+ id=String(id), kind="choice", instructions=String(instructions),
+ choices=_copy_strings(choices), rubric=List[String](),
+ )
+
+
+def noul_question(id: String, instructions: String) raises -> Question:
+ if id.strip() == "" or instructions.strip() == "":
+ raise Error("noul question requires id and instructions")
+ return Question(
+ id=String(id), kind="noul", instructions=String(instructions),
+ choices=List[String](), rubric=List[String](),
+ )
+
+
+def score_question(id: String, instructions: String, rubric: List[String]) raises -> Question:
+ if id.strip() == "" or instructions.strip() == "":
+ raise Error("score question requires id and instructions")
+ if len(rubric) < 2:
+ raise Error("score question requires at least two rubric levels")
+ return Question(
+ id=String(id), kind="score", instructions=String(instructions),
+ choices=List[String](), rubric=_copy_strings(rubric),
+ )
+
+
+def question_bundle(
+ bundle_id: String, version: String, model: String, questions: List[Question]
+) raises -> QuestionBundle:
+ if bundle_id.strip() == "" or version.strip() == "" or model.strip() == "":
+ raise Error("question bundle requires id, version and model")
+ if len(questions) == 0:
+ raise Error("question bundle must contain at least one question")
+ var copied = List[Question]()
+ for question in questions:
+ copied.append(question.copy())
+ return QuestionBundle(
+ bundle_id=String(bundle_id), version=String(version),
+ model=String(model), questions=copied^,
+ )
+
+
+def bundle_pins_configuration(bundle: QuestionBundle) -> Bool:
+ return bundle.version != "" and bundle.model != ""
diff --git a/tests/test_assist.mojo b/tests/test_assist.mojo
@@ -1,6 +1,7 @@
from std.collections import List
from std.testing import TestSuite, assert_equal, assert_raises, assert_true
+from hyf_assist.questions import Question
from hyf_assist.evaluator import (
DisabledSemanticEvaluator,
SemanticEvaluatorRequest,
@@ -88,3 +89,39 @@ def test_scripted_evaluator_is_strict_and_bounded() raises:
_ = scripted_evaluate(
evaluator, SemanticEvaluatorRequest(state="", question_bundle="qb1")
)
+
+
+from hyf_assist.questions import (
+ bundle_pins_configuration,
+ choice_question,
+ noul_question,
+ question_bundle,
+ score_question,
+)
+
+
+def test_question_bundles_are_versioned_and_validated() raises:
+ var choices = List[String]()
+ choices.append("offered")
+ choices.append("forecast")
+ choices.append("unavailable")
+ choices.append("unclear")
+ var questions = List[Question]()
+ questions.append(choice_question("supply_status", "status?", choices))
+ questions.append(noul_question("seconds_ok", "seconds permitted?"))
+ var rubric = List[String]()
+ rubric.append("unsuitable")
+ rubric.append("limited")
+ rubric.append("suitable")
+ questions.append(score_question("culinary_fit", "fit?", rubric))
+ var bundle = question_bundle("qb1", "1", "jev-1.13.0", questions)
+ assert_true(bundle_pins_configuration(bundle))
+ assert_equal(len(bundle.questions), 3)
+ var too_few = List[String]()
+ too_few.append("only")
+ with assert_raises():
+ _ = choice_question("x", "y", too_few)
+ var one = List[String]()
+ one.append("only")
+ with assert_raises():
+ _ = score_question("s", "i", one)