churnkit 1.2.8a6__tar.gz → 1.2.8a8__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.
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/PKG-INFO +1 -1
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/accounts_view.py +48 -2
- churnkit-1.2.8a8/apps/databricks_app/src/auth.py +71 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/data.py +166 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/pyproject.toml +1 -1
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/__init__.py +1 -1
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/databricks_renderer.py +43 -21
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/.gitignore +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/LICENSE +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/README.md +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/archetype_view.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/config.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/customer_profile.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/default_profile.css +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/default_profile.html +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/masthead.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/state.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/template.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/theme.css +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/apps/databricks_app/src/treemap.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/causal_notebooks/c01_publish_definitions.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/causal_notebooks/c02_archetype_derivation.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/causal_notebooks/c03_approval_gate.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/causal_notebooks/c04_batch_inference.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/causal_notebooks/c05_snapshot_and_dashboard.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/constraints/.gitkeep +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/-1_sample_datasets.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/00_start_here.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/01_data_discovery.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/01a_a_temporal_text_deep_dive.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/01a_temporal_deep_dive.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/01b_temporal_quality.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/01c_temporal_patterns.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/01d_event_aggregation.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/02_source_integrity.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/03_dataset_merge.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/04_column_deep_dive.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/04a_text_columns_deep_dive.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/05_relationship_analysis.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/06_feature_opportunities.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/07_modeling_readiness.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/08_baseline_experiments.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/09_business_alignment.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/10_spec_generation.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/11_scoring_validation.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/12_view_documentation.ipynb +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/.claude/settings.local.json +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/README.md +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/build_framework_phase_map.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/data/create_snapshot.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/data/generate_edi_ticketing_dataset.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/data/generate_retail_dataset.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/data/generate_test_data.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/data/migrate_parquet_to_delta.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/data/migrate_to_temporal.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/databricks/build_wheel.sh +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/databricks/capture_runtime.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/databricks/dbr_init.sh +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/databricks/deploy_dev.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/databricks/generate_constraints.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/databricks/notebook_setup.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/experiments/capture_notebook_outputs.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/experiments/cell_profiling.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/experiments/compare_exploration_runs.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/experiments/compare_versions.sh +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/experiments/patch_dataset_config.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/generate_requirements.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/build_causal_notebooks.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/clean_notebook_outputs.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/export_tutorial_html.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/init_project.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/migrate_notebook_cell_ids.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/plotly_image_preprocessor.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/run_exploration.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/sync_notebooks.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/tag_framework_cells.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/tag_markdown_cells.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/notebooks/test_notebooks.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/release.sh +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/seed_playbooks_volume.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/templates/tutorial_html/conf.json +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/templates/tutorial_html/index.html.j2 +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/scripts/update_notebook_paths.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/active_dataset_store.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/analysis_context.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/column_describer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/dataset_fingerprinter.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/entity_timestamp_deriver.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/exploration_manager.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/explorer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/field_availability_audit.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/findings.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/intent_defaults.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/key_resolver.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/layered_recommendations.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/objective_support_communicator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/prediction_objective_detector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/project_context.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/recommendation_builder.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/recommendations.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/run_namespace.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/sampling.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/schema_report.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/service_unit_detector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/session.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/skip_logic.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/auto_explorer/snapshot_grid.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/business/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/business/ab_test_designer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/business/fairness_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/business/intervention_matcher.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/business/ratio_features.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/business/report_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/business/risk_profile.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/business/roi_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/calibration_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/cv_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/error_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/exploration_ledger.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/feature_provenance.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/feature_stability.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/leakage_detector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/model_diagnostics_report.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/noise_tester.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/overfitting_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/parity_report.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/diagnostics/segment_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/discovery/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/discovery/config_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/discovery/discovery_flow.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/discovery/type_inferencer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/interpretability/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/interpretability/cohort_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/interpretability/counterfactual.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/interpretability/individual_explainer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/interpretability/pdp_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/interpretability/shap_explainer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/jupyter_save_hook.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/notebook_html_exporter.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/notebook_progress.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/plotly_preprocessor.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/base.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/cleaning/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/cleaning/consistency.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/cleaning/deduplicate.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/cleaning/impute.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/cleaning/outlier.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/datetime/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/datetime/extract.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/encoding/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/encoding/categorical.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/pipeline.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/registry.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/selection/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/selection/drop_column.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/transform/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/transform/power.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/recommendations/transform/scale.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/visualization/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/visualization/attention_scorer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/visualization/chart_builder.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/visualization/column_paginator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/visualization/console.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/visualization/display.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/analysis/visualization/number_formatter.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/artifacts/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/artifacts/fit_artifact_registry.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/cli.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/compat/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/compat/bulk_profiling.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/compat/cell_profiling_hooks.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/compat/detection.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/compat/ops.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/compat/pandas_backend.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/compat/remote_path.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/compat/spark_backend.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/compat/timing.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/base.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/components/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/components/deployer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/components/explainer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/components/feature_eng.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/components/ingester.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/components/profiler.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/components/trainer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/components/transformer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/components/validator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/enums.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/orchestrator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/components/registry.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/config/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/config/column_config.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/config/experiments.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/config/pipeline_config.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/config/source_config.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/naming.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/utils/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/utils/leakage.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/utils/severity.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/core/utils/statistics.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/base.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/cell_builder.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/config.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/databricks_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/local_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/project_init.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/runner.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/scoring_replay.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/script_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/base_stage.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/causal_setup_cell.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s01_ingestion.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s02_profiling.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s03_cleaning.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s04_transformation.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s05_feature_engineering.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s06_feature_selection.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s07_model_training.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s08_deployment.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s09_monitoring.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s10_batch_inference.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s11_feature_store.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s_c01_publish_definitions.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s_c02_archetype_derivation.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s_c03_approval_gate.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s_c04_batch_inference.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_generator/stages/s_c05_snapshot_and_dashboard.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_merge/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_merge/cli.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_merge/config_merger.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_merge/config_parser.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_merge/conflict.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_merge/merge_engine.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_merge/merge_report.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_sync/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_sync/cell_id_standardizer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_sync/cell_types.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_sync/cli.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_sync/sync_engine.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/notebook_sync/sync_report.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/orchestration/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/orchestration/code_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/orchestration/context.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/orchestration/data_materializer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/orchestration/databricks_exporter.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/orchestration/doc_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/databricks_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/exploration_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/findings_parser.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/generation_manifest.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/gold_transform_applicator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/llm_docs_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/models.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/override_merge.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/protocols.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/renderer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/pipeline_generator/user_extensions_emitter.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/spec_generator/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/generators/spec_generator/mlflow_pipeline_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/base.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/factory.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/feature_store/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/feature_store/base.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/feature_store/databricks.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/feature_store/feast_adapter.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/feature_store/local.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/mlflow/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/mlflow/base.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/mlflow/databricks.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/mlflow/experiment_tracker.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/mlflow/local.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/storage/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/storage/base.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/storage/databricks.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/adapters/storage/local.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/databricks_init.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/databricks_job_capture.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/feature_store/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/feature_store/definitions.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/feature_store/manager.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/feature_store/registry.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/iteration/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/iteration/context.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/iteration/feedback_collector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/iteration/orchestrator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/iteration/recommendation_tracker.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/iteration/signals.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/llm_context/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/llm_context/context_builder.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/llm_context/prompts.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/requirements_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/streaming/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/streaming/batch_integration.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/streaming/early_warning_model.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/streaming/event_schema.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/streaming/online_store_writer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/streaming/realtime_scorer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/streaming/trigger_engine.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/integrations/streaming/window_aggregator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/api.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/dataset_resolution.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/decorator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/flags.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/harvest.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/logging.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/persistence.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/registry.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/replay.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/summary.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/runtime/validation.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/approval_gate.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/clusterer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/column_descriptions_writer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/dashboard_profile_override.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/dashboard_views.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/delta_writer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/derivation.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/feature_meta_writer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/archetype_context.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/business_phrase.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/discovery.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/enrichment_pipeline.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/feature_meta_builder.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/llm_prompt.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/markdown_bootstrap.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/predicate_prose.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/prose_backfill.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/quantile_phrasing.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/interpretation/sidecars.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/llm_namer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/playbook_loader.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/playbook_mapper.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/policy_loader.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/population_stats.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/predicate_compiler.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/rule_extractor.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/run_context_writer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/schemas.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/seed_yamls/decision_policy.yaml +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/seed_yamls/response_schemas.yaml +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/seed_yamls/vocabularies.yaml +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/shap_runner.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/snapshot_writer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/sql/dashboard_views.sql +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/causal/top_drivers_writer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/cleaning/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/cleaning/base.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/cleaning/missing_handler.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/cleaning/outlier_handler.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/deployment/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/deployment/batch_scorer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/deployment/champion_challenger.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/deployment/model_registry.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/deployment/retraining_trigger.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/features/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/features/behavioral_features.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/features/customer_segmentation.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/features/feature_definitions.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/features/feature_engineer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/features/feature_manifest.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/features/feature_selector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/features/interaction_features.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/features/temporal_features.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/ingestion/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/ingestion/load_result.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/ingestion/loaders.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/ingestion/source_registry.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/lifecycle/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/lifecycle/config.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/lifecycle/enrich.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/lifecycle/validation.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/baseline_trainer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/cross_validator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/data_splitter.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/feature_profile.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/feature_scaler.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/feature_spec.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/hyperparameter_tuner.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/imbalance_handler.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/mlflow_logger.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/model_comparator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/model_evaluator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/shap_attribution.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/spark_baseline_trainer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/spark_classifier_wrapper.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/spark_feature_scaler.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/threshold_optimizer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/modeling/training_preparator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/monitoring/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/monitoring/alert_manager.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/monitoring/drift_detector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/monitoring/performance_monitor.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/preprocessing/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/preprocessing/transformer_manager.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/categorical_distribution.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/categorical_target_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/column_profiler.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/distribution_analysis.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/drift_detector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/feature_capacity.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/pattern_analysis_config.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/profile_result.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/quality_checks.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/recommendation_filter.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/relationship_detector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/relationship_recommender.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/report_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/scd_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/segment_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/segment_aware_outlier.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/spark_segment_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/spark_temporal_feature_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/spark_temporal_feature_engineer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/spark_time_window_aggregator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/stats_helpers.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/target_level_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/target_validator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/temporal_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/temporal_coverage.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/temporal_feature_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/temporal_feature_engineer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/temporal_pattern_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/temporal_quality_checks.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/temporal_target_analyzer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/text_embedder.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/text_processor.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/text_reducer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/time_series_profiler.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/time_window_aggregator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/type_detector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/profiling/window_recommendation.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scd_history/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scd_history/augment.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scd_history/config.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scd_history/reconstruct.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scd_history/validation.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scoring/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scoring/batch_inference.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scoring/config.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scoring/data_loader.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scoring/exceptions.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/scoring/pipeline_discovery.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/access_guard.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/data_preparer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/point_in_time_join.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/point_in_time_registry.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/scenario_detector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/snapshot_manager.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/spark_temporal_merger.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/synthetic_coordinator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/temporal_merger.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/timestamp_discovery.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/temporal/timestamp_manager.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/transformation/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/transformation/binary_handler.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/transformation/categorical_encoder.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/transformation/datetime_transformer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/transformation/numeric_transformer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/transformation/pipeline.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/adversarial_scoring_validator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/business_sense_gate.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/data_quality_gate.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/data_validators.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/feature_quality_gate.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/gates.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/leakage_gate.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/model_validity_gate.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/pipeline_validation_runner.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/quality_scorer.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/rule_generator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/scoring_pipeline_validator.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/stages/validation/timeseries_detector.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/transforms/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/transforms/artifact_store.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/transforms/executor.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/transforms/fitted.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/transforms/ops.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/src/customer_retention/transforms/spark_ops.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/tests/exploration_notebooks/test_distributed_dtype_safety.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/tests/exploration_notebooks/test_nb10_run_pipeline_ordering.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/tests/scripts/__init__.py +0 -0
- {churnkit-1.2.8a6 → churnkit-1.2.8a8}/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.2.
|
|
3
|
+
Version: 1.2.8a8
|
|
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
|
|
@@ -6,9 +6,10 @@ available — click a header to resort.
|
|
|
6
6
|
"""
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import pandas as pd
|
|
9
10
|
import streamlit as st
|
|
10
11
|
|
|
11
|
-
from . import data, state
|
|
12
|
+
from . import auth, data, state
|
|
12
13
|
|
|
13
14
|
_TIER_GLYPH = {"Low": "● ", "Medium": "● ", "High": "● "} # dot prefix, coloured via column_config? Streamlit doesn't style per-row — use unicode dots.
|
|
14
15
|
|
|
@@ -20,6 +21,25 @@ def _tier_label(tier) -> str:
|
|
|
20
21
|
return f"{glyph} {tier}"
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
def _assignment_label(assigned_to, me: str | None) -> str:
|
|
25
|
+
"""Render the ``assigned_to`` cell.
|
|
26
|
+
|
|
27
|
+
``Mine`` is reserved for the current user so a CSM can scan their own
|
|
28
|
+
column at a glance; everyone else surfaces the local-part of their
|
|
29
|
+
email (``jane.doe`` from ``jane.doe@churnkit.com``) for compactness.
|
|
30
|
+
Returns an empty string for unassigned rows so the column visually
|
|
31
|
+
recedes when most rows are blank.
|
|
32
|
+
"""
|
|
33
|
+
if assigned_to is None or (isinstance(assigned_to, float) and pd.isna(assigned_to)):
|
|
34
|
+
return ""
|
|
35
|
+
text = str(assigned_to).strip()
|
|
36
|
+
if not text:
|
|
37
|
+
return ""
|
|
38
|
+
if me and text.lower() == me.lower():
|
|
39
|
+
return "Mine"
|
|
40
|
+
return text.split("@", 1)[0] if "@" in text else text
|
|
41
|
+
|
|
42
|
+
|
|
23
43
|
def render() -> None:
|
|
24
44
|
pb = state.get("selected_playbook")
|
|
25
45
|
ar = state.get("selected_archetype")
|
|
@@ -29,13 +49,34 @@ def render() -> None:
|
|
|
29
49
|
st.info("No accounts in this scope.")
|
|
30
50
|
return
|
|
31
51
|
|
|
52
|
+
# Left-merge the cached assignments frame so the In-scope table can
|
|
53
|
+
# surface "Mine" / owner handle / blank without modifying the upstream
|
|
54
|
+
# ``v_eligible_all_playbooks`` view. Empty assignments DataFrame leaves
|
|
55
|
+
# every row unassigned.
|
|
56
|
+
me = auth.current_user_email()
|
|
57
|
+
try:
|
|
58
|
+
assigns = data.assignments()
|
|
59
|
+
except Exception:
|
|
60
|
+
assigns = pd.DataFrame(columns=["entity_id", "assigned_to"])
|
|
61
|
+
if assigns is None or assigns.empty:
|
|
62
|
+
df = df.assign(assigned_to=None)
|
|
63
|
+
else:
|
|
64
|
+
df = df.merge(
|
|
65
|
+
assigns[["entity_id", "assigned_to"]],
|
|
66
|
+
on="entity_id",
|
|
67
|
+
how="left",
|
|
68
|
+
)
|
|
69
|
+
|
|
32
70
|
display = df[[
|
|
33
71
|
"entity_id", "churn_probability", "expected_loss", "value_at_risk",
|
|
34
72
|
"risk_tier", "archetype_name",
|
|
35
73
|
"policy_rank_among_eligible", "eligible_playbook_count",
|
|
36
|
-
"recommended", "is_holdout",
|
|
74
|
+
"recommended", "is_holdout", "assigned_to",
|
|
37
75
|
]].copy()
|
|
38
76
|
display["risk_tier"] = display["risk_tier"].map(_tier_label)
|
|
77
|
+
display["assigned_to"] = display["assigned_to"].map(
|
|
78
|
+
lambda v: _assignment_label(v, me)
|
|
79
|
+
)
|
|
39
80
|
|
|
40
81
|
display = display.rename(columns={
|
|
41
82
|
"entity_id": "Entity",
|
|
@@ -48,6 +89,7 @@ def render() -> None:
|
|
|
48
89
|
"eligible_playbook_count": "Plays",
|
|
49
90
|
"recommended": "Rec.",
|
|
50
91
|
"is_holdout": "Holdout",
|
|
92
|
+
"assigned_to": "Assigned to",
|
|
51
93
|
})
|
|
52
94
|
|
|
53
95
|
col_config = {
|
|
@@ -61,6 +103,10 @@ def render() -> None:
|
|
|
61
103
|
"Plays": st.column_config.NumberColumn(format="%d", width="small"),
|
|
62
104
|
"Rec.": st.column_config.CheckboxColumn(width="small"),
|
|
63
105
|
"Holdout": st.column_config.CheckboxColumn(width="small"),
|
|
106
|
+
"Assigned to": st.column_config.TextColumn(
|
|
107
|
+
width="small",
|
|
108
|
+
help="CSM who has self-assigned this account. 'Mine' = you.",
|
|
109
|
+
),
|
|
64
110
|
}
|
|
65
111
|
|
|
66
112
|
event = st.dataframe(
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Identity helpers for the Streamlit app.
|
|
2
|
+
|
|
3
|
+
Databricks Apps inject the OAuth-authenticated user's identity into every
|
|
4
|
+
inbound request as ``X-Forwarded-*`` headers. Streamlit exposes those via
|
|
5
|
+
``st.context.headers`` (>=1.37). The notebook-side ``get_current_username``
|
|
6
|
+
in ``customer_retention.analysis.auto_explorer.session`` is NOT reusable
|
|
7
|
+
here -- it reads ``DATABRICKS_USERNAME`` (set only inside notebook tasks)
|
|
8
|
+
and falls back to ``getpass.getuser`` (the App service principal, identical
|
|
9
|
+
for everyone). The App needs request-scoped resolution.
|
|
10
|
+
|
|
11
|
+
Resolution order for ``current_user_email``:
|
|
12
|
+
1. ``X-Forwarded-Email`` request header (production)
|
|
13
|
+
2. ``st.experimental_user.email`` (Streamlit-native fallback)
|
|
14
|
+
3. ``CR_USER`` env var (local dev / tests)
|
|
15
|
+
4. ``None`` (caller decides whether to block)
|
|
16
|
+
|
|
17
|
+
``current_user_handle`` returns the local-part of the email (``jane.doe``
|
|
18
|
+
from ``jane.doe@churnkit.com``) for compact display in tables and button
|
|
19
|
+
labels. Falls back to ``getpass.getuser()`` when no email is resolvable so
|
|
20
|
+
local dev still works without setting any env vars.
|
|
21
|
+
|
|
22
|
+
Streamlit is imported lazily so this module loads cleanly in the test
|
|
23
|
+
venv (which intentionally omits the live-app dependencies).
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import getpass
|
|
28
|
+
import os
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _from_request_header() -> str | None:
|
|
32
|
+
try:
|
|
33
|
+
import streamlit as st # noqa: PLC0415 - lazy so the module imports without streamlit
|
|
34
|
+
email = st.context.headers.get("X-Forwarded-Email")
|
|
35
|
+
except Exception:
|
|
36
|
+
return None
|
|
37
|
+
return email.strip().lower() if email else None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _from_streamlit_user() -> str | None:
|
|
41
|
+
try:
|
|
42
|
+
import streamlit as st # noqa: PLC0415
|
|
43
|
+
email = getattr(st.experimental_user, "email", None)
|
|
44
|
+
except Exception:
|
|
45
|
+
return None
|
|
46
|
+
return email.strip().lower() if email else None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _from_env() -> str | None:
|
|
50
|
+
raw = os.environ.get("CR_USER")
|
|
51
|
+
return raw.strip().lower() if raw else None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def current_user_email() -> str | None:
|
|
55
|
+
"""Return the logged-in user's email, or ``None`` when not resolvable."""
|
|
56
|
+
return _from_request_header() or _from_streamlit_user() or _from_env()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def current_user_handle() -> str:
|
|
60
|
+
"""Short display handle for the logged-in user.
|
|
61
|
+
|
|
62
|
+
``jane.doe@churnkit.com`` -> ``jane.doe``. Falls back to the OS username
|
|
63
|
+
when no email is available so local dev without ``CR_USER`` still
|
|
64
|
+
renders something deterministic.
|
|
65
|
+
"""
|
|
66
|
+
email = current_user_email()
|
|
67
|
+
if email and "@" in email:
|
|
68
|
+
return email.split("@", 1)[0]
|
|
69
|
+
if email:
|
|
70
|
+
return email
|
|
71
|
+
return getpass.getuser()
|
|
@@ -320,3 +320,169 @@ def run_history() -> pd.DataFrame:
|
|
|
320
320
|
ORDER BY as_of_date DESC
|
|
321
321
|
LIMIT 20
|
|
322
322
|
""")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# ---------------------------------------------------------------------------
|
|
326
|
+
# Self-assignment — ``account_assignments`` Delta table
|
|
327
|
+
# ---------------------------------------------------------------------------
|
|
328
|
+
# A CSM clicks "Assign to me" on the L4 customer profile to claim ownership
|
|
329
|
+
# of an account. One row per assigned entity: no row means unassigned. The
|
|
330
|
+
# table is additive (no view modification, no schema migration) and lazy-
|
|
331
|
+
# created on first read or write so the dashboard can drop into existing
|
|
332
|
+
# deployments without an out-of-band DDL step.
|
|
333
|
+
#
|
|
334
|
+
# Race semantics: two CSMs clicking the same unassigned entity within the
|
|
335
|
+
# same second land on a Delta MERGE with ``WHEN NOT MATCHED THEN INSERT``,
|
|
336
|
+
# so exactly one INSERT wins and the loser's next rerun reads the winner's
|
|
337
|
+
# row and renders "Claimed by …". Unassign is restricted to the current
|
|
338
|
+
# owner -- you cannot clear someone else's claim from the UI.
|
|
339
|
+
_ASSIGNMENTS_TABLE = "account_assignments"
|
|
340
|
+
|
|
341
|
+
_TOGGLE_ASSIGNED = "assigned"
|
|
342
|
+
_TOGGLE_UNASSIGNED = "unassigned"
|
|
343
|
+
_TOGGLE_CLAIMED_BY_OTHER = "claimed_by_other"
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _assignments_fqn(cfg: AppConfig) -> str:
|
|
347
|
+
return f"{cfg.fqn_prefix}.{_ASSIGNMENTS_TABLE}"
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _ensure_assignments_table(cfg: AppConfig) -> None:
|
|
351
|
+
"""Idempotent ``CREATE TABLE IF NOT EXISTS`` for the assignments table.
|
|
352
|
+
|
|
353
|
+
Runs once per process — the Streamlit session keeps a flag so we don't
|
|
354
|
+
fire DDL on every rerun. Safe to call concurrently: ``CREATE TABLE IF
|
|
355
|
+
NOT EXISTS`` is a no-op on a pre-existing table.
|
|
356
|
+
"""
|
|
357
|
+
flag_key = f"_assignments_table_ready::{cfg.fqn_prefix}"
|
|
358
|
+
try:
|
|
359
|
+
if st.session_state.get(flag_key):
|
|
360
|
+
return
|
|
361
|
+
except Exception:
|
|
362
|
+
pass
|
|
363
|
+
with _connect(cfg) as conn:
|
|
364
|
+
cur = conn.cursor()
|
|
365
|
+
try:
|
|
366
|
+
cur.execute(f"""
|
|
367
|
+
CREATE TABLE IF NOT EXISTS {_assignments_fqn(cfg)} (
|
|
368
|
+
entity_id STRING NOT NULL,
|
|
369
|
+
assigned_to STRING NOT NULL,
|
|
370
|
+
assigned_at TIMESTAMP NOT NULL
|
|
371
|
+
) USING DELTA
|
|
372
|
+
""")
|
|
373
|
+
finally:
|
|
374
|
+
cur.close()
|
|
375
|
+
try:
|
|
376
|
+
st.session_state[flag_key] = True
|
|
377
|
+
except Exception:
|
|
378
|
+
pass
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@st.cache_data(ttl=10, show_spinner=False)
|
|
382
|
+
def assignments() -> pd.DataFrame:
|
|
383
|
+
"""Return all current account assignments.
|
|
384
|
+
|
|
385
|
+
Two columns: ``entity_id`` and ``assigned_to`` (the owner's email).
|
|
386
|
+
Cached for 10s so the In-scope table stays fresh without hammering the
|
|
387
|
+
warehouse on every rerun. Bust the cache via ``data.assignments.clear()``
|
|
388
|
+
after a toggle so the UI reflects the change immediately.
|
|
389
|
+
|
|
390
|
+
Returns an empty DataFrame on the first call before any rows exist —
|
|
391
|
+
callers can safely left-merge against it.
|
|
392
|
+
"""
|
|
393
|
+
cfg = load_config()
|
|
394
|
+
_ensure_assignments_table(cfg)
|
|
395
|
+
return _query(
|
|
396
|
+
cfg,
|
|
397
|
+
f"SELECT entity_id, assigned_to FROM {_assignments_fqn(cfg)}",
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def toggle_assignment(entity_id: str, user_email: str) -> str:
|
|
402
|
+
"""Self-assign or unassign ``entity_id`` for ``user_email``.
|
|
403
|
+
|
|
404
|
+
Returns one of:
|
|
405
|
+
- ``"assigned"`` — row was inserted (entity now owned by user)
|
|
406
|
+
- ``"unassigned"`` — row was deleted (user released their own claim)
|
|
407
|
+
- ``"claimed_by_other"`` — entity is owned by someone else; no DML
|
|
408
|
+
|
|
409
|
+
The MERGE-on-INSERT path protects against the read-then-write race
|
|
410
|
+
where another CSM claimed the entity between our SELECT and our
|
|
411
|
+
INSERT: ``WHEN NOT MATCHED`` collapses to a no-op and we surface
|
|
412
|
+
``claimed_by_other`` on the next rerun via the cached read.
|
|
413
|
+
"""
|
|
414
|
+
if not entity_id or not user_email:
|
|
415
|
+
raise ValueError("entity_id and user_email are required")
|
|
416
|
+
cfg = load_config()
|
|
417
|
+
_ensure_assignments_table(cfg)
|
|
418
|
+
fqn = _assignments_fqn(cfg)
|
|
419
|
+
user = user_email.strip().lower()
|
|
420
|
+
|
|
421
|
+
with _connect(cfg) as conn:
|
|
422
|
+
cur = conn.cursor()
|
|
423
|
+
try:
|
|
424
|
+
cur.execute(
|
|
425
|
+
f"SELECT assigned_to FROM {fqn} WHERE entity_id = :eid LIMIT 1",
|
|
426
|
+
{"eid": entity_id},
|
|
427
|
+
)
|
|
428
|
+
rows = cur.fetchall()
|
|
429
|
+
current = rows[0][0] if rows else None
|
|
430
|
+
|
|
431
|
+
if current is not None and current.strip().lower() != user:
|
|
432
|
+
return _TOGGLE_CLAIMED_BY_OTHER
|
|
433
|
+
|
|
434
|
+
if current is not None:
|
|
435
|
+
cur.execute(
|
|
436
|
+
f"DELETE FROM {fqn} WHERE entity_id = :eid AND assigned_to = :user",
|
|
437
|
+
{"eid": entity_id, "user": user},
|
|
438
|
+
)
|
|
439
|
+
return _TOGGLE_UNASSIGNED
|
|
440
|
+
|
|
441
|
+
# No prior row -- MERGE so a concurrent claim by another CSM
|
|
442
|
+
# cannot produce a duplicate row. The source row carries the
|
|
443
|
+
# candidate (entity, user, now()); the target only inserts when
|
|
444
|
+
# ``entity_id`` is still absent.
|
|
445
|
+
cur.execute(
|
|
446
|
+
f"""
|
|
447
|
+
MERGE INTO {fqn} t
|
|
448
|
+
USING (
|
|
449
|
+
SELECT :eid AS entity_id, :user AS assigned_to, current_timestamp() AS assigned_at
|
|
450
|
+
) s
|
|
451
|
+
ON t.entity_id = s.entity_id
|
|
452
|
+
WHEN NOT MATCHED THEN INSERT (entity_id, assigned_to, assigned_at)
|
|
453
|
+
VALUES (s.entity_id, s.assigned_to, s.assigned_at)
|
|
454
|
+
""",
|
|
455
|
+
{"eid": entity_id, "user": user},
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
cur.execute(
|
|
459
|
+
f"SELECT assigned_to FROM {fqn} WHERE entity_id = :eid LIMIT 1",
|
|
460
|
+
{"eid": entity_id},
|
|
461
|
+
)
|
|
462
|
+
verify = cur.fetchall()
|
|
463
|
+
winner = verify[0][0] if verify else None
|
|
464
|
+
if winner is None:
|
|
465
|
+
return _TOGGLE_CLAIMED_BY_OTHER
|
|
466
|
+
if winner.strip().lower() == user:
|
|
467
|
+
return _TOGGLE_ASSIGNED
|
|
468
|
+
return _TOGGLE_CLAIMED_BY_OTHER
|
|
469
|
+
finally:
|
|
470
|
+
cur.close()
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def assignment_for(entity_id: str) -> str | None:
|
|
474
|
+
"""Return the current owner email for ``entity_id``, or ``None``.
|
|
475
|
+
|
|
476
|
+
Reads through the cached ``assignments()`` frame so the L4 button can
|
|
477
|
+
label itself ("Assign to me" / "Unassign" / "Claimed") without firing
|
|
478
|
+
its own SQL on every rerun.
|
|
479
|
+
"""
|
|
480
|
+
if not entity_id:
|
|
481
|
+
return None
|
|
482
|
+
df = assignments()
|
|
483
|
+
if df is None or df.empty or "entity_id" not in df.columns:
|
|
484
|
+
return None
|
|
485
|
+
hit = df[df["entity_id"].astype(str) == str(entity_id)]
|
|
486
|
+
if hit.empty:
|
|
487
|
+
return None
|
|
488
|
+
return str(hit.iloc[0]["assigned_to"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "churnkit"
|
|
3
|
-
version = "1.02.
|
|
3
|
+
version = "1.02.8a8"
|
|
4
4
|
description = "Structured ML framework for customer churn prediction -- from exploration notebooks to production pipelines, locally or on Databricks."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {text = "Apache-2.0"}
|
|
@@ -30,10 +30,12 @@ def render_spark_step_call(step: TransformationStep) -> str:
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def _impute_null(col, p):
|
|
33
|
+
# Guard against recommendations that target a column not in this stage's
|
|
34
|
+
# input — same rationale as the per-column transforms below.
|
|
33
35
|
value = p.get("value", 0)
|
|
34
36
|
if isinstance(value, str):
|
|
35
|
-
return f'df.fillna("{value}", subset=["{col}"])'
|
|
36
|
-
return f'df.fillna({value}, subset=["{col}"])'
|
|
37
|
+
return f'(df.fillna("{value}", subset=["{col}"]) if "{col}" in df.columns else df)'
|
|
38
|
+
return f'(df.fillna({value}, subset=["{col}"]) if "{col}" in df.columns else df)'
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
def _cap_outlier(col, p):
|
|
@@ -100,8 +102,9 @@ def _derived_ratio(col, p):
|
|
|
100
102
|
num = p.get("numerator", "")
|
|
101
103
|
den = p.get("denominator", "")
|
|
102
104
|
return (
|
|
103
|
-
f'df.withColumn("{col}", '
|
|
104
|
-
f'F.col("{num}") / F.when(F.col("{den}") != 0, F.col("{den}")).otherwise(F.lit(None)))'
|
|
105
|
+
f'(df.withColumn("{col}", '
|
|
106
|
+
f'F.col("{num}") / F.when(F.col("{den}") != 0, F.col("{den}")).otherwise(F.lit(None))) '
|
|
107
|
+
f'if all(c in df.columns for c in ["{num}", "{den}"]) else df)'
|
|
105
108
|
)
|
|
106
109
|
|
|
107
110
|
|
|
@@ -109,7 +112,10 @@ def _derived_interaction(col, p):
|
|
|
109
112
|
features = p.get("features", [])
|
|
110
113
|
col_a = features[0] if len(features) > 0 else p.get("col_a", "")
|
|
111
114
|
col_b = features[1] if len(features) > 1 else p.get("col_b", "")
|
|
112
|
-
return
|
|
115
|
+
return (
|
|
116
|
+
f'(df.withColumn("{col}", F.col("{col_a}") * F.col("{col_b}")) '
|
|
117
|
+
f'if all(c in df.columns for c in ["{col_a}", "{col_b}"]) else df)'
|
|
118
|
+
)
|
|
113
119
|
|
|
114
120
|
|
|
115
121
|
def _derived_composite(col, p):
|
|
@@ -117,23 +123,31 @@ def _derived_composite(col, p):
|
|
|
117
123
|
if not columns:
|
|
118
124
|
raise ValueError(f"Composite derived column '{col}' requires non-empty 'columns' parameter")
|
|
119
125
|
expr_parts = " + ".join(f'F.col("{c}")' for c in columns)
|
|
120
|
-
|
|
126
|
+
cols_repr = "[" + ", ".join(f'"{c}"' for c in columns) + "]"
|
|
127
|
+
return (
|
|
128
|
+
f'(df.withColumn("{col}", ({expr_parts}) / {len(columns)}) '
|
|
129
|
+
f'if all(c in df.columns for c in {cols_repr}) else df)'
|
|
130
|
+
)
|
|
121
131
|
|
|
122
132
|
|
|
123
133
|
def _segment_aware_cap(col, p):
|
|
124
134
|
n_segments = p.get("n_segments", 2)
|
|
125
|
-
return
|
|
135
|
+
return (
|
|
136
|
+
f'(_segment_aware_cap(df, "{col}", n_segments={n_segments}) '
|
|
137
|
+
f'if "{col}" in df.columns else df)'
|
|
138
|
+
)
|
|
126
139
|
|
|
127
140
|
|
|
128
141
|
def _zero_inflation_handling(col, _p):
|
|
129
142
|
return (
|
|
130
|
-
f'df.withColumn("{col}_is_zero", F.when(F.col("{col}") == 0, 1).otherwise(0))'
|
|
131
|
-
f'.withColumn("{col}_log", F.when(F.col("{col}") > 0, F.log1p(F.col("{col}"))).otherwise(0))'
|
|
143
|
+
f'(df.withColumn("{col}_is_zero", F.when(F.col("{col}") == 0, 1).otherwise(0))'
|
|
144
|
+
f'.withColumn("{col}_log", F.when(F.col("{col}") > 0, F.log1p(F.col("{col}"))).otherwise(0)) '
|
|
145
|
+
f'if "{col}" in df.columns else df)'
|
|
132
146
|
)
|
|
133
147
|
|
|
134
148
|
|
|
135
149
|
def _cap_then_log(col, _p):
|
|
136
|
-
return f'_cap_then_log(df, "{col}")'
|
|
150
|
+
return f'(_cap_then_log(df, "{col}") if "{col}" in df.columns else df)'
|
|
137
151
|
|
|
138
152
|
|
|
139
153
|
def _type_cast(col, p):
|
|
@@ -208,10 +222,11 @@ def _derived_recency(col, p):
|
|
|
208
222
|
return f'df # silver_derived recency {col!r}: source unresolvable'
|
|
209
223
|
src = sources[0]
|
|
210
224
|
return (
|
|
211
|
-
f'df.withColumn("{col}", '
|
|
225
|
+
f'(df.withColumn("{col}", '
|
|
212
226
|
f'F.when(F.col("{src}").isNotNull() & F.col("as_of_date").isNotNull(), '
|
|
213
227
|
f'F.datediff(F.col("as_of_date"), F.to_date(F.col("{src}"))).cast("double"))'
|
|
214
|
-
f'.otherwise(F.lit(None)))'
|
|
228
|
+
f'.otherwise(F.lit(None))) '
|
|
229
|
+
f'if "{src}" in df.columns else df)'
|
|
215
230
|
)
|
|
216
231
|
|
|
217
232
|
|
|
@@ -221,10 +236,11 @@ def _derived_duration(col, p):
|
|
|
221
236
|
return f'df # silver_derived duration {col!r}: source unresolvable'
|
|
222
237
|
a, b = sources[0], sources[1]
|
|
223
238
|
return (
|
|
224
|
-
f'df.withColumn("{col}", '
|
|
239
|
+
f'(df.withColumn("{col}", '
|
|
225
240
|
f'F.when(F.col("{a}").isNotNull() & F.col("{b}").isNotNull(), '
|
|
226
241
|
f'F.datediff(F.to_date(F.col("{b}")), F.to_date(F.col("{a}"))).cast("double"))'
|
|
227
|
-
f'.otherwise(F.lit(None)))'
|
|
242
|
+
f'.otherwise(F.lit(None))) '
|
|
243
|
+
f'if all(c in df.columns for c in ["{a}", "{b}"]) else df)'
|
|
228
244
|
)
|
|
229
245
|
|
|
230
246
|
|
|
@@ -243,12 +259,13 @@ def _derived_cyclical(col, p):
|
|
|
243
259
|
return f'df # silver_derived cyclical {col!r}: source unresolvable'
|
|
244
260
|
src = sources[0]
|
|
245
261
|
return (
|
|
246
|
-
f'df.withColumn("{src}_month_sin", '
|
|
262
|
+
f'(df.withColumn("{src}_month_sin", '
|
|
247
263
|
f'F.when(F.col("{src}").isNotNull(), '
|
|
248
264
|
f'F.sin(2 * 3.141592653589793 * F.month(F.col("{src}")) / 12)).otherwise(F.lit(None)))'
|
|
249
265
|
f'.withColumn("{src}_month_cos", '
|
|
250
266
|
f'F.when(F.col("{src}").isNotNull(), '
|
|
251
|
-
f'F.cos(2 * 3.141592653589793 * F.month(F.col("{src}")) / 12)).otherwise(F.lit(None)))'
|
|
267
|
+
f'F.cos(2 * 3.141592653589793 * F.month(F.col("{src}")) / 12)).otherwise(F.lit(None))) '
|
|
268
|
+
f'if "{src}" in df.columns else df)'
|
|
252
269
|
)
|
|
253
270
|
|
|
254
271
|
|
|
@@ -258,10 +275,11 @@ def _derived_tenure(col, p):
|
|
|
258
275
|
return f'df # silver_derived tenure {col!r}: source unresolvable'
|
|
259
276
|
src = sources[0]
|
|
260
277
|
return (
|
|
261
|
-
f'df.withColumn("{col}", '
|
|
278
|
+
f'(df.withColumn("{col}", '
|
|
262
279
|
f'F.when(F.col("{src}").isNotNull() & F.col("as_of_date").isNotNull(), '
|
|
263
280
|
f'(F.datediff(F.col("as_of_date"), F.to_date(F.col("{src}"))) / 365.25).cast("double"))'
|
|
264
|
-
f'.otherwise(F.lit(None)))'
|
|
281
|
+
f'.otherwise(F.lit(None))) '
|
|
282
|
+
f'if "{src}" in df.columns else df)'
|
|
265
283
|
)
|
|
266
284
|
|
|
267
285
|
|
|
@@ -276,12 +294,16 @@ def _derived_extraction(col, p):
|
|
|
276
294
|
if not sources:
|
|
277
295
|
return f'df # silver_derived extraction {col!r}: source unresolvable'
|
|
278
296
|
src = sources[0]
|
|
297
|
+
# Three-way fall-through: passthrough if `{col}` is already present
|
|
298
|
+
# (landing emitted it), derive from `{src}` when present, otherwise no-op
|
|
299
|
+
# so a missing source from a phantom rec doesn't crash the stage.
|
|
279
300
|
return (
|
|
280
|
-
f'df if "{col}" in df.columns else '
|
|
281
|
-
f'df.withColumn("{col}", '
|
|
301
|
+
f'(df if "{col}" in df.columns else '
|
|
302
|
+
f'(df.withColumn("{col}", '
|
|
282
303
|
f'F.when(F.col("{src}").isNotNull(), '
|
|
283
304
|
f'F.when(F.dayofweek(F.to_date(F.col("{src}"))).isin(1, 7), 1.0).otherwise(0.0))'
|
|
284
|
-
f'.otherwise(F.lit(None)))'
|
|
305
|
+
f'.otherwise(F.lit(None))) '
|
|
306
|
+
f'if "{src}" in df.columns else df))'
|
|
285
307
|
)
|
|
286
308
|
|
|
287
309
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/01a_a_temporal_text_deep_dive.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{churnkit-1.2.8a6 → churnkit-1.2.8a8}/exploration_notebooks/04a_text_columns_deep_dive.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|