SuperModelingFactory 0.6.6__tar.gz → 0.6.7__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 (116) hide show
  1. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Feature_Screen.py +169 -2
  2. supermodelingfactory-0.6.7/Modeling_Tool/Feature/Screen_Gates.py +470 -0
  3. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Weighted_Screen.py +94 -4
  4. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/LRM_Tool.py +31 -1
  5. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/LRM_Tool.pyi +1 -0
  6. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/_common.py +15 -0
  7. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/credit_model.py +80 -0
  8. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/feature_validation.py +256 -6
  9. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/field_meta.py +41 -0
  10. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/sample_analysis.py +188 -19
  11. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/screening_artifact.py +46 -0
  12. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +489 -55
  13. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +9 -4
  14. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/__init__.py +1 -1
  15. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/PKG-INFO +2 -2
  16. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/README.md +1 -1
  17. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/PKG-INFO +2 -2
  18. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/SOURCES.txt +1 -0
  19. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/pyproject.toml +1 -1
  20. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/setup.py +1 -1
  21. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/ExcelMaster/ExcelFormatTool.py +0 -0
  22. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/ExcelMaster/ExcelMaster.py +0 -0
  23. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/ExcelMaster/Template.py +0 -0
  24. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/ExcelMaster/Utility.py +0 -0
  25. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/ExcelMaster/__init__.py +0 -0
  26. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/LICENSE +0 -0
  27. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/MANIFEST.in +0 -0
  28. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Binning_Tool.py +0 -0
  29. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
  30. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
  31. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
  32. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
  33. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
  34. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
  35. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
  36. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Proc_Compare.py +0 -0
  37. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Slope_Tool.py +0 -0
  38. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
  39. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
  40. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
  41. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/__init__.py +0 -0
  42. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/kDataFrame.py +0 -0
  43. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
  44. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
  45. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Core/utils.py +0 -0
  46. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
  47. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
  48. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
  49. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
  50. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/__init__.py +0 -0
  51. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/evaluate_model.py +0 -0
  52. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
  53. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
  54. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
  55. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
  56. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Explainability/__init__.py +0 -0
  57. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
  58. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
  59. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
  60. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
  61. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/ODPS_Distribution_Tool.py +0 -0
  62. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/ODPS_Distribution_Tool.pyi +0 -0
  63. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
  64. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
  65. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
  66. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Feature/__init__.py +0 -0
  67. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/Backward_Tool.py +0 -0
  68. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
  69. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
  70. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/GBM_Tool.py +0 -0
  71. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
  72. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Model/__init__.py +0 -0
  73. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/__init__.py +0 -0
  74. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
  75. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
  76. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
  77. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
  78. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
  79. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
  80. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
  81. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
  82. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
  83. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Sample_Split.py +0 -0
  84. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
  85. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/Sample/__init__.py +0 -0
  86. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
  87. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/UAT/__init__.py +0 -0
  88. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
  89. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
  90. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Master.py +0 -0
  91. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
  92. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
  93. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
  94. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
  95. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
  96. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
  97. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
  98. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/__init__.py +0 -0
  99. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
  100. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
  101. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/_utils/__init__.py +0 -0
  102. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/_utils/nan_guard.py +0 -0
  103. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/_utils/robust.py +0 -0
  104. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/_utils/sentinels.py +0 -0
  105. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
  106. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
  107. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/ref_font/__init__.py +0 -0
  108. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Modeling_Tool/ref_font/simsun.ttc +0 -0
  109. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Report/Report_Tool.py +0 -0
  110. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/Report/__init__.py +0 -0
  111. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
  112. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
  113. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/requires.txt +0 -0
  114. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/SuperModelingFactory.egg-info/top_level.txt +0 -0
  115. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/requirements.txt +0 -0
  116. {supermodelingfactory-0.6.6 → supermodelingfactory-0.6.7}/setup.cfg +0 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import warnings
5
6
  from dataclasses import dataclass, field
6
7
  from pathlib import Path
7
8
  from typing import Any, Literal, Mapping
@@ -14,9 +15,11 @@ from Modeling_Tool._utils.sentinels import SMF_MISSING_BIN
14
15
 
15
16
  from .Weighted_Screen import (
16
17
  WeightedScreenResult,
18
+ _DROPPED_DETAIL_COLS,
17
19
  _apply_missing_rate_stage,
18
20
  _apply_stage_keep,
19
21
  _corr_dedup_weighted,
22
+ _iv_band_keep,
20
23
  _legacy_unweighted_screen,
21
24
  _psi_from_distributions,
22
25
  _resolve_splits,
@@ -37,6 +40,14 @@ _MONOTONE_INIT_KEYS = frozenset({
37
40
  "missing_woe",
38
41
  "special_values",
39
42
  "bin_label_decimals",
43
+ "min_bad_count",
44
+ "min_good_count",
45
+ "small_bin_policy",
46
+ "monotone_direction",
47
+ "reference_target",
48
+ "direction_conflict_policy",
49
+ "missing_bin_strategy",
50
+ "refine_min_n_bins_policy",
40
51
  })
41
52
  _MONOTONE_FIT_KEYS = frozenset({"chi2_binning", "chi2_p", "chi2_init_size", "n_jobs"})
42
53
 
@@ -50,6 +61,7 @@ class FeatureScreenConfig:
50
61
  psi_use_woe_bins: bool = False
51
62
  iv_enabled: bool = True
52
63
  iv_threshold: float = 0.02
64
+ iv_upper_threshold: float | None = None
53
65
  iv_bins: int = 10
54
66
  iv_min_bin_prop: float = 0.05
55
67
  iv_equal_freq: bool = True
@@ -77,6 +89,28 @@ class FeatureScreenConfig:
77
89
  content: float = 1e-6
78
90
  precision: int = 5
79
91
  min_bin_prop: float = 0.05
92
+ # --- Post-corr selection gates (G03/G04/G05/G06); all legacy-off ------
93
+ # G03 group stability (requires SelectionEvidence — FVP only)
94
+ monthly_iv_min: float | None = None
95
+ monthly_iv_cv_max: float | None = None
96
+ direction_consistency_min: float | None = None
97
+ min_group_n: int | None = None
98
+ insufficient_group_policy: Literal["keep_warn", "drop", "raise"] = "keep_warn"
99
+ # G04 multi-target joint gate (requires SelectionEvidence — FVP only)
100
+ target_rules: Literal["all", "any", "min_pass_count"] | None = None
101
+ min_pass_count: int | None = None
102
+ per_target_iv_range: Any = None # (low, high) or {target: (low, high)}
103
+ direction_reference_target: str | None = None
104
+ # G05 hard top-N truncation
105
+ max_selected_features: int | None = None
106
+ min_selected_features: int | None = None
107
+ ranking_metric: str = "iv"
108
+ tie_breaker: str = "name"
109
+ # G06 VIF gate (needs the optional statsmodels extra)
110
+ vif_enabled: bool = False
111
+ vif_threshold: float = 10.0
112
+ vif_min_features: int = 2
113
+ vif_tie_break_metric: str = "iv"
80
114
 
81
115
 
82
116
  def screen_config_from_mapping(
@@ -100,6 +134,9 @@ def screen_config_from_mapping(
100
134
  psi_use_woe_bins=bool(cfg.get("psi_use_woe_bins", False)),
101
135
  iv_enabled=bool(cfg.get("iv_enabled", True)),
102
136
  iv_threshold=float(cfg.get("iv_threshold", 0.02)),
137
+ iv_upper_threshold=(
138
+ float(cfg["iv_upper_threshold"]) if cfg.get("iv_upper_threshold") is not None else None
139
+ ),
103
140
  iv_bins=iv_nbins,
104
141
  iv_min_bin_prop=float(cfg.get("iv_min_bin_prop", 0.05)),
105
142
  iv_equal_freq=bool(cfg.get("iv_equal_freq", True)),
@@ -125,6 +162,23 @@ def screen_config_from_mapping(
125
162
  content=float(cfg.get("content", 1e-6)),
126
163
  precision=int(cfg.get("precision", 5)),
127
164
  min_bin_prop=float(cfg.get("min_bin_prop", cfg.get("iv_min_bin_prop", 0.05))),
165
+ monthly_iv_min=cfg.get("monthly_iv_min"),
166
+ monthly_iv_cv_max=cfg.get("monthly_iv_cv_max"),
167
+ direction_consistency_min=cfg.get("direction_consistency_min"),
168
+ min_group_n=cfg.get("min_group_n"),
169
+ insufficient_group_policy=str(cfg.get("insufficient_group_policy", "keep_warn")), # type: ignore[arg-type]
170
+ target_rules=cfg.get("target_rules"),
171
+ min_pass_count=cfg.get("min_pass_count"),
172
+ per_target_iv_range=cfg.get("per_target_iv_range"),
173
+ direction_reference_target=cfg.get("direction_reference_target"),
174
+ max_selected_features=cfg.get("max_selected_features"),
175
+ min_selected_features=cfg.get("min_selected_features"),
176
+ ranking_metric=str(cfg.get("ranking_metric", "iv")),
177
+ tie_breaker=str(cfg.get("tie_breaker", "name")),
178
+ vif_enabled=bool(cfg.get("vif_enabled", False)),
179
+ vif_threshold=float(cfg.get("vif_threshold", 10.0)),
180
+ vif_min_features=int(cfg.get("vif_min_features", 2)),
181
+ vif_tie_break_metric=str(cfg.get("vif_tie_break_metric", "iv")),
128
182
  )
129
183
 
130
184
 
@@ -213,12 +267,15 @@ def _woe_bins_unweighted_screen(
213
267
  config: FeatureScreenConfig,
214
268
  *,
215
269
  prefit_woe_engine: Any | None = None,
270
+ selection_evidence: Any | None = None,
216
271
  ) -> FeatureScreenResult:
217
272
  from Modeling_Tool import CorrelationFilter, PSICalculator, VarExtractionInsights
218
273
 
219
274
  ins, oos, oot = splits["ins"], splits["oos"], splits["oot"]
220
275
  current = list(feature_cols)
221
276
  summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, None)]
277
+ dropped_rows: list[dict] = []
278
+ stage_tables: dict[str, pd.DataFrame] = {}
222
279
  current, missing_rate_table, missing_rate_dropped = _apply_missing_rate_stage(
223
280
  ins,
224
281
  current,
@@ -228,6 +285,7 @@ def _woe_bins_unweighted_screen(
228
285
  on_empty_stage=config.on_empty_stage,
229
286
  )
230
287
  binner = _resolve_screening_binner(splits, current, target_col, config, prefit_woe_engine)
288
+ engine_fit_features = list(current)
231
289
 
232
290
  psi_table = pd.DataFrame(columns=["var", "psi_ins_oos", "psi_ins_oot", "psi_max"])
233
291
  if config.psi_enabled:
@@ -290,7 +348,7 @@ def _woe_bins_unweighted_screen(
290
348
  )
291
349
  if iv is not None and not iv.empty:
292
350
  iv_table = iv.rename(columns={"iv": "iv_weighted"})[["var", "iv_weighted", "n_bins", "missing_rate"]]
293
- keep = iv.loc[iv["iv"] >= config.iv_threshold, "var"].tolist()
351
+ keep = _iv_band_keep(iv, "iv", config.iv_threshold, config.iv_upper_threshold, dropped_rows)
294
352
  n_before = len(current)
295
353
  current = _apply_stage_keep(
296
354
  current, keep, "iv", summary_rows,
@@ -312,7 +370,25 @@ def _woe_bins_unweighted_screen(
312
370
  current = cf.remove_highly_correlated(current, max_iterations=config.corr_max_iterations)
313
371
  summary_rows.append(_summary_row("corr", n_before, len(current), config.corr_threshold, None))
314
372
 
373
+ from .Screen_Gates import apply_post_corr_gates
374
+
375
+ gate_iv_map = (
376
+ dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
377
+ )
378
+ current = apply_post_corr_gates(
379
+ ins, current, config, selection_evidence, gate_iv_map,
380
+ summary_rows, dropped_rows, stage_tables,
381
+ weight_col=None, on_empty_stage=config.on_empty_stage,
382
+ )
383
+
315
384
  summary_rows.append(_summary_row("final", len(feature_cols), len(current), None, None))
385
+ engine_obj, engine_meta = _screen_engine_payload(
386
+ binner,
387
+ fit_features=engine_fit_features,
388
+ target_col=target_col,
389
+ weight_col=None,
390
+ summary_rows=summary_rows,
391
+ )
316
392
  return FeatureScreenResult(
317
393
  selected_features=list(current),
318
394
  iv_table=iv_table,
@@ -321,6 +397,13 @@ def _woe_bins_unweighted_screen(
321
397
  summary=pd.DataFrame(summary_rows),
322
398
  missing_rate_table=missing_rate_table,
323
399
  missing_rate_dropped=missing_rate_dropped,
400
+ woe_engine=engine_obj,
401
+ woe_engine_meta=engine_meta,
402
+ dropped_detail=(
403
+ pd.DataFrame(dropped_rows, columns=_DROPPED_DETAIL_COLS)
404
+ if dropped_rows else pd.DataFrame(columns=_DROPPED_DETAIL_COLS)
405
+ ),
406
+ stage_tables=stage_tables,
324
407
  )
325
408
 
326
409
 
@@ -330,6 +413,52 @@ def _bins_to_numpy(bins: Any) -> np.ndarray:
330
413
  return np.asarray(bins, dtype=object)
331
414
 
332
415
 
416
+ def _screen_engine_payload(
417
+ binner: Any | None,
418
+ *,
419
+ fit_features: list[str],
420
+ target_col: str,
421
+ weight_col: str | None,
422
+ summary_rows: list[dict],
423
+ ) -> tuple[Any | None, dict[str, Any]]:
424
+ """Build the (woe_engine, woe_engine_meta) result payload (G00).
425
+
426
+ Monotone engines attach as-is — they hold only per-feature bin tables.
427
+ WOE_Master engines are never attached (they retain the full training
428
+ frame at ``self.train_data``); their woe_table + metadata travel instead.
429
+ """
430
+ if binner is None:
431
+ return None, {}
432
+ engine_name = type(binner).__name__
433
+ meta: dict[str, Any] = {
434
+ "engine_name": engine_name,
435
+ "fit_features": list(fit_features),
436
+ "target_col": target_col,
437
+ "weight_col": weight_col,
438
+ "screen_stages": [row.get("stage") for row in summary_rows],
439
+ }
440
+ if engine_name == "WOE_Master":
441
+ from Modeling_Tool.WOE.WOE_Adapter import as_woe_engine
442
+
443
+ try:
444
+ meta["woe_table"] = as_woe_engine(binner).get_woe_table(varlist=list(fit_features))
445
+ except Exception as exc:
446
+ meta["woe_table"] = None
447
+ meta["woe_table_error"] = repr(exc)
448
+ meta["engine_attached"] = False
449
+ warnings.warn(
450
+ "screening WOE engine is a WOE_Master, which retains the full "
451
+ "training frame — attaching its woe_table and metadata only. "
452
+ "Use woe_engine='monotone' to get a reusable, serializable "
453
+ "screening engine on the result.",
454
+ UserWarning,
455
+ stacklevel=3,
456
+ )
457
+ return None, meta
458
+ meta["engine_attached"] = True
459
+ return binner, meta
460
+
461
+
333
462
  def _weighted_woe_bins_screen(
334
463
  splits: dict[str, pd.DataFrame],
335
464
  feature_cols: list[str],
@@ -338,6 +467,7 @@ def _weighted_woe_bins_screen(
338
467
  config: FeatureScreenConfig,
339
468
  *,
340
469
  prefit_woe_engine: Any | None = None,
470
+ selection_evidence: Any | None = None,
341
471
  ) -> FeatureScreenResult:
342
472
  from Modeling_Tool.WOE.WOE_Adapter import as_woe_engine
343
473
 
@@ -346,6 +476,8 @@ def _weighted_woe_bins_screen(
346
476
  y_ins = ins[target_col].astype(float).to_numpy()
347
477
  current = list(feature_cols)
348
478
  summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, weight_col)]
479
+ dropped_rows: list[dict] = []
480
+ stage_tables: dict[str, pd.DataFrame] = {}
349
481
  current, missing_rate_table, missing_rate_dropped = _apply_missing_rate_stage(
350
482
  ins,
351
483
  current,
@@ -369,6 +501,7 @@ def _weighted_woe_bins_screen(
369
501
  categorical_features=config.categorical_features,
370
502
  )
371
503
  adapter = as_woe_engine(binner)
504
+ engine_fit_features = list(current)
372
505
 
373
506
  # Assign fitted bins once per split. PSI and IV both consume these labels;
374
507
  # doing it here avoids a full WOE transform for every feature and stage.
@@ -447,7 +580,7 @@ def _weighted_woe_bins_screen(
447
580
  columns=["var", "iv_weighted", "n_bins", "missing_rate"],
448
581
  )
449
582
  if not iv_table.empty:
450
- keep = iv_table.loc[iv_table["iv_weighted"] >= config.iv_threshold, "var"].tolist()
583
+ keep = _iv_band_keep(iv_table, "iv_weighted", config.iv_threshold, config.iv_upper_threshold, dropped_rows)
451
584
  n_before = len(current)
452
585
  current = _apply_stage_keep(
453
586
  current, keep, "iv", summary_rows,
@@ -479,7 +612,25 @@ def _weighted_woe_bins_screen(
479
612
  )
480
613
  summary_rows.append(_summary_row("corr", n_before, len(current), config.corr_threshold, weight_col))
481
614
 
615
+ from .Screen_Gates import apply_post_corr_gates
616
+
617
+ gate_iv_map = (
618
+ dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
619
+ )
620
+ current = apply_post_corr_gates(
621
+ ins, current, config, selection_evidence, gate_iv_map,
622
+ summary_rows, dropped_rows, stage_tables,
623
+ weight_col=weight_col, on_empty_stage=config.on_empty_stage,
624
+ )
625
+
482
626
  summary_rows.append(_summary_row("final", len(feature_cols), len(current), None, weight_col))
627
+ engine_obj, engine_meta = _screen_engine_payload(
628
+ binner,
629
+ fit_features=engine_fit_features,
630
+ target_col=target_col,
631
+ weight_col=weight_col,
632
+ summary_rows=summary_rows,
633
+ )
483
634
  return FeatureScreenResult(
484
635
  selected_features=list(current),
485
636
  iv_table=iv_table,
@@ -488,6 +639,13 @@ def _weighted_woe_bins_screen(
488
639
  summary=pd.DataFrame(summary_rows),
489
640
  missing_rate_table=missing_rate_table,
490
641
  missing_rate_dropped=missing_rate_dropped,
642
+ woe_engine=engine_obj,
643
+ woe_engine_meta=engine_meta,
644
+ dropped_detail=(
645
+ pd.DataFrame(dropped_rows, columns=_DROPPED_DETAIL_COLS)
646
+ if dropped_rows else pd.DataFrame(columns=_DROPPED_DETAIL_COLS)
647
+ ),
648
+ stage_tables=stage_tables,
491
649
  )
492
650
 
493
651
 
@@ -499,6 +657,7 @@ def feature_screen(
499
657
  weight_col: str | None = None,
500
658
  config: FeatureScreenConfig | None = None,
501
659
  prefit_woe_engine: Any | None = None,
660
+ selection_evidence: Any | None = None,
502
661
  ) -> FeatureScreenResult:
503
662
  """Run PSI -> IV -> correlation screening on pre-split INS/OOS/OOT frames."""
504
663
  cfg = config or FeatureScreenConfig()
@@ -513,6 +672,7 @@ def feature_screen(
513
672
  weight_col,
514
673
  cfg,
515
674
  prefit_woe_engine=prefit_woe_engine,
675
+ selection_evidence=selection_evidence,
516
676
  )
517
677
  return _weighted_screen_impl(
518
678
  splits,
@@ -524,6 +684,7 @@ def feature_screen(
524
684
  psi_compare_splits=list(cfg.psi_compare_splits),
525
685
  iv_enabled=cfg.iv_enabled,
526
686
  iv_threshold=cfg.iv_threshold,
687
+ iv_upper_threshold=cfg.iv_upper_threshold,
527
688
  iv_bins=cfg.iv_bins,
528
689
  min_bin_prop=cfg.min_bin_prop,
529
690
  corr_enabled=cfg.corr_enabled,
@@ -538,6 +699,8 @@ def feature_screen(
538
699
  prefit_woe_engine=prefit_woe_engine,
539
700
  missing_rate_threshold=cfg.missing_rate_threshold,
540
701
  missing_rate_ref=cfg.missing_rate_ref,
702
+ gates_config=cfg,
703
+ selection_evidence=selection_evidence,
541
704
  )
542
705
 
543
706
  if use_woe_bins or prefit_woe_engine is not None:
@@ -547,6 +710,7 @@ def feature_screen(
547
710
  target_col,
548
711
  cfg,
549
712
  prefit_woe_engine=prefit_woe_engine,
713
+ selection_evidence=selection_evidence,
550
714
  )
551
715
 
552
716
  return _legacy_unweighted_screen(
@@ -558,6 +722,7 @@ def feature_screen(
558
722
  psi_compare_splits=list(cfg.psi_compare_splits),
559
723
  iv_enabled=cfg.iv_enabled,
560
724
  iv_threshold=cfg.iv_threshold,
725
+ iv_upper_threshold=cfg.iv_upper_threshold,
561
726
  iv_bins=cfg.iv_bins,
562
727
  iv_min_bin_prop=cfg.iv_min_bin_prop,
563
728
  corr_enabled=cfg.corr_enabled,
@@ -570,6 +735,8 @@ def feature_screen(
570
735
  on_empty_stage=cfg.on_empty_stage,
571
736
  missing_rate_threshold=cfg.missing_rate_threshold,
572
737
  missing_rate_ref=cfg.missing_rate_ref,
738
+ gates_config=cfg,
739
+ selection_evidence=selection_evidence,
573
740
  )
574
741
 
575
742