Skip to main content

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/// Note: the full TypeScript SDK performs LP-to-rule compilation client-side; the Rust port of
18/// that compiler is deferred. For now this client exposes a direct passthrough to the planned
19/// `/api/v1/optimize/production` endpoint and a `solve` stub that returns
20/// [`Error::Validation`] directing callers to the endpoint-level API. Future versions will
21/// include the full compiler.
22#[derive(Debug, Clone)]
23pub struct OptimizeClient {
24    http: HttpClient,
25}
26
27impl OptimizeClient {
28    pub(crate) fn new(http: HttpClient) -> Self {
29        Self { http }
30    }
31
32    /// Solve an LP problem via the backend `/optimize/production` endpoint.
33    ///
34    /// The backend accepts a JSON representation of the problem; serialization of
35    /// [`LinearProgramDefinition`] uses the field layout documented in
36    /// [`crate::types::optimize`]. Call [`OptimizeClient::solve_raw`] if you need to pass a
37    /// custom JSON shape.
38    pub async fn solve(
39        &self,
40        problem: &LinearProgramDefinition,
41        _opts: &SolveOptions,
42        options: Option<&RequestOptions>,
43    ) -> Result<OptimizationResult, Error> {
44        let raw: serde_json::Value = self
45            .http
46            .post("/optimize/production", problem, options)
47            .await?;
48        // The backend returns either {"status": "optimal", ...} or {"status": "infeasible", ...}.
49        let status = raw.get("status").and_then(|v| v.as_str()).unwrap_or("");
50        let solve_time_ms = raw
51            .get("solve_time_ms")
52            .and_then(|v| v.as_u64())
53            .unwrap_or(0);
54        match status {
55            "optimal" => {
56                let variables: BTreeMap<String, f64> = raw
57                    .get("variables")
58                    .and_then(|v| serde_json::from_value(v.clone()).ok())
59                    .unwrap_or_default();
60                let objective_value = raw
61                    .get("objective_value")
62                    .and_then(|v| v.as_f64())
63                    .unwrap_or(0.0);
64                Ok(OptimizationResult::Optimal {
65                    variables,
66                    objective_value,
67                    solve_time_ms,
68                })
69            }
70            "infeasible" => Ok(OptimizationResult::Infeasible { solve_time_ms }),
71            other => Err(Error::validation_msg(format!(
72                "unexpected optimization status: {other}"
73            ))),
74        }
75    }
76
77    /// Send a raw JSON payload to `/optimize/production` and return the raw response.
78    ///
79    /// Use this if the LP structure documented in [`crate::types::optimize`] does not match
80    /// the current backend contract.
81    pub async fn solve_raw(
82        &self,
83        payload: &serde_json::Value,
84        options: Option<&RequestOptions>,
85    ) -> Result<serde_json::Value, Error> {
86        self.http
87            .post("/optimize/production", payload, options)
88            .await
89    }
90}