churnkit 0.75.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/00_start_here.ipynb +647 -0
  2. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01_data_discovery.ipynb +1165 -0
  3. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01a_a_temporal_text_deep_dive.ipynb +961 -0
  4. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01a_temporal_deep_dive.ipynb +1690 -0
  5. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01b_temporal_quality.ipynb +679 -0
  6. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01c_temporal_patterns.ipynb +3305 -0
  7. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01d_event_aggregation.ipynb +1463 -0
  8. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/02_column_deep_dive.ipynb +1430 -0
  9. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/02a_text_columns_deep_dive.ipynb +854 -0
  10. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/03_quality_assessment.ipynb +1639 -0
  11. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/04_relationship_analysis.ipynb +1890 -0
  12. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/05_multi_dataset.ipynb +1457 -0
  13. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/06_feature_opportunities.ipynb +1624 -0
  14. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/07_modeling_readiness.ipynb +780 -0
  15. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/08_baseline_experiments.ipynb +979 -0
  16. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/09_business_alignment.ipynb +572 -0
  17. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/10_spec_generation.ipynb +1179 -0
  18. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/11_scoring_validation.ipynb +1418 -0
  19. churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/12_view_documentation.ipynb +151 -0
  20. churnkit-0.75.0a1.dist-info/METADATA +229 -0
  21. churnkit-0.75.0a1.dist-info/RECORD +302 -0
  22. churnkit-0.75.0a1.dist-info/WHEEL +4 -0
  23. churnkit-0.75.0a1.dist-info/entry_points.txt +2 -0
  24. churnkit-0.75.0a1.dist-info/licenses/LICENSE +202 -0
  25. customer_retention/__init__.py +37 -0
  26. customer_retention/analysis/__init__.py +0 -0
  27. customer_retention/analysis/auto_explorer/__init__.py +62 -0
  28. customer_retention/analysis/auto_explorer/exploration_manager.py +470 -0
  29. customer_retention/analysis/auto_explorer/explorer.py +258 -0
  30. customer_retention/analysis/auto_explorer/findings.py +291 -0
  31. customer_retention/analysis/auto_explorer/layered_recommendations.py +485 -0
  32. customer_retention/analysis/auto_explorer/recommendation_builder.py +148 -0
  33. customer_retention/analysis/auto_explorer/recommendations.py +418 -0
  34. customer_retention/analysis/business/__init__.py +26 -0
  35. customer_retention/analysis/business/ab_test_designer.py +144 -0
  36. customer_retention/analysis/business/fairness_analyzer.py +166 -0
  37. customer_retention/analysis/business/intervention_matcher.py +121 -0
  38. customer_retention/analysis/business/report_generator.py +222 -0
  39. customer_retention/analysis/business/risk_profile.py +199 -0
  40. customer_retention/analysis/business/roi_analyzer.py +139 -0
  41. customer_retention/analysis/diagnostics/__init__.py +20 -0
  42. customer_retention/analysis/diagnostics/calibration_analyzer.py +133 -0
  43. customer_retention/analysis/diagnostics/cv_analyzer.py +144 -0
  44. customer_retention/analysis/diagnostics/error_analyzer.py +107 -0
  45. customer_retention/analysis/diagnostics/leakage_detector.py +394 -0
  46. customer_retention/analysis/diagnostics/noise_tester.py +140 -0
  47. customer_retention/analysis/diagnostics/overfitting_analyzer.py +190 -0
  48. customer_retention/analysis/diagnostics/segment_analyzer.py +122 -0
  49. customer_retention/analysis/discovery/__init__.py +8 -0
  50. customer_retention/analysis/discovery/config_generator.py +49 -0
  51. customer_retention/analysis/discovery/discovery_flow.py +19 -0
  52. customer_retention/analysis/discovery/type_inferencer.py +147 -0
  53. customer_retention/analysis/interpretability/__init__.py +13 -0
  54. customer_retention/analysis/interpretability/cohort_analyzer.py +185 -0
  55. customer_retention/analysis/interpretability/counterfactual.py +175 -0
  56. customer_retention/analysis/interpretability/individual_explainer.py +141 -0
  57. customer_retention/analysis/interpretability/pdp_generator.py +103 -0
  58. customer_retention/analysis/interpretability/shap_explainer.py +106 -0
  59. customer_retention/analysis/jupyter_save_hook.py +28 -0
  60. customer_retention/analysis/notebook_html_exporter.py +136 -0
  61. customer_retention/analysis/notebook_progress.py +60 -0
  62. customer_retention/analysis/plotly_preprocessor.py +154 -0
  63. customer_retention/analysis/recommendations/__init__.py +54 -0
  64. customer_retention/analysis/recommendations/base.py +158 -0
  65. customer_retention/analysis/recommendations/cleaning/__init__.py +11 -0
  66. customer_retention/analysis/recommendations/cleaning/consistency.py +107 -0
  67. customer_retention/analysis/recommendations/cleaning/deduplicate.py +94 -0
  68. customer_retention/analysis/recommendations/cleaning/impute.py +67 -0
  69. customer_retention/analysis/recommendations/cleaning/outlier.py +71 -0
  70. customer_retention/analysis/recommendations/datetime/__init__.py +3 -0
  71. customer_retention/analysis/recommendations/datetime/extract.py +149 -0
  72. customer_retention/analysis/recommendations/encoding/__init__.py +3 -0
  73. customer_retention/analysis/recommendations/encoding/categorical.py +114 -0
  74. customer_retention/analysis/recommendations/pipeline.py +74 -0
  75. customer_retention/analysis/recommendations/registry.py +76 -0
  76. customer_retention/analysis/recommendations/selection/__init__.py +3 -0
  77. customer_retention/analysis/recommendations/selection/drop_column.py +56 -0
  78. customer_retention/analysis/recommendations/transform/__init__.py +4 -0
  79. customer_retention/analysis/recommendations/transform/power.py +94 -0
  80. customer_retention/analysis/recommendations/transform/scale.py +112 -0
  81. customer_retention/analysis/visualization/__init__.py +15 -0
  82. customer_retention/analysis/visualization/chart_builder.py +2619 -0
  83. customer_retention/analysis/visualization/console.py +122 -0
  84. customer_retention/analysis/visualization/display.py +171 -0
  85. customer_retention/analysis/visualization/number_formatter.py +36 -0
  86. customer_retention/artifacts/__init__.py +3 -0
  87. customer_retention/artifacts/fit_artifact_registry.py +146 -0
  88. customer_retention/cli.py +93 -0
  89. customer_retention/core/__init__.py +0 -0
  90. customer_retention/core/compat/__init__.py +193 -0
  91. customer_retention/core/compat/detection.py +99 -0
  92. customer_retention/core/compat/ops.py +48 -0
  93. customer_retention/core/compat/pandas_backend.py +57 -0
  94. customer_retention/core/compat/spark_backend.py +75 -0
  95. customer_retention/core/components/__init__.py +11 -0
  96. customer_retention/core/components/base.py +79 -0
  97. customer_retention/core/components/components/__init__.py +13 -0
  98. customer_retention/core/components/components/deployer.py +26 -0
  99. customer_retention/core/components/components/explainer.py +26 -0
  100. customer_retention/core/components/components/feature_eng.py +33 -0
  101. customer_retention/core/components/components/ingester.py +34 -0
  102. customer_retention/core/components/components/profiler.py +34 -0
  103. customer_retention/core/components/components/trainer.py +38 -0
  104. customer_retention/core/components/components/transformer.py +36 -0
  105. customer_retention/core/components/components/validator.py +37 -0
  106. customer_retention/core/components/enums.py +33 -0
  107. customer_retention/core/components/orchestrator.py +94 -0
  108. customer_retention/core/components/registry.py +59 -0
  109. customer_retention/core/config/__init__.py +39 -0
  110. customer_retention/core/config/column_config.py +95 -0
  111. customer_retention/core/config/experiments.py +71 -0
  112. customer_retention/core/config/pipeline_config.py +117 -0
  113. customer_retention/core/config/source_config.py +83 -0
  114. customer_retention/core/utils/__init__.py +28 -0
  115. customer_retention/core/utils/leakage.py +85 -0
  116. customer_retention/core/utils/severity.py +53 -0
  117. customer_retention/core/utils/statistics.py +90 -0
  118. customer_retention/generators/__init__.py +0 -0
  119. customer_retention/generators/notebook_generator/__init__.py +167 -0
  120. customer_retention/generators/notebook_generator/base.py +55 -0
  121. customer_retention/generators/notebook_generator/cell_builder.py +49 -0
  122. customer_retention/generators/notebook_generator/config.py +47 -0
  123. customer_retention/generators/notebook_generator/databricks_generator.py +48 -0
  124. customer_retention/generators/notebook_generator/local_generator.py +48 -0
  125. customer_retention/generators/notebook_generator/project_init.py +174 -0
  126. customer_retention/generators/notebook_generator/runner.py +150 -0
  127. customer_retention/generators/notebook_generator/script_generator.py +110 -0
  128. customer_retention/generators/notebook_generator/stages/__init__.py +19 -0
  129. customer_retention/generators/notebook_generator/stages/base_stage.py +86 -0
  130. customer_retention/generators/notebook_generator/stages/s01_ingestion.py +100 -0
  131. customer_retention/generators/notebook_generator/stages/s02_profiling.py +95 -0
  132. customer_retention/generators/notebook_generator/stages/s03_cleaning.py +180 -0
  133. customer_retention/generators/notebook_generator/stages/s04_transformation.py +165 -0
  134. customer_retention/generators/notebook_generator/stages/s05_feature_engineering.py +115 -0
  135. customer_retention/generators/notebook_generator/stages/s06_feature_selection.py +97 -0
  136. customer_retention/generators/notebook_generator/stages/s07_model_training.py +176 -0
  137. customer_retention/generators/notebook_generator/stages/s08_deployment.py +81 -0
  138. customer_retention/generators/notebook_generator/stages/s09_monitoring.py +112 -0
  139. customer_retention/generators/notebook_generator/stages/s10_batch_inference.py +642 -0
  140. customer_retention/generators/notebook_generator/stages/s11_feature_store.py +348 -0
  141. customer_retention/generators/orchestration/__init__.py +23 -0
  142. customer_retention/generators/orchestration/code_generator.py +196 -0
  143. customer_retention/generators/orchestration/context.py +147 -0
  144. customer_retention/generators/orchestration/data_materializer.py +188 -0
  145. customer_retention/generators/orchestration/databricks_exporter.py +411 -0
  146. customer_retention/generators/orchestration/doc_generator.py +311 -0
  147. customer_retention/generators/pipeline_generator/__init__.py +26 -0
  148. customer_retention/generators/pipeline_generator/findings_parser.py +727 -0
  149. customer_retention/generators/pipeline_generator/generator.py +142 -0
  150. customer_retention/generators/pipeline_generator/models.py +166 -0
  151. customer_retention/generators/pipeline_generator/renderer.py +2125 -0
  152. customer_retention/generators/spec_generator/__init__.py +37 -0
  153. customer_retention/generators/spec_generator/databricks_generator.py +433 -0
  154. customer_retention/generators/spec_generator/generic_generator.py +373 -0
  155. customer_retention/generators/spec_generator/mlflow_pipeline_generator.py +685 -0
  156. customer_retention/generators/spec_generator/pipeline_spec.py +298 -0
  157. customer_retention/integrations/__init__.py +0 -0
  158. customer_retention/integrations/adapters/__init__.py +13 -0
  159. customer_retention/integrations/adapters/base.py +10 -0
  160. customer_retention/integrations/adapters/factory.py +25 -0
  161. customer_retention/integrations/adapters/feature_store/__init__.py +6 -0
  162. customer_retention/integrations/adapters/feature_store/base.py +57 -0
  163. customer_retention/integrations/adapters/feature_store/databricks.py +94 -0
  164. customer_retention/integrations/adapters/feature_store/feast_adapter.py +97 -0
  165. customer_retention/integrations/adapters/feature_store/local.py +75 -0
  166. customer_retention/integrations/adapters/mlflow/__init__.py +6 -0
  167. customer_retention/integrations/adapters/mlflow/base.py +32 -0
  168. customer_retention/integrations/adapters/mlflow/databricks.py +54 -0
  169. customer_retention/integrations/adapters/mlflow/experiment_tracker.py +161 -0
  170. customer_retention/integrations/adapters/mlflow/local.py +50 -0
  171. customer_retention/integrations/adapters/storage/__init__.py +5 -0
  172. customer_retention/integrations/adapters/storage/base.py +33 -0
  173. customer_retention/integrations/adapters/storage/databricks.py +76 -0
  174. customer_retention/integrations/adapters/storage/local.py +59 -0
  175. customer_retention/integrations/feature_store/__init__.py +47 -0
  176. customer_retention/integrations/feature_store/definitions.py +215 -0
  177. customer_retention/integrations/feature_store/manager.py +744 -0
  178. customer_retention/integrations/feature_store/registry.py +412 -0
  179. customer_retention/integrations/iteration/__init__.py +28 -0
  180. customer_retention/integrations/iteration/context.py +212 -0
  181. customer_retention/integrations/iteration/feedback_collector.py +184 -0
  182. customer_retention/integrations/iteration/orchestrator.py +168 -0
  183. customer_retention/integrations/iteration/recommendation_tracker.py +341 -0
  184. customer_retention/integrations/iteration/signals.py +212 -0
  185. customer_retention/integrations/llm_context/__init__.py +4 -0
  186. customer_retention/integrations/llm_context/context_builder.py +201 -0
  187. customer_retention/integrations/llm_context/prompts.py +100 -0
  188. customer_retention/integrations/streaming/__init__.py +103 -0
  189. customer_retention/integrations/streaming/batch_integration.py +149 -0
  190. customer_retention/integrations/streaming/early_warning_model.py +227 -0
  191. customer_retention/integrations/streaming/event_schema.py +214 -0
  192. customer_retention/integrations/streaming/online_store_writer.py +249 -0
  193. customer_retention/integrations/streaming/realtime_scorer.py +261 -0
  194. customer_retention/integrations/streaming/trigger_engine.py +293 -0
  195. customer_retention/integrations/streaming/window_aggregator.py +393 -0
  196. customer_retention/stages/__init__.py +0 -0
  197. customer_retention/stages/cleaning/__init__.py +9 -0
  198. customer_retention/stages/cleaning/base.py +28 -0
  199. customer_retention/stages/cleaning/missing_handler.py +160 -0
  200. customer_retention/stages/cleaning/outlier_handler.py +204 -0
  201. customer_retention/stages/deployment/__init__.py +28 -0
  202. customer_retention/stages/deployment/batch_scorer.py +106 -0
  203. customer_retention/stages/deployment/champion_challenger.py +299 -0
  204. customer_retention/stages/deployment/model_registry.py +182 -0
  205. customer_retention/stages/deployment/retraining_trigger.py +245 -0
  206. customer_retention/stages/features/__init__.py +73 -0
  207. customer_retention/stages/features/behavioral_features.py +266 -0
  208. customer_retention/stages/features/customer_segmentation.py +505 -0
  209. customer_retention/stages/features/feature_definitions.py +265 -0
  210. customer_retention/stages/features/feature_engineer.py +551 -0
  211. customer_retention/stages/features/feature_manifest.py +340 -0
  212. customer_retention/stages/features/feature_selector.py +239 -0
  213. customer_retention/stages/features/interaction_features.py +160 -0
  214. customer_retention/stages/features/temporal_features.py +243 -0
  215. customer_retention/stages/ingestion/__init__.py +9 -0
  216. customer_retention/stages/ingestion/load_result.py +32 -0
  217. customer_retention/stages/ingestion/loaders.py +195 -0
  218. customer_retention/stages/ingestion/source_registry.py +130 -0
  219. customer_retention/stages/modeling/__init__.py +31 -0
  220. customer_retention/stages/modeling/baseline_trainer.py +139 -0
  221. customer_retention/stages/modeling/cross_validator.py +125 -0
  222. customer_retention/stages/modeling/data_splitter.py +205 -0
  223. customer_retention/stages/modeling/feature_scaler.py +99 -0
  224. customer_retention/stages/modeling/hyperparameter_tuner.py +107 -0
  225. customer_retention/stages/modeling/imbalance_handler.py +282 -0
  226. customer_retention/stages/modeling/mlflow_logger.py +95 -0
  227. customer_retention/stages/modeling/model_comparator.py +149 -0
  228. customer_retention/stages/modeling/model_evaluator.py +138 -0
  229. customer_retention/stages/modeling/threshold_optimizer.py +131 -0
  230. customer_retention/stages/monitoring/__init__.py +37 -0
  231. customer_retention/stages/monitoring/alert_manager.py +328 -0
  232. customer_retention/stages/monitoring/drift_detector.py +201 -0
  233. customer_retention/stages/monitoring/performance_monitor.py +242 -0
  234. customer_retention/stages/preprocessing/__init__.py +5 -0
  235. customer_retention/stages/preprocessing/transformer_manager.py +284 -0
  236. customer_retention/stages/profiling/__init__.py +256 -0
  237. customer_retention/stages/profiling/categorical_distribution.py +269 -0
  238. customer_retention/stages/profiling/categorical_target_analyzer.py +274 -0
  239. customer_retention/stages/profiling/column_profiler.py +527 -0
  240. customer_retention/stages/profiling/distribution_analysis.py +483 -0
  241. customer_retention/stages/profiling/drift_detector.py +310 -0
  242. customer_retention/stages/profiling/feature_capacity.py +507 -0
  243. customer_retention/stages/profiling/pattern_analysis_config.py +513 -0
  244. customer_retention/stages/profiling/profile_result.py +212 -0
  245. customer_retention/stages/profiling/quality_checks.py +1632 -0
  246. customer_retention/stages/profiling/relationship_detector.py +256 -0
  247. customer_retention/stages/profiling/relationship_recommender.py +454 -0
  248. customer_retention/stages/profiling/report_generator.py +520 -0
  249. customer_retention/stages/profiling/scd_analyzer.py +151 -0
  250. customer_retention/stages/profiling/segment_analyzer.py +632 -0
  251. customer_retention/stages/profiling/segment_aware_outlier.py +265 -0
  252. customer_retention/stages/profiling/target_level_analyzer.py +217 -0
  253. customer_retention/stages/profiling/temporal_analyzer.py +388 -0
  254. customer_retention/stages/profiling/temporal_coverage.py +488 -0
  255. customer_retention/stages/profiling/temporal_feature_analyzer.py +692 -0
  256. customer_retention/stages/profiling/temporal_feature_engineer.py +703 -0
  257. customer_retention/stages/profiling/temporal_pattern_analyzer.py +636 -0
  258. customer_retention/stages/profiling/temporal_quality_checks.py +278 -0
  259. customer_retention/stages/profiling/temporal_target_analyzer.py +241 -0
  260. customer_retention/stages/profiling/text_embedder.py +87 -0
  261. customer_retention/stages/profiling/text_processor.py +115 -0
  262. customer_retention/stages/profiling/text_reducer.py +60 -0
  263. customer_retention/stages/profiling/time_series_profiler.py +303 -0
  264. customer_retention/stages/profiling/time_window_aggregator.py +376 -0
  265. customer_retention/stages/profiling/type_detector.py +382 -0
  266. customer_retention/stages/profiling/window_recommendation.py +288 -0
  267. customer_retention/stages/temporal/__init__.py +166 -0
  268. customer_retention/stages/temporal/access_guard.py +180 -0
  269. customer_retention/stages/temporal/cutoff_analyzer.py +235 -0
  270. customer_retention/stages/temporal/data_preparer.py +178 -0
  271. customer_retention/stages/temporal/point_in_time_join.py +134 -0
  272. customer_retention/stages/temporal/point_in_time_registry.py +148 -0
  273. customer_retention/stages/temporal/scenario_detector.py +163 -0
  274. customer_retention/stages/temporal/snapshot_manager.py +259 -0
  275. customer_retention/stages/temporal/synthetic_coordinator.py +66 -0
  276. customer_retention/stages/temporal/timestamp_discovery.py +531 -0
  277. customer_retention/stages/temporal/timestamp_manager.py +255 -0
  278. customer_retention/stages/transformation/__init__.py +13 -0
  279. customer_retention/stages/transformation/binary_handler.py +85 -0
  280. customer_retention/stages/transformation/categorical_encoder.py +245 -0
  281. customer_retention/stages/transformation/datetime_transformer.py +97 -0
  282. customer_retention/stages/transformation/numeric_transformer.py +181 -0
  283. customer_retention/stages/transformation/pipeline.py +257 -0
  284. customer_retention/stages/validation/__init__.py +60 -0
  285. customer_retention/stages/validation/adversarial_scoring_validator.py +205 -0
  286. customer_retention/stages/validation/business_sense_gate.py +173 -0
  287. customer_retention/stages/validation/data_quality_gate.py +235 -0
  288. customer_retention/stages/validation/data_validators.py +511 -0
  289. customer_retention/stages/validation/feature_quality_gate.py +183 -0
  290. customer_retention/stages/validation/gates.py +117 -0
  291. customer_retention/stages/validation/leakage_gate.py +352 -0
  292. customer_retention/stages/validation/model_validity_gate.py +213 -0
  293. customer_retention/stages/validation/pipeline_validation_runner.py +264 -0
  294. customer_retention/stages/validation/quality_scorer.py +544 -0
  295. customer_retention/stages/validation/rule_generator.py +57 -0
  296. customer_retention/stages/validation/scoring_pipeline_validator.py +446 -0
  297. customer_retention/stages/validation/timeseries_detector.py +769 -0
  298. customer_retention/transforms/__init__.py +47 -0
  299. customer_retention/transforms/artifact_store.py +50 -0
  300. customer_retention/transforms/executor.py +157 -0
  301. customer_retention/transforms/fitted.py +92 -0
  302. customer_retention/transforms/ops.py +148 -0
@@ -0,0 +1,261 @@
1
+ import statistics
2
+ import threading
3
+ import time
4
+ from dataclasses import dataclass, field
5
+ from datetime import datetime
6
+ from typing import Dict, List, Optional
7
+
8
+
9
+ @dataclass
10
+ class ScoringConfig:
11
+ endpoint_name: str = "churn_scorer"
12
+ timeout_ms: int = 200
13
+ model_version: str = "v1.0"
14
+ min_replicas: int = 2
15
+ max_replicas: int = 10
16
+ scale_target_cpu: int = 70
17
+
18
+
19
+ @dataclass
20
+ class ScoringRequest:
21
+ customer_id: str
22
+ include_explanation: bool = False
23
+ include_recommendation: bool = False
24
+
25
+
26
+ @dataclass
27
+ class RiskFactor:
28
+ factor: str
29
+ impact: float
30
+
31
+
32
+ @dataclass
33
+ class ScoringResponse:
34
+ customer_id: str
35
+ churn_probability: Optional[float] = None
36
+ risk_segment: Optional[str] = None
37
+ warning_signals: List[str] = field(default_factory=list)
38
+ top_risk_factors: List[RiskFactor] = field(default_factory=list)
39
+ recommended_action: Optional[str] = None
40
+ model_version: str = "v1.0"
41
+ scored_at: datetime = field(default_factory=datetime.now)
42
+ latency_ms: float = 0.0
43
+ error: Optional[str] = None
44
+ is_fallback: bool = False
45
+
46
+
47
+ @dataclass
48
+ class EndpointHealth:
49
+ status: str = "healthy"
50
+ model_loaded: bool = True
51
+ feature_store_connected: bool = True
52
+ model_version: Optional[str] = None
53
+ uptime_seconds: float = 0.0
54
+ last_request_time: Optional[datetime] = None
55
+
56
+
57
+ @dataclass
58
+ class ScalingMetrics:
59
+ current_cpu_percent: float = 0.0
60
+ current_replicas: int = 2
61
+ requests_per_second: float = 0.0
62
+
63
+
64
+ @dataclass
65
+ class ScalingDecision:
66
+ should_scale_up: bool = False
67
+ should_scale_down: bool = False
68
+ target_replicas: int = 2
69
+
70
+
71
+ @dataclass
72
+ class SLAMetrics:
73
+ availability_percent: float = 100.0
74
+ error_rate_percent: float = 0.0
75
+ throughput_per_second: float = 0.0
76
+ avg_latency_ms: float = 0.0
77
+ p99_latency_ms: float = 0.0
78
+
79
+
80
+ @dataclass
81
+ class ScorerMetrics:
82
+ total_requests: int = 0
83
+ successful_requests: int = 0
84
+ failed_requests: int = 0
85
+ avg_latency_ms: float = 0.0
86
+ p99_latency_ms: float = 0.0
87
+
88
+
89
+ class AutoScaler:
90
+ def __init__(self, config: ScoringConfig):
91
+ self._config = config
92
+
93
+ def evaluate(self, metrics: ScalingMetrics) -> ScalingDecision:
94
+ import math
95
+ target = metrics.current_replicas
96
+ if metrics.current_cpu_percent > self._config.scale_target_cpu:
97
+ scale_factor = metrics.current_cpu_percent / self._config.scale_target_cpu
98
+ target = min(math.ceil(metrics.current_replicas * scale_factor), self._config.max_replicas)
99
+ target = max(target, metrics.current_replicas + 1)
100
+ target = min(target, self._config.max_replicas)
101
+ return ScalingDecision(should_scale_up=True, target_replicas=target)
102
+ elif metrics.current_cpu_percent < self._config.scale_target_cpu * 0.5:
103
+ target = max(metrics.current_replicas - 1, self._config.min_replicas)
104
+ if target < metrics.current_replicas:
105
+ return ScalingDecision(should_scale_down=True, target_replicas=target)
106
+ return ScalingDecision(target_replicas=max(target, self._config.min_replicas))
107
+
108
+
109
+ class RealtimeScorer:
110
+ def __init__(self, model, feature_store, config: Optional[ScoringConfig] = None,
111
+ fallback_scores: Optional[Dict[str, float]] = None):
112
+ self._model = model
113
+ self._feature_store = feature_store
114
+ self._config = config or ScoringConfig()
115
+ self._fallback_scores = fallback_scores or {}
116
+ self._start_time = datetime.now()
117
+ self._last_request_time: Optional[datetime] = None
118
+ self._latencies: List[float] = []
119
+ self._errors: int = 0
120
+ self._total_requests: int = 0
121
+ self._cache: Dict[str, ScoringResponse] = {}
122
+ self._required_features: List[str] = []
123
+ self._lock = threading.Lock()
124
+
125
+ def health_check(self) -> EndpointHealth:
126
+ model_loaded = self._model is not None
127
+ store_connected = True
128
+ try:
129
+ self._feature_store.read_batch("__health_check__", [])
130
+ except Exception:
131
+ store_connected = True
132
+ return EndpointHealth(
133
+ status="healthy" if model_loaded and store_connected else "unhealthy",
134
+ model_loaded=model_loaded,
135
+ feature_store_connected=store_connected,
136
+ model_version=self._config.model_version,
137
+ uptime_seconds=(datetime.now() - self._start_time).total_seconds(),
138
+ last_request_time=self._last_request_time
139
+ )
140
+
141
+ def set_required_features(self, features: List[str]):
142
+ self._required_features = features
143
+
144
+ def score(self, request: ScoringRequest) -> ScoringResponse:
145
+ start = time.time()
146
+ self._total_requests += 1
147
+ self._last_request_time = datetime.now()
148
+ try:
149
+ features = self._feature_store.read_batch(request.customer_id, self._required_features or ["page_views_1h", "orders_7d"])
150
+ if not features:
151
+ if request.customer_id in self._fallback_scores:
152
+ return ScoringResponse(
153
+ customer_id=request.customer_id,
154
+ churn_probability=self._fallback_scores[request.customer_id],
155
+ risk_segment=self._get_risk_segment(self._fallback_scores[request.customer_id]),
156
+ model_version=self._config.model_version,
157
+ latency_ms=(time.time() - start) * 1000,
158
+ is_fallback=True
159
+ )
160
+ if request.customer_id in self._cache:
161
+ cached = self._cache[request.customer_id]
162
+ cached.is_fallback = True
163
+ cached.latency_ms = (time.time() - start) * 1000
164
+ return cached
165
+ feature_vector = self._prepare_features(features)
166
+ proba = self._model.predict_proba(feature_vector)[0]
167
+ churn_prob = proba[1] if len(proba) > 1 else proba[0]
168
+ latency = (time.time() - start) * 1000
169
+ self._latencies.append(latency)
170
+ response = ScoringResponse(
171
+ customer_id=request.customer_id,
172
+ churn_probability=churn_prob,
173
+ risk_segment=self._get_risk_segment(churn_prob),
174
+ warning_signals=[],
175
+ model_version=self._config.model_version,
176
+ latency_ms=latency
177
+ )
178
+ if request.include_explanation:
179
+ response.top_risk_factors = self._compute_explanations(features, churn_prob)
180
+ if request.include_recommendation:
181
+ response.recommended_action = self._get_recommendation(churn_prob)
182
+ self._cache[request.customer_id] = response
183
+ return response
184
+ except Exception as e:
185
+ self._errors += 1
186
+ latency = (time.time() - start) * 1000
187
+ if request.customer_id in self._fallback_scores:
188
+ return ScoringResponse(
189
+ customer_id=request.customer_id,
190
+ churn_probability=self._fallback_scores[request.customer_id],
191
+ risk_segment=self._get_risk_segment(self._fallback_scores[request.customer_id]),
192
+ latency_ms=latency,
193
+ is_fallback=True
194
+ )
195
+ if request.customer_id in self._cache:
196
+ cached = self._cache[request.customer_id]
197
+ cached.is_fallback = True
198
+ cached.latency_ms = latency
199
+ return cached
200
+ return ScoringResponse(
201
+ customer_id=request.customer_id,
202
+ error=str(e),
203
+ latency_ms=latency
204
+ )
205
+
206
+ def score_batch(self, customer_ids: List[str]) -> List[ScoringResponse]:
207
+ return [self.score(ScoringRequest(customer_id=cid)) for cid in customer_ids]
208
+
209
+ def get_sla_metrics(self) -> SLAMetrics:
210
+ if not self._latencies:
211
+ return SLAMetrics()
212
+ sorted_lat = sorted(self._latencies)
213
+ return SLAMetrics(
214
+ availability_percent=100.0 * (self._total_requests - self._errors) / max(self._total_requests, 1),
215
+ error_rate_percent=100.0 * self._errors / max(self._total_requests, 1),
216
+ throughput_per_second=self._total_requests / max((datetime.now() - self._start_time).total_seconds(), 1),
217
+ avg_latency_ms=statistics.mean(self._latencies),
218
+ p99_latency_ms=sorted_lat[int(len(sorted_lat) * 0.99)] if len(sorted_lat) > 1 else sorted_lat[0]
219
+ )
220
+
221
+ def get_metrics(self) -> ScorerMetrics:
222
+ if not self._latencies:
223
+ return ScorerMetrics(total_requests=self._total_requests)
224
+ sorted_lat = sorted(self._latencies)
225
+ return ScorerMetrics(
226
+ total_requests=self._total_requests,
227
+ successful_requests=self._total_requests - self._errors,
228
+ failed_requests=self._errors,
229
+ avg_latency_ms=statistics.mean(self._latencies),
230
+ p99_latency_ms=sorted_lat[int(len(sorted_lat) * 0.99)] if len(sorted_lat) > 1 else sorted_lat[0]
231
+ )
232
+
233
+ def _prepare_features(self, features: Dict[str, float]) -> List[List[float]]:
234
+ return [[features.get(f, 0.0) for f in (self._required_features or list(features.keys()))]]
235
+
236
+ def _get_risk_segment(self, probability: float) -> str:
237
+ if probability >= 0.80:
238
+ return "Critical"
239
+ elif probability >= 0.50:
240
+ return "High"
241
+ elif probability >= 0.30:
242
+ return "Medium"
243
+ return "Low"
244
+
245
+ def _compute_explanations(self, features: Dict[str, float], probability: float) -> List[RiskFactor]:
246
+ explanations = []
247
+ for name, value in features.items():
248
+ if value > 0:
249
+ impact = value * 0.1
250
+ explanations.append(RiskFactor(factor=name, impact=impact))
251
+ explanations.sort(key=lambda x: x.impact, reverse=True)
252
+ return explanations[:5]
253
+
254
+ def _get_recommendation(self, probability: float) -> str:
255
+ if probability >= 0.80:
256
+ return "immediate_outreach"
257
+ elif probability >= 0.50:
258
+ return "retention_campaign"
259
+ elif probability >= 0.30:
260
+ return "engagement_email"
261
+ return "standard_communication"
@@ -0,0 +1,293 @@
1
+ import uuid
2
+ from dataclasses import dataclass, field
3
+ from datetime import datetime, timedelta
4
+ from enum import Enum
5
+ from typing import Any, Dict, List, Optional, Tuple
6
+
7
+ from .early_warning_model import WarningResult
8
+ from .event_schema import Event
9
+
10
+
11
+ class StreamTriggerType(Enum):
12
+ THRESHOLD = "threshold"
13
+ PATTERN = "pattern"
14
+ ANOMALY = "anomaly"
15
+ TIME_BASED = "time_based"
16
+ COMPOSITE = "composite"
17
+
18
+
19
+ class ActionType(Enum):
20
+ IMMEDIATE_ALERT = "immediate_alert"
21
+ QUEUE_FOR_OUTREACH = "queue_for_outreach"
22
+ ADD_TO_CAMPAIGN = "add_to_campaign"
23
+ RETENTION_OFFER = "retention_offer"
24
+ DISCOUNT_EMAIL = "discount_email"
25
+ ALERT_CS = "alert_cs"
26
+ FRAUD_CHECK = "fraud_check"
27
+ EMAIL = "email"
28
+ PHONE_CALL = "phone_call"
29
+
30
+
31
+ @dataclass
32
+ class TriggerConfig:
33
+ evaluation_interval_seconds: int = 60
34
+ cooldown_period_seconds: int = 3600
35
+ max_triggers_per_customer_per_day: int = 3
36
+
37
+
38
+ @dataclass
39
+ class TriggerContext:
40
+ customer_id: str
41
+ current_activity: float = 0.0
42
+ baseline_activity: float = 0.0
43
+ activity_drop_percent: float = 0.0
44
+ current_spending: float = 0.0
45
+ baseline_spending: float = 0.0
46
+ spending_deviation_zscore: float = 0.0
47
+
48
+
49
+ @dataclass
50
+ class TriggerResult:
51
+ triggered: bool
52
+ trigger_id: str = field(default_factory=lambda: str(uuid.uuid4()))
53
+ customer_id: str = ""
54
+ trigger_type: Optional[StreamTriggerType] = None
55
+ trigger_name: str = ""
56
+ trigger_time: datetime = field(default_factory=datetime.now)
57
+ action: Optional[ActionType] = None
58
+ priority: int = 3
59
+ context: Dict[str, Any] = field(default_factory=dict)
60
+ cooldown_active: bool = False
61
+
62
+ def to_alert(self):
63
+ from customer_retention.stages.monitoring import Alert, AlertLevel
64
+ level = AlertLevel.WARNING
65
+ if self.action == ActionType.IMMEDIATE_ALERT:
66
+ level = AlertLevel.CRITICAL
67
+ return Alert(
68
+ alert_id=self.trigger_id,
69
+ condition_id=f"TRIGGER_{self.trigger_name}",
70
+ level=level,
71
+ message=f"Trigger {self.trigger_name} fired for customer {self.customer_id}",
72
+ timestamp=self.trigger_time
73
+ )
74
+
75
+
76
+ @dataclass
77
+ class TriggerDefinition:
78
+ name: str
79
+ action: ActionType
80
+ trigger_type: StreamTriggerType = field(default=StreamTriggerType.THRESHOLD)
81
+ priority: int = 3
82
+ cooldown_seconds: int = 3600
83
+
84
+
85
+ @dataclass
86
+ class ThresholdTrigger(TriggerDefinition):
87
+ threshold: float = 0.80
88
+ trigger_type: StreamTriggerType = field(default=StreamTriggerType.THRESHOLD)
89
+
90
+ @classmethod
91
+ def from_alert_condition(cls, condition) -> "ThresholdTrigger":
92
+ return cls(
93
+ name=condition.name,
94
+ threshold=condition.threshold,
95
+ action=ActionType.IMMEDIATE_ALERT,
96
+ priority=1 if condition.level.value == "critical" else 3
97
+ )
98
+
99
+
100
+ @dataclass
101
+ class PatternTrigger(TriggerDefinition):
102
+ pattern: List[str] = field(default_factory=list)
103
+ window_minutes: int = 60
104
+ trigger_type: StreamTriggerType = field(default=StreamTriggerType.PATTERN)
105
+
106
+
107
+ @dataclass
108
+ class AnomalyTrigger(TriggerDefinition):
109
+ anomaly_threshold: float = 0.80
110
+ zscore_threshold: float = 3.0
111
+ window_hours: int = 24
112
+ trigger_type: StreamTriggerType = field(default=StreamTriggerType.ANOMALY)
113
+
114
+
115
+ @dataclass
116
+ class CompositeTrigger(TriggerDefinition):
117
+ conditions: List[Tuple[str, Dict[str, Any]]] = field(default_factory=list)
118
+ logic: str = "AND"
119
+ trigger_type: StreamTriggerType = field(default=StreamTriggerType.COMPOSITE)
120
+
121
+
122
+ class TriggerEngine:
123
+ def __init__(self, config: Optional[TriggerConfig] = None):
124
+ self._config = config or TriggerConfig()
125
+ self._triggers: List[TriggerDefinition] = []
126
+ self._cooldowns: Dict[str, Dict[str, datetime]] = {}
127
+ self._trigger_counts: Dict[str, Dict[str, int]] = {}
128
+ self._action_executor: Optional[Any] = None
129
+
130
+ @property
131
+ def config(self) -> TriggerConfig:
132
+ return self._config
133
+
134
+ def register_trigger(self, trigger: TriggerDefinition):
135
+ self._triggers.append(trigger)
136
+ self._triggers.sort(key=lambda t: t.priority)
137
+
138
+ def set_action_executor(self, executor):
139
+ self._action_executor = executor
140
+
141
+ def evaluate(self, warning: WarningResult) -> TriggerResult:
142
+ for trigger in self._triggers:
143
+ if isinstance(trigger, ThresholdTrigger):
144
+ if self._check_cooldown(warning.customer_id, trigger.name):
145
+ return TriggerResult(
146
+ triggered=False,
147
+ customer_id=warning.customer_id,
148
+ cooldown_active=True
149
+ )
150
+ if self._check_daily_limit(warning.customer_id):
151
+ return TriggerResult(triggered=False, customer_id=warning.customer_id)
152
+ if warning.warning_score >= trigger.threshold:
153
+ self._set_cooldown(warning.customer_id, trigger.name, trigger.cooldown_seconds)
154
+ self._increment_daily_count(warning.customer_id)
155
+ return TriggerResult(
156
+ triggered=True,
157
+ customer_id=warning.customer_id,
158
+ trigger_type=StreamTriggerType.THRESHOLD,
159
+ trigger_name=trigger.name,
160
+ action=trigger.action,
161
+ priority=trigger.priority,
162
+ context={"warning_score": warning.warning_score}
163
+ )
164
+ return TriggerResult(triggered=False, customer_id=warning.customer_id)
165
+
166
+ def evaluate_all(self, warning: WarningResult) -> List[TriggerResult]:
167
+ results = []
168
+ for trigger in self._triggers:
169
+ if isinstance(trigger, ThresholdTrigger):
170
+ if warning.warning_score >= trigger.threshold:
171
+ results.append(TriggerResult(
172
+ triggered=True,
173
+ customer_id=warning.customer_id,
174
+ trigger_type=StreamTriggerType.THRESHOLD,
175
+ trigger_name=trigger.name,
176
+ action=trigger.action,
177
+ priority=trigger.priority,
178
+ context={"warning_score": warning.warning_score}
179
+ ))
180
+ results.sort(key=lambda r: r.priority)
181
+ return results
182
+
183
+ def evaluate_first_match(self, warning: WarningResult) -> TriggerResult:
184
+ results = self.evaluate_all(warning)
185
+ return results[0] if results else TriggerResult(triggered=False, customer_id=warning.customer_id)
186
+
187
+ def evaluate_pattern(self, events: List[Event], customer_id: str) -> TriggerResult:
188
+ for trigger in self._triggers:
189
+ if isinstance(trigger, PatternTrigger):
190
+ if self._match_pattern(events, trigger):
191
+ return TriggerResult(
192
+ triggered=True,
193
+ customer_id=customer_id,
194
+ trigger_type=StreamTriggerType.PATTERN,
195
+ trigger_name=trigger.name,
196
+ action=trigger.action,
197
+ priority=trigger.priority
198
+ )
199
+ return TriggerResult(triggered=False, customer_id=customer_id)
200
+
201
+ def evaluate_anomaly(self, context: TriggerContext) -> TriggerResult:
202
+ for trigger in self._triggers:
203
+ if isinstance(trigger, AnomalyTrigger):
204
+ if context.activity_drop_percent >= trigger.anomaly_threshold:
205
+ return TriggerResult(
206
+ triggered=True,
207
+ customer_id=context.customer_id,
208
+ trigger_type=StreamTriggerType.ANOMALY,
209
+ trigger_name=trigger.name,
210
+ action=trigger.action,
211
+ priority=trigger.priority
212
+ )
213
+ if context.spending_deviation_zscore >= trigger.zscore_threshold:
214
+ return TriggerResult(
215
+ triggered=True,
216
+ customer_id=context.customer_id,
217
+ trigger_type=StreamTriggerType.ANOMALY,
218
+ trigger_name=trigger.name,
219
+ action=trigger.action,
220
+ priority=trigger.priority
221
+ )
222
+ return TriggerResult(triggered=False, customer_id=context.customer_id)
223
+
224
+ def evaluate_composite(self, warning: WarningResult, trigger: CompositeTrigger) -> TriggerResult:
225
+ results = []
226
+ for condition_type, params in trigger.conditions:
227
+ if condition_type == "threshold":
228
+ results.append(warning.warning_score >= params.get("threshold", 0.5))
229
+ elif condition_type == "signal":
230
+ signal = params.get("signal")
231
+ results.append(signal in warning.warning_signals)
232
+ if trigger.logic == "AND":
233
+ triggered = all(results)
234
+ else:
235
+ triggered = any(results)
236
+ return TriggerResult(
237
+ triggered=triggered,
238
+ customer_id=warning.customer_id,
239
+ trigger_type=StreamTriggerType.COMPOSITE,
240
+ trigger_name=trigger.name,
241
+ action=trigger.action if triggered else None,
242
+ priority=trigger.priority
243
+ )
244
+
245
+ def evaluate_and_execute(self, warning: WarningResult):
246
+ result = self.evaluate(warning)
247
+ if result.triggered and self._action_executor:
248
+ self._action_executor.execute(result)
249
+ return result
250
+
251
+ def _match_pattern(self, events: List[Event], trigger: PatternTrigger) -> bool:
252
+ cutoff = datetime.now() - timedelta(minutes=trigger.window_minutes)
253
+ recent_events = [e for e in events if e.event_timestamp >= cutoff]
254
+ pattern_index = 0
255
+ for event in sorted(recent_events, key=lambda e: e.event_timestamp):
256
+ pattern_element = trigger.pattern[pattern_index]
257
+ if ":" in pattern_element:
258
+ event_type, qualifier = pattern_element.split(":", 1)
259
+ if event.event_type.value == event_type:
260
+ page = event.event_properties.get("page", "")
261
+ query = event.event_properties.get("query", "")
262
+ if qualifier in page or qualifier in query:
263
+ pattern_index += 1
264
+ else:
265
+ if event.event_type.value == pattern_element:
266
+ pattern_index += 1
267
+ if pattern_index >= len(trigger.pattern):
268
+ return True
269
+ return False
270
+
271
+ def _check_cooldown(self, customer_id: str, trigger_name: str) -> bool:
272
+ if customer_id not in self._cooldowns:
273
+ return False
274
+ if trigger_name not in self._cooldowns[customer_id]:
275
+ return False
276
+ cooldown_until = self._cooldowns[customer_id][trigger_name]
277
+ return datetime.now() < cooldown_until
278
+
279
+ def _set_cooldown(self, customer_id: str, trigger_name: str, seconds: int):
280
+ if customer_id not in self._cooldowns:
281
+ self._cooldowns[customer_id] = {}
282
+ self._cooldowns[customer_id][trigger_name] = datetime.now() + timedelta(seconds=seconds)
283
+
284
+ def _check_daily_limit(self, customer_id: str) -> bool:
285
+ today = datetime.now().strftime("%Y-%m-%d")
286
+ count = self._trigger_counts.get(customer_id, {}).get(today, 0)
287
+ return count >= self._config.max_triggers_per_customer_per_day
288
+
289
+ def _increment_daily_count(self, customer_id: str):
290
+ today = datetime.now().strftime("%Y-%m-%d")
291
+ if customer_id not in self._trigger_counts:
292
+ self._trigger_counts[customer_id] = {}
293
+ self._trigger_counts[customer_id][today] = self._trigger_counts[customer_id].get(today, 0) + 1