hyf

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

commit d17f335f02e068ea59429f5de58a4ee5b1b4d96f
parent 067e7ce760f162cceecc2066a58f3496089420fe
Author: triesap <tyson@radroots.org>
Date:   Mon, 21 Sep 2026 13:03:41 +0000

core: add source-span candidate discovery

Diffstat:
Asrc/hyf_core/normalization/candidates.mojo | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/test_domain.mojo | 32++++++++++++++++++++++++++++++++
2 files changed, 126 insertions(+), 0 deletions(-)

diff --git a/src/hyf_core/normalization/candidates.mojo b/src/hyf_core/normalization/candidates.mojo @@ -0,0 +1,94 @@ +from std.collections import List + + +@fieldwise_init +struct Candidate(Copyable, Movable): + var kind: String + var text: String + var start: Int + var end: Int + + +def _is_digit_byte(byte: UInt8) -> Bool: + return byte >= UInt8(ord("0")) and byte <= UInt8(ord("9")) + + +def _backward_number_start(text: String, unit_start: Int) -> Int: + var bytes = text.as_bytes() + var index = unit_start - 1 + while index >= 0: + var byte = bytes[index] + if byte == UInt8(ord(" ")) or byte == UInt8(ord("\t")): + index -= 1 + continue + break + var end = index + while index >= 0 and _is_digit_byte(bytes[index]): + index -= 1 + if index == end: + return -1 + return index + 1 + + +def discover_candidates( + text: String, known_products: List[String], known_units: List[String], known_dates: List[String] +) -> List[Candidate]: + var candidates = List[Candidate]() + var lowered = text.lower() + + for product in known_products: + if product.strip() == "": + continue + var index = lowered.find(product.lower()) + if index >= 0: + candidates.append( + Candidate( + kind="product", + text=String(product), + start=index, + end=index + product.byte_length(), + ) + ) + + for unit in known_units: + if unit.strip() == "": + continue + var index = lowered.find(unit.lower()) + if index >= 0: + var number_start = _backward_number_start(text, index) + if number_start >= 0: + candidates.append( + Candidate( + kind="quantity", + text=String(text[byte=number_start:index + unit.byte_length()]), + start=number_start, + end=index + unit.byte_length(), + ) + ) + + for date_word in known_dates: + if date_word.strip() == "": + continue + var index = lowered.find(date_word.lower()) + if index >= 0: + candidates.append( + Candidate( + kind="date", + text=String(date_word), + start=index, + end=index + date_word.byte_length(), + ) + ) + return candidates^ + + +def candidate_kinds(candidates: List[Candidate]) -> List[String]: + var kinds = List[String]() + for candidate in candidates: + var seen = False + for kind in kinds: + if kind == candidate.kind: + seen = True + if not seen: + kinds.append(String(candidate.kind)) + return kinds^ diff --git a/tests/test_domain.mojo b/tests/test_domain.mojo @@ -559,3 +559,35 @@ def test_execution_metadata_is_separate_from_business_outcome() raises: _ = execution_meta("degraded", 0, None, None, None) with assert_raises(): _ = execution_meta("weird", 0, None, None, None) + + +from hyf_core.normalization.candidates import Candidate, candidate_kinds, discover_candidates + + +def test_candidate_discovery_spans_and_unknown_products() raises: + var products = List[String]() + products.append("roma tomatoes") + var units = List[String]() + units.append("lb") + var dates = List[String]() + dates.append("friday") + var candidates = discover_candidates( + "Got about 80 lb of Roma tomatoes. Can deliver Friday.", products, units, dates + ) + var kinds = candidate_kinds(candidates) + assert_true(len(kinds) == 3) + var saw_quantity = False + for candidate in candidates: + if candidate.kind == "quantity": + assert_equal(candidate.text, "80 lb") + saw_quantity = True + assert_true(saw_quantity) + + var unknown_products = List[String]() + unknown_products.append("kohlrabi") + var empty_units = List[String]() + var empty_dates = List[String]() + assert_equal( + len(discover_candidates("mystery greens", unknown_products, empty_units, empty_dates)), + 0, + )