diff --git a/Cargo.lock b/Cargo.lock index 7882ae0..ac4dc13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,7 +147,7 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "livestock-rs" -version = "0.10.0" +version = "0.12.0" dependencies = [ "Inflector", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 99c95b2..59b4c20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livestock-rs" -version = "0.10.0" +version = "0.12.0" edition = "2021" authors = ["Jaken Herman "] license = "MIT" diff --git a/README.md b/README.md index 6aaad16..18b20fe 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A comprehensive library and CLI tool for managing, identifying, and working with Features - 🌱 Support for over 1,000+ livestock breeds, categorized by species. - - 📊 Provides calculators for growth metrics like Average Daily Gain (ADG). + - 📊 Provides calculators for growth metrics like Average Daily Gain (ADG) and Feed Conversion Ration (FCR). - 🐄 Includes common cattle breeds like Angus and Brahman. - 🐐 Includes common goat breeds like Alpine and Boer. - 🐑 Includes common sheep breeds like Dorper and St. Croix. @@ -24,7 +24,7 @@ Add the crate to your `Cargo.toml`: ``` [dependencies] -livestock_rs = "0.10.0" +livestock_rs = "0.12.0" ``` or @@ -39,6 +39,31 @@ use livestock_rs::calculators::growth::adg::calculate_adg; let adg = calculate_adg(100.0, 150.0, 50); ``` +For CLI, use +``` +stocktools adg -i 100 -f 150 -d 50 +``` + +## FCR & Feed Efficiency Rating Usage Example +``` rust +use livestock_rs::calculators::feed::fcr::calculate_fcr; +use livestock_rs::calculators::feed::efficiency::{calculate_feed_efficiency, FeedEfficiency, FeedEfficiencyRating}; +use livestock_rs::types::LivestockType; + +let fcr = calculate_fcr(100.0, 30.0); // FCR = 3.33 +let feed_efficiency = calculate_feed_efficiency(fcr, LivestockType::Swine)?; // Swine should be from 3.0 - 3.9 + +// feed_efficiency.rating = FeedEfficiencyRating::Average +// feed_efficiency.fcr = 3.33 +// feed_efficiency.min_fcr = 3.0 +// feed_efficiency.max_fcr = 3.9 +``` + +For CLI, use +``` +stocktools fcr -i 100 -g 300 +``` + ## Breed Usage Example ``` rust use livestock_rs::breeds::GoatBreed; diff --git a/src/bin/stocktools/adg.rs b/src/bin/stocktools/adg.rs index a60b613..9f08dd8 100644 --- a/src/bin/stocktools/adg.rs +++ b/src/bin/stocktools/adg.rs @@ -29,7 +29,7 @@ use livestock_rs::calculators::growth::adg::calculate_adg; Calculate the ADG for an animal that starts at 100 kg and ends at 150 kg over 50 days: ``` - livestock adg -i 100 -f 150 -d 50 + stocktools adg -i 100 -f 150 -d 50 ``` The result will be `1.0`, which means the animal gained 1 kg per day. diff --git a/src/bin/stocktools/efficiency.rs b/src/bin/stocktools/efficiency.rs new file mode 100644 index 0000000..13eafa5 --- /dev/null +++ b/src/bin/stocktools/efficiency.rs @@ -0,0 +1,81 @@ +use anyhow::{anyhow, Context, Result}; +use clap::Parser; +use livestock_rs::{calculators::feed::{efficiency::calculate_feed_efficiency, fcr::calculate_fcr}, types::LivestockType}; + +#[derive(Parser, Debug)] +#[command( + arg_required_else_help(true), + about = "Calculate Feed Efficiency for livestock.", + long_about = " + Calculate Feed Efficiency for livestock. + + The Feed Efficiency is a measure of how well livestock convert feed into body mass. It is + calculated using the Feed Conversion Ratio (FCR) of the animal. + + The formula is: + + Feed Efficiency = 1 / FCR + + where: + + - `Feed Efficiency` is the efficiency of feed conversion. + - `FCR` is the Feed Conversion Ratio of the animal. + + # Example + + Calculate the Feed Efficiency for an animal with a FCR of 2.0: + + ``` + stocktools feed-efficiency --fcr 2.0 --livestock-type Cattle + ``` + + The result will be `0.5`, which means the animal converted feed with an efficiency of 0.5. This is very good for cattle. + " +)] +pub struct FeedEfficiencySubcommand { + #[arg(help = "Amount of feed intake (in kg or lbs)", long, short = 'i')] + feed_intake: Option, + #[arg(help = "Weight gain of livestock (in kg or lbs)", long, short = 'g')] + weight_gain: Option, + #[arg(help = "Feed efficiency ratio (FCR)", long)] + fcr: Option, + #[arg( + long, + help = "The type of livestock.", + short = 't', + )] + livestock_type: LivestockType, +} + +impl FeedEfficiencySubcommand { + pub fn run(&self) -> Result<()> { + // ensure that we either have feed intake and weight gain or FCR + let fcr = match (self.feed_intake, self.weight_gain, self.fcr) { + (Some(feed_intake), Some(weight_gain), None) => { + calculate_fcr(feed_intake, weight_gain).context( + "Failed to calculate FCR, which is required if feed intake and weight gain are provided. + FCR is needed to calculate feed efficiency.", + )? + } + (None, None, Some(fcr)) => fcr, + _ => { + return Err(anyhow!( + "Either feed intake and weight gain or FCR must be provided." + )) + } + }; + + let feed_efficiency = calculate_feed_efficiency(fcr, self.livestock_type.clone()).with_context(|| format!( + "Failed to calculate feed efficiency with FCR: {} and livestock type: {:?}.", + fcr, self.livestock_type + ))?; + + println!(" "); + println!("Feed Efficiency Rating: {:?}", feed_efficiency.rating); + println!("FCR: {:.2}", feed_efficiency.value); + println!("{:?} should aim for a FCR between {:.2} and {:.2}.", self.livestock_type, feed_efficiency.avg_min_fcr, feed_efficiency.avg_max_fcr); + println!(" "); + + Ok(()) + } +} diff --git a/src/bin/stocktools/fcr.rs b/src/bin/stocktools/fcr.rs new file mode 100644 index 0000000..ebab037 --- /dev/null +++ b/src/bin/stocktools/fcr.rs @@ -0,0 +1,52 @@ +use anyhow::{Context, Result}; +use clap::Parser; +use livestock_rs::calculators::feed::fcr::calculate_fcr; + +#[derive(Parser, Debug)] +#[command( + arg_required_else_help(true), + about = "Calculate Feed Conversion Ratio (FCR) for livestock.", + long_about = " + Calculate Feed Conversion Ratio (FCR) for livestock. + + The Feed Conversion Ratio (FCR) is a measure of feed efficiency that indicates the amount of + feed required to produce a unit of weight gain in livestock. It is calculated by dividing the + amount of feed consumed by the weight gain of the animal. + + The formula is: + + FCR = feed_intake / weight_gain + + where: + + - `FCR` is the feed conversion ratio as a ratio of feed intake to weight gain. + - `feed_intake` is the amount of feed consumed by the animal (in kg or lbs). + - `weight_gain` is the weight gain of the animal (in kg or lbs). + + # Example + + Calculate the FCR for an animal that consumes 100 kg of feed and gains 150 kg: + + ``` + livestock fcr -i 100 -g 150 + ``` + + The result will be `0.67`, which means the animal required 0.67 kg of feed per kg of weight gain. + " +)] +pub struct FcrSubcommand { + #[arg(help = "Amount of feed intake (in kg or lbs)", long, short = 'i')] + feed_intake: f64, + #[arg(help = "Weight gain of livestock (in kg or lbs)", long, short = 'g')] + weight_gain: f64, +} + +impl FcrSubcommand { + pub fn run(&self) -> Result<()> { + let fcr = calculate_fcr(self.feed_intake, self.weight_gain) + .context("Failed to calculate FCR.")?; + + println!("Feed Conversion Ratio (FCR): {:.2}", fcr); + Ok(()) + } +} diff --git a/src/bin/stocktools/main.rs b/src/bin/stocktools/main.rs index ad945ea..4bc8881 100644 --- a/src/bin/stocktools/main.rs +++ b/src/bin/stocktools/main.rs @@ -4,9 +4,17 @@ use clap::{Parser, Subcommand}; mod adg; use adg::AdgSubcommand; +mod efficiency; +use efficiency::FeedEfficiencySubcommand; + +mod fcr; +use fcr::FcrSubcommand; + #[derive(Subcommand, Debug)] enum Commands { Adg(AdgSubcommand), + Fcr(FcrSubcommand), + FeedEfficiency(FeedEfficiencySubcommand), } #[derive(Parser)] @@ -28,5 +36,7 @@ fn main() -> Result<()> { match cli.command { Commands::Adg(subcommand) => subcommand.run(), + Commands::Fcr(subcommand) => subcommand.run(), + Commands::FeedEfficiency(subcommand) => subcommand.run(), } } diff --git a/src/calculators/feed/efficiency.rs b/src/calculators/feed/efficiency.rs new file mode 100644 index 0000000..54caf2e --- /dev/null +++ b/src/calculators/feed/efficiency.rs @@ -0,0 +1,118 @@ +use anyhow::{ensure, Result}; +use crate::types::LivestockType; + +#[derive(Debug, Eq, PartialEq)] +pub enum FeedEfficiencyRating { + Poor, + Average, + Good, +} + +#[derive(Debug, PartialEq)] +pub struct FeedEfficiency { + pub rating: FeedEfficiencyRating, + pub value: f64, + pub avg_min_fcr: f64, + pub avg_max_fcr: f64 +} + +/// Calculate Feed Efficiency for livestock. +/// +/// # Arguments +/// - `fcr`: Feed Conversion Ratio (FCR) of the animal. +/// - `livestock_type`: The type of livestock. +/// +/// # Returns +/// The feed efficiency of the animal. +/// +/// # Example +/// ``` +/// use livestock_rs::calculators::feed::efficiency::{calculate_feed_efficiency, FeedEfficiency, FeedEfficiencyRating}; +/// use livestock_rs::types::LivestockType; +/// +/// let fcr = 10.0; +/// let livestock_type = LivestockType::Cattle; +/// +/// let feed_efficiency = calculate_feed_efficiency(fcr, livestock_type).unwrap(); +/// +/// // The feed efficiency is average for a FCR of 10.0 for cattle. +/// assert_eq!(feed_efficiency.rating, FeedEfficiencyRating::Average); +/// +/// ``` +/// +pub fn calculate_feed_efficiency(fcr: f64, livestock_type: LivestockType) -> Result { + ensure!(fcr > 0.0, "Feed Conversion Ratio (FCR) must be greater than 0."); + + // Feed efficiency values for different animals. + // + // Source: + let (min_fcr, max_fcr) = match livestock_type { + LivestockType::Cattle => (8.0, 12.0), + LivestockType::Goat | LivestockType::Sheep => (4.5, 5.5), + LivestockType::Swine => (3.0, 3.9), + LivestockType::Chicken => (1.5, 2.0), + LivestockType::Rabbit => (3.5, 5.0), + }; + + let feed_efficiency = if fcr > max_fcr { + FeedEfficiency { + rating: FeedEfficiencyRating::Poor, + value: fcr, + avg_min_fcr: min_fcr, + avg_max_fcr: max_fcr + } + } else if fcr < min_fcr { + FeedEfficiency { + rating: FeedEfficiencyRating::Good, + value: fcr, + avg_min_fcr: min_fcr, + avg_max_fcr: max_fcr + } + } else { + FeedEfficiency { + rating: FeedEfficiencyRating::Average, + value: fcr, + avg_min_fcr: min_fcr, + avg_max_fcr: max_fcr + } + }; + + Ok(feed_efficiency) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::LivestockType; + + #[test] + fn test_calculate_feed_efficiency() { + let feed_efficiency_test_cases = [ + (10.0, LivestockType::Cattle, FeedEfficiencyRating::Average), + (4.0, LivestockType::Cattle, FeedEfficiencyRating::Good), + (2.0, LivestockType::Cattle, FeedEfficiencyRating::Good), + (15.0, LivestockType::Cattle, FeedEfficiencyRating::Poor), + (5.0, LivestockType::Goat, FeedEfficiencyRating::Average), + (4.5, LivestockType::Goat, FeedEfficiencyRating::Average), + (5.5, LivestockType::Goat, FeedEfficiencyRating::Average), + (3.0, LivestockType::Swine, FeedEfficiencyRating::Average), + (3.9, LivestockType::Swine, FeedEfficiencyRating::Average), + (1.5, LivestockType::Chicken, FeedEfficiencyRating::Average), + (2.0, LivestockType::Chicken, FeedEfficiencyRating::Average), + (3.5, LivestockType::Rabbit, FeedEfficiencyRating::Average), + (5.0, LivestockType::Rabbit, FeedEfficiencyRating::Average), + ]; + + for (fcr, livestock_type, expected) in feed_efficiency_test_cases.iter() { + let result = calculate_feed_efficiency(*fcr, livestock_type.clone()); + assert!(result.is_ok()); + assert_eq!(result.unwrap().rating, *expected); + } + } + + #[test] + fn test_calculate_feed_efficiency_zero_fcr() { + let result = calculate_feed_efficiency(0.0, LivestockType::Cattle); + assert!(result.is_err()); + } +} \ No newline at end of file diff --git a/src/calculators/feed/fcr.rs b/src/calculators/feed/fcr.rs new file mode 100644 index 0000000..be5d846 --- /dev/null +++ b/src/calculators/feed/fcr.rs @@ -0,0 +1,60 @@ +use anyhow::{ensure, Result}; + +/// Calculate Feed Conversion Ratio (FCR) +/// +/// # Arguments +/// - `feed_intake`: Amount of feed consumed by the animal (in lb or kg). +/// - `weight_gain`: Weight gain of the animal (in lb or kg). +/// +/// # Returns +/// The feed conversion ratio (FCR) as a ratio of feed intake to weight gain. +/// +/// # Example +/// ``` +/// use livestock_rs::calculators::feed::fcr::calculate_fcr; +/// let fcr = calculate_fcr(100.0, 300.0).unwrap(); +/// assert_eq!(fcr, 0.3333333333333333); // 0.33 of feed per kg of weight gain where is lb or kg. +/// ``` +/// +/// # Notes +/// - A lower FCR indicates better feed efficiency. +/// - FCR is calculated as `feed_intake / weight_gain`. +pub fn calculate_fcr(feed_intake: f64, weight_gain: f64) -> Result { + ensure!(feed_intake > 0.0, "Feed intake must be greater than 0."); + ensure!(weight_gain > 0.0, "Weight gain must be greater than 0."); + + Ok(feed_intake / weight_gain) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_calculate_fcr() { + let fcr_test_cases = [ + (100.0, 300.0, 0.3333333333333333), + (100.0, 200.0, 0.5), + (200.0, 100.0, 2.0), + (150.0, 100.0, 1.5), + ]; + + for (feed_intake, weight_gain, expected) in fcr_test_cases.iter() { + let result = calculate_fcr(*feed_intake, *weight_gain); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), *expected); + } + } + + #[test] + fn test_calculate_fcr_zero_feed_intake() { + let result = calculate_fcr(0.0, 150.0); + assert!(result.is_err()); + } + + #[test] + fn test_calculate_fcr_zero_weight_gain() { + let result = calculate_fcr(100.0, 0.0); + assert!(result.is_err()); + } +} \ No newline at end of file diff --git a/src/calculators/feed/mod.rs b/src/calculators/feed/mod.rs new file mode 100644 index 0000000..2749ada --- /dev/null +++ b/src/calculators/feed/mod.rs @@ -0,0 +1,2 @@ +pub mod efficiency; +pub mod fcr; \ No newline at end of file diff --git a/src/calculators/mod.rs b/src/calculators/mod.rs index 51637b1..05e3141 100644 --- a/src/calculators/mod.rs +++ b/src/calculators/mod.rs @@ -1 +1,2 @@ +pub mod feed; pub mod growth; diff --git a/src/lib.rs b/src/lib.rs index f05e826..3ded7f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod breeds; pub mod calculators; +pub mod types; diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..c618524 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,16 @@ +use clap::ValueEnum; +use serde::Serialize; + +/// The type of livestock +/// +/// This enum represents the different types of livestock that can be used in the livestock management system. +/// This list will grow, and more types will be added in the future, as needed. +#[derive(Clone, Serialize, Debug, Eq, PartialEq, ValueEnum)] +pub enum LivestockType { + Cattle, + Swine, + Chicken, + Rabbit, + Sheep, + Goat +} \ No newline at end of file