use rust_decimal::Decimal;
use rust_decimal_macros::dec;
pub struct ValueEstimator {
min_margin: Decimal,
target_margin: Decimal,
}
impl ValueEstimator {
pub fn new() -> Self {
Self {
min_margin: dec!(0.1), target_margin: dec!(0.3), }
}
pub fn estimate(&self, _description: &str, estimated_cost: Decimal) -> Decimal {
let margin = estimated_cost * self.target_margin;
estimated_cost + margin
}
pub fn minimum_bid(&self, estimated_cost: Decimal) -> Decimal {
estimated_cost + (estimated_cost * self.min_margin)
}
pub fn ideal_bid(&self, estimated_cost: Decimal) -> Decimal {
estimated_cost + (estimated_cost * self.target_margin)
}
pub fn is_profitable(&self, price: Decimal, estimated_cost: Decimal) -> bool {
if price.is_zero() {
return estimated_cost < Decimal::ZERO;
}
let margin = (price - estimated_cost) / price;
margin >= self.min_margin
}
pub fn calculate_profit(&self, earnings: Decimal, actual_cost: Decimal) -> Decimal {
earnings - actual_cost
}
pub fn calculate_margin(&self, earnings: Decimal, actual_cost: Decimal) -> Decimal {
if earnings.is_zero() {
return Decimal::ZERO;
}
(earnings - actual_cost) / earnings
}
pub fn set_min_margin(&mut self, margin: Decimal) {
self.min_margin = margin;
}
pub fn set_target_margin(&mut self, margin: Decimal) {
self.target_margin = margin;
}
}
impl Default for ValueEstimator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_value_estimation() {
let estimator = ValueEstimator::new();
let cost = dec!(10.0);
let value = estimator.estimate("test job", cost);
assert!(value > cost);
}
#[test]
fn test_profitability() {
let estimator = ValueEstimator::new();
let cost = dec!(10.0);
assert!(estimator.is_profitable(dec!(15.0), cost));
assert!(!estimator.is_profitable(dec!(10.5), cost)); }
#[test]
fn test_margin_calculation() {
let estimator = ValueEstimator::new();
let margin = estimator.calculate_margin(dec!(100.0), dec!(70.0));
assert_eq!(margin, dec!(0.30)); }
#[test]
fn test_profitability_zero_price() {
let estimator = ValueEstimator::new();
assert!(!estimator.is_profitable(Decimal::ZERO, dec!(10.0)));
assert!(!estimator.is_profitable(Decimal::ZERO, Decimal::ZERO));
assert!(estimator.is_profitable(Decimal::ZERO, dec!(-10.0)));
}
#[test]
fn test_profitability_negative_cost() {
let estimator = ValueEstimator::new();
assert!(estimator.is_profitable(dec!(100.0), dec!(-50.0)));
assert!(estimator.is_profitable(dec!(1.0), dec!(-0.01)));
}
#[test]
fn test_profitability_cost_exceeds_price() {
let estimator = ValueEstimator::new();
assert!(!estimator.is_profitable(dec!(10.0), dec!(100.0)));
}
#[test]
fn test_margin_zero_earnings() {
let estimator = ValueEstimator::new();
assert_eq!(
estimator.calculate_margin(Decimal::ZERO, dec!(50.0)),
Decimal::ZERO
);
assert_eq!(
estimator.calculate_margin(Decimal::ZERO, Decimal::ZERO),
Decimal::ZERO
);
}
#[test]
fn test_estimate_zero_cost() {
let estimator = ValueEstimator::new();
let value = estimator.estimate("free task", Decimal::ZERO);
assert_eq!(value, Decimal::ZERO);
}
#[test]
fn test_minimum_vs_ideal_bid() {
let estimator = ValueEstimator::new();
let cost = dec!(100.0);
let min_bid = estimator.minimum_bid(cost);
let ideal_bid = estimator.ideal_bid(cost);
assert!(min_bid < ideal_bid);
assert!(min_bid > cost);
assert!(ideal_bid > cost);
}
#[test]
fn test_profit_calculation() {
let estimator = ValueEstimator::new();
assert_eq!(
estimator.calculate_profit(dec!(150.0), dec!(100.0)),
dec!(50.0)
);
assert_eq!(
estimator.calculate_profit(dec!(50.0), dec!(100.0)),
dec!(-50.0)
);
}
#[test]
fn is_profitable_with_very_large_values() {
let estimator = ValueEstimator::new();
let big = Decimal::new(i64::MAX, 0); let small = Decimal::new(1, 0);
assert!(estimator.is_profitable(big, small));
assert!(!estimator.is_profitable(small, big));
assert!(!estimator.is_profitable(big, big));
}
#[test]
fn estimate_value_with_very_large_cost() {
let estimator = ValueEstimator::new();
let big = Decimal::new(i64::MAX / 2, 0);
let value = estimator.estimate("big job", big);
assert!(value > big);
}
#[test]
fn is_profitable_with_negative_price() {
let estimator = ValueEstimator::new();
assert!(estimator.is_profitable(dec!(-10.0), dec!(5.0)));
assert!(!estimator.is_profitable(dec!(-10.0), dec!(-20.0)));
}
#[test]
fn calculate_margin_with_negative_earnings() {
let estimator = ValueEstimator::new();
let margin = estimator.calculate_margin(dec!(-100.0), dec!(50.0));
assert_eq!(margin, dec!(1.5));
}
#[test]
fn calculate_margin_with_both_negative() {
let estimator = ValueEstimator::new();
let margin = estimator.calculate_margin(dec!(-50.0), dec!(-100.0));
assert_eq!(margin, dec!(-1.0));
}
#[test]
fn minimum_bid_with_zero_cost() {
let estimator = ValueEstimator::new();
assert_eq!(estimator.minimum_bid(Decimal::ZERO), Decimal::ZERO);
assert_eq!(estimator.ideal_bid(Decimal::ZERO), Decimal::ZERO);
}
#[test]
fn minimum_bid_with_negative_cost() {
let estimator = ValueEstimator::new();
let min_bid = estimator.minimum_bid(dec!(-100.0));
let ideal_bid = estimator.ideal_bid(dec!(-100.0));
assert!(min_bid < Decimal::ZERO);
assert!(ideal_bid < Decimal::ZERO);
assert!(ideal_bid < min_bid);
}
#[test]
fn estimate_with_negative_cost() {
let estimator = ValueEstimator::new();
let value = estimator.estimate("refund task", dec!(-100.0));
assert_eq!(value, dec!(-130.0));
}
#[test]
fn custom_margins_affect_profitability() {
let mut estimator = ValueEstimator::new();
let price = dec!(110.0);
let cost = dec!(100.0);
assert!(!estimator.is_profitable(price, cost));
estimator.set_min_margin(dec!(0.05));
assert!(estimator.is_profitable(price, cost));
estimator.set_min_margin(dec!(0.50));
assert!(!estimator.is_profitable(price, cost));
}
#[test]
fn custom_target_margin_affects_bids() {
let mut estimator = ValueEstimator::new();
let cost = dec!(100.0);
let default_ideal = estimator.ideal_bid(cost);
assert_eq!(default_ideal, dec!(130.0));
estimator.set_target_margin(dec!(0.5));
let new_ideal = estimator.ideal_bid(cost);
assert_eq!(new_ideal, dec!(150.0)); }
#[test]
fn is_profitable_at_exact_margin_boundary() {
let estimator = ValueEstimator::new();
assert!(estimator.is_profitable(dec!(100.0), dec!(90.0)));
assert!(!estimator.is_profitable(dec!(100.0), dec!(90.01)));
}
#[test]
fn profit_with_zero_values() {
let estimator = ValueEstimator::new();
assert_eq!(
estimator.calculate_profit(Decimal::ZERO, Decimal::ZERO),
Decimal::ZERO
);
assert_eq!(
estimator.calculate_profit(Decimal::ZERO, dec!(100.0)),
dec!(-100.0)
);
assert_eq!(
estimator.calculate_profit(dec!(100.0), Decimal::ZERO),
dec!(100.0)
);
}
#[test]
fn default_impl_matches_new() {
let from_new = ValueEstimator::new();
let from_default = ValueEstimator::default();
let cost = dec!(100.0);
assert_eq!(
from_new.estimate("x", cost),
from_default.estimate("x", cost)
);
assert_eq!(from_new.minimum_bid(cost), from_default.minimum_bid(cost));
assert_eq!(from_new.ideal_bid(cost), from_default.ideal_bid(cost));
assert_eq!(
from_new.is_profitable(dec!(150.0), cost),
from_default.is_profitable(dec!(150.0), cost)
);
}
}