Functional Weave
Code in Rust

logistics.container-fit@1.0.0

impl/rust.rs

4,879 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::logistics_container_fit_data::CONTAINERS;  ← this capability’s own data, compiled from data/containers.json into the same file by fune build

/// How many identical cartons fit in a container or trailer: the best block
/// stow over the allowed orientations that pass the door, capped by payload.
///
/// # Panics
/// Panics on a dimension or weight below 1, an unknown container, a payload
/// limit above the container's rating, or a carton no orientation fits.
pub fn container_fit(
    container: &str,
    length_mm: i64,
    width_mm: i64,
    height_mm: i64,
    carton_grams: i64,
    keep_upright: bool,
    max_payload_kg: Option<i64>,
) -> ContainerFit {
    for d in [length_mm, width_mm, height_mm] {
        if d < 1 {
            panic!("carton dimensions must be 1 mm or more, received {}", d);
        }
    }
    if carton_grams < 1 {
        panic!("cartonGrams must be 1 or more, received {}", carton_grams);
    }
    let spec = match CONTAINERS.iter().find(|c| c.code == container) {
        Some(s) => s,
        None => {
            let codes: Vec<&str> = CONTAINERS.iter().map(|c| c.code).collect();
            panic!("unknown container \"{}\": expected {}", container, codes.join(", "));
        }
    };
    let mut payload_kg = spec.max_payload_kg;
    if let Some(limit) = max_payload_kg {
        if limit < 1 {
            panic!("maxPayloadKg must be 1 or more, received {}", limit);
        }
        if limit > spec.max_payload_kg {
            panic!(
                "maxPayloadKg must not exceed {} kg for {}, received {}",
                spec.max_payload_kg, container, limit
            );
        }
        payload_kg = limit;
    }

    let (l, w, h) = (length_mm, width_mm, height_mm);
    // Upright first (as given, then turned on the floor), then the four tipped ones.
    let orientations: Vec<(i64, i64, i64)> = if keep_upright {
        vec![(l, w, h), (w, l, h)]
    } else {
        vec![(l, w, h), (w, l, h), (l, h, w), (h, l, w), (w, h, l), (h, w, l)]
    };

    let mut best: Option<(i64, i64, i64, (i64, i64, i64))> = None;
    let mut best_count = 0;
    for o in orientations {
        let (a, b, c) = o;
        if a > spec.length_mm || b > spec.width_mm || c > spec.height_mm {
            continue;
        }
        // End-loaded containers: the carton's cross-section has to pass the door.
        if let Some(door) = spec.door_width_mm {
            if b > door {
                continue;
            }
        }
        if let Some(door) = spec.door_height_mm {
            if c > door {
                continue;
            }
        }
        let along = spec.length_mm / a;
        let across = spec.width_mm / b;
        let layers = spec.height_mm / c;
        let count = along * across * layers;
        if best.is_none() || count > best_count {
            best = Some((along, across, layers, o));
            best_count = count;
        }
    }
    let (along, across, layers, placed) = match best {
        Some(b) => b,
        None => panic!("carton does not fit in {} in any allowed orientation", container),
    };

    let weight_limit = payload_kg * 1000 / carton_grams;
    let cartons = best_count.min(weight_limit);
    let space = spec.length_mm * spec.width_mm * spec.height_mm;
    ContainerFit {
        container: container.to_string(),
        cartons,
        space_limit: best_count,
        weight_limit,
        limited_by: if weight_limit < best_count { "weight" } else { "space" }.to_string(),
        along_length: along,
        across_width: across,
        layers,
        placed_length_mm: placed.0,
        placed_width_mm: placed.1,
        placed_height_mm: placed.2,
        cargo_grams: cartons * carton_grams,
        volume_used_basis_points: cartons * l * w * h * 10000 / space,
    }
}

pub fn container_fit_to_value(f: &ContainerFit) -> Value {
    Value::obj(vec![
        ("container", Value::str(&f.container)),
        ("cartons", Value::Int(f.cartons)),
        ("spaceLimit", Value::Int(f.space_limit)),
        ("weightLimit", Value::Int(f.weight_limit)),
        ("limitedBy", Value::str(&f.limited_by)),
        ("alongLength", Value::Int(f.along_length)),
        ("acrossWidth", Value::Int(f.across_width)),
        ("layers", Value::Int(f.layers)),
        ("placedLengthMm", Value::Int(f.placed_length_mm)),
        ("placedWidthMm", Value::Int(f.placed_width_mm)),
        ("placedHeightMm", Value::Int(f.placed_height_mm)),
        ("cargoGrams", Value::Int(f.cargo_grams)),
        ("volumeUsedBasisPoints", Value::Int(f.volume_used_basis_points)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let limit = if args[6].is_null() { None } else { Some(args[6].as_i64()) };
    container_fit_to_value(&container_fit(
        args[0].as_str(),
        args[1].as_i64(),
        args[2].as_i64(),
        args[3].as_i64(),
        args[4].as_i64(),
        args[5].as_bool(),
        limit,
    ))
}