Skip to content

Commit

Permalink
Add FCR, Efficiency, CLI (#32)
Browse files Browse the repository at this point in the history
* Add ADG calculator, tests, and CLI infrastructure

* Import ADG for doctest

* Fix ADG doctest

* Added FCR Calculations and tests

* Added feed efficiency rating
  • Loading branch information
JakenHerman authored Jan 25, 2025
1 parent cd30659 commit a9c618d
Show file tree
Hide file tree
Showing 13 changed files with 371 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "livestock-rs"
version = "0.10.0"
version = "0.12.0"
edition = "2021"
authors = ["Jaken Herman <jaken@rowanranch.com>"]
license = "MIT"
Expand Down
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -24,7 +24,7 @@ Add the crate to your `Cargo.toml`:

```
[dependencies]
livestock_rs = "0.10.0"
livestock_rs = "0.12.0"
```

or
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/bin/stocktools/adg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
81 changes: 81 additions & 0 deletions src/bin/stocktools/efficiency.rs
Original file line number Diff line number Diff line change
@@ -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<f64>,
#[arg(help = "Weight gain of livestock (in kg or lbs)", long, short = 'g')]
weight_gain: Option<f64>,
#[arg(help = "Feed efficiency ratio (FCR)", long)]
fcr: Option<f64>,
#[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(())
}
}
52 changes: 52 additions & 0 deletions src/bin/stocktools/fcr.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
10 changes: 10 additions & 0 deletions src/bin/stocktools/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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(),
}
}
118 changes: 118 additions & 0 deletions src/calculators/feed/efficiency.rs
Original file line number Diff line number Diff line change
@@ -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<FeedEfficiency> {
ensure!(fcr > 0.0, "Feed Conversion Ratio (FCR) must be greater than 0.");

// Feed efficiency values for different animals.
//
// Source: <https://www.farmbrite.com/post/feed-conversion-ratio-calculator>
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());
}
}
Loading

0 comments on commit a9c618d

Please sign in to comment.