more functional handling of Quantity

This commit is contained in:
Sebastian Kuzminsky 2025-01-18 11:22:05 -07:00
parent 424cbaedf8
commit dd2f41e3cf
22 changed files with 131 additions and 99 deletions

View file

@ -1,5 +1,5 @@
[outputs] [outputs]
bearing_roller = {quantity=1} bearing_roller = {quantity={amount=1}}
# inputs list, similar to a cargo.toml file # inputs list, similar to a cargo.toml file
[inputs] [inputs]
@ -9,25 +9,25 @@ bearing_hub = {version=0.1, type="printed"}
# alternate representation of an element of the inputs list # alternate representation of an element of the inputs list
[inputs.m4_20_shcs] [inputs.m4_20_shcs]
type = "M4x20 SHCS" type = "M4x20 SHCS"
quantity = 3 quantity = { amount=3 }
comment = """These will hold the bearing rollers. Possibly Button Heads comment = """These will hold the bearing rollers. Possibly Button Heads
Cap Screws would be better? Probably either will work fine.""" Cap Screws would be better? Probably either will work fine."""
[inputs.m4_washer] [inputs.m4_washer]
type = "M4 washer" type = "M4 washer"
quantity = 6 quantity = { amount=6 }
dimensions = "12 mm OD, 4.4 mm ID, 1 mm thick" dimensions = "12 mm OD, 4.4 mm ID, 1 mm thick"
comment = """The upstream repo calls for 12 mm OD but i think that's comment = """The upstream repo calls for 12 mm OD but i think that's
too big, it hits the outer race of the bearings. Use 8mm OD instead.""" too big, it hits the outer race of the bearings. Use 8mm OD instead."""
[inputs.bearing_624] [inputs.bearing_624]
type = "624 bearing" type = "624 bearing"
quantity = 6 quantity = { amount=6 }
comments = "13mm OD, 4mm ID, 5mm wide" comments = "13mm OD, 4mm ID, 5mm wide"
[inputs.m4_nuts] [inputs.m4_nuts]
type = "M4 nuts" type = "M4 nuts"
quantity = 3 quantity = { amount=3 }
[dependencies] [dependencies]
tools = [ tools = [

View file

@ -1,24 +1,24 @@
[inputs] [inputs]
pump_housing_front_with_stepper = {quantity=1} pump_housing_front_with_stepper = {quantity={amount=1}}
bearing_roller = {quantity=1} bearing_roller = {quantity={amount=1}}
pump_housing_rear_with_stepper = {quantity=1} pump_housing_rear_with_stepper = {quantity={amount=1}}
tube_support = {quantity=1} tube_support = {quantity={amount=1}}
[inputs.silicone_tubing] [inputs.silicone_tubing]
type = "silicone tubing, 6mm OD, 4mm ID" type = "silicone tubing, 6mm OD, 4mm ID"
amount = "1 meter" quantity = { amount = 1, unit="Meter" }
comment = "PVC tube isn't flexible enough. Adjust quantity to suit your application." comment = "PVC tube isn't flexible enough. Adjust quantity to suit your application."
[inputs.m4_25_bhcs] [inputs.m4_25_bhcs]
name = "m4_25_bhcs" name = "m4_25_bhcs"
type = "M4x25 BHCS" type = "M4x25 BHCS"
quantity = 4 quantity = { amount = 4 }
comment = """BHCS is preferred over SHCS so the screw heads sit recessed comment = """BHCS is preferred over SHCS so the screw heads sit recessed
below the surface of the pump body.""" below the surface of the pump body."""
[inputs.m4_nuts] [inputs.m4_nuts]
type = "M4 nuts" type = "M4 nuts"
quantity = 4 quantity = { amount = 4 }
[dependencies] [dependencies]
tools = [ "3 mm hex driver" ] tools = [ "3 mm hex driver" ]

View file

@ -1,5 +1,5 @@
[outputs] [outputs]
bearing_hub={version=0.1, quantity=1, type="printed"} bearing_hub={version=0.1, quantity={amount=1}, type="printed"}
[inputs] [inputs]
filament={} filament={}

View file

@ -1,5 +1,5 @@
[outputs] [outputs]
bearing_hub_top={version=0.1, quantity=1, type="printed"} bearing_hub_top={version=0.1, quantity={amount=1}, type="printed"}
[inputs] [inputs]
filament={} filament={}

View file

@ -1,5 +1,5 @@
[outputs] [outputs]
pump_housing_front_untapped={version=0.1, quantity=1, type="printed"} pump_housing_front_untapped={version=0.1, quantity={amount=1}, type="printed"}
[inputs] [inputs]
filament={} filament={}

View file

@ -1,5 +1,5 @@
[outputs] [outputs]
pump_housing_rear_untapped={version=0.1, quantity=1, type="printed"} pump_housing_rear_untapped={version=0.1, quantity={amount=1}, type="printed"}
[inputs] [inputs]
filament={} filament={}

View file

@ -1,5 +1,5 @@
[outputs] [outputs]
tube_support={version=0.1, quantity=1, type="printed"} tube_support={version=0.1, quantity={amount=1}, type="printed"}
[inputs] [inputs]
filament={} filament={}

View file

@ -1,5 +1,5 @@
[outputs] [outputs]
pump_housing_front={quantity=1} pump_housing_front={quantity={amount=1}}
[inputs] [inputs]
pump_housing_front_untapped={version=0.1, type="printed"} pump_housing_front_untapped={version=0.1, type="printed"}

View file

@ -1,10 +1,10 @@
[outputs] [outputs]
pump_housing_front_with_stepper={quantity=1} pump_housing_front_with_stepper={quantity={amount=1}}
[inputs] [inputs]
pump_housing_front={version=0.1, quantity=1} pump_housing_front={version=0.1, quantity={amount=1}}
m4_5_shcs={quantity=2, description="M4x5 SHCS"} m4_5_shcs={quantity={amount=2}, description="M4x5 SHCS"}
stepper_28byj_48={quantity=1} stepper_28byj_48={quantity={amount=1}}
[dependencies] [dependencies]
tools = [ "3 mm hex driver" ] tools = [ "3 mm hex driver" ]

View file

@ -1,8 +1,8 @@
[outputs] [outputs]
pump_housing_rear = {quantity=1} pump_housing_rear = {quantity={amount=1}}
[inputs] [inputs]
pump_housing_rear_untapped = {quantity=1} pump_housing_rear_untapped = {quantity={amount=1}}
[dependencies] [dependencies]
tools = [ "M4 tap" ] tools = [ "M4 tap" ]

View file

@ -1,10 +1,10 @@
[outputs] [outputs]
pump_housing_rear_with_stepper = {quantity=1} pump_housing_rear_with_stepper = {quantity={amount=1}}
[inputs] [inputs]
pump_housing_rear = {version=0.1, quantity=1} pump_housing_rear = {version=0.1, quantity={amount=1}}
m4_5_shcs = {quantity=2} m4_5_shcs = {quantity={amount=2}}
stepper_28byj_48={quantity=1} stepper_28byj_48={quantity={amount=1}}
[dependencies] [dependencies]
tools = [ "3 mm hex driver" ] tools = [ "3 mm hex driver" ]

View file

@ -1,9 +1,9 @@
[outputs.bearing_624] [outputs.bearing_624]
quantity=10 quantity = { amount=10 }
comments = "13mm OD, 4mm ID, 5mm wide" comments = "13mm OD, 4mm ID, 5mm wide"
[inputs] [inputs]
capital={amount="8.99 usd"} capital={quantity={amount=8.99, unit="USDollar"}}
[dependencies] [dependencies]
operator = {skills=["vendor interaction"]} operator = {skills=["vendor interaction"]}

View file

@ -1,8 +1,8 @@
[outputs] [outputs]
filament = {amount="1 kg"} filament = { quantity = { amount = 1000, unit = "Gram" } }
[inputs] [inputs]
capital={amount="23.99 usd"} capital={ quantity = { amount=23.99, unit="USDollar" } }
[dependencies] [dependencies]
operator = {skills=["vendor interaction"]} operator = {skills=["vendor interaction"]}

View file

@ -1,8 +1,8 @@
[outputs] [outputs]
m4_20_shcs = {quantity=100} m4_20_shcs = {quantity={amount=100}}
[inputs] [inputs]
capital={amount="15.49 usd"} capital={quantity={amount=15.49, unit="USDollar"}}
[dependencies] [dependencies]
operator = {skills=["vendor interaction"]} operator = {skills=["vendor interaction"]}

View file

@ -1,8 +1,8 @@
[outputs] [outputs]
m4_25_bhcs = {quantity=100} m4_25_bhcs = {quantity={amount=100}}
[inputs] [inputs]
capital={amount="18.45 usd"} capital={quantity={amount=18.45, unit="USDollar"}}
[dependencies] [dependencies]
operator = {skills=["vendor interaction"]} operator = {skills=["vendor interaction"]}

View file

@ -1,8 +1,8 @@
[outputs] [outputs]
m4_20_shcs = {quantity=100} m4_20_shcs = {quantity={amount=100}}
[inputs] [inputs]
capital={amount="13.65 usd"} capital={quantity={amount=13.65, unit="USDollar"}}
[dependencies] [dependencies]
operator = {skills=["vendor interaction"]} operator = {skills=["vendor interaction"]}

View file

@ -1,8 +1,8 @@
[outputs] [outputs]
m4_nuts = {quantity=100} m4_nuts = {quantity={amount=100}}
[inputs] [inputs]
capital={amount="1.35 usd"} capital = { quantity = { amount = 1.35, unit = "USDollar" } }
[dependencies] [dependencies]
operator = {skills=["vendor interaction"]} operator = {skills=["vendor interaction"]}

View file

@ -1,9 +1,9 @@
[outputs.m4_washer] [outputs.m4_washer]
quantity=100 quantity = { amount=100 }
comments = "4.4 mm ID, 8.8 mm OD, 0.8 mm thick" comments = "4.4 mm ID, 8.8 mm OD, 0.8 mm thick"
[inputs] [inputs]
capital={amount="7.18 usd"} capital={quantity={amount=7.18, unit="USDollar"}}
[dependencies] [dependencies]
operator = {skills=["vendor interaction"]} operator = {skills=["vendor interaction"]}

View file

@ -1,8 +1,8 @@
[outputs] [outputs]
silicone_tubing = {amount="3 m"} silicone_tubing = {quantity={amount=3, unit="Meter"}}
[inputs] [inputs]
capital={amount="18.75 usd"} capital={quantity={amount=18.75, unit="USDollar"}}
[dependencies] [dependencies]
operator = {skills=["vendor interaction"]} operator = {skills=["vendor interaction"]}

View file

@ -1,8 +1,8 @@
[outputs] [outputs]
stepper_28byj_48 = {quantity=1} stepper_28byj_48 = {quantity={amount=1}}
[inputs] [inputs]
capital={amount="1.35 usd"} capital={quantity={amount=1.35, unit="USDollar"}}
[dependencies] [dependencies]
operator = {skills=["vendor interaction"]} operator = {skills=["vendor interaction"]}

View file

@ -4,34 +4,30 @@
// Amount(String), // Amount(String),
// } // }
#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize)]
pub enum Unit {
USDollar,
Meter,
Gram,
}
/// `Quantity` measures the amount of a resource (Input or Output).
#[derive(Clone, Copy, Debug, serde::Deserialize)]
pub struct Quantity {
pub amount: f32,
pub unit: Option<Unit>,
}
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
pub struct Input { pub struct Input {
/// Each input has either a `quantity` (for discrete things, like // Quantity defaults to "amount=1" if omitted.
/// apples or bearings) or an `amount` (for continuous things, pub quantity: Option<Quantity>,
/// like rope or flour).
///
/// FIXME: Is there a good way to handle the "units" of the
/// quantity/amount? Maybe the units could default to "count", but
/// be optionally overridden by the recipe to things like "meters"
/// (of tubing) or "grams" (of chromic acid). Then this struct
/// would be `quantity: usize, units: Option<Unit>`.
pub quantity: Option<usize>,
pub amount: Option<String>,
} }
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
pub struct Output { pub struct Output {
/// Each output has either a `quantity` (for discrete things, like // Quantity defaults to "amount=1" if omitted.
/// apples or bearings) or an `amount` (for continuous things, pub quantity: Option<Quantity>,
/// like rope or flour).
///
/// FIXME: Is there a good way to handle the "units" of the
/// quantity/amount? Maybe the units could default to "count", but
/// be optionally overridden by the recipe to things like "meters"
/// (of tubing) or "grams" (of chromic acid). Then this struct
/// would be `quantity: usize, units: Option<Unit>`.
pub quantity: Option<usize>,
pub amount: Option<String>,
} }
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
@ -77,6 +73,30 @@ pub struct Recipe {
pub outputs: Option<std::collections::HashMap<String, Output>>, pub outputs: Option<std::collections::HashMap<String, Output>>,
} }
impl Input {
pub fn get_quantity(&self) -> Quantity {
match &self.quantity {
None => Quantity {
amount: 1.0,
unit: None,
},
Some(q) => q.clone(),
}
}
}
impl Output {
pub fn get_quantity(&self) -> Quantity {
match &self.quantity {
None => Quantity {
amount: 1.0,
unit: None,
},
Some(q) => q.clone(),
}
}
}
impl Recipe { impl Recipe {
pub fn from_file(file: &std::path::PathBuf) -> anyhow::Result<Self> { pub fn from_file(file: &std::path::PathBuf) -> anyhow::Result<Self> {
let recipe_contents = std::fs::read_to_string(file)?; let recipe_contents = std::fs::read_to_string(file)?;
@ -84,7 +104,9 @@ impl Recipe {
// let r = recipe.validate_recipe(); // let r = recipe.validate_recipe();
Ok(recipe) Ok(recipe)
} }
}
impl Recipe {
fn validate_recipe(self: &Self) -> anyhow::Result<()> { fn validate_recipe(self: &Self) -> anyhow::Result<()> {
// if recipe.inputs.len() == 0 { // if recipe.inputs.len() == 0 {
// Err("recipe has no inputs!"); // Err("recipe has no inputs!");

View file

@ -85,51 +85,61 @@ impl Repo {
} }
fn compile_inner(self: &Self, recipe: &Recipe, indent: usize) -> anyhow::Result<()> { fn compile_inner(self: &Self, recipe: &Recipe, indent: usize) -> anyhow::Result<()> {
for (key, val) in recipe.inputs.iter() { for (input_name, input_info) in recipe.inputs.iter() {
for _ in 0..indent { for _ in 0..indent {
print!(" "); print!(" ");
} }
if key == "capital" { if input_name == "capital" {
// The build process begins in capitalism :-( // The build process begins in capitalism :-(
if let Some(amount) = &val.amount { let input_quantity = input_info.get_quantity();
// I'd like to divide the amount of capital by the
// quantity of the thing purchased.
//
// We can probably assume the `recipe.outputs` Option
// is Some, but inside the Some is a container with
// potentially any number of output things, which
// makes it hard to account.
//
// For now i'll just handle the common case specially.
if let Some(a) = amount.split_whitespace().next() { let input_unit = match &input_quantity.unit {
let cost = a.parse::<f32>().unwrap(); None => {
if let Some(outputs) = &recipe.outputs { return Err(anyhow::Error::msg(format!(
if outputs.len() == 1 { "expected quantity unit USDollar on capital input"
for (_k, v) in outputs.iter() { )))
if let Some(quantity) = &v.quantity { }
println!( Some(unit) if *unit != crate::recipe::Unit::USDollar => {
"capital: {cost}/{quantity} = {:.2} each :-(", return Err(anyhow::Error::msg(format!(
cost / *quantity as f32 "expected quantity unit USDollar on capital input"
); )))
} else if let Some(amount) = &v.amount { }
println!("capital: {cost}/{amount:?} :-(",); Some(unit) => unit,
} };
}
} let cost = input_quantity.amount;
let outputs = match &recipe.outputs {
None => return Err(anyhow::Error::msg(format!("no outputs!"))),
Some(outputs) => outputs,
};
if outputs.len() == 1 {
for (_output_name, output_info) in outputs.iter() {
let output_quantity = output_info.get_quantity();
let cost_each = cost / output_quantity.amount;
if let Some(output_unit) = output_quantity.unit {
println!(
"capital: {:.3} {:?} / {:?} :-(",
cost_each, input_unit, output_unit
);
} else {
println!("capital: {:.3} {:?} each :-(", cost_each, input_unit);
} }
} }
} else { } else {
panic!("no amount of capital?"); return Err(anyhow::Error::msg(format!("no output!")));
} }
continue; continue;
} }
println!("{key:?} {val:?}"); println!("{input_name:?} {input_info:?}");
match self.get_recipe(key) { match self.get_recipe(input_name) {
None => { None => {
return Err(anyhow::Error::msg(format!("recipe for {key} not found"))); return Err(anyhow::Error::msg(format!(
"recipe for {input_name} not found"
)));
} }
Some(input_recipe) => { Some(input_recipe) => {
self.compile_inner(input_recipe, indent + 4)?; self.compile_inner(input_recipe, indent + 4)?;