pcm_engine/
eligibility.rs1use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct EligibilityRule {
9 pub id: Uuid,
10 pub product_offering_id: Uuid,
11 pub conditions: Vec<EligibilityCondition>,
12 pub rule_type: EligibilityRuleType,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
18pub enum EligibilityRuleType {
19 All,
21 Any,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct EligibilityCondition {
28 pub field: String,
29 pub operator: EligibilityConditionOperator,
30 pub value: String,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
35#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
36pub enum EligibilityConditionOperator {
37 Equals,
38 NotEquals,
39 GreaterThan,
40 LessThan,
41 Contains,
42 NotContains,
43 In,
44 NotIn,
45}
46
47#[derive(Debug, Clone)]
49pub struct EligibilityContext {
50 pub customer_id: Option<Uuid>,
51 pub customer_segment: Option<String>,
52 pub existing_products: Vec<Uuid>,
53 pub customer_attributes: std::collections::HashMap<String, String>,
54}
55
56pub fn is_eligible(rule: &EligibilityRule, context: &EligibilityContext) -> bool {
58 match rule.rule_type {
59 EligibilityRuleType::All => rule
60 .conditions
61 .iter()
62 .all(|condition| evaluate_condition(condition, context)),
63 EligibilityRuleType::Any => rule
64 .conditions
65 .iter()
66 .any(|condition| evaluate_condition(condition, context)),
67 }
68}
69
70fn evaluate_condition(condition: &EligibilityCondition, context: &EligibilityContext) -> bool {
71 let field_value = get_field_value(&condition.field, context);
72
73 match condition.operator {
74 EligibilityConditionOperator::Equals => field_value == condition.value,
75 EligibilityConditionOperator::NotEquals => field_value != condition.value,
76 EligibilityConditionOperator::GreaterThan => {
77 if let (Ok(field_num), Ok(cond_num)) =
78 (field_value.parse::<f64>(), condition.value.parse::<f64>())
79 {
80 field_num > cond_num
81 } else {
82 false
83 }
84 }
85 EligibilityConditionOperator::LessThan => {
86 if let (Ok(field_num), Ok(cond_num)) =
87 (field_value.parse::<f64>(), condition.value.parse::<f64>())
88 {
89 field_num < cond_num
90 } else {
91 false
92 }
93 }
94 EligibilityConditionOperator::Contains => field_value.contains(&condition.value),
95 EligibilityConditionOperator::NotContains => !field_value.contains(&condition.value),
96 EligibilityConditionOperator::In => {
97 condition.value.split(',').any(|v| v.trim() == field_value)
98 }
99 EligibilityConditionOperator::NotIn => {
100 !condition.value.split(',').any(|v| v.trim() == field_value)
101 }
102 }
103}
104
105fn get_field_value(field: &str, context: &EligibilityContext) -> String {
106 match field {
107 "customer_segment" => context.customer_segment.clone().unwrap_or_default(),
108 "has_product" => {
109 "false".to_string()
112 }
113 _ => context
114 .customer_attributes
115 .get(field)
116 .cloned()
117 .unwrap_or_default(),
118 }
119}