reasoninglayer/resources/optimize.rs
1//! Linear programming / optimization.
2//!
3//! Thin wrapper that composes inference calls: the LP is compiled into inference rules, submitted
4//! via backward chaining, and the solution is extracted. A minimal Phase 1 version is shipped here;
5//! the KB-driven variant is available via helper methods that use [`SortsClient`],
6//! [`InferenceClient`], [`QueryClient`], and [`TermsClient`] in concert.
7
8use std::collections::BTreeMap;
9
10use crate::error::Error;
11use crate::http::HttpClient;
12use crate::types::common::RequestOptions;
13use crate::types::optimize::{LinearProgramDefinition, OptimizationResult, SolveOptions};
14
15/// Optimization client — thin facade for LP solving via the inference engine.
16///
17/// Currently a direct passthrough to `/api/v1/optimize/production`. The `solve` method
18/// returns [`Error::Validation`] directing callers to the endpoint-level API; client-side
19/// LP-to-rule compilation will land in a future release.
20#[derive(Debug, Clone)]
21pub struct OptimizeClient {
22 http: HttpClient,
23}
24
25impl OptimizeClient {
26 pub(crate) fn new(http: HttpClient) -> Self {
27 Self { http }
28 }
29
30 /// Solve an LP problem via the backend `/optimize/production` endpoint.
31 ///
32 /// The backend accepts a JSON representation of the problem; serialization of
33 /// [`LinearProgramDefinition`] uses the field layout documented in
34 /// [`crate::types::optimize`]. Call [`OptimizeClient::solve_raw`] if you need to pass a
35 /// custom JSON shape.
36 pub async fn solve(
37 &self,
38 problem: &LinearProgramDefinition,
39 _opts: &SolveOptions,
40 options: Option<&RequestOptions>,
41 ) -> Result<OptimizationResult, Error> {
42 let raw: serde_json::Value = self
43 .http
44 .post("/optimize/production", problem, options)
45 .await?;
46 // The backend returns either {"status": "optimal", ...} or {"status": "infeasible", ...}.
47 let status = raw.get("status").and_then(|v| v.as_str()).unwrap_or("");
48 let solve_time_ms = raw
49 .get("solve_time_ms")
50 .and_then(|v| v.as_u64())
51 .unwrap_or(0);
52 match status {
53 "optimal" => {
54 let variables: BTreeMap<String, f64> = raw
55 .get("variables")
56 .and_then(|v| serde_json::from_value(v.clone()).ok())
57 .unwrap_or_default();
58 let objective_value = raw
59 .get("objective_value")
60 .and_then(|v| v.as_f64())
61 .unwrap_or(0.0);
62 Ok(OptimizationResult::Optimal {
63 variables,
64 objective_value,
65 solve_time_ms,
66 })
67 }
68 "infeasible" => Ok(OptimizationResult::Infeasible { solve_time_ms }),
69 other => Err(Error::validation_msg(format!(
70 "unexpected optimization status: {other}"
71 ))),
72 }
73 }
74
75 /// Send a raw JSON payload to `/optimize/production` and return the raw response.
76 ///
77 /// Use this if the LP structure documented in [`crate::types::optimize`] does not match
78 /// the current backend contract.
79 pub async fn solve_raw(
80 &self,
81 payload: &serde_json::Value,
82 options: Option<&RequestOptions>,
83 ) -> Result<serde_json::Value, Error> {
84 self.http
85 .post("/optimize/production", payload, options)
86 .await
87 }
88}