reasoninglayer 1.0.3

Rust client SDK for the Reasoning Layer API
Documentation
//! Linear programming / optimization.
//!
//! Thin wrapper that composes inference calls: the LP is compiled into inference rules, submitted
//! via backward chaining, and the solution is extracted. A minimal Phase 1 version is shipped here;
//! the KB-driven variant is available via helper methods that use [`SortsClient`],
//! [`InferenceClient`], [`QueryClient`], and [`TermsClient`] in concert.

use std::collections::BTreeMap;

use crate::error::Error;
use crate::http::HttpClient;
use crate::types::common::RequestOptions;
use crate::types::optimize::{LinearProgramDefinition, OptimizationResult, SolveOptions};

/// Optimization client — thin facade for LP solving via the inference engine.
///
/// Currently a direct passthrough to `/api/v1/optimize/production`. The `solve` method
/// returns [`Error::Validation`] directing callers to the endpoint-level API; client-side
/// LP-to-rule compilation will land in a future release.
#[derive(Debug, Clone)]
pub struct OptimizeClient {
    http: HttpClient,
}

impl OptimizeClient {
    pub(crate) fn new(http: HttpClient) -> Self {
        Self { http }
    }

    /// Solve an LP problem via the backend `/optimize/production` endpoint.
    ///
    /// The backend accepts a JSON representation of the problem; serialization of
    /// [`LinearProgramDefinition`] uses the field layout documented in
    /// [`crate::types::optimize`]. Call [`OptimizeClient::solve_raw`] if you need to pass a
    /// custom JSON shape.
    pub async fn solve(
        &self,
        problem: &LinearProgramDefinition,
        _opts: &SolveOptions,
        options: Option<&RequestOptions>,
    ) -> Result<OptimizationResult, Error> {
        let raw: serde_json::Value = self
            .http
            .post("/optimize/production", problem, options)
            .await?;
        // The backend returns either {"status": "optimal", ...} or {"status": "infeasible", ...}.
        let status = raw.get("status").and_then(|v| v.as_str()).unwrap_or("");
        let solve_time_ms = raw
            .get("solve_time_ms")
            .and_then(|v| v.as_u64())
            .unwrap_or(0);
        match status {
            "optimal" => {
                let variables: BTreeMap<String, f64> = raw
                    .get("variables")
                    .and_then(|v| serde_json::from_value(v.clone()).ok())
                    .unwrap_or_default();
                let objective_value = raw
                    .get("objective_value")
                    .and_then(|v| v.as_f64())
                    .unwrap_or(0.0);
                Ok(OptimizationResult::Optimal {
                    variables,
                    objective_value,
                    solve_time_ms,
                })
            }
            "infeasible" => Ok(OptimizationResult::Infeasible { solve_time_ms }),
            other => Err(Error::validation_msg(format!(
                "unexpected optimization status: {other}"
            ))),
        }
    }

    /// Send a raw JSON payload to `/optimize/production` and return the raw response.
    ///
    /// Use this if the LP structure documented in [`crate::types::optimize`] does not match
    /// the current backend contract.
    pub async fn solve_raw(
        &self,
        payload: &serde_json::Value,
        options: Option<&RequestOptions>,
    ) -> Result<serde_json::Value, Error> {
        self.http
            .post("/optimize/production", payload, options)
            .await
    }
}