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}