mockforge_data/
persona_lifecycle_response.rs

1//! Lifecycle state response modifiers
2//!
3//! This module provides utilities for modifying response data based on persona lifecycle states.
4//! It ensures that endpoints like billing and support reflect the current lifecycle state
5//! of the persona.
6
7use crate::persona_lifecycle::{LifecycleState, PersonaLifecycle};
8use serde_json::Value;
9
10/// Apply lifecycle state effects to a response for billing endpoints
11///
12/// Modifies billing-related fields based on the persona's lifecycle state.
13/// For example, ChurnRisk personas might have payment issues, while
14/// PaymentFailed personas have failed payment attempts.
15pub fn apply_billing_lifecycle_effects(response: &mut Value, lifecycle: &PersonaLifecycle) {
16    if let Some(obj) = response.as_object_mut() {
17        match lifecycle.current_state {
18            LifecycleState::NewSignup => {
19                // New signups have no billing history
20                obj.insert("payment_method".to_string(), Value::String("none".to_string()));
21                obj.insert("billing_status".to_string(), Value::String("pending".to_string()));
22                obj.insert("subscription_status".to_string(), Value::String("trial".to_string()));
23            }
24            LifecycleState::Active => {
25                // Active users have normal billing
26                obj.insert("billing_status".to_string(), Value::String("active".to_string()));
27                obj.insert("subscription_status".to_string(), Value::String("active".to_string()));
28                obj.insert("payment_method".to_string(), Value::String("credit_card".to_string()));
29            }
30            LifecycleState::PowerUser => {
31                // Power users have premium billing
32                obj.insert("billing_status".to_string(), Value::String("active".to_string()));
33                obj.insert("subscription_status".to_string(), Value::String("premium".to_string()));
34                obj.insert("payment_method".to_string(), Value::String("credit_card".to_string()));
35            }
36            LifecycleState::ChurnRisk => {
37                // Churn risk users may have payment issues
38                obj.insert("billing_status".to_string(), Value::String("warning".to_string()));
39                obj.insert("subscription_status".to_string(), Value::String("at_risk".to_string()));
40                obj.insert("payment_method".to_string(), Value::String("credit_card".to_string()));
41                obj.insert("last_payment_failed".to_string(), Value::Bool(true));
42            }
43            LifecycleState::Churned => {
44                // Churned users have cancelled billing
45                obj.insert("billing_status".to_string(), Value::String("cancelled".to_string()));
46                obj.insert(
47                    "subscription_status".to_string(),
48                    Value::String("cancelled".to_string()),
49                );
50                obj.insert("payment_method".to_string(), Value::String("none".to_string()));
51            }
52            LifecycleState::UpgradePending => {
53                // Upgrade pending users have pending billing changes
54                obj.insert(
55                    "billing_status".to_string(),
56                    Value::String("pending_upgrade".to_string()),
57                );
58                obj.insert(
59                    "subscription_status".to_string(),
60                    Value::String("upgrading".to_string()),
61                );
62            }
63            LifecycleState::PaymentFailed => {
64                // Payment failed users have failed payment attempts
65                obj.insert("billing_status".to_string(), Value::String("failed".to_string()));
66                obj.insert(
67                    "subscription_status".to_string(),
68                    Value::String("suspended".to_string()),
69                );
70                obj.insert("payment_method".to_string(), Value::String("credit_card".to_string()));
71                obj.insert("last_payment_failed".to_string(), Value::Bool(true));
72                obj.insert(
73                    "failed_payment_count".to_string(),
74                    Value::Number(
75                        lifecycle
76                            .get_metadata("payment_failed_count")
77                            .and_then(|v| v.as_u64())
78                            .unwrap_or(1)
79                            .into(),
80                    ),
81                );
82            }
83        }
84    }
85}
86
87/// Apply lifecycle state effects to a response for support endpoints
88///
89/// Modifies support-related fields based on the persona's lifecycle state.
90/// For example, ChurnRisk personas might have support tickets, while
91/// PaymentFailed personas have billing-related support issues.
92pub fn apply_support_lifecycle_effects(response: &mut Value, lifecycle: &PersonaLifecycle) {
93    if let Some(obj) = response.as_object_mut() {
94        match lifecycle.current_state {
95            LifecycleState::NewSignup => {
96                // New signups have onboarding support
97                obj.insert("support_tier".to_string(), Value::String("onboarding".to_string()));
98                obj.insert("open_tickets".to_string(), Value::Number(0.into()));
99                obj.insert("priority".to_string(), Value::String("normal".to_string()));
100            }
101            LifecycleState::Active => {
102                // Active users have normal support
103                obj.insert("support_tier".to_string(), Value::String("standard".to_string()));
104                obj.insert("open_tickets".to_string(), Value::Number(0.into()));
105                obj.insert("priority".to_string(), Value::String("normal".to_string()));
106            }
107            LifecycleState::PowerUser => {
108                // Power users have premium support
109                obj.insert("support_tier".to_string(), Value::String("premium".to_string()));
110                obj.insert("open_tickets".to_string(), Value::Number(0.into()));
111                obj.insert("priority".to_string(), Value::String("high".to_string()));
112            }
113            LifecycleState::ChurnRisk => {
114                // Churn risk users have retention support
115                obj.insert("support_tier".to_string(), Value::String("retention".to_string()));
116                obj.insert("open_tickets".to_string(), Value::Number(1.into()));
117                obj.insert("priority".to_string(), Value::String("high".to_string()));
118                obj.insert("last_contact_days_ago".to_string(), Value::Number(30.into()));
119            }
120            LifecycleState::Churned => {
121                // Churned users have no active support
122                obj.insert("support_tier".to_string(), Value::String("none".to_string()));
123                obj.insert("open_tickets".to_string(), Value::Number(0.into()));
124                obj.insert("priority".to_string(), Value::String("low".to_string()));
125            }
126            LifecycleState::UpgradePending => {
127                // Upgrade pending users have sales support
128                obj.insert("support_tier".to_string(), Value::String("sales".to_string()));
129                obj.insert("open_tickets".to_string(), Value::Number(1.into()));
130                obj.insert("priority".to_string(), Value::String("high".to_string()));
131            }
132            LifecycleState::PaymentFailed => {
133                // Payment failed users have billing support
134                obj.insert("support_tier".to_string(), Value::String("billing".to_string()));
135                obj.insert("open_tickets".to_string(), Value::Number(1.into()));
136                obj.insert("priority".to_string(), Value::String("urgent".to_string()));
137                obj.insert("ticket_type".to_string(), Value::String("payment_issue".to_string()));
138            }
139        }
140    }
141}
142
143/// Apply lifecycle state effects for user engagement endpoints
144///
145/// Modifies user profile, activity, and notification fields based on the persona's lifecycle state.
146/// Affects endpoints like:
147/// - User profile endpoints (status, engagement_level, last_active)
148/// - Activity endpoints (recent_activity, engagement_score)
149/// - Notification endpoints (notification_preferences, engagement_alerts)
150pub fn apply_user_engagement_lifecycle_effects(response: &mut Value, lifecycle: &PersonaLifecycle) {
151    if let Some(obj) = response.as_object_mut() {
152        match lifecycle.current_state {
153            LifecycleState::NewSignup => {
154                // New users have minimal engagement
155                obj.insert("status".to_string(), Value::String("new".to_string()));
156                obj.insert("engagement_level".to_string(), Value::String("low".to_string()));
157                obj.insert("last_active".to_string(), Value::String("recent".to_string()));
158                obj.insert("engagement_score".to_string(), Value::Number(10.into()));
159                obj.insert("recent_activity".to_string(), Value::Array(vec![]));
160                obj.insert(
161                    "notification_preferences".to_string(),
162                    Value::Object(serde_json::Map::new()),
163                );
164                obj.insert("engagement_alerts".to_string(), Value::Bool(false));
165            }
166            LifecycleState::Active => {
167                // Active users have good engagement
168                obj.insert("status".to_string(), Value::String("active".to_string()));
169                obj.insert("engagement_level".to_string(), Value::String("medium".to_string()));
170                obj.insert("last_active".to_string(), Value::String("recent".to_string()));
171                obj.insert("engagement_score".to_string(), Value::Number(75.into()));
172                obj.insert(
173                    "recent_activity".to_string(),
174                    Value::Array(vec![
175                        serde_json::json!({"type": "login", "timestamp": "recent"}),
176                        serde_json::json!({"type": "action", "timestamp": "recent"}),
177                    ]),
178                );
179                obj.insert(
180                    "notification_preferences".to_string(),
181                    Value::Object({
182                        let mut prefs = serde_json::Map::new();
183                        prefs.insert("email".to_string(), Value::Bool(true));
184                        prefs.insert("push".to_string(), Value::Bool(true));
185                        prefs
186                    }),
187                );
188                obj.insert("engagement_alerts".to_string(), Value::Bool(false));
189            }
190            LifecycleState::ChurnRisk => {
191                // Churn risk users have declining engagement
192                obj.insert("status".to_string(), Value::String("at_risk".to_string()));
193                obj.insert("engagement_level".to_string(), Value::String("low".to_string()));
194                obj.insert("last_active".to_string(), Value::String("30_days_ago".to_string()));
195                obj.insert("engagement_score".to_string(), Value::Number(25.into()));
196                obj.insert("recent_activity".to_string(), Value::Array(vec![]));
197                obj.insert(
198                    "notification_preferences".to_string(),
199                    Value::Object({
200                        let mut prefs = serde_json::Map::new();
201                        prefs.insert("email".to_string(), Value::Bool(true));
202                        prefs.insert("push".to_string(), Value::Bool(false));
203                        prefs
204                    }),
205                );
206                obj.insert("engagement_alerts".to_string(), Value::Bool(true));
207                obj.insert(
208                    "churn_risk_reason".to_string(),
209                    Value::String("inactivity".to_string()),
210                );
211            }
212            LifecycleState::Churned => {
213                // Churned users have no engagement
214                obj.insert("status".to_string(), Value::String("churned".to_string()));
215                obj.insert("engagement_level".to_string(), Value::String("none".to_string()));
216                obj.insert("last_active".to_string(), Value::String("90_days_ago".to_string()));
217                obj.insert("engagement_score".to_string(), Value::Number(0.into()));
218                obj.insert("recent_activity".to_string(), Value::Array(vec![]));
219                obj.insert(
220                    "notification_preferences".to_string(),
221                    Value::Object(serde_json::Map::new()),
222                );
223                obj.insert("engagement_alerts".to_string(), Value::Bool(false));
224                obj.insert("churned_at".to_string(), Value::String("recent".to_string()));
225            }
226            _ => {
227                // For other states, use default active behavior
228                obj.insert("status".to_string(), Value::String("active".to_string()));
229                obj.insert("engagement_level".to_string(), Value::String("medium".to_string()));
230            }
231        }
232    }
233}
234
235/// Apply lifecycle state effects for order fulfillment endpoints
236///
237/// Modifies order-related fields based on the persona's lifecycle state.
238/// Maps lifecycle states to order fulfillment states:
239/// - NewSignup -> PENDING
240/// - Active -> PROCESSING
241/// - PowerUser -> SHIPPED
242/// - UpgradePending -> DELIVERED
243/// - Churned -> COMPLETED
244pub fn apply_order_fulfillment_lifecycle_effects(
245    response: &mut Value,
246    lifecycle: &PersonaLifecycle,
247) {
248    if let Some(obj) = response.as_object_mut() {
249        match lifecycle.current_state {
250            LifecycleState::NewSignup => {
251                obj.insert("status".to_string(), Value::String("pending".to_string()));
252                obj.insert("estimated_delivery".to_string(), Value::Null);
253            }
254            LifecycleState::Active => {
255                obj.insert("status".to_string(), Value::String("processing".to_string()));
256                obj.insert("estimated_delivery".to_string(), Value::Null);
257            }
258            LifecycleState::PowerUser => {
259                obj.insert("status".to_string(), Value::String("shipped".to_string()));
260                obj.insert("tracking_number".to_string(), Value::String("TRACK123456".to_string()));
261                obj.insert(
262                    "shipped_at".to_string(),
263                    Value::String(chrono::Utc::now().to_rfc3339()),
264                );
265            }
266            LifecycleState::UpgradePending => {
267                obj.insert("status".to_string(), Value::String("delivered".to_string()));
268                obj.insert(
269                    "delivered_at".to_string(),
270                    Value::String(chrono::Utc::now().to_rfc3339()),
271                );
272            }
273            LifecycleState::Churned => {
274                obj.insert("status".to_string(), Value::String("completed".to_string()));
275                obj.insert(
276                    "completed_at".to_string(),
277                    Value::String(chrono::Utc::now().to_rfc3339()),
278                );
279            }
280            _ => {
281                // Default to processing for other states
282                obj.insert("status".to_string(), Value::String("processing".to_string()));
283            }
284        }
285    }
286}
287
288/// Apply lifecycle state effects for loan endpoints
289///
290/// Modifies loan-related fields based on the persona's lifecycle state.
291/// Maps lifecycle states to loan states:
292/// - NewSignup -> APPLICATION
293/// - Active -> APPROVED/ACTIVE
294/// - PaymentFailed -> PAST_DUE
295/// - Churned -> DEFAULTED
296pub fn apply_loan_lifecycle_effects(response: &mut Value, lifecycle: &PersonaLifecycle) {
297    if let Some(obj) = response.as_object_mut() {
298        match lifecycle.current_state {
299            LifecycleState::NewSignup => {
300                obj.insert("status".to_string(), Value::String("application".to_string()));
301                obj.insert("approved".to_string(), Value::Bool(false));
302            }
303            LifecycleState::Active => {
304                obj.insert("status".to_string(), Value::String("active".to_string()));
305                obj.insert("approved".to_string(), Value::Bool(true));
306                obj.insert(
307                    "approved_at".to_string(),
308                    Value::String(chrono::Utc::now().to_rfc3339()),
309                );
310            }
311            LifecycleState::PaymentFailed => {
312                obj.insert("status".to_string(), Value::String("past_due".to_string()));
313                obj.insert(
314                    "days_past_due".to_string(),
315                    Value::Number(
316                        lifecycle
317                            .get_metadata("payment_failed_count")
318                            .and_then(|v| v.as_u64())
319                            .unwrap_or(1)
320                            .into(),
321                    ),
322                );
323            }
324            LifecycleState::Churned => {
325                obj.insert("status".to_string(), Value::String("defaulted".to_string()));
326                obj.insert(
327                    "defaulted_at".to_string(),
328                    Value::String(chrono::Utc::now().to_rfc3339()),
329                );
330            }
331            _ => {
332                // Default to application for other states
333                obj.insert("status".to_string(), Value::String("application".to_string()));
334            }
335        }
336    }
337}
338
339/// Apply lifecycle state effects to a response based on endpoint type
340///
341/// This is a convenience function that routes to the appropriate lifecycle
342/// effect function based on the endpoint path or type.
343pub fn apply_lifecycle_effects(
344    response: &mut Value,
345    lifecycle: &PersonaLifecycle,
346    endpoint_type: &str,
347) {
348    match endpoint_type {
349        "billing" | "billing_status" | "payment" | "subscription" => {
350            apply_billing_lifecycle_effects(response, lifecycle);
351        }
352        "support" | "support_tickets" | "tickets" | "help" => {
353            apply_support_lifecycle_effects(response, lifecycle);
354        }
355        "order" | "orders" | "fulfillment" | "shipment" | "delivery" => {
356            apply_order_fulfillment_lifecycle_effects(response, lifecycle);
357        }
358        "loan" | "loans" | "credit" | "application" => {
359            apply_loan_lifecycle_effects(response, lifecycle);
360        }
361        "profile" | "user" | "users" | "activity" | "engagement" | "notifications"
362        | "notification" => {
363            apply_user_engagement_lifecycle_effects(response, lifecycle);
364        }
365        _ => {
366            // For other endpoints, apply basic lifecycle traits
367            if let Some(obj) = response.as_object_mut() {
368                let effects = lifecycle.apply_lifecycle_effects();
369                for (key, value) in effects {
370                    obj.insert(key, Value::String(value));
371                }
372            }
373        }
374    }
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380    use crate::persona_lifecycle::PersonaLifecycle;
381    use serde_json::json;
382
383    #[test]
384    fn test_apply_billing_lifecycle_effects_new_signup() {
385        let lifecycle = PersonaLifecycle::new("user123".to_string(), LifecycleState::NewSignup);
386        let mut response = json!({});
387        apply_billing_lifecycle_effects(&mut response, &lifecycle);
388
389        assert_eq!(response["billing_status"], "pending");
390        assert_eq!(response["subscription_status"], "trial");
391    }
392
393    #[test]
394    fn test_apply_billing_lifecycle_effects_payment_failed() {
395        let lifecycle = PersonaLifecycle::new("user123".to_string(), LifecycleState::PaymentFailed);
396        let mut response = json!({});
397        apply_billing_lifecycle_effects(&mut response, &lifecycle);
398
399        assert_eq!(response["billing_status"], "failed");
400        assert_eq!(response["subscription_status"], "suspended");
401        assert_eq!(response["last_payment_failed"], true);
402    }
403
404    #[test]
405    fn test_apply_support_lifecycle_effects_churn_risk() {
406        let lifecycle = PersonaLifecycle::new("user123".to_string(), LifecycleState::ChurnRisk);
407        let mut response = json!({});
408        apply_support_lifecycle_effects(&mut response, &lifecycle);
409
410        assert_eq!(response["support_tier"], "retention");
411        assert_eq!(response["open_tickets"], 1);
412        assert_eq!(response["priority"], "high");
413    }
414
415    #[test]
416    fn test_apply_lifecycle_effects_routing() {
417        let lifecycle = PersonaLifecycle::new("user123".to_string(), LifecycleState::Active);
418        let mut billing_response = json!({});
419        apply_lifecycle_effects(&mut billing_response, &lifecycle, "billing");
420        assert_eq!(billing_response["billing_status"], "active");
421
422        let mut support_response = json!({});
423        apply_lifecycle_effects(&mut support_response, &lifecycle, "support");
424        assert_eq!(support_response["support_tier"], "standard");
425    }
426}