wb_cache/test/simulation/scriptwriter/entity/
product.rs

1use std::sync::Arc;
2
3use fieldx::fxstruct;
4use rand_distr::Distribution;
5use rand_distr::SkewNormal;
6
7use crate::test::simulation::db::entity::Product as DbProduct;
8use crate::test::simulation::scriptwriter::model::product::ProductModel;
9
10#[derive(Clone, Debug)]
11#[fxstruct(sync, no_new, builder, get(copy))]
12pub struct Product {
13    /// Unique product ID
14    id:                   i32,
15    /// Product name
16    #[fieldx(get(copy(off)))]
17    name:                 String,
18    /// Product price
19    price:                f64,
20    /// The expected daily customer interest for the product, expressed as a fraction of 1.0 where 1.0 means every
21    /// customer wants it. This metric quantifies the product's popularity. Note that the product's price also
22    /// influences final sales.
23    daily_quotient:       f64,
24    /// Expected return rate of the product, %/period.
25    expected_return_rate: f64,
26    /// Expected terms of shipment arrival from the supplier, in days. The final value for each shipment is sampled
27    /// using SkewedNormal distribution with the parameters derived from the following three values. stock_supplies_in
28    /// is the mean or location parameter.
29    stock_supplies_in:    f64,
30    /// Specifies the variability of the shipment terms. The value is a percentage of the stock_supplies_in value. The
31    /// higher the value, the more uncertain the shipment terms are; values above 20% are not accepted. This value
32    /// translates into the SkewNormal distribution scale parameter by the formula:
33    /// scale = supplier_inaccuracy * stock_supplies_in / 1.6448536
34    supplier_inaccuracy:  f64,
35    /// Specifies the tendency of a supplier to delay the shipment. Positive values indicate a tendency to delay and
36    /// measured as a percentage of the late shipments. Negative values indicate a tendency to deliver faster. Reasonable
37    /// range for the value is between -0.9 and 0.7. Whereas the former is simply optimistic, the latter is rather
38    /// the maximum reasonably acceptable in real life.
39    /// The value translates into SkewedNormal distribution shape parameter by the formula:
40    /// shape = tan(PI * (supplier_tardiness - 0.5))
41    supplier_tardiness:   f64,
42
43    #[fieldx(get(clone), serde(off))]
44    product_model: Arc<ProductModel>,
45
46    #[fieldx(lazy, private, get(copy(off)), builder(off), serde(off))]
47    supply_distribution: SkewNormal<f64>,
48
49    /// The expected number of items of this product sold to a single customer per day.  This value is used in the
50    /// sampling process to simulate how many items of this product a customer buys in a single order.
51    #[fieldx(lazy, get(copy), builder(off))]
52    daily_estimate: f64,
53
54    /// How likely the product is to be viewed by a customer when purchase decision is being made.
55    #[fieldx(lazy, get(copy))]
56    view_probability: f64,
57}
58
59impl Product {
60    fn build_supply_distribution(&self) -> SkewNormal<f64> {
61        self.product_model()
62            .supply_distribution(
63                self.stock_supplies_in(),
64                self.supplier_inaccuracy(),
65                self.supplier_tardiness(),
66            )
67            .unwrap()
68    }
69
70    fn build_daily_estimate(&self) -> f64 {
71        self.product_model().customer_interest(self.price()) * self.daily_quotient()
72    }
73
74    fn build_view_probability(&self) -> f64 {
75        // Here we try to simulate a psychological effect of the product price on the customer. The higher the price,
76        // the more likely the customer to view it; and vice versa. In the latter case they would rather buy something
77        // cheap by immediately sending a product directly to the cart, without entering its page. Contrary, they're
78        // less likely to buy a more expensive product but would still willing to look at it.
79        // The formula simulates the effect by squeezing the customer interest curve towards its center by 30%.
80        ((self.product_model().customer_interest(self.price()) - 0.5) * 0.85 + 0.5) * self.daily_quotient()
81    }
82
83    pub fn supplies_in(&self) -> f64 {
84        self.supply_distribution().sample(&mut rand::rng())
85    }
86}
87
88impl From<Product> for DbProduct {
89    fn from(product: Product) -> Self {
90        DbProduct {
91            id:    product.id,
92            name:  product.name,
93            price: product.price,
94            views: 0,
95        }
96    }
97}