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/// 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}