commit 6bff99fe46be482d778bec2f2753a067e574db2d
parent 33ef1c93cfa5fbc3bfb4f44d3a6f596822a355dd
Author: triesap <tyson@radroots.org>
Date: Mon, 21 Sep 2026 15:00:00 +0000
core: wire interpretation and matching pipelines into exposed operations
Diffstat:
3 files changed, 232 insertions(+), 23 deletions(-)
diff --git a/src/hyf_application/buyer_operation.mojo b/src/hyf_application/buyer_operation.mojo
@@ -1,6 +1,12 @@
+from std.collections import List
from json import Value, loads
from hyf_application.context import interpretation_source
+from hyf_core.domain.eligibility import (
+ ConstraintAssessment,
+ compose_eligibility,
+ constraint_assessment,
+)
def _has_key(value: Value, key: String) -> Bool:
@@ -10,6 +16,16 @@ def _has_key(value: Value, key: String) -> Bool:
return False
+def _string_list(value: Value) raises -> List[String]:
+ var items = List[String]()
+ if not value.is_array():
+ return items^
+ for entry in value.array_items():
+ if entry.is_string():
+ items.append(entry.string_value())
+ return items^
+
+
def _validate_source(input: Value) raises:
if not input.is_object() or not _has_key(input, "source"):
raise Error("buyer operation input requires 'source'")
@@ -27,16 +43,39 @@ def _validate_source(input: Value) raises:
def execute_buyer_request_interpret(input: Value) raises -> Value:
_validate_source(input)
- var output = loads("{}")
+ var phrases = List[String]()
+ if _has_key(input, "products"):
+ phrases = _string_list(input["products"])
+
var lines = loads("[]")
+ var index = 1
+ for phrase in phrases:
+ var line = loads("{}")
+ line.set("line_id", Value("line-" + String(index)))
+ var product = loads("{}")
+ product.set("phrase", Value(String(phrase)))
+ product.set("catalogue_id", Value(String(phrase)))
+ product.set("resolution", Value("resolved"))
+ line.set("product", product)
+ var quantity = loads("{}")
+ quantity.set("state", Value("unknown"))
+ line.set("quantity", quantity)
+ line.set("conditions", loads("[]"))
+ line.set("evidence", loads("[]"))
+ line.set("review_required", Value(True))
+ lines.append(line)
+ index += 1
+
+ var output = loads("{}")
output.set("demand_lines", lines)
var review = loads("{}")
- review.set("required", Value(True))
+ review.set("required", Value(len(phrases) == 0))
var clarifications = loads("[]")
- var clarification = loads("{}")
- clarification.set("field", Value("demand_lines"))
- clarification.set("reason", Value("information_needed"))
- clarifications.append(clarification)
+ if len(phrases) == 0:
+ var clarification = loads("{}")
+ clarification.set("field", Value("demand_lines"))
+ clarification.set("reason", Value("information_needed"))
+ clarifications.append(clarification)
review.set("clarifications", clarifications)
output.set("review", review)
var execution = loads("{}")
@@ -46,19 +85,127 @@ def execute_buyer_request_interpret(input: Value) raises -> Value:
return output^
+def _assessment_value(
+ lot_id: String, checks: List[ConstraintAssessment]
+) raises -> Value:
+ var value = loads("{}")
+ value.set("candidate_id", Value(String(lot_id)))
+ value.set("eligibility", Value(compose_eligibility(checks)))
+ var check_values = loads("[]")
+ for check in checks:
+ var entry = loads("{}")
+ entry.set("kind", Value(String(check.kind)))
+ entry.set("result", Value(String(check.result)))
+ entry.set("reason", Value(String(check.reason)))
+ entry.set("evidence", loads("[]"))
+ check_values.append(entry)
+ value.set("checks", check_values)
+ return value^
+
+
def execute_buyer_request_match(input: Value) raises -> Value:
if not input.is_object() or not _has_key(input, "need"):
raise Error("buyer_request.match input requires 'need'")
if not _has_key(input, "snapshots"):
raise Error("buyer_request.match input requires 'snapshots'")
+
+ var need = input["need"]
+ var required_phrase = ""
+ if _has_key(need, "product_phrase"):
+ required_phrase = need["product_phrase"].string_value()
+ var required_value = 0
+ var required_scale = 0
+ var required_unit = "kg"
+ if _has_key(need, "quantity"):
+ var quantity = need["quantity"]
+ if _has_key(quantity, "value"):
+ required_value = Int(quantity["value"].int_value())
+ if _has_key(quantity, "scale"):
+ required_scale = Int(quantity["scale"].int_value())
+ if _has_key(quantity, "unit"):
+ required_unit = quantity["unit"].string_value()
+
+ var assessments = loads("[]")
+ var plans = loads("[]")
+ var plan_index = 1
+ for snapshot in input["snapshots"].array_items():
+ var lot_id = snapshot["lot_id"].string_value()
+ var supplier_id = ""
+ if _has_key(snapshot, "supplier_id"):
+ supplier_id = snapshot["supplier_id"].string_value()
+ var lot_phrase = required_phrase
+ if _has_key(snapshot, "product_phrase"):
+ lot_phrase = snapshot["product_phrase"].string_value()
+
+ var checks = List[ConstraintAssessment]()
+ checks.append(
+ constraint_assessment(
+ "product",
+ "pass" if required_phrase == lot_phrase else "fail",
+ True,
+ "product_match" if required_phrase == lot_phrase else "product_mismatch",
+ )
+ )
+ var available_state = "unknown"
+ if _has_key(snapshot, "unreserved_state"):
+ available_state = snapshot["unreserved_state"].string_value()
+ checks.append(
+ constraint_assessment(
+ "availability",
+ "pass" if available_state == "known" else "unknown",
+ True,
+ "availability_known" if available_state == "known" else "stock_unknown",
+ )
+ )
+ if available_state == "known" and required_value > 0:
+ var available_value = Int(snapshot["unreserved_value"].int_value())
+ var available_scale = Int(snapshot["unreserved_scale"].int_value())
+ var comparison = "fail"
+ if available_value >= required_value:
+ comparison = "pass"
+ checks.append(
+ constraint_assessment(
+ "quantity",
+ comparison,
+ True,
+ "quantity_sufficient" if comparison == "pass" else "quantity_insufficient",
+ )
+ )
+ assessments.append(_assessment_value(lot_id, checks))
+
+ if compose_eligibility(checks) == "eligible" and supplier_id != "":
+ var plan = loads("{}")
+ plan.set("plan_id", Value("plan-" + String(plan_index)))
+ plan.set("supplier_id", Value(String(supplier_id)))
+ var allocations = loads("[]")
+ var allocation = loads("{}")
+ allocation.set("lot_id", Value(String(lot_id)))
+ allocation.set("line_id", Value("line-1"))
+ var quantity = loads("{}")
+ quantity.set("state", Value("known"))
+ quantity.set("value", Value(required_value))
+ quantity.set("unit", Value(String(required_unit)))
+ quantity.set("qualifier", Value("exact"))
+ allocation.set("quantity", quantity)
+ allocations.append(allocation)
+ plan.set("allocations", allocations)
+ var coverage = loads("{}")
+ coverage.set("lines_covered", loads('["line-1"]'))
+ coverage.set("uncovered", loads("[]"))
+ coverage.set("partial", Value(False))
+ plan.set("coverage", coverage)
+ plan.set("unresolved", loads("[]"))
+ plans.append(plan)
+ plan_index += 1
+
var output = loads("{}")
- output.set("assessments", loads("[]"))
- output.set("plans", loads("[]"))
+ output.set("assessments", assessments)
+ output.set("plans", plans)
var limitations = loads("{}")
limitations.set("scope", Value("supplied_only"))
limitations.set("truncated", Value(False))
limitations.set("supported_mode", Value("single_supplier_compatible_lots"))
- limitations.set("unsupported", loads("[]"))
+ limitations.set("unsupported", loads('["multi_supplier"]'))
output.set("limitations", limitations)
var execution = loads("{}")
execution.set("status", Value("complete"))
diff --git a/src/hyf_application/farm_operation.mojo b/src/hyf_application/farm_operation.mojo
@@ -1,10 +1,8 @@
+from std.collections import List
from json import Value, loads
from hyf_application.context import interpretation_source
-from hyf_application.farm_failure import farm_inference_failure
-from hyf_application.farm_review_output import assemble_farm_output
-from hyf_core.domain.execution import execution_meta
-from hyf_core.domain.review import review_required
+from hyf_core.normalization.candidates import Candidate, discover_candidates
def _has_key(value: Value, key: String) -> Bool:
@@ -14,6 +12,31 @@ def _has_key(value: Value, key: String) -> Bool:
return False
+def _string_list(value: Value) raises -> List[String]:
+ var items = List[String]()
+ if not value.is_array():
+ return items^
+ for entry in value.array_items():
+ if entry.is_string():
+ items.append(entry.string_value())
+ return items^
+
+
+def _string_array_value(items: List[String]) raises -> Value:
+ var array = loads("[]")
+ for item in items:
+ array.append(Value(String(item)))
+ return array^
+
+
+def _evidence_value(evidence: Candidate) raises -> Value:
+ var value = loads("{}")
+ value.set("start", Value(evidence.start))
+ value.set("end", Value(evidence.end))
+ value.set("method", Value("span"))
+ return value^
+
+
def execute_farm_update_interpret(input: Value) raises -> Value:
if not input.is_object() or not _has_key(input, "source"):
raise Error("farm_update.interpret input requires 'source'")
@@ -27,19 +50,56 @@ def execute_farm_update_interpret(input: Value) raises -> Value:
actor_id=source["actor_id"].string_value(),
farm_id=source["farm_id"].string_value(),
)
- _ = parsed
- var output = loads("{}")
+
+ var products = List[String]()
+ var units = List[String]()
+ var dates = List[String]()
+ if _has_key(input, "taxonomy"):
+ var taxonomy = input["taxonomy"]
+ if _has_key(taxonomy, "products"):
+ products = _string_list(taxonomy["products"])
+ if _has_key(taxonomy, "units"):
+ units = _string_list(taxonomy["units"])
+ if _has_key(taxonomy, "dates"):
+ dates = _string_list(taxonomy["dates"])
+
+ var candidates = discover_candidates(
+ parsed.text, products, units, dates
+ )
+
var claims = loads("[]")
+ var review_required = False
+ var claim_index = 1
+ for candidate in candidates:
+ if candidate.kind != "product":
+ continue
+ var claim = loads("{}")
+ claim.set("claim_id", Value("claim-" + String(claim_index)))
+ var product = loads("{}")
+ product.set("phrase", Value(String(candidate.text)))
+ product.set("catalogue_id", Value(String(candidate.text)))
+ product.set("resolution", Value("resolved"))
+ claim.set("product", product)
+ claim.set("status", Value("unclear"))
+ var evidence = loads("[]")
+ evidence.append(_evidence_value(candidate))
+ claim.set("evidence", evidence)
+ claim.set("review_required", Value(True))
+ claims.append(claim)
+ review_required = True
+ claim_index += 1
+
+ var output = loads("{}")
output.set("claims", claims)
- var changes = loads("[]")
- output.set("proposed_changes", changes)
+ output.set("proposed_changes", loads("[]"))
var review = loads("{}")
- review.set("required", Value(True))
+ review.set("required", Value(review_required))
var clarifications = loads("[]")
- var clarification = loads("{}")
- clarification.set("field", Value("review"))
- clarification.set("reason", Value("information_needed"))
- clarifications.append(clarification)
+ if review_required:
+ var clarification = loads("{}")
+ clarification.set("field", Value("status"))
+ clarification.set("reason", Value("information_needed"))
+ clarifications.append(clarification)
review.set("clarifications", clarifications)
output.set("review", review)
var execution = loads("{}")
diff --git a/tests/test_application.mojo b/tests/test_application.mojo
@@ -740,9 +740,11 @@ def test_farm_update_operation_returns_proposal_output() raises:
var input = _json_loads(
'{"source":{"source_id":"s1","revision":"r1","text":"Got about 80 lb of Roma tomatoes.",'
'"source_time":"2026-09-21T09:00:00-07:00","timezone":"America/Vancouver",'
- '"actor_id":"farm-1","farm_id":"farm-1"}}'
+ '"actor_id":"farm-1","farm_id":"farm-1"},'
+ '"taxonomy":{"products":["roma tomatoes"],"units":["lb"],"dates":[]}}'
)
var output = execute_farm_update_interpret(input)
+ assert_equal(len(output["claims"].array_items()), 1)
assert_true(output["review"]["required"].bool_value())
assert_equal(output["execution"]["status"].string_value(), "complete")
assert_true(output["original_source_preserved"].bool_value())