commit ee0915aef8fa778ebb3e527397a2b3fa14d17840
parent d842040d71c4533872979a220a02f1fbbfcbaffd
Author: triesap <tyson@radroots.org>
Date: Mon, 21 Sep 2026 13:02:51 +0000
core: define the narrow semantic evaluator boundary
Diffstat:
3 files changed, 138 insertions(+), 0 deletions(-)
diff --git a/pixi.toml b/pixi.toml
@@ -25,6 +25,7 @@ test-repo-local-process = "mojo -I src tests/test_repo_local_process_contract.mo
test-stdio = "mojo -I src tests/test_stdio_contract.mojo"
test-provider-adapter = "mojo -I src -I tests tests/test_provider_adapter.mojo"
test-domain = "mojo -I src -I tests tests/test_domain.mojo"
+test-assist = "mojo -I src -I tests tests/test_assist.mojo"
test-spec = "mojo -I src -I tests tests/test_traceability.mojo"
check-build = "mkdir -p .pixi/build && mojo build -I src src/main.mojo -o .pixi/build/hyfd"
check-format = "sh tools/check_format.sh"
@@ -38,6 +39,7 @@ test-runtime-contract = { depends-on = [
test = { depends-on = [
"test-unit",
"test-domain",
+ "test-assist",
"test-provider-adapter",
"test-spec",
"test-architecture",
diff --git a/src/hyf_assist/evaluator.mojo b/src/hyf_assist/evaluator.mojo
@@ -0,0 +1,90 @@
+from std.collections import List
+
+
+@fieldwise_init
+struct TypedAnswer(Copyable, Movable):
+ var question_id: String
+ var kind: String
+ var choice: String
+ var score: Int
+ var noul: Float64
+ var confidence: Float64
+
+
+@fieldwise_init
+struct SemanticEvaluatorRequest(Copyable, Movable):
+ var state: String
+ var question_bundle: String
+
+
+@fieldwise_init
+struct SemanticEvaluatorResponse(Copyable, Movable):
+ var model: String
+ var answers: List[TypedAnswer]
+
+
+@fieldwise_init
+struct DisabledSemanticEvaluator(Copyable, Movable):
+ def evaluate(
+ self, request: SemanticEvaluatorRequest
+ ) raises -> SemanticEvaluatorResponse:
+ raise Error("semantic evaluator is disabled")
+
+
+def typed_choice(
+ question_id: String, choice: String, confidence: Float64
+) raises -> TypedAnswer:
+ if question_id.strip() == "" or choice.strip() == "":
+ raise Error("choice answer requires question id and choice")
+ if confidence < 0.0 or confidence > 1.0:
+ raise Error("choice confidence must be within [0, 1]")
+ return TypedAnswer(
+ question_id=String(question_id),
+ kind="choice",
+ choice=String(choice),
+ score=0,
+ noul=0.0,
+ confidence=confidence,
+ )
+
+
+def typed_noul(question_id: String, noul: Float64) raises -> TypedAnswer:
+ if noul < 0.0 or noul > 1.0:
+ raise Error("noul probability must be within [0, 1]")
+ return TypedAnswer(
+ question_id=String(question_id),
+ kind="noul",
+ choice="",
+ score=0,
+ noul=noul,
+ confidence=noul,
+ )
+
+
+def typed_score(
+ question_id: String, score: Int, levels: Int, confidence: Float64
+) raises -> TypedAnswer:
+ if levels < 2:
+ raise Error("score rubric requires at least two levels")
+ if score < 0 or score >= levels:
+ raise Error("score is out of rubric range")
+ if confidence < 0.0 or confidence > 1.0:
+ raise Error("score confidence must be within [0, 1]")
+ return TypedAnswer(
+ question_id=String(question_id),
+ kind="score",
+ choice="",
+ score=score,
+ noul=0.0,
+ confidence=confidence,
+ )
+
+
+def normalize_score(answer: TypedAnswer, levels: Int) raises -> Float64:
+ if answer.kind != "score":
+ raise Error("normalize_score requires a score answer")
+ if levels < 2:
+ raise Error("score rubric requires at least two levels")
+ if answer.score < 0 or answer.score >= levels:
+ raise Error("score is out of rubric range")
+ return Float64(answer.score) / Float64(levels - 1)
diff --git a/tests/test_assist.mojo b/tests/test_assist.mojo
@@ -0,0 +1,46 @@
+from std.collections import List
+from std.testing import TestSuite, assert_equal, assert_raises, assert_true
+
+from hyf_assist.evaluator import (
+ DisabledSemanticEvaluator,
+ SemanticEvaluatorRequest,
+ SemanticEvaluatorResponse,
+ TypedAnswer,
+ normalize_score,
+ typed_choice,
+ typed_noul,
+ typed_score,
+)
+
+
+def test_evaluator_boundary_is_typed_and_scriptable() raises:
+ var answers = List[TypedAnswer]()
+ answers.append(typed_choice("supply_status", "offered", 1.0))
+ var response = SemanticEvaluatorResponse(model="test-model", answers=answers^)
+ assert_equal(response.model, "test-model")
+ assert_equal(len(response.answers), 1)
+ assert_equal(response.answers[0].kind, "choice")
+ assert_equal(response.answers[0].choice, "offered")
+
+ var disabled = DisabledSemanticEvaluator()
+ with assert_raises():
+ _ = disabled.evaluate(
+ SemanticEvaluatorRequest(state="s", question_bundle="q")
+ )
+
+
+def test_typed_answers_and_score_normalization() raises:
+ assert_equal(typed_noul("q", 0.5).kind, "noul")
+ assert_equal(typed_score("s", 2, 3, 1.0).score, 2)
+ assert_equal(normalize_score(typed_score("s", 2, 3, 1.0), 3), 1.0)
+ assert_equal(normalize_score(typed_score("s", 1, 3, 1.0), 3), 0.5)
+ with assert_raises():
+ _ = typed_choice("q", "x", 1.5)
+ with assert_raises():
+ _ = typed_noul("q", -0.1)
+ with assert_raises():
+ _ = typed_score("s", 5, 3, 1.0)
+
+
+def main() raises:
+ TestSuite.discover_tests[__functions_in_module()]().run()