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.
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/00_start_here.ipynb +647 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01_data_discovery.ipynb +1165 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01a_a_temporal_text_deep_dive.ipynb +961 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01a_temporal_deep_dive.ipynb +1690 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01b_temporal_quality.ipynb +679 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01c_temporal_patterns.ipynb +3305 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01d_event_aggregation.ipynb +1463 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/02_column_deep_dive.ipynb +1430 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/02a_text_columns_deep_dive.ipynb +854 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/03_quality_assessment.ipynb +1639 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/04_relationship_analysis.ipynb +1890 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/05_multi_dataset.ipynb +1457 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/06_feature_opportunities.ipynb +1624 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/07_modeling_readiness.ipynb +780 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/08_baseline_experiments.ipynb +979 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/09_business_alignment.ipynb +572 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/10_spec_generation.ipynb +1179 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/11_scoring_validation.ipynb +1418 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/12_view_documentation.ipynb +151 -0
- churnkit-0.75.0a1.dist-info/METADATA +229 -0
- churnkit-0.75.0a1.dist-info/RECORD +302 -0
- churnkit-0.75.0a1.dist-info/WHEEL +4 -0
- churnkit-0.75.0a1.dist-info/entry_points.txt +2 -0
- churnkit-0.75.0a1.dist-info/licenses/LICENSE +202 -0
- customer_retention/__init__.py +37 -0
- customer_retention/analysis/__init__.py +0 -0
- customer_retention/analysis/auto_explorer/__init__.py +62 -0
- customer_retention/analysis/auto_explorer/exploration_manager.py +470 -0
- customer_retention/analysis/auto_explorer/explorer.py +258 -0
- customer_retention/analysis/auto_explorer/findings.py +291 -0
- customer_retention/analysis/auto_explorer/layered_recommendations.py +485 -0
- customer_retention/analysis/auto_explorer/recommendation_builder.py +148 -0
- customer_retention/analysis/auto_explorer/recommendations.py +418 -0
- customer_retention/analysis/business/__init__.py +26 -0
- customer_retention/analysis/business/ab_test_designer.py +144 -0
- customer_retention/analysis/business/fairness_analyzer.py +166 -0
- customer_retention/analysis/business/intervention_matcher.py +121 -0
- customer_retention/analysis/business/report_generator.py +222 -0
- customer_retention/analysis/business/risk_profile.py +199 -0
- customer_retention/analysis/business/roi_analyzer.py +139 -0
- customer_retention/analysis/diagnostics/__init__.py +20 -0
- customer_retention/analysis/diagnostics/calibration_analyzer.py +133 -0
- customer_retention/analysis/diagnostics/cv_analyzer.py +144 -0
- customer_retention/analysis/diagnostics/error_analyzer.py +107 -0
- customer_retention/analysis/diagnostics/leakage_detector.py +394 -0
- customer_retention/analysis/diagnostics/noise_tester.py +140 -0
- customer_retention/analysis/diagnostics/overfitting_analyzer.py +190 -0
- customer_retention/analysis/diagnostics/segment_analyzer.py +122 -0
- customer_retention/analysis/discovery/__init__.py +8 -0
- customer_retention/analysis/discovery/config_generator.py +49 -0
- customer_retention/analysis/discovery/discovery_flow.py +19 -0
- customer_retention/analysis/discovery/type_inferencer.py +147 -0
- customer_retention/analysis/interpretability/__init__.py +13 -0
- customer_retention/analysis/interpretability/cohort_analyzer.py +185 -0
- customer_retention/analysis/interpretability/counterfactual.py +175 -0
- customer_retention/analysis/interpretability/individual_explainer.py +141 -0
- customer_retention/analysis/interpretability/pdp_generator.py +103 -0
- customer_retention/analysis/interpretability/shap_explainer.py +106 -0
- customer_retention/analysis/jupyter_save_hook.py +28 -0
- customer_retention/analysis/notebook_html_exporter.py +136 -0
- customer_retention/analysis/notebook_progress.py +60 -0
- customer_retention/analysis/plotly_preprocessor.py +154 -0
- customer_retention/analysis/recommendations/__init__.py +54 -0
- customer_retention/analysis/recommendations/base.py +158 -0
- customer_retention/analysis/recommendations/cleaning/__init__.py +11 -0
- customer_retention/analysis/recommendations/cleaning/consistency.py +107 -0
- customer_retention/analysis/recommendations/cleaning/deduplicate.py +94 -0
- customer_retention/analysis/recommendations/cleaning/impute.py +67 -0
- customer_retention/analysis/recommendations/cleaning/outlier.py +71 -0
- customer_retention/analysis/recommendations/datetime/__init__.py +3 -0
- customer_retention/analysis/recommendations/datetime/extract.py +149 -0
- customer_retention/analysis/recommendations/encoding/__init__.py +3 -0
- customer_retention/analysis/recommendations/encoding/categorical.py +114 -0
- customer_retention/analysis/recommendations/pipeline.py +74 -0
- customer_retention/analysis/recommendations/registry.py +76 -0
- customer_retention/analysis/recommendations/selection/__init__.py +3 -0
- customer_retention/analysis/recommendations/selection/drop_column.py +56 -0
- customer_retention/analysis/recommendations/transform/__init__.py +4 -0
- customer_retention/analysis/recommendations/transform/power.py +94 -0
- customer_retention/analysis/recommendations/transform/scale.py +112 -0
- customer_retention/analysis/visualization/__init__.py +15 -0
- customer_retention/analysis/visualization/chart_builder.py +2619 -0
- customer_retention/analysis/visualization/console.py +122 -0
- customer_retention/analysis/visualization/display.py +171 -0
- customer_retention/analysis/visualization/number_formatter.py +36 -0
- customer_retention/artifacts/__init__.py +3 -0
- customer_retention/artifacts/fit_artifact_registry.py +146 -0
- customer_retention/cli.py +93 -0
- customer_retention/core/__init__.py +0 -0
- customer_retention/core/compat/__init__.py +193 -0
- customer_retention/core/compat/detection.py +99 -0
- customer_retention/core/compat/ops.py +48 -0
- customer_retention/core/compat/pandas_backend.py +57 -0
- customer_retention/core/compat/spark_backend.py +75 -0
- customer_retention/core/components/__init__.py +11 -0
- customer_retention/core/components/base.py +79 -0
- customer_retention/core/components/components/__init__.py +13 -0
- customer_retention/core/components/components/deployer.py +26 -0
- customer_retention/core/components/components/explainer.py +26 -0
- customer_retention/core/components/components/feature_eng.py +33 -0
- customer_retention/core/components/components/ingester.py +34 -0
- customer_retention/core/components/components/profiler.py +34 -0
- customer_retention/core/components/components/trainer.py +38 -0
- customer_retention/core/components/components/transformer.py +36 -0
- customer_retention/core/components/components/validator.py +37 -0
- customer_retention/core/components/enums.py +33 -0
- customer_retention/core/components/orchestrator.py +94 -0
- customer_retention/core/components/registry.py +59 -0
- customer_retention/core/config/__init__.py +39 -0
- customer_retention/core/config/column_config.py +95 -0
- customer_retention/core/config/experiments.py +71 -0
- customer_retention/core/config/pipeline_config.py +117 -0
- customer_retention/core/config/source_config.py +83 -0
- customer_retention/core/utils/__init__.py +28 -0
- customer_retention/core/utils/leakage.py +85 -0
- customer_retention/core/utils/severity.py +53 -0
- customer_retention/core/utils/statistics.py +90 -0
- customer_retention/generators/__init__.py +0 -0
- customer_retention/generators/notebook_generator/__init__.py +167 -0
- customer_retention/generators/notebook_generator/base.py +55 -0
- customer_retention/generators/notebook_generator/cell_builder.py +49 -0
- customer_retention/generators/notebook_generator/config.py +47 -0
- customer_retention/generators/notebook_generator/databricks_generator.py +48 -0
- customer_retention/generators/notebook_generator/local_generator.py +48 -0
- customer_retention/generators/notebook_generator/project_init.py +174 -0
- customer_retention/generators/notebook_generator/runner.py +150 -0
- customer_retention/generators/notebook_generator/script_generator.py +110 -0
- customer_retention/generators/notebook_generator/stages/__init__.py +19 -0
- customer_retention/generators/notebook_generator/stages/base_stage.py +86 -0
- customer_retention/generators/notebook_generator/stages/s01_ingestion.py +100 -0
- customer_retention/generators/notebook_generator/stages/s02_profiling.py +95 -0
- customer_retention/generators/notebook_generator/stages/s03_cleaning.py +180 -0
- customer_retention/generators/notebook_generator/stages/s04_transformation.py +165 -0
- customer_retention/generators/notebook_generator/stages/s05_feature_engineering.py +115 -0
- customer_retention/generators/notebook_generator/stages/s06_feature_selection.py +97 -0
- customer_retention/generators/notebook_generator/stages/s07_model_training.py +176 -0
- customer_retention/generators/notebook_generator/stages/s08_deployment.py +81 -0
- customer_retention/generators/notebook_generator/stages/s09_monitoring.py +112 -0
- customer_retention/generators/notebook_generator/stages/s10_batch_inference.py +642 -0
- customer_retention/generators/notebook_generator/stages/s11_feature_store.py +348 -0
- customer_retention/generators/orchestration/__init__.py +23 -0
- customer_retention/generators/orchestration/code_generator.py +196 -0
- customer_retention/generators/orchestration/context.py +147 -0
- customer_retention/generators/orchestration/data_materializer.py +188 -0
- customer_retention/generators/orchestration/databricks_exporter.py +411 -0
- customer_retention/generators/orchestration/doc_generator.py +311 -0
- customer_retention/generators/pipeline_generator/__init__.py +26 -0
- customer_retention/generators/pipeline_generator/findings_parser.py +727 -0
- customer_retention/generators/pipeline_generator/generator.py +142 -0
- customer_retention/generators/pipeline_generator/models.py +166 -0
- customer_retention/generators/pipeline_generator/renderer.py +2125 -0
- customer_retention/generators/spec_generator/__init__.py +37 -0
- customer_retention/generators/spec_generator/databricks_generator.py +433 -0
- customer_retention/generators/spec_generator/generic_generator.py +373 -0
- customer_retention/generators/spec_generator/mlflow_pipeline_generator.py +685 -0
- customer_retention/generators/spec_generator/pipeline_spec.py +298 -0
- customer_retention/integrations/__init__.py +0 -0
- customer_retention/integrations/adapters/__init__.py +13 -0
- customer_retention/integrations/adapters/base.py +10 -0
- customer_retention/integrations/adapters/factory.py +25 -0
- customer_retention/integrations/adapters/feature_store/__init__.py +6 -0
- customer_retention/integrations/adapters/feature_store/base.py +57 -0
- customer_retention/integrations/adapters/feature_store/databricks.py +94 -0
- customer_retention/integrations/adapters/feature_store/feast_adapter.py +97 -0
- customer_retention/integrations/adapters/feature_store/local.py +75 -0
- customer_retention/integrations/adapters/mlflow/__init__.py +6 -0
- customer_retention/integrations/adapters/mlflow/base.py +32 -0
- customer_retention/integrations/adapters/mlflow/databricks.py +54 -0
- customer_retention/integrations/adapters/mlflow/experiment_tracker.py +161 -0
- customer_retention/integrations/adapters/mlflow/local.py +50 -0
- customer_retention/integrations/adapters/storage/__init__.py +5 -0
- customer_retention/integrations/adapters/storage/base.py +33 -0
- customer_retention/integrations/adapters/storage/databricks.py +76 -0
- customer_retention/integrations/adapters/storage/local.py +59 -0
- customer_retention/integrations/feature_store/__init__.py +47 -0
- customer_retention/integrations/feature_store/definitions.py +215 -0
- customer_retention/integrations/feature_store/manager.py +744 -0
- customer_retention/integrations/feature_store/registry.py +412 -0
- customer_retention/integrations/iteration/__init__.py +28 -0
- customer_retention/integrations/iteration/context.py +212 -0
- customer_retention/integrations/iteration/feedback_collector.py +184 -0
- customer_retention/integrations/iteration/orchestrator.py +168 -0
- customer_retention/integrations/iteration/recommendation_tracker.py +341 -0
- customer_retention/integrations/iteration/signals.py +212 -0
- customer_retention/integrations/llm_context/__init__.py +4 -0
- customer_retention/integrations/llm_context/context_builder.py +201 -0
- customer_retention/integrations/llm_context/prompts.py +100 -0
- customer_retention/integrations/streaming/__init__.py +103 -0
- customer_retention/integrations/streaming/batch_integration.py +149 -0
- customer_retention/integrations/streaming/early_warning_model.py +227 -0
- customer_retention/integrations/streaming/event_schema.py +214 -0
- customer_retention/integrations/streaming/online_store_writer.py +249 -0
- customer_retention/integrations/streaming/realtime_scorer.py +261 -0
- customer_retention/integrations/streaming/trigger_engine.py +293 -0
- customer_retention/integrations/streaming/window_aggregator.py +393 -0
- customer_retention/stages/__init__.py +0 -0
- customer_retention/stages/cleaning/__init__.py +9 -0
- customer_retention/stages/cleaning/base.py +28 -0
- customer_retention/stages/cleaning/missing_handler.py +160 -0
- customer_retention/stages/cleaning/outlier_handler.py +204 -0
- customer_retention/stages/deployment/__init__.py +28 -0
- customer_retention/stages/deployment/batch_scorer.py +106 -0
- customer_retention/stages/deployment/champion_challenger.py +299 -0
- customer_retention/stages/deployment/model_registry.py +182 -0
- customer_retention/stages/deployment/retraining_trigger.py +245 -0
- customer_retention/stages/features/__init__.py +73 -0
- customer_retention/stages/features/behavioral_features.py +266 -0
- customer_retention/stages/features/customer_segmentation.py +505 -0
- customer_retention/stages/features/feature_definitions.py +265 -0
- customer_retention/stages/features/feature_engineer.py +551 -0
- customer_retention/stages/features/feature_manifest.py +340 -0
- customer_retention/stages/features/feature_selector.py +239 -0
- customer_retention/stages/features/interaction_features.py +160 -0
- customer_retention/stages/features/temporal_features.py +243 -0
- customer_retention/stages/ingestion/__init__.py +9 -0
- customer_retention/stages/ingestion/load_result.py +32 -0
- customer_retention/stages/ingestion/loaders.py +195 -0
- customer_retention/stages/ingestion/source_registry.py +130 -0
- customer_retention/stages/modeling/__init__.py +31 -0
- customer_retention/stages/modeling/baseline_trainer.py +139 -0
- customer_retention/stages/modeling/cross_validator.py +125 -0
- customer_retention/stages/modeling/data_splitter.py +205 -0
- customer_retention/stages/modeling/feature_scaler.py +99 -0
- customer_retention/stages/modeling/hyperparameter_tuner.py +107 -0
- customer_retention/stages/modeling/imbalance_handler.py +282 -0
- customer_retention/stages/modeling/mlflow_logger.py +95 -0
- customer_retention/stages/modeling/model_comparator.py +149 -0
- customer_retention/stages/modeling/model_evaluator.py +138 -0
- customer_retention/stages/modeling/threshold_optimizer.py +131 -0
- customer_retention/stages/monitoring/__init__.py +37 -0
- customer_retention/stages/monitoring/alert_manager.py +328 -0
- customer_retention/stages/monitoring/drift_detector.py +201 -0
- customer_retention/stages/monitoring/performance_monitor.py +242 -0
- customer_retention/stages/preprocessing/__init__.py +5 -0
- customer_retention/stages/preprocessing/transformer_manager.py +284 -0
- customer_retention/stages/profiling/__init__.py +256 -0
- customer_retention/stages/profiling/categorical_distribution.py +269 -0
- customer_retention/stages/profiling/categorical_target_analyzer.py +274 -0
- customer_retention/stages/profiling/column_profiler.py +527 -0
- customer_retention/stages/profiling/distribution_analysis.py +483 -0
- customer_retention/stages/profiling/drift_detector.py +310 -0
- customer_retention/stages/profiling/feature_capacity.py +507 -0
- customer_retention/stages/profiling/pattern_analysis_config.py +513 -0
- customer_retention/stages/profiling/profile_result.py +212 -0
- customer_retention/stages/profiling/quality_checks.py +1632 -0
- customer_retention/stages/profiling/relationship_detector.py +256 -0
- customer_retention/stages/profiling/relationship_recommender.py +454 -0
- customer_retention/stages/profiling/report_generator.py +520 -0
- customer_retention/stages/profiling/scd_analyzer.py +151 -0
- customer_retention/stages/profiling/segment_analyzer.py +632 -0
- customer_retention/stages/profiling/segment_aware_outlier.py +265 -0
- customer_retention/stages/profiling/target_level_analyzer.py +217 -0
- customer_retention/stages/profiling/temporal_analyzer.py +388 -0
- customer_retention/stages/profiling/temporal_coverage.py +488 -0
- customer_retention/stages/profiling/temporal_feature_analyzer.py +692 -0
- customer_retention/stages/profiling/temporal_feature_engineer.py +703 -0
- customer_retention/stages/profiling/temporal_pattern_analyzer.py +636 -0
- customer_retention/stages/profiling/temporal_quality_checks.py +278 -0
- customer_retention/stages/profiling/temporal_target_analyzer.py +241 -0
- customer_retention/stages/profiling/text_embedder.py +87 -0
- customer_retention/stages/profiling/text_processor.py +115 -0
- customer_retention/stages/profiling/text_reducer.py +60 -0
- customer_retention/stages/profiling/time_series_profiler.py +303 -0
- customer_retention/stages/profiling/time_window_aggregator.py +376 -0
- customer_retention/stages/profiling/type_detector.py +382 -0
- customer_retention/stages/profiling/window_recommendation.py +288 -0
- customer_retention/stages/temporal/__init__.py +166 -0
- customer_retention/stages/temporal/access_guard.py +180 -0
- customer_retention/stages/temporal/cutoff_analyzer.py +235 -0
- customer_retention/stages/temporal/data_preparer.py +178 -0
- customer_retention/stages/temporal/point_in_time_join.py +134 -0
- customer_retention/stages/temporal/point_in_time_registry.py +148 -0
- customer_retention/stages/temporal/scenario_detector.py +163 -0
- customer_retention/stages/temporal/snapshot_manager.py +259 -0
- customer_retention/stages/temporal/synthetic_coordinator.py +66 -0
- customer_retention/stages/temporal/timestamp_discovery.py +531 -0
- customer_retention/stages/temporal/timestamp_manager.py +255 -0
- customer_retention/stages/transformation/__init__.py +13 -0
- customer_retention/stages/transformation/binary_handler.py +85 -0
- customer_retention/stages/transformation/categorical_encoder.py +245 -0
- customer_retention/stages/transformation/datetime_transformer.py +97 -0
- customer_retention/stages/transformation/numeric_transformer.py +181 -0
- customer_retention/stages/transformation/pipeline.py +257 -0
- customer_retention/stages/validation/__init__.py +60 -0
- customer_retention/stages/validation/adversarial_scoring_validator.py +205 -0
- customer_retention/stages/validation/business_sense_gate.py +173 -0
- customer_retention/stages/validation/data_quality_gate.py +235 -0
- customer_retention/stages/validation/data_validators.py +511 -0
- customer_retention/stages/validation/feature_quality_gate.py +183 -0
- customer_retention/stages/validation/gates.py +117 -0
- customer_retention/stages/validation/leakage_gate.py +352 -0
- customer_retention/stages/validation/model_validity_gate.py +213 -0
- customer_retention/stages/validation/pipeline_validation_runner.py +264 -0
- customer_retention/stages/validation/quality_scorer.py +544 -0
- customer_retention/stages/validation/rule_generator.py +57 -0
- customer_retention/stages/validation/scoring_pipeline_validator.py +446 -0
- customer_retention/stages/validation/timeseries_detector.py +769 -0
- customer_retention/transforms/__init__.py +47 -0
- customer_retention/transforms/artifact_store.py +50 -0
- customer_retention/transforms/executor.py +157 -0
- customer_retention/transforms/fitted.py +92 -0
- customer_retention/transforms/ops.py +148 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"id": "cell-0",
|
|
6
|
+
"metadata": {
|
|
7
|
+
"papermill": {
|
|
8
|
+
"duration": 0.001463,
|
|
9
|
+
"end_time": "2026-02-02T13:04:38.753851",
|
|
10
|
+
"exception": false,
|
|
11
|
+
"start_time": "2026-02-02T13:04:38.752388",
|
|
12
|
+
"status": "completed"
|
|
13
|
+
},
|
|
14
|
+
"tags": []
|
|
15
|
+
},
|
|
16
|
+
"source": [
|
|
17
|
+
"# View Documentation\n",
|
|
18
|
+
"\n",
|
|
19
|
+
"This notebook exports the previous notebook's HTML and lists all generated documentation files.\n",
|
|
20
|
+
"\n",
|
|
21
|
+
"Run this after completing the exploration sequence to ensure all notebooks are exported."
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"cell_type": "code",
|
|
26
|
+
"execution_count": null,
|
|
27
|
+
"id": "cell-1",
|
|
28
|
+
"metadata": {
|
|
29
|
+
"execution": {
|
|
30
|
+
"iopub.execute_input": "2026-02-02T13:04:38.756679Z",
|
|
31
|
+
"iopub.status.busy": "2026-02-02T13:04:38.756540Z",
|
|
32
|
+
"iopub.status.idle": "2026-02-02T13:04:39.108351Z",
|
|
33
|
+
"shell.execute_reply": "2026-02-02T13:04:39.107690Z"
|
|
34
|
+
},
|
|
35
|
+
"papermill": {
|
|
36
|
+
"duration": 0.354251,
|
|
37
|
+
"end_time": "2026-02-02T13:04:39.109163",
|
|
38
|
+
"exception": false,
|
|
39
|
+
"start_time": "2026-02-02T13:04:38.754912",
|
|
40
|
+
"status": "completed"
|
|
41
|
+
},
|
|
42
|
+
"tags": []
|
|
43
|
+
},
|
|
44
|
+
"outputs": [],
|
|
45
|
+
"source": [
|
|
46
|
+
"from customer_retention.analysis.notebook_progress import track_and_export_previous\n",
|
|
47
|
+
"track_and_export_previous(\"12_view_documentation.ipynb\")"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"cell_type": "code",
|
|
52
|
+
"execution_count": null,
|
|
53
|
+
"id": "cell-2",
|
|
54
|
+
"metadata": {
|
|
55
|
+
"execution": {
|
|
56
|
+
"iopub.execute_input": "2026-02-02T13:04:39.111866Z",
|
|
57
|
+
"iopub.status.busy": "2026-02-02T13:04:39.111687Z",
|
|
58
|
+
"iopub.status.idle": "2026-02-02T13:04:39.115457Z",
|
|
59
|
+
"shell.execute_reply": "2026-02-02T13:04:39.114883Z"
|
|
60
|
+
},
|
|
61
|
+
"papermill": {
|
|
62
|
+
"duration": 0.006137,
|
|
63
|
+
"end_time": "2026-02-02T13:04:39.116176",
|
|
64
|
+
"exception": false,
|
|
65
|
+
"start_time": "2026-02-02T13:04:39.110039",
|
|
66
|
+
"status": "completed"
|
|
67
|
+
},
|
|
68
|
+
"tags": []
|
|
69
|
+
},
|
|
70
|
+
"outputs": [],
|
|
71
|
+
"source": [
|
|
72
|
+
"from pathlib import Path\n",
|
|
73
|
+
"from customer_retention.core.config.experiments import get_notebook_experiments_dir\n",
|
|
74
|
+
"from customer_retention.analysis.notebook_html_exporter import check_exported_html\n",
|
|
75
|
+
"\n",
|
|
76
|
+
"docs_dir = get_notebook_experiments_dir() / \"docs\"\n",
|
|
77
|
+
"notebook_dir = Path(\"exploration_notebooks\")\n",
|
|
78
|
+
"\n",
|
|
79
|
+
"found, missing = check_exported_html(docs_dir, notebook_dir)\n",
|
|
80
|
+
"\n",
|
|
81
|
+
"print(f\"Exported HTML documentation: {len(found)} found, {len(missing)} missing\")\n",
|
|
82
|
+
"print(\"=\" * 60)\n",
|
|
83
|
+
"for p in found:\n",
|
|
84
|
+
" size_kb = p.stat().st_size / 1024\n",
|
|
85
|
+
" print(f\" ✓ {p.name:<45} {size_kb:>8.1f} KB\")\n",
|
|
86
|
+
"for stem in missing:\n",
|
|
87
|
+
" print(f\" ✗ {stem}\")\n"
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"cell_type": "code",
|
|
92
|
+
"execution_count": null,
|
|
93
|
+
"id": "8f7rbn1v0my",
|
|
94
|
+
"metadata": {
|
|
95
|
+
"execution": {
|
|
96
|
+
"iopub.execute_input": "2026-02-02T13:04:39.118583Z",
|
|
97
|
+
"iopub.status.busy": "2026-02-02T13:04:39.118469Z",
|
|
98
|
+
"iopub.status.idle": "2026-02-02T13:04:39.194067Z",
|
|
99
|
+
"shell.execute_reply": "2026-02-02T13:04:39.193202Z"
|
|
100
|
+
},
|
|
101
|
+
"papermill": {
|
|
102
|
+
"duration": 0.080946,
|
|
103
|
+
"end_time": "2026-02-02T13:04:39.197931",
|
|
104
|
+
"exception": false,
|
|
105
|
+
"start_time": "2026-02-02T13:04:39.116985",
|
|
106
|
+
"status": "completed"
|
|
107
|
+
},
|
|
108
|
+
"tags": []
|
|
109
|
+
},
|
|
110
|
+
"outputs": [],
|
|
111
|
+
"source": [
|
|
112
|
+
"from customer_retention.analysis.notebook_html_exporter import display_html_documentation\n",
|
|
113
|
+
"\n",
|
|
114
|
+
"display_html_documentation(docs_dir)\n"
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"metadata": {
|
|
119
|
+
"kernelspec": {
|
|
120
|
+
"display_name": "Python 3",
|
|
121
|
+
"language": "python",
|
|
122
|
+
"name": "python3"
|
|
123
|
+
},
|
|
124
|
+
"language_info": {
|
|
125
|
+
"codemirror_mode": {
|
|
126
|
+
"name": "ipython",
|
|
127
|
+
"version": 3
|
|
128
|
+
},
|
|
129
|
+
"file_extension": ".py",
|
|
130
|
+
"mimetype": "text/x-python",
|
|
131
|
+
"name": "python",
|
|
132
|
+
"nbconvert_exporter": "python",
|
|
133
|
+
"pygments_lexer": "ipython3",
|
|
134
|
+
"version": "3.12.4"
|
|
135
|
+
},
|
|
136
|
+
"papermill": {
|
|
137
|
+
"default_parameters": {},
|
|
138
|
+
"duration": 1.272056,
|
|
139
|
+
"end_time": "2026-02-02T13:04:39.465128",
|
|
140
|
+
"environment_variables": {},
|
|
141
|
+
"exception": null,
|
|
142
|
+
"input_path": "/Users/Vital/python/CustomerRetention/exploration_notebooks/12_view_documentation.ipynb",
|
|
143
|
+
"output_path": "/Users/Vital/python/CustomerRetention/exploration_notebooks/12_view_documentation.ipynb",
|
|
144
|
+
"parameters": {},
|
|
145
|
+
"start_time": "2026-02-02T13:04:38.193072",
|
|
146
|
+
"version": "2.6.0"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
"nbformat": 4,
|
|
150
|
+
"nbformat_minor": 5
|
|
151
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: churnkit
|
|
3
|
+
Version: 0.75.0a1
|
|
4
|
+
Summary: Structured ML framework for customer churn prediction -- from exploration notebooks to production pipelines, locally or on Databricks.
|
|
5
|
+
Project-URL: Homepage, https://github.com/aladjov/CR
|
|
6
|
+
Project-URL: Documentation, https://github.com/aladjov/CR/wiki
|
|
7
|
+
Project-URL: Repository, https://github.com/aladjov/CR
|
|
8
|
+
Project-URL: Issues, https://github.com/aladjov/CR/issues
|
|
9
|
+
Author: Customer Retention Contributors
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: churn-prediction,customer-retention,databricks,delta-lake,feast,machine-learning,mlflow,mlops
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: fsspec>=2023.0.0
|
|
25
|
+
Requires-Dist: ipykernel>=7.1.0
|
|
26
|
+
Requires-Dist: kaleido>=0.2.1
|
|
27
|
+
Requires-Dist: matplotlib>=3.7.0
|
|
28
|
+
Requires-Dist: pandas>=2.0.0
|
|
29
|
+
Requires-Dist: papermill>=2.4.0
|
|
30
|
+
Requires-Dist: plotly>=5.15.0
|
|
31
|
+
Requires-Dist: pyarrow>=12.0.0
|
|
32
|
+
Requires-Dist: pydantic>=2.0.0
|
|
33
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
34
|
+
Requires-Dist: rich>=13.0.0
|
|
35
|
+
Requires-Dist: s3fs>=2023.0.0
|
|
36
|
+
Requires-Dist: scipy>=1.10.0
|
|
37
|
+
Requires-Dist: seaborn>=0.12.0
|
|
38
|
+
Requires-Dist: statsmodels>=0.14.0
|
|
39
|
+
Requires-Dist: tabulate>=0.9.0
|
|
40
|
+
Provides-Extra: all
|
|
41
|
+
Requires-Dist: deltalake>=0.17.0; extra == 'all'
|
|
42
|
+
Requires-Dist: feast>=0.40.0; extra == 'all'
|
|
43
|
+
Requires-Dist: imbalanced-learn>=0.12.0; extra == 'all'
|
|
44
|
+
Requires-Dist: lightgbm>=4.0.0; extra == 'all'
|
|
45
|
+
Requires-Dist: mlflow>=2.10.0; extra == 'all'
|
|
46
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == 'all'
|
|
47
|
+
Requires-Dist: sentence-transformers>=2.7.0; extra == 'all'
|
|
48
|
+
Requires-Dist: xgboost>=2.0.0; extra == 'all'
|
|
49
|
+
Provides-Extra: all-shap
|
|
50
|
+
Requires-Dist: deltalake>=0.17.0; extra == 'all-shap'
|
|
51
|
+
Requires-Dist: feast>=0.40.0; extra == 'all-shap'
|
|
52
|
+
Requires-Dist: imbalanced-learn>=0.12.0; extra == 'all-shap'
|
|
53
|
+
Requires-Dist: lightgbm>=4.0.0; extra == 'all-shap'
|
|
54
|
+
Requires-Dist: llvmlite<0.46.0,>=0.43.0; (python_version < '3.11') and extra == 'all-shap'
|
|
55
|
+
Requires-Dist: llvmlite>=0.46.0; (python_version >= '3.11') and extra == 'all-shap'
|
|
56
|
+
Requires-Dist: mlflow>=2.10.0; extra == 'all-shap'
|
|
57
|
+
Requires-Dist: numba<0.63.0,>=0.59.0; (python_version < '3.11') and extra == 'all-shap'
|
|
58
|
+
Requires-Dist: numba>=0.63.0; (python_version >= '3.11') and extra == 'all-shap'
|
|
59
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == 'all-shap'
|
|
60
|
+
Requires-Dist: sentence-transformers>=2.7.0; extra == 'all-shap'
|
|
61
|
+
Requires-Dist: shap<0.50.0,>=0.44.0; (python_version < '3.11') and extra == 'all-shap'
|
|
62
|
+
Requires-Dist: shap>=0.50.0; (python_version >= '3.11') and extra == 'all-shap'
|
|
63
|
+
Requires-Dist: xgboost>=2.0.0; extra == 'all-shap'
|
|
64
|
+
Provides-Extra: delta
|
|
65
|
+
Requires-Dist: deltalake>=0.17.0; extra == 'delta'
|
|
66
|
+
Provides-Extra: dev
|
|
67
|
+
Requires-Dist: jinja2>=3.0.0; extra == 'dev'
|
|
68
|
+
Requires-Dist: nbconvert>=7.0.0; extra == 'dev'
|
|
69
|
+
Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
|
|
70
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
71
|
+
Requires-Dist: pytest-timeout>=2.2.0; extra == 'dev'
|
|
72
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
73
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
74
|
+
Provides-Extra: ml
|
|
75
|
+
Requires-Dist: deltalake>=0.17.0; extra == 'ml'
|
|
76
|
+
Requires-Dist: feast>=0.40.0; extra == 'ml'
|
|
77
|
+
Requires-Dist: imbalanced-learn>=0.12.0; extra == 'ml'
|
|
78
|
+
Requires-Dist: lightgbm>=4.0.0; extra == 'ml'
|
|
79
|
+
Requires-Dist: mlflow>=2.10.0; extra == 'ml'
|
|
80
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == 'ml'
|
|
81
|
+
Requires-Dist: xgboost>=2.0.0; extra == 'ml'
|
|
82
|
+
Provides-Extra: ml-cpu
|
|
83
|
+
Requires-Dist: deltalake>=0.17.0; extra == 'ml-cpu'
|
|
84
|
+
Requires-Dist: feast>=0.40.0; extra == 'ml-cpu'
|
|
85
|
+
Requires-Dist: imbalanced-learn>=0.12.0; extra == 'ml-cpu'
|
|
86
|
+
Requires-Dist: lightgbm>=4.0.0; extra == 'ml-cpu'
|
|
87
|
+
Requires-Dist: mlflow>=2.10.0; extra == 'ml-cpu'
|
|
88
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == 'ml-cpu'
|
|
89
|
+
Requires-Dist: torch>=2.2.0; extra == 'ml-cpu'
|
|
90
|
+
Requires-Dist: torchvision>=0.17.0; extra == 'ml-cpu'
|
|
91
|
+
Requires-Dist: xgboost>=2.0.0; extra == 'ml-cpu'
|
|
92
|
+
Provides-Extra: ml-cuda
|
|
93
|
+
Requires-Dist: deltalake>=0.17.0; extra == 'ml-cuda'
|
|
94
|
+
Requires-Dist: feast>=0.40.0; extra == 'ml-cuda'
|
|
95
|
+
Requires-Dist: imbalanced-learn>=0.12.0; extra == 'ml-cuda'
|
|
96
|
+
Requires-Dist: lightgbm>=4.0.0; extra == 'ml-cuda'
|
|
97
|
+
Requires-Dist: mlflow>=2.10.0; extra == 'ml-cuda'
|
|
98
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == 'ml-cuda'
|
|
99
|
+
Requires-Dist: torch>=2.2.0; extra == 'ml-cuda'
|
|
100
|
+
Requires-Dist: torchvision>=0.17.0; extra == 'ml-cuda'
|
|
101
|
+
Requires-Dist: xgboost>=2.0.0; extra == 'ml-cuda'
|
|
102
|
+
Provides-Extra: ml-shap
|
|
103
|
+
Requires-Dist: deltalake>=0.17.0; extra == 'ml-shap'
|
|
104
|
+
Requires-Dist: feast>=0.40.0; extra == 'ml-shap'
|
|
105
|
+
Requires-Dist: imbalanced-learn>=0.12.0; extra == 'ml-shap'
|
|
106
|
+
Requires-Dist: lightgbm>=4.0.0; extra == 'ml-shap'
|
|
107
|
+
Requires-Dist: llvmlite<0.46.0,>=0.43.0; (python_version < '3.11') and extra == 'ml-shap'
|
|
108
|
+
Requires-Dist: llvmlite>=0.46.0; (python_version >= '3.11') and extra == 'ml-shap'
|
|
109
|
+
Requires-Dist: mlflow>=2.10.0; extra == 'ml-shap'
|
|
110
|
+
Requires-Dist: numba<0.63.0,>=0.59.0; (python_version < '3.11') and extra == 'ml-shap'
|
|
111
|
+
Requires-Dist: numba>=0.63.0; (python_version >= '3.11') and extra == 'ml-shap'
|
|
112
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == 'ml-shap'
|
|
113
|
+
Requires-Dist: shap<0.50.0,>=0.44.0; (python_version < '3.11') and extra == 'ml-shap'
|
|
114
|
+
Requires-Dist: shap>=0.50.0; (python_version >= '3.11') and extra == 'ml-shap'
|
|
115
|
+
Requires-Dist: xgboost>=2.0.0; extra == 'ml-shap'
|
|
116
|
+
Provides-Extra: ml-shap-intel
|
|
117
|
+
Requires-Dist: deltalake>=0.17.0; extra == 'ml-shap-intel'
|
|
118
|
+
Requires-Dist: feast>=0.40.0; extra == 'ml-shap-intel'
|
|
119
|
+
Requires-Dist: imbalanced-learn>=0.12.0; extra == 'ml-shap-intel'
|
|
120
|
+
Requires-Dist: lightgbm>=4.0.0; extra == 'ml-shap-intel'
|
|
121
|
+
Requires-Dist: llvmlite<0.46.0,>=0.43.0; extra == 'ml-shap-intel'
|
|
122
|
+
Requires-Dist: mlflow>=2.10.0; extra == 'ml-shap-intel'
|
|
123
|
+
Requires-Dist: numba<0.63.0,>=0.59.0; extra == 'ml-shap-intel'
|
|
124
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == 'ml-shap-intel'
|
|
125
|
+
Requires-Dist: shap<0.50.0,>=0.44.0; extra == 'ml-shap-intel'
|
|
126
|
+
Requires-Dist: xgboost>=2.0.0; extra == 'ml-shap-intel'
|
|
127
|
+
Provides-Extra: text
|
|
128
|
+
Requires-Dist: sentence-transformers>=2.7.0; extra == 'text'
|
|
129
|
+
Description-Content-Type: text/markdown
|
|
130
|
+
|
|
131
|
+
# Customer Retention ML Framework
|
|
132
|
+
A structured backbone for the messy, iterative reality of ML model development. Exploration and production deployment are treated as parts of the same process -- not separate phases -- reflecting how data science actually works: you explore, decide, build, evaluate, learn something new, and circle back.
|
|
133
|
+
|
|
134
|
+
Handles both entity-level and event-level datasets. Experiments and production can share the same tables without copying data (Delta Lake), features are served consistently across training and inference (Feast / Feature Store), and every experiment is tracked and reproducible (MLflow). Runs locally or deploys to Databricks.
|
|
135
|
+
|
|
136
|
+
[](https://www.python.org/downloads/)
|
|
137
|
+
[](LICENSE)
|
|
138
|
+
[](https://github.com/aladjov/CR/actions/workflows/ci.yaml)
|
|
139
|
+
[](https://codecov.io/gh/aladjov/CR)
|
|
140
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
141
|
+
[](https://docs.pydantic.dev/)
|
|
142
|
+
[](https://mlflow.org/)
|
|
143
|
+
[](https://databricks.com/)
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Why This Exists
|
|
148
|
+
|
|
149
|
+
Most ML tutorials jump straight to `model.fit()`. Real projects fail earlier -- in data issues you didn't notice, leakage you didn't check for, or feature choices you can't explain to your stakeholders three months later. This framework tries to close that gap.
|
|
150
|
+
|
|
151
|
+
It serves two audiences:
|
|
152
|
+
|
|
153
|
+
1. **If you're learning**, the notebooks walk through a realistic end-to-end process and explain the reasoning behind each step. Why does a 93-day median inter-event gap rule out short aggregation windows? Why might the model that wins validation degrade in production? The goal is to build intuition for the decisions that don't appear in textbooks.
|
|
154
|
+
|
|
155
|
+
2. **If you're experienced**, you can `pip install`, point to a new dataset, and get an opinionated exploration scaffold. The output is loosely-coupled production code (Bronze / Silver / Gold) with the provenance of every decision captured in self-contained HTML documentation -- useful when you need to explain *why* the pipeline does what it does.
|
|
156
|
+
|
|
157
|
+
### The approach
|
|
158
|
+
|
|
159
|
+
- **Exploration is a first-class concept.** The framework records what it found in the data, what it recommends, and why -- in versioned YAML artifacts. Each downstream transformation traces back to a specific observation in a specific notebook, so nothing happens without a documented reason.
|
|
160
|
+
- **Experimentation is version-controlled end to end.** Not just code and features, but the actual data observations and actions taken on them can be frozen in time together. Delta tables support time-travel on live production datasets, so you can always go back to what the data looked like when a decision was made.
|
|
161
|
+
- **Iteration is the default.** Model feedback -- feature importances, error analysis, drift signals -- feeds back into the next exploration cycle. The framework tracks iteration lineage rather than treating each experiment as independent.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Quick Start
|
|
166
|
+
|
|
167
|
+
### 1. Install
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pip install "churnkit[ml]"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 2. Bootstrap notebooks into your project
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
churnkit-init --output ./my_project
|
|
177
|
+
cd my_project
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 3. Point to your data
|
|
181
|
+
|
|
182
|
+
Open `exploration_notebooks/01_data_discovery.ipynb` and set the data path:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
DATA_PATH = "experiments/data/your_file.csv" # csv, parquet, or delta
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 4. Run
|
|
189
|
+
|
|
190
|
+
Execute cells sequentially. The framework auto-detects column types, data granularity (entity vs event-level), text columns, and temporal patterns -- then routes you through the relevant notebooks.
|
|
191
|
+
|
|
192
|
+
Findings, recommendations, and production pipeline specs are generated as you go.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Learn More
|
|
197
|
+
|
|
198
|
+
Detailed documentation lives in the [Wiki](https://github.com/aladjov/CR/wiki):
|
|
199
|
+
|
|
200
|
+
| Topic | Wiki Page |
|
|
201
|
+
|-------|-----------|
|
|
202
|
+
| Installation options & environment setup | [Getting Started](https://github.com/aladjov/CR/wiki/Getting-Started) |
|
|
203
|
+
| Medallion architecture & system design | [Architecture](https://github.com/aladjov/CR/wiki/Architecture) |
|
|
204
|
+
| Notebook workflow & iteration tracking | [Exploration Loop](https://github.com/aladjov/CR/wiki/Exploration-Loop) |
|
|
205
|
+
| Leakage-safe temporal data preparation | [Temporal Framework](https://github.com/aladjov/CR/wiki/Temporal-Framework) |
|
|
206
|
+
| Feast & Databricks feature management | [Feature Store](https://github.com/aladjov/CR/wiki/Feature-Store) |
|
|
207
|
+
| Local execution with Feast + MLFlow | [Local Track](https://github.com/aladjov/CR/wiki/Local-Track) |
|
|
208
|
+
| Databricks with Unity Catalog + Delta Lake | [Databricks Track](https://github.com/aladjov/CR/wiki/Databricks-Track) |
|
|
209
|
+
|
|
210
|
+
### Tutorials
|
|
211
|
+
|
|
212
|
+
| Tutorial | What it walks through |
|
|
213
|
+
|----------|-----------------------|
|
|
214
|
+
| [Retail Customer Retention](https://github.com/aladjov/CR/wiki/Tutorial-Retail-Churn) | Entity-level data: point-in-time snapshots, quality assessment, baseline models, and a production scoring check that reveals how distribution drift affects different model families -- [browse HTML](https://aladjov.github.io/CR/tutorial/retail-churn/) |
|
|
215
|
+
| [Customer Email Engagement](https://github.com/aladjov/CR/wiki/Tutorial-Customer-Emails) | Event-level data: temporal window selection driven by inter-event cadence, aggregating 83K email events into customer-level features, and tracing each decision from data observation to production pipeline -- [browse HTML](https://aladjov.github.io/CR/tutorial/customer-emails/) |
|
|
216
|
+
| [Bank Customer Churn](https://github.com/aladjov/CR/wiki/Tutorial-Bank-Churn) | Dataset setup instructions |
|
|
217
|
+
| [Netflix Churn](https://github.com/aladjov/CR/wiki/Tutorial-Netflix-Churn) | Dataset setup instructions |
|
|
218
|
+
|
|
219
|
+
### [Acknowledgments](https://github.com/aladjov/CR/wiki/Acknowledgments)
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Contributing
|
|
224
|
+
|
|
225
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
Apache 2.0 -- See [LICENSE](LICENSE) for details.
|