hyf

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

commit d82d4a21c8010a39d982560fd6096eef8c0dce62
parent 117e865fc88083e3ffc81f258297bbe6f8139088
Author: triesap <tyson@radroots.org>
Date:   Mon, 21 Sep 2026 12:55:52 +0000

core: add unit dimensions and conversion rules

Diffstat:
Asrc/hyf_core/domain/units.mojo | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/test_domain.mojo | 33+++++++++++++++++++++++++++++++++
2 files changed, 106 insertions(+), 0 deletions(-)

diff --git a/src/hyf_core/domain/units.mojo b/src/hyf_core/domain/units.mojo @@ -0,0 +1,73 @@ +from hyf_core.domain.quantity import Quantity, new_quantity + + +@fieldwise_init +struct ConversionRule(Copyable, Movable): + var from_unit: String + var to_unit: String + var numerator: Int + var denominator: Int + var version: String + + +def unit_dimension(unit: String) -> String: + var lowered = unit.lower() + if lowered == "kg" or lowered == "lb" or lowered == "g" or lowered == "oz": + return "mass" + if lowered == "l" or lowered == "ml": + return "volume" + if lowered == "each" or lowered == "box" or lowered == "bunch": + return "count" + return "unknown" + + +def _power_of_ten_exponent(denominator: Int) raises -> Int: + if denominator <= 0: + raise Error("conversion denominator must be positive") + var remaining = denominator + var exponent = 0 + while remaining > 1: + if remaining % 10 != 0: + raise Error( + "unsupported conversion denominator (must be a power of ten)" + ) + remaining //= 10 + exponent += 1 + return exponent + + +def conversion_rule( + from_unit: String, + to_unit: String, + numerator: Int, + denominator: Int, + version: String, +) raises -> ConversionRule: + if from_unit.strip() == "" or to_unit.strip() == "": + raise Error("conversion requires from_unit and to_unit") + if numerator <= 0: + raise Error("conversion numerator must be positive") + if unit_dimension(from_unit) != unit_dimension(to_unit): + raise Error("conversion rule crosses incompatible dimensions") + _ = _power_of_ten_exponent(denominator) + return ConversionRule( + from_unit=String(from_unit), + to_unit=String(to_unit), + numerator=numerator, + denominator=denominator, + version=String(version), + ) + + +def apply_conversion(quantity: Quantity, rule: ConversionRule) raises -> Quantity: + if quantity.unit != rule.from_unit: + raise Error("conversion rule does not apply to unit " + quantity.unit) + if quantity.dimension != unit_dimension(rule.to_unit): + raise Error("conversion rule crosses incompatible dimensions") + var exponent = _power_of_ten_exponent(rule.denominator) + var result = quantity.copy() + result.value = quantity.value * rule.numerator + result.scale = quantity.scale + exponent + result.unit = String(rule.to_unit) + result.dimension = String(unit_dimension(rule.to_unit)) + return result^ diff --git a/tests/test_domain.mojo b/tests/test_domain.mojo @@ -130,3 +130,36 @@ def test_quantity_add_overflow_and_approximation() raises: _ = quantity_add(huge, exact) var adjustment = new_quantity(-5, 0, "kg", "mass", "exact") assert_true(quantity_is_negative(adjustment)) + + +from hyf_core.domain.units import ( + apply_conversion, + conversion_rule, + unit_dimension, +) + + +def test_unit_dimensions_and_exact_mass_conversion() raises: + assert_equal(unit_dimension("lb"), "mass") + assert_equal(unit_dimension("ml"), "volume") + assert_equal(unit_dimension("widget"), "unknown") + + var pounds = new_quantity(80, 0, "lb", "mass", "exact") + var rule = conversion_rule("lb", "kg", 45359237, 100000000, "test-1") + var kilograms = apply_conversion(pounds, rule) + assert_equal(kilograms.unit, "kg") + assert_equal(kilograms.dimension, "mass") + assert_equal(kilograms.value, 3628738960) + assert_equal(kilograms.scale, 8) + + +def test_unit_conversion_rejects_dimension_mismatch_and_unknown_denominator() raises: + with assert_raises(): + _ = conversion_rule("lb", "l", 1, 1, "v1") + with assert_raises(): + _ = conversion_rule("lb", "kg", 1, 3, "v1") + var pounds = new_quantity(1, 0, "lb", "mass", "exact") + var kg_rule = conversion_rule("lb", "kg", 45359237, 100000000, "test-1") + with assert_raises(): + _ = apply_conversion(new_quantity(1, 0, "kg", "mass", "exact"), kg_rule) + _ = pounds