churnkit 1.3.2a0__tar.gz → 1.3.2a2__tar.gz

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 (524) hide show
  1. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/PKG-INFO +1 -1
  2. churnkit-1.3.2a2/apps/databricks_app/src/archetypes_view.py +445 -0
  3. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/data.py +151 -0
  4. churnkit-1.3.2a2/apps/databricks_app/src/playbooks_view.py +346 -0
  5. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/theme.css +241 -0
  6. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/causal_notebooks/c01_publish_definitions.ipynb +118 -6
  7. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/causal_notebooks/c02_archetype_derivation.ipynb +43 -10
  8. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/causal_notebooks/c03_approval_gate.ipynb +104 -6
  9. churnkit-1.3.2a2/causal_notebooks/c04_batch_inference.ipynb +549 -0
  10. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/causal_notebooks/c05_snapshot_and_dashboard.ipynb +153 -13
  11. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/pyproject.toml +1 -1
  12. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/__init__.py +1 -1
  13. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/playbook_loader.py +9 -0
  14. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/schemas.py +10 -0
  15. churnkit-1.3.2a0/causal_notebooks/c04_batch_inference.ipynb +0 -368
  16. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/.gitignore +0 -0
  17. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/LICENSE +0 -0
  18. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/README.md +0 -0
  19. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/__init__.py +0 -0
  20. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/accounts_view.py +0 -0
  21. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/archetype_view.py +0 -0
  22. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/auth.py +0 -0
  23. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/config.py +0 -0
  24. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/customer_profile.py +0 -0
  25. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/default_profile.css +0 -0
  26. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/default_profile.html +0 -0
  27. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/masthead.py +0 -0
  28. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/state.py +0 -0
  29. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/template.py +0 -0
  30. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/apps/databricks_app/src/treemap.py +0 -0
  31. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/causal_notebooks/_diagnostic_scoring_probe.py +0 -0
  32. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/constraints/.gitkeep +0 -0
  33. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/-1_parity_contract.ipynb +0 -0
  34. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/-1_sample_datasets.ipynb +0 -0
  35. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/00_start_here.ipynb +0 -0
  36. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/01_data_discovery.ipynb +0 -0
  37. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/01a_a_temporal_text_deep_dive.ipynb +0 -0
  38. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/01a_temporal_deep_dive.ipynb +0 -0
  39. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/01b_temporal_quality.ipynb +0 -0
  40. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/01c_temporal_patterns.ipynb +0 -0
  41. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/01d_event_aggregation.ipynb +0 -0
  42. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/02_source_integrity.ipynb +0 -0
  43. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/03_dataset_merge.ipynb +0 -0
  44. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/04_column_deep_dive.ipynb +0 -0
  45. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/04a_text_columns_deep_dive.ipynb +0 -0
  46. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/05_relationship_analysis.ipynb +0 -0
  47. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/06_feature_opportunities.ipynb +0 -0
  48. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/07_modeling_readiness.ipynb +0 -0
  49. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/08_baseline_experiments.ipynb +0 -0
  50. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/09_business_alignment.ipynb +0 -0
  51. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/10_spec_generation.ipynb +0 -0
  52. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/11_scoring_validation.ipynb +0 -0
  53. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/exploration_notebooks/12_view_documentation.ipynb +0 -0
  54. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/.claude/settings.local.json +0 -0
  55. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/README.md +0 -0
  56. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/build_framework_phase_map.py +0 -0
  57. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/data/create_snapshot.py +0 -0
  58. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/data/generate_edi_ticketing_dataset.py +0 -0
  59. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/data/generate_retail_dataset.py +0 -0
  60. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/data/generate_test_data.py +0 -0
  61. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/data/migrate_parquet_to_delta.py +0 -0
  62. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/data/migrate_to_temporal.py +0 -0
  63. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/databricks/build_wheel.sh +0 -0
  64. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/databricks/capture_runtime.py +0 -0
  65. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/databricks/dbr_init.sh +0 -0
  66. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/databricks/deploy_dev.py +0 -0
  67. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/databricks/generate_constraints.py +0 -0
  68. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/databricks/notebook_setup.py +0 -0
  69. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/experiments/capture_notebook_outputs.py +0 -0
  70. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/experiments/cell_profiling.py +0 -0
  71. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/experiments/compare_exploration_runs.py +0 -0
  72. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/experiments/compare_versions.sh +0 -0
  73. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/experiments/patch_dataset_config.py +0 -0
  74. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/generate_requirements.py +0 -0
  75. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/build_causal_notebooks.py +0 -0
  76. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/clean_notebook_outputs.py +0 -0
  77. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/export_tutorial_html.py +0 -0
  78. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/init_project.py +0 -0
  79. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/migrate_notebook_cell_ids.py +0 -0
  80. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/plotly_image_preprocessor.py +0 -0
  81. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/run_exploration.py +0 -0
  82. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/sync_notebooks.py +0 -0
  83. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/tag_framework_cells.py +0 -0
  84. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/tag_markdown_cells.py +0 -0
  85. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/notebooks/test_notebooks.py +0 -0
  86. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/release.sh +0 -0
  87. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/seed_playbooks_volume.py +0 -0
  88. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/templates/tutorial_html/conf.json +0 -0
  89. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/templates/tutorial_html/index.html.j2 +0 -0
  90. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/scripts/update_notebook_paths.py +0 -0
  91. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/__init__.py +0 -0
  92. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/__init__.py +0 -0
  93. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/active_dataset_store.py +0 -0
  94. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/analysis_context.py +0 -0
  95. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/column_describer.py +0 -0
  96. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/dataset_fingerprinter.py +0 -0
  97. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/entity_timestamp_deriver.py +0 -0
  98. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/exploration_manager.py +0 -0
  99. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/explorer.py +0 -0
  100. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/field_availability_audit.py +0 -0
  101. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/findings.py +0 -0
  102. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/intent_defaults.py +0 -0
  103. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/key_resolver.py +0 -0
  104. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/layered_recommendations.py +0 -0
  105. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/objective_support_communicator.py +0 -0
  106. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/prediction_objective_detector.py +0 -0
  107. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/project_context.py +0 -0
  108. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/recommendation_builder.py +0 -0
  109. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/recommendations.py +0 -0
  110. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/run_namespace.py +0 -0
  111. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/sampling.py +0 -0
  112. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/schema_report.py +0 -0
  113. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/service_unit_detector.py +0 -0
  114. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/session.py +0 -0
  115. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/skip_logic.py +0 -0
  116. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/auto_explorer/snapshot_grid.py +0 -0
  117. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/business/__init__.py +0 -0
  118. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/business/ab_test_designer.py +0 -0
  119. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/business/fairness_analyzer.py +0 -0
  120. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/business/intervention_matcher.py +0 -0
  121. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/business/ratio_features.py +0 -0
  122. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/business/report_generator.py +0 -0
  123. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/business/risk_profile.py +0 -0
  124. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/business/roi_analyzer.py +0 -0
  125. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/__init__.py +0 -0
  126. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/calibration_analyzer.py +0 -0
  127. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/cv_analyzer.py +0 -0
  128. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/error_analyzer.py +0 -0
  129. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/exploration_ledger.py +0 -0
  130. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/feature_provenance.py +0 -0
  131. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/feature_stability.py +0 -0
  132. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/leakage_detector.py +0 -0
  133. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/model_diagnostics_report.py +0 -0
  134. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/noise_tester.py +0 -0
  135. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/overfitting_analyzer.py +0 -0
  136. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/parity_report.py +0 -0
  137. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/diagnostics/segment_analyzer.py +0 -0
  138. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/discovery/__init__.py +0 -0
  139. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/discovery/config_generator.py +0 -0
  140. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/discovery/discovery_flow.py +0 -0
  141. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/discovery/type_inferencer.py +0 -0
  142. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/interpretability/__init__.py +0 -0
  143. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/interpretability/cohort_analyzer.py +0 -0
  144. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/interpretability/counterfactual.py +0 -0
  145. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/interpretability/individual_explainer.py +0 -0
  146. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/interpretability/pdp_generator.py +0 -0
  147. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/interpretability/shap_explainer.py +0 -0
  148. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/jupyter_save_hook.py +0 -0
  149. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/notebook_html_exporter.py +0 -0
  150. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/notebook_progress.py +0 -0
  151. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/plotly_preprocessor.py +0 -0
  152. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/__init__.py +0 -0
  153. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/base.py +0 -0
  154. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/cleaning/__init__.py +0 -0
  155. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/cleaning/consistency.py +0 -0
  156. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/cleaning/deduplicate.py +0 -0
  157. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/cleaning/impute.py +0 -0
  158. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/cleaning/outlier.py +0 -0
  159. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/datetime/__init__.py +0 -0
  160. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/datetime/extract.py +0 -0
  161. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/encoding/__init__.py +0 -0
  162. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/encoding/categorical.py +0 -0
  163. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/pipeline.py +0 -0
  164. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/registry.py +0 -0
  165. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/selection/__init__.py +0 -0
  166. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/selection/drop_column.py +0 -0
  167. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/transform/__init__.py +0 -0
  168. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/transform/power.py +0 -0
  169. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/recommendations/transform/scale.py +0 -0
  170. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/visualization/__init__.py +0 -0
  171. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/visualization/attention_scorer.py +0 -0
  172. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/visualization/chart_builder.py +0 -0
  173. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/visualization/column_paginator.py +0 -0
  174. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/visualization/console.py +0 -0
  175. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/visualization/display.py +0 -0
  176. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/analysis/visualization/number_formatter.py +0 -0
  177. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/artifacts/__init__.py +0 -0
  178. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/artifacts/fit_artifact_registry.py +0 -0
  179. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/cli.py +0 -0
  180. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/__init__.py +0 -0
  181. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/compat/__init__.py +0 -0
  182. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/compat/bulk_profiling.py +0 -0
  183. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/compat/cell_profiling_hooks.py +0 -0
  184. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/compat/detection.py +0 -0
  185. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/compat/ops.py +0 -0
  186. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/compat/pandas_backend.py +0 -0
  187. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/compat/remote_path.py +0 -0
  188. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/compat/spark_backend.py +0 -0
  189. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/compat/timing.py +0 -0
  190. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/__init__.py +0 -0
  191. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/base.py +0 -0
  192. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/components/__init__.py +0 -0
  193. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/components/deployer.py +0 -0
  194. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/components/explainer.py +0 -0
  195. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/components/feature_eng.py +0 -0
  196. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/components/ingester.py +0 -0
  197. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/components/profiler.py +0 -0
  198. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/components/trainer.py +0 -0
  199. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/components/transformer.py +0 -0
  200. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/components/validator.py +0 -0
  201. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/enums.py +0 -0
  202. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/orchestrator.py +0 -0
  203. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/components/registry.py +0 -0
  204. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/config/__init__.py +0 -0
  205. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/config/column_config.py +0 -0
  206. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/config/experiments.py +0 -0
  207. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/config/pipeline_config.py +0 -0
  208. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/config/source_config.py +0 -0
  209. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/naming.py +0 -0
  210. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/utils/__init__.py +0 -0
  211. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/utils/leakage.py +0 -0
  212. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/utils/severity.py +0 -0
  213. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/core/utils/statistics.py +0 -0
  214. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/__init__.py +0 -0
  215. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/__init__.py +0 -0
  216. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/base.py +0 -0
  217. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/cell_builder.py +0 -0
  218. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/config.py +0 -0
  219. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/databricks_generator.py +0 -0
  220. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/local_generator.py +0 -0
  221. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/project_init.py +0 -0
  222. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/runner.py +0 -0
  223. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/scoring_replay.py +0 -0
  224. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/script_generator.py +0 -0
  225. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/__init__.py +0 -0
  226. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/base_stage.py +0 -0
  227. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/causal_setup_cell.py +0 -0
  228. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s01_ingestion.py +0 -0
  229. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s02_profiling.py +0 -0
  230. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s03_cleaning.py +0 -0
  231. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s04_transformation.py +0 -0
  232. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s05_feature_engineering.py +0 -0
  233. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s06_feature_selection.py +0 -0
  234. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s07_model_training.py +0 -0
  235. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s08_deployment.py +0 -0
  236. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s09_monitoring.py +0 -0
  237. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s10_batch_inference.py +0 -0
  238. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s11_feature_store.py +0 -0
  239. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s_c01_publish_definitions.py +0 -0
  240. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s_c02_archetype_derivation.py +0 -0
  241. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s_c03_approval_gate.py +0 -0
  242. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s_c04_batch_inference.py +0 -0
  243. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_generator/stages/s_c05_snapshot_and_dashboard.py +0 -0
  244. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_merge/__init__.py +0 -0
  245. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_merge/cli.py +0 -0
  246. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_merge/config_merger.py +0 -0
  247. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_merge/config_parser.py +0 -0
  248. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_merge/conflict.py +0 -0
  249. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_merge/merge_engine.py +0 -0
  250. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_merge/merge_report.py +0 -0
  251. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_sync/__init__.py +0 -0
  252. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_sync/cell_id_standardizer.py +0 -0
  253. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_sync/cell_types.py +0 -0
  254. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_sync/cli.py +0 -0
  255. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_sync/sync_engine.py +0 -0
  256. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/notebook_sync/sync_report.py +0 -0
  257. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/orchestration/__init__.py +0 -0
  258. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/orchestration/code_generator.py +0 -0
  259. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/orchestration/context.py +0 -0
  260. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/orchestration/data_materializer.py +0 -0
  261. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/orchestration/databricks_exporter.py +0 -0
  262. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/orchestration/doc_generator.py +0 -0
  263. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/__init__.py +0 -0
  264. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/databricks_generator.py +0 -0
  265. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/databricks_renderer.py +0 -0
  266. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/exploration_generator.py +0 -0
  267. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/findings_parser.py +0 -0
  268. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/generation_manifest.py +0 -0
  269. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/generator.py +0 -0
  270. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/gold_transform_applicator.py +0 -0
  271. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/llm_docs_generator.py +0 -0
  272. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/models.py +0 -0
  273. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/override_merge.py +0 -0
  274. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/protocols.py +0 -0
  275. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/renderer.py +0 -0
  276. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/pipeline_generator/user_extensions_emitter.py +0 -0
  277. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/spec_generator/__init__.py +0 -0
  278. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/generators/spec_generator/mlflow_pipeline_generator.py +0 -0
  279. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/__init__.py +0 -0
  280. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/__init__.py +0 -0
  281. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/base.py +0 -0
  282. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/factory.py +0 -0
  283. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/feature_store/__init__.py +0 -0
  284. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/feature_store/base.py +0 -0
  285. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/feature_store/databricks.py +0 -0
  286. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/feature_store/feast_adapter.py +0 -0
  287. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/feature_store/local.py +0 -0
  288. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/mlflow/__init__.py +0 -0
  289. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/mlflow/base.py +0 -0
  290. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/mlflow/databricks.py +0 -0
  291. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/mlflow/experiment_tracker.py +0 -0
  292. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/mlflow/local.py +0 -0
  293. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/storage/__init__.py +0 -0
  294. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/storage/base.py +0 -0
  295. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/storage/databricks.py +0 -0
  296. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/adapters/storage/local.py +0 -0
  297. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/databricks_init.py +0 -0
  298. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/databricks_job_capture.py +0 -0
  299. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/feature_store/__init__.py +0 -0
  300. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/feature_store/definitions.py +0 -0
  301. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/feature_store/manager.py +0 -0
  302. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/feature_store/registry.py +0 -0
  303. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/iteration/__init__.py +0 -0
  304. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/iteration/context.py +0 -0
  305. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/iteration/feedback_collector.py +0 -0
  306. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/iteration/orchestrator.py +0 -0
  307. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/iteration/recommendation_tracker.py +0 -0
  308. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/iteration/signals.py +0 -0
  309. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/llm_context/__init__.py +0 -0
  310. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/llm_context/context_builder.py +0 -0
  311. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/llm_context/prompts.py +0 -0
  312. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/requirements_generator.py +0 -0
  313. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/streaming/__init__.py +0 -0
  314. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/streaming/batch_integration.py +0 -0
  315. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/streaming/early_warning_model.py +0 -0
  316. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/streaming/event_schema.py +0 -0
  317. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/streaming/online_store_writer.py +0 -0
  318. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/streaming/realtime_scorer.py +0 -0
  319. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/streaming/trigger_engine.py +0 -0
  320. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/integrations/streaming/window_aggregator.py +0 -0
  321. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/parity/__init__.py +0 -0
  322. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/parity/audit.py +0 -0
  323. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/parity/decorator.py +0 -0
  324. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/parity/exploration_scan.py +0 -0
  325. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/parity/gaps.py +0 -0
  326. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/parity/kinds.py +0 -0
  327. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/parity/manifest.py +0 -0
  328. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/parity/production_scan.py +0 -0
  329. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/parity/schedule.py +0 -0
  330. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/parity/trace.py +0 -0
  331. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/__init__.py +0 -0
  332. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/api.py +0 -0
  333. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/dataset_resolution.py +0 -0
  334. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/decorator.py +0 -0
  335. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/flags.py +0 -0
  336. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/harvest.py +0 -0
  337. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/logging.py +0 -0
  338. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/persistence.py +0 -0
  339. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/registry.py +0 -0
  340. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/replay.py +0 -0
  341. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/summary.py +0 -0
  342. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/runtime/validation.py +0 -0
  343. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/__init__.py +0 -0
  344. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/__init__.py +0 -0
  345. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/approval_gate.py +0 -0
  346. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/clusterer.py +0 -0
  347. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/column_descriptions_writer.py +0 -0
  348. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/dashboard_profile_override.py +0 -0
  349. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/dashboard_views.py +0 -0
  350. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/delta_writer.py +0 -0
  351. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/derivation.py +0 -0
  352. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/feature_meta_writer.py +0 -0
  353. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/__init__.py +0 -0
  354. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/archetype_context.py +0 -0
  355. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/business_phrase.py +0 -0
  356. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/discovery.py +0 -0
  357. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/enrichment_pipeline.py +0 -0
  358. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/feature_meta_builder.py +0 -0
  359. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/llm_prompt.py +0 -0
  360. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/markdown_bootstrap.py +0 -0
  361. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/predicate_prose.py +0 -0
  362. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/prose_backfill.py +0 -0
  363. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/quantile_phrasing.py +0 -0
  364. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/interpretation/sidecars.py +0 -0
  365. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/llm_namer.py +0 -0
  366. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/playbook_mapper.py +0 -0
  367. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/policy_loader.py +0 -0
  368. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/population_stats.py +0 -0
  369. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/predicate_compiler.py +0 -0
  370. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/rule_extractor.py +0 -0
  371. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/run_context_writer.py +0 -0
  372. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/seed_yamls/decision_policy.yaml +0 -0
  373. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/seed_yamls/response_schemas.yaml +0 -0
  374. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/seed_yamls/vocabularies.yaml +0 -0
  375. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/shap_runner.py +0 -0
  376. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/snapshot_writer.py +0 -0
  377. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/sql/dashboard_views.sql +0 -0
  378. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/causal/top_drivers_writer.py +0 -0
  379. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/cleaning/__init__.py +0 -0
  380. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/cleaning/base.py +0 -0
  381. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/cleaning/missing_handler.py +0 -0
  382. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/cleaning/outlier_handler.py +0 -0
  383. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/deployment/__init__.py +0 -0
  384. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/deployment/batch_scorer.py +0 -0
  385. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/deployment/champion_challenger.py +0 -0
  386. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/deployment/model_registry.py +0 -0
  387. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/deployment/retraining_trigger.py +0 -0
  388. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/features/__init__.py +0 -0
  389. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/features/behavioral_features.py +0 -0
  390. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/features/customer_segmentation.py +0 -0
  391. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/features/feature_definitions.py +0 -0
  392. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/features/feature_engineer.py +0 -0
  393. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/features/feature_manifest.py +0 -0
  394. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/features/feature_selector.py +0 -0
  395. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/features/interaction_features.py +0 -0
  396. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/features/temporal_features.py +0 -0
  397. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/ingestion/__init__.py +0 -0
  398. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/ingestion/load_result.py +0 -0
  399. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/ingestion/loaders.py +0 -0
  400. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/ingestion/source_registry.py +0 -0
  401. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/lifecycle/__init__.py +0 -0
  402. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/lifecycle/config.py +0 -0
  403. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/lifecycle/enrich.py +0 -0
  404. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/lifecycle/validation.py +0 -0
  405. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/__init__.py +0 -0
  406. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/baseline_trainer.py +0 -0
  407. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/cross_validator.py +0 -0
  408. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/data_splitter.py +0 -0
  409. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/feature_profile.py +0 -0
  410. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/feature_scaler.py +0 -0
  411. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/feature_spec.py +0 -0
  412. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/hyperparameter_tuner.py +0 -0
  413. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/imbalance_handler.py +0 -0
  414. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/mlflow_logger.py +0 -0
  415. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/model_comparator.py +0 -0
  416. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/model_evaluator.py +0 -0
  417. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/shap_attribution.py +0 -0
  418. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/spark_baseline_trainer.py +0 -0
  419. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/spark_classifier_wrapper.py +0 -0
  420. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/spark_feature_scaler.py +0 -0
  421. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/threshold_optimizer.py +0 -0
  422. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/modeling/training_preparator.py +0 -0
  423. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/monitoring/__init__.py +0 -0
  424. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/monitoring/alert_manager.py +0 -0
  425. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/monitoring/drift_detector.py +0 -0
  426. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/monitoring/performance_monitor.py +0 -0
  427. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/preprocessing/__init__.py +0 -0
  428. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/preprocessing/transformer_manager.py +0 -0
  429. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/__init__.py +0 -0
  430. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/case_collision.py +0 -0
  431. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/categorical_distribution.py +0 -0
  432. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/categorical_target_analyzer.py +0 -0
  433. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/column_profiler.py +0 -0
  434. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/distribution_analysis.py +0 -0
  435. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/drift_detector.py +0 -0
  436. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/feature_capacity.py +0 -0
  437. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/pattern_analysis_config.py +0 -0
  438. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/profile_result.py +0 -0
  439. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/quality_checks.py +0 -0
  440. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/recommendation_filter.py +0 -0
  441. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/relationship_detector.py +0 -0
  442. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/relationship_recommender.py +0 -0
  443. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/report_generator.py +0 -0
  444. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/scd_analyzer.py +0 -0
  445. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/segment_analyzer.py +0 -0
  446. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/segment_aware_outlier.py +0 -0
  447. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/spark_segment_analyzer.py +0 -0
  448. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/spark_temporal_feature_analyzer.py +0 -0
  449. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/spark_temporal_feature_engineer.py +0 -0
  450. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/spark_time_window_aggregator.py +0 -0
  451. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/stats_helpers.py +0 -0
  452. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/target_level_analyzer.py +0 -0
  453. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/target_validator.py +0 -0
  454. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/temporal_analyzer.py +0 -0
  455. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/temporal_coverage.py +0 -0
  456. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/temporal_feature_analyzer.py +0 -0
  457. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/temporal_feature_engineer.py +0 -0
  458. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/temporal_pattern_analyzer.py +0 -0
  459. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/temporal_quality_checks.py +0 -0
  460. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/temporal_target_analyzer.py +0 -0
  461. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/text_embedder.py +0 -0
  462. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/text_processor.py +0 -0
  463. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/text_reducer.py +0 -0
  464. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/time_series_profiler.py +0 -0
  465. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/time_window_aggregator.py +0 -0
  466. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/type_detector.py +0 -0
  467. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/profiling/window_recommendation.py +0 -0
  468. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scd_history/__init__.py +0 -0
  469. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scd_history/augment.py +0 -0
  470. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scd_history/config.py +0 -0
  471. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scd_history/reconstruct.py +0 -0
  472. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scd_history/validation.py +0 -0
  473. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scoring/__init__.py +0 -0
  474. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scoring/batch_inference.py +0 -0
  475. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scoring/config.py +0 -0
  476. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scoring/data_loader.py +0 -0
  477. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scoring/exceptions.py +0 -0
  478. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/scoring/pipeline_discovery.py +0 -0
  479. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/__init__.py +0 -0
  480. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/access_guard.py +0 -0
  481. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/data_preparer.py +0 -0
  482. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/point_in_time_join.py +0 -0
  483. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/point_in_time_registry.py +0 -0
  484. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/scenario_detector.py +0 -0
  485. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/snapshot_manager.py +0 -0
  486. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/spark_temporal_merger.py +0 -0
  487. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/synthetic_coordinator.py +0 -0
  488. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/temporal_merger.py +0 -0
  489. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/timestamp_discovery.py +0 -0
  490. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/temporal/timestamp_manager.py +0 -0
  491. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/transformation/__init__.py +0 -0
  492. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/transformation/binary_handler.py +0 -0
  493. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/transformation/categorical_encoder.py +0 -0
  494. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/transformation/datetime_transformer.py +0 -0
  495. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/transformation/numeric_transformer.py +0 -0
  496. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/transformation/pipeline.py +0 -0
  497. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/__init__.py +0 -0
  498. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/adversarial_scoring_validator.py +0 -0
  499. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/business_sense_gate.py +0 -0
  500. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/data_quality_gate.py +0 -0
  501. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/data_validators.py +0 -0
  502. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/feature_quality_gate.py +0 -0
  503. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/gates.py +0 -0
  504. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/leakage_gate.py +0 -0
  505. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/model_validity_gate.py +0 -0
  506. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/pipeline_validation_runner.py +0 -0
  507. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/quality_scorer.py +0 -0
  508. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/rule_generator.py +0 -0
  509. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/scoring_pipeline_validator.py +0 -0
  510. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/stages/validation/timeseries_detector.py +0 -0
  511. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/transforms/__init__.py +0 -0
  512. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/transforms/artifact_store.py +0 -0
  513. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/transforms/executor.py +0 -0
  514. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/transforms/fitted.py +0 -0
  515. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/transforms/ops.py +0 -0
  516. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/src/customer_retention/transforms/spark_ops.py +0 -0
  517. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/tests/exploration_notebooks/test_distributed_dtype_safety.py +0 -0
  518. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/tests/exploration_notebooks/test_nb02_case_collision_detection.py +0 -0
  519. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/tests/exploration_notebooks/test_nb06_ratio_registration.py +0 -0
  520. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/tests/exploration_notebooks/test_nb08_categorical_baseline.py +0 -0
  521. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/tests/exploration_notebooks/test_nb08_early_drops.py +0 -0
  522. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/tests/exploration_notebooks/test_nb10_run_pipeline_ordering.py +0 -0
  523. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/tests/scripts/__init__.py +0 -0
  524. {churnkit-1.3.2a0 → churnkit-1.3.2a2}/tests/scripts/test_build_framework_phase_map.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: churnkit
3
- Version: 1.3.2a0
3
+ Version: 1.3.2a2
4
4
  Summary: Structured ML framework for customer churn prediction -- from exploration notebooks to production pipelines, locally or on Databricks.
5
5
  Project-URL: Homepage, https://github.com/aladjov/CR
6
6
  Project-URL: Documentation, https://github.com/aladjov/CR/wiki
@@ -0,0 +1,445 @@
1
+ """Archetypes tab — master table of model-derived archetypes + readonly detail.
2
+
3
+ Master row click reveals a complex profile: summary header (cluster size,
4
+ mean churn, derivation), prose blocks (description, rationale), then the
5
+ top SHAP drivers split into positive (risk-pushing) and negative
6
+ (protective) lists. Each driver shows the readable business phrase on
7
+ the surface; an Expander reveals the raw technical metadata (column name,
8
+ mean SHAP, mean value, p25/p50/p75 from feature_thresholds, source
9
+ dataset / window from feature provenance).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from html import escape
14
+ from typing import Any, Iterable
15
+
16
+ import pandas as pd
17
+ import streamlit as st
18
+
19
+ from . import data
20
+
21
+ _STATE_KEY = "arch_detail_id"
22
+
23
+ _DIRECTION_POSITIVE = "positive"
24
+ _DIRECTION_NEGATIVE = "negative"
25
+
26
+
27
+ def _fmt_pct(v: Any) -> str:
28
+ try:
29
+ return f"{float(v) * 100:.1f}%"
30
+ except (TypeError, ValueError):
31
+ return "—"
32
+
33
+
34
+ def _fmt_int(v: Any) -> str:
35
+ try:
36
+ if pd.isna(v):
37
+ return "—"
38
+ except (TypeError, ValueError):
39
+ pass
40
+ try:
41
+ return f"{int(v):,}"
42
+ except (TypeError, ValueError):
43
+ return "—"
44
+
45
+
46
+ def _fmt_float(v: Any, *, precision: int = 3) -> str:
47
+ if v is None:
48
+ return "—"
49
+ try:
50
+ if pd.isna(v):
51
+ return "—"
52
+ except (TypeError, ValueError):
53
+ pass
54
+ try:
55
+ return f"{float(v):.{precision}f}"
56
+ except (TypeError, ValueError):
57
+ return "—"
58
+
59
+
60
+ def _chip(label: str, value: str, *, accent: bool = False) -> str:
61
+ cls = "catalog-chip accent" if accent else "catalog-chip"
62
+ return (
63
+ f'<span class="{cls}">'
64
+ f'<span class="label">{escape(label)}</span>'
65
+ f'<span class="value">{escape(value)}</span>'
66
+ f'</span>'
67
+ )
68
+
69
+
70
+ def _prose_block(title: str, text: Any) -> str:
71
+ if text is None:
72
+ return ""
73
+ try:
74
+ if pd.isna(text):
75
+ return ""
76
+ except (TypeError, ValueError):
77
+ pass
78
+ s = str(text).strip()
79
+ if not s:
80
+ return ""
81
+ return (
82
+ '<section class="catalog-section">'
83
+ f'<h4>{escape(title)}</h4>'
84
+ f'<p class="prose">{escape(s)}</p>'
85
+ '</section>'
86
+ )
87
+
88
+
89
+ def _render_master_table(df: pd.DataFrame) -> int | None:
90
+ display = pd.DataFrame({
91
+ "Archetype": df["archetype_name"].astype(str),
92
+ "ID": df["archetype_id"].astype(str),
93
+ "Version": df["archetype_version"].astype(str),
94
+ "Cluster": df["cluster_size"].astype("Int64"),
95
+ "Mean churn": df["cluster_mean_churn_probability"],
96
+ "Stability": df.get("stability_vs_prior_version", pd.Series([None] * len(df))),
97
+ "Method": df["derivation_method"].astype(str),
98
+ })
99
+
100
+ col_config = {
101
+ "Archetype": st.column_config.TextColumn(width="medium"),
102
+ "ID": st.column_config.TextColumn(width="small", help="archetype_id"),
103
+ "Version": st.column_config.TextColumn(width="small"),
104
+ "Cluster": st.column_config.NumberColumn(format="%,d", width="small"),
105
+ "Mean churn": st.column_config.ProgressColumn(
106
+ format="%.2f", min_value=0.0, max_value=1.0, width="small"
107
+ ),
108
+ "Stability": st.column_config.NumberColumn(format="%.2f", width="small",
109
+ help="Jaccard-like overlap with the same archetype in the prior version (1.0 = identical)"),
110
+ "Method": st.column_config.TextColumn(width="small"),
111
+ }
112
+
113
+ event = st.dataframe(
114
+ display,
115
+ use_container_width=True,
116
+ hide_index=True,
117
+ column_config=col_config,
118
+ on_select="rerun",
119
+ selection_mode="single-row",
120
+ height=420,
121
+ key="archetypes_master_table",
122
+ )
123
+ rows = (event.selection or {}).get("rows") if event else None
124
+ return rows[0] if rows else None
125
+
126
+
127
+ # ---------------------------------------------------------------------------
128
+ # Feature struct helpers
129
+ # ---------------------------------------------------------------------------
130
+
131
+
132
+ def _as_dict(item: Any) -> dict[str, Any]:
133
+ """Coerce a row of ``top_shap_features`` (struct or pyspark Row) to dict."""
134
+ if item is None:
135
+ return {}
136
+ if isinstance(item, dict):
137
+ return item
138
+ if hasattr(item, "asDict"):
139
+ try:
140
+ return item.asDict()
141
+ except Exception:
142
+ pass
143
+ # plain object with attribute access (e.g. namedtuple-like)
144
+ out: dict[str, Any] = {}
145
+ for key in ("feature", "mean_shap", "mean_value", "direction"):
146
+ if hasattr(item, key):
147
+ out[key] = getattr(item, key)
148
+ return out
149
+
150
+
151
+ def _split_drivers(features: Any) -> tuple[list[dict], list[dict]]:
152
+ """Return ``(positive, negative)`` SHAP-driver dicts, ordered by |mean_shap|."""
153
+ if features is None:
154
+ return [], []
155
+ try:
156
+ seq: Iterable[Any] = list(features)
157
+ except TypeError:
158
+ return [], []
159
+ if not seq:
160
+ return [], []
161
+
162
+ pos: list[dict] = []
163
+ neg: list[dict] = []
164
+ for raw in seq:
165
+ d = _as_dict(raw)
166
+ if not d.get("feature"):
167
+ continue
168
+ direction = (d.get("direction") or "").strip().lower()
169
+ mean_shap = d.get("mean_shap")
170
+ if direction == _DIRECTION_POSITIVE:
171
+ pos.append(d)
172
+ elif direction == _DIRECTION_NEGATIVE:
173
+ neg.append(d)
174
+ else:
175
+ # No explicit direction — fall back to sign of mean_shap.
176
+ try:
177
+ if mean_shap is not None and float(mean_shap) >= 0:
178
+ pos.append(d)
179
+ else:
180
+ neg.append(d)
181
+ except (TypeError, ValueError):
182
+ pos.append(d)
183
+
184
+ def _abs_shap(d: dict) -> float:
185
+ v = d.get("mean_shap")
186
+ try:
187
+ return abs(float(v))
188
+ except (TypeError, ValueError):
189
+ return 0.0
190
+
191
+ pos.sort(key=_abs_shap, reverse=True)
192
+ neg.sort(key=_abs_shap, reverse=True)
193
+ return pos, neg
194
+
195
+
196
+ def _coerce_map(item: Any) -> dict[str, Any]:
197
+ if item is None:
198
+ return {}
199
+ if isinstance(item, dict):
200
+ return item
201
+ if hasattr(item, "asDict"):
202
+ try:
203
+ return item.asDict()
204
+ except Exception:
205
+ return {}
206
+ return {}
207
+
208
+
209
+ def _threshold_for(thresholds: dict, feature_name: str) -> dict[str, Any]:
210
+ raw = thresholds.get(feature_name)
211
+ if raw is None:
212
+ return {}
213
+ return _coerce_map(raw)
214
+
215
+
216
+ def _phrase_lookup(phrases_df: pd.DataFrame) -> dict[str, dict[str, Any]]:
217
+ if phrases_df is None or phrases_df.empty:
218
+ return {}
219
+ out: dict[str, dict[str, Any]] = {}
220
+ for _, r in phrases_df.iterrows():
221
+ name = r.get("feature_name")
222
+ if not name:
223
+ continue
224
+ out[str(name)] = {
225
+ "business_phrase": r.get("business_phrase"),
226
+ "source_dataset": r.get("source_dataset"),
227
+ "aggregation_kind": r.get("aggregation_kind"),
228
+ "window_phrase": r.get("window_phrase"),
229
+ "polarity": r.get("polarity"),
230
+ }
231
+ return out
232
+
233
+
234
+ def _render_driver_row(
235
+ driver: dict,
236
+ *,
237
+ direction: str,
238
+ thresholds: dict,
239
+ phrases: dict[str, dict[str, Any]],
240
+ expander_key: str,
241
+ ) -> None:
242
+ feature = str(driver.get("feature") or "")
243
+ phrase_meta = phrases.get(feature, {})
244
+ display_phrase = (
245
+ str(phrase_meta.get("business_phrase") or "").strip()
246
+ or feature
247
+ )
248
+ mean_shap = driver.get("mean_shap")
249
+ glyph = "▲" if direction == _DIRECTION_POSITIVE else "▼"
250
+ cls = "pos" if direction == _DIRECTION_POSITIVE else "neg"
251
+
252
+ # Summary row (always visible)
253
+ st.markdown(
254
+ f'<div class="feature-row {cls}">'
255
+ f'<span class="glyph">{glyph}</span>'
256
+ f'<span class="phrase">{escape(display_phrase)}</span>'
257
+ f'<span class="shap">SHAP {_fmt_float(mean_shap, precision=4)}</span>'
258
+ '</div>',
259
+ unsafe_allow_html=True,
260
+ )
261
+
262
+ # Technical detail (click-to-expand)
263
+ threshold = _threshold_for(thresholds, feature)
264
+ with st.expander("technical detail", expanded=False):
265
+ meta_lines: list[str] = [f"**Column name** &nbsp;`{feature}`"]
266
+ mean_value = driver.get("mean_value")
267
+ if mean_value is not None:
268
+ meta_lines.append(f"**Cluster mean value** &nbsp;{_fmt_float(mean_value, precision=4)}")
269
+ if mean_shap is not None:
270
+ meta_lines.append(f"**Cluster mean SHAP** &nbsp;{_fmt_float(mean_shap, precision=6)}")
271
+ explicit_dir = driver.get("direction")
272
+ if explicit_dir:
273
+ meta_lines.append(f"**Direction** &nbsp;{explicit_dir}")
274
+ if threshold:
275
+ t_bits = []
276
+ for tk in ("p25", "p50", "p75"):
277
+ if tk in threshold and threshold[tk] is not None:
278
+ t_bits.append(f"{tk}={_fmt_float(threshold[tk], precision=3)}")
279
+ if t_bits:
280
+ meta_lines.append(f"**Cluster quantiles** &nbsp;{' · '.join(t_bits)}")
281
+ for key, label in (
282
+ ("source_dataset", "Source dataset"),
283
+ ("aggregation_kind", "Aggregation"),
284
+ ("window_phrase", "Window"),
285
+ ("polarity", "Polarity (training)"),
286
+ ):
287
+ val = phrase_meta.get(key)
288
+ if val is None:
289
+ continue
290
+ try:
291
+ if pd.isna(val):
292
+ continue
293
+ except (TypeError, ValueError):
294
+ pass
295
+ s = str(val).strip()
296
+ if not s:
297
+ continue
298
+ meta_lines.append(f"**{label}** &nbsp;{s}")
299
+ st.markdown(" \n".join(meta_lines))
300
+ # The key is referenced to keep streamlit expander state stable per row.
301
+ _ = expander_key # noqa: F841 — kept for future per-row keying if needed
302
+
303
+
304
+ def _render_drivers_section(
305
+ title: str,
306
+ drivers: list[dict],
307
+ *,
308
+ direction: str,
309
+ thresholds: dict,
310
+ phrases: dict[str, dict[str, Any]],
311
+ archetype_id: str,
312
+ ) -> None:
313
+ if not drivers:
314
+ return
315
+ st.markdown(
316
+ f'<section class="catalog-section">'
317
+ f'<h4>{escape(title)} · {len(drivers)}</h4>'
318
+ '</section>',
319
+ unsafe_allow_html=True,
320
+ )
321
+ for i, d in enumerate(drivers):
322
+ _render_driver_row(
323
+ d,
324
+ direction=direction,
325
+ thresholds=thresholds,
326
+ phrases=phrases,
327
+ expander_key=f"{archetype_id}::{direction}::{i}",
328
+ )
329
+
330
+
331
+ def _render_detail(row: pd.Series, phrases: dict[str, dict[str, Any]]) -> None:
332
+ name = str(row.get("archetype_name") or row.get("archetype_id"))
333
+ aid = str(row.get("archetype_id"))
334
+ version = str(row.get("archetype_version"))
335
+
336
+ chips = [
337
+ _chip("ID", aid),
338
+ _chip("Version", version),
339
+ _chip("Cluster size", _fmt_int(row.get("cluster_size"))),
340
+ _chip("Mean churn", _fmt_pct(row.get("cluster_mean_churn_probability")), accent=True),
341
+ _chip("Method", str(row.get("derivation_method") or "—")),
342
+ ]
343
+ stability = row.get("stability_vs_prior_version")
344
+ try:
345
+ if stability is not None and not pd.isna(stability):
346
+ chips.append(_chip("Stability vs prior", _fmt_float(stability, precision=2)))
347
+ except (TypeError, ValueError):
348
+ pass
349
+ model_name = row.get("model_name")
350
+ model_version = row.get("model_version")
351
+ if model_name:
352
+ mv = f"{model_name}"
353
+ if model_version:
354
+ mv = f"{mv} · v{model_version}"
355
+ chips.append(_chip("Model", mv))
356
+
357
+ sections_html = ''.join([
358
+ _prose_block("Description", row.get("archetype_description")),
359
+ _prose_block("Rationale", row.get("rationale")),
360
+ ])
361
+
362
+ st.markdown(
363
+ f"""
364
+ <article class="catalog-detail">
365
+ <div class="eyebrow">Archetype · readonly</div>
366
+ <h3 class="title">{escape(name)}</h3>
367
+ <p class="subtitle">{escape(aid)} · v{escape(version)}</p>
368
+ <div class="chip-row">{''.join(chips)}</div>
369
+ {sections_html}
370
+ </article>
371
+ """,
372
+ unsafe_allow_html=True,
373
+ )
374
+
375
+ pos, neg = _split_drivers(row.get("top_shap_features"))
376
+ thresholds = _coerce_map(row.get("feature_thresholds"))
377
+
378
+ if not pos and not neg:
379
+ st.markdown(
380
+ '<section class="catalog-section">'
381
+ '<h4>Driver features</h4>'
382
+ '<p class="prose"><em>No SHAP drivers recorded for this archetype.</em></p>'
383
+ '</section>',
384
+ unsafe_allow_html=True,
385
+ )
386
+ return
387
+
388
+ _render_drivers_section(
389
+ "Risk-driving features",
390
+ pos,
391
+ direction=_DIRECTION_POSITIVE,
392
+ thresholds=thresholds,
393
+ phrases=phrases,
394
+ archetype_id=aid,
395
+ )
396
+ _render_drivers_section(
397
+ "Protective features",
398
+ neg,
399
+ direction=_DIRECTION_NEGATIVE,
400
+ thresholds=thresholds,
401
+ phrases=phrases,
402
+ archetype_id=aid,
403
+ )
404
+
405
+
406
+ def render() -> None:
407
+ st.markdown(
408
+ '<p class="catalog-lead">'
409
+ 'Behavioural clusters the model has identified in the current '
410
+ 'feature space. Click a row to see the archetype’s description, '
411
+ 'rationale, and the SHAP drivers — split into risk-driving '
412
+ '(pushes prediction toward churn) and protective. Expand any '
413
+ 'driver for its technical detail.'
414
+ '</p>',
415
+ unsafe_allow_html=True,
416
+ )
417
+
418
+ try:
419
+ df = data.archetype_catalog_all()
420
+ except Exception as exc:
421
+ st.error(f"Could not load archetype catalog: {exc}")
422
+ return
423
+ if df is None or df.empty:
424
+ st.info("No active archetypes — run c03 / c04 to derive and publish.")
425
+ return
426
+
427
+ try:
428
+ phrases_df = data.feature_business_phrases()
429
+ except Exception:
430
+ phrases_df = pd.DataFrame()
431
+ phrases = _phrase_lookup(phrases_df)
432
+
433
+ idx = _render_master_table(df)
434
+ if idx is None:
435
+ prior = st.session_state.get(_STATE_KEY)
436
+ if not prior:
437
+ return
438
+ match = df.index[df["archetype_id"] == prior]
439
+ if len(match) == 0:
440
+ return
441
+ idx = int(match[0])
442
+ else:
443
+ st.session_state[_STATE_KEY] = str(df.iloc[idx]["archetype_id"])
444
+
445
+ _render_detail(df.iloc[idx], phrases)
@@ -309,6 +309,157 @@ def load_template_html_from_uc(composite_name: Optional[str] = None) -> Optional
309
309
  return str(html) if html else None
310
310
 
311
311
 
312
+ @st.cache_data(ttl=300, show_spinner=False)
313
+ def playbook_catalog_all() -> pd.DataFrame:
314
+ """Latest-version row per playbook from ``playbook_catalog``.
315
+
316
+ The catalog is append-only — every YAML re-ingest writes a new
317
+ ``(playbook_id, version)`` row. We project the most-recently-active
318
+ row per playbook so the master table shows one line per intervention
319
+ design. Eligible / recommended counts come from
320
+ ``v_capacity_utilization`` for the latest scoring run; left-joined so
321
+ a freshly-loaded playbook with no scoring yet still renders.
322
+ """
323
+ cfg = load_config()
324
+ return _query(cfg, f"""
325
+ WITH latest_pb AS (
326
+ SELECT
327
+ playbook_id,
328
+ version,
329
+ name,
330
+ description,
331
+ when_applicable,
332
+ cost_per_customer_default,
333
+ expected_uplift_pct_default,
334
+ outcome_windows_days,
335
+ outcome_definition_version,
336
+ time_zero_definition,
337
+ grace_period_days,
338
+ followup_start_rule,
339
+ followup_end_rule,
340
+ default_estimand,
341
+ analysis_population_rule,
342
+ active_from,
343
+ active_to,
344
+ ROW_NUMBER() OVER (
345
+ PARTITION BY playbook_id
346
+ ORDER BY active_from DESC NULLS LAST, version DESC
347
+ ) AS rn
348
+ FROM {cfg.fqn_prefix}.playbook_catalog
349
+ ),
350
+ capacity AS (
351
+ SELECT playbook_id,
352
+ SUM(eligible_count) AS eligible_count,
353
+ SUM(recommended_count) AS recommended_count,
354
+ SUM(holdout_count) AS holdout_count
355
+ FROM {cfg.fqn_prefix}.v_capacity_utilization
356
+ GROUP BY playbook_id
357
+ ),
358
+ step_counts AS (
359
+ SELECT playbook_id, playbook_version, COUNT(*) AS step_count
360
+ FROM {cfg.fqn_prefix}.playbook_steps
361
+ GROUP BY playbook_id, playbook_version
362
+ )
363
+ SELECT
364
+ p.playbook_id,
365
+ p.version,
366
+ p.name,
367
+ p.description,
368
+ p.when_applicable,
369
+ p.cost_per_customer_default,
370
+ p.expected_uplift_pct_default,
371
+ p.outcome_windows_days,
372
+ p.outcome_definition_version,
373
+ p.time_zero_definition,
374
+ p.grace_period_days,
375
+ p.followup_start_rule,
376
+ p.followup_end_rule,
377
+ p.default_estimand,
378
+ p.analysis_population_rule,
379
+ p.active_from,
380
+ p.active_to,
381
+ COALESCE(c.eligible_count, 0) AS eligible_count,
382
+ COALESCE(c.recommended_count, 0) AS recommended_count,
383
+ COALESCE(c.holdout_count, 0) AS holdout_count,
384
+ COALESCE(sc.step_count, 0) AS step_count
385
+ FROM latest_pb p
386
+ LEFT JOIN capacity c ON c.playbook_id = p.playbook_id
387
+ LEFT JOIN step_counts sc ON sc.playbook_id = p.playbook_id
388
+ AND sc.playbook_version = p.version
389
+ WHERE p.rn = 1
390
+ ORDER BY COALESCE(c.recommended_count, 0) DESC, p.name ASC
391
+ """)
392
+
393
+
394
+ @st.cache_data(ttl=300, show_spinner=False)
395
+ def playbook_steps_for(playbook_id: str, version: str) -> pd.DataFrame:
396
+ """Ordered step rows for one ``(playbook_id, version)`` pair."""
397
+ cfg = load_config()
398
+ return _query(cfg, f"""
399
+ SELECT step_id, step_sequence, step_name, action_type,
400
+ automation_level, owner_role, cadence_trigger,
401
+ cadence_relative_to, cadence_offset_days, cadence_condition,
402
+ timeout_days, response_schema_id, intensity_param_name,
403
+ skip_conditions, stop_conditions
404
+ FROM {cfg.fqn_prefix}.playbook_steps
405
+ WHERE playbook_id = :pid AND playbook_version = :ver
406
+ ORDER BY step_sequence ASC
407
+ """, {"pid": playbook_id, "ver": version})
408
+
409
+
410
+ @st.cache_data(ttl=300, show_spinner=False)
411
+ def archetype_catalog_all() -> pd.DataFrame:
412
+ """Active archetypes from ``v_archetype_overview``.
413
+
414
+ One row per active archetype with cluster stats and the top-SHAP
415
+ driver array. The view is already filtered to ``status = 'active'``
416
+ so the master table reflects what's deployed against the latest
417
+ model build.
418
+ """
419
+ cfg = load_config()
420
+ return _query(cfg, f"""
421
+ SELECT
422
+ archetype_id,
423
+ archetype_version,
424
+ archetype_name,
425
+ archetype_description,
426
+ rationale,
427
+ cluster_size,
428
+ cluster_mean_churn_probability,
429
+ top_shap_features,
430
+ feature_thresholds,
431
+ model_name,
432
+ model_version,
433
+ derivation_method,
434
+ stability_vs_prior_version,
435
+ status,
436
+ valid_from,
437
+ valid_to
438
+ FROM {cfg.fqn_prefix}.v_archetype_overview
439
+ ORDER BY cluster_mean_churn_probability DESC NULLS LAST, cluster_size DESC
440
+ """)
441
+
442
+
443
+ @st.cache_data(ttl=300, show_spinner=False)
444
+ def feature_business_phrases() -> pd.DataFrame:
445
+ """``feature_name → business_phrase`` map for archetype detail.
446
+
447
+ Joined client-side onto the raw SHAP-feature list so the Archetypes
448
+ tab can show a readable phrase next to each technical column name.
449
+ Reads ``v_feature_provenance``; returns an empty DataFrame when the
450
+ view is missing.
451
+ """
452
+ cfg = load_config()
453
+ try:
454
+ return _query(cfg, f"""
455
+ SELECT feature_name, business_phrase, source_dataset,
456
+ aggregation_kind, window_phrase, polarity
457
+ FROM {cfg.fqn_prefix}.v_feature_provenance
458
+ """)
459
+ except Exception:
460
+ return pd.DataFrame()
461
+
462
+
312
463
  @st.cache_data(ttl=300, show_spinner=False)
313
464
  def run_history() -> pd.DataFrame:
314
465
  cfg = load_config()