SuperModelingFactory 0.6.4__tar.gz → 0.6.5__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.
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/_common.py +43 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/credit_model.py +275 -21
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/feature_validation.py +26 -2
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/field_meta.py +10 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.6.4/SuperModelingFactory.egg-info → supermodelingfactory-0.6.5}/PKG-INFO +2 -2
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/README.md +1 -1
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5/SuperModelingFactory.egg-info}/PKG-INFO +2 -2
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/pyproject.toml +1 -1
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/setup.py +1 -1
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/LICENSE +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/MANIFEST.in +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Binning_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Proc_Compare.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/utils.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Feature_Screen.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/ODPS_Distribution_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/ODPS_Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Weighted_Screen.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/GBM_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/LRM_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/screening_artifact.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/Sample_Split.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Master.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/_utils/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/_utils/nan_guard.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/_utils/robust.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/_utils/sentinels.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Report/__init__.py +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/requirements.txt +0 -0
- {supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/setup.cfg +0 -0
|
@@ -532,6 +532,49 @@ def copy_column_length_checked(
|
|
|
532
532
|
dst[col] = src[col].to_numpy()
|
|
533
533
|
|
|
534
534
|
|
|
535
|
+
def resolve_missing_oot(
|
|
536
|
+
oos: pd.DataFrame,
|
|
537
|
+
synthesize: bool,
|
|
538
|
+
pipeline: str,
|
|
539
|
+
) -> pd.DataFrame | None:
|
|
540
|
+
"""Decide what to do when a run has no real OOT rows.
|
|
541
|
+
|
|
542
|
+
``synthesize=True`` preserves the historical behavior — the OOS frame is
|
|
543
|
+
copied in as a stand-in OOT — but now emits a loud ``UserWarning`` so the
|
|
544
|
+
placeholder can never be mistaken for a real out-of-time sample.
|
|
545
|
+
``synthesize=False`` returns ``None``: the caller must then leave OOT out
|
|
546
|
+
of its splits (missing key or empty frame) so no downstream stage
|
|
547
|
+
evaluates, searches, or reports against a synthetic OOT.
|
|
548
|
+
"""
|
|
549
|
+
if synthesize:
|
|
550
|
+
warnings.warn(
|
|
551
|
+
f"{pipeline}: no real OOT rows found; OOS ({len(oos)} rows) was "
|
|
552
|
+
f"copied in as a stand-in OOT. Metrics labeled 'oot' are OOS "
|
|
553
|
+
f"metrics, NOT out-of-time performance. Set "
|
|
554
|
+
f"synthesize_missing_oot=False (or candidate_mode=True) to drop "
|
|
555
|
+
f"the placeholder instead.",
|
|
556
|
+
UserWarning,
|
|
557
|
+
stacklevel=3,
|
|
558
|
+
)
|
|
559
|
+
return oos.copy()
|
|
560
|
+
return None
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def assert_split_allowed(
|
|
564
|
+
split: str,
|
|
565
|
+
forbidden: list[str] | set[str] | None,
|
|
566
|
+
component: str,
|
|
567
|
+
) -> None:
|
|
568
|
+
"""Hard gate for split governance: raise if ``component`` is about to
|
|
569
|
+
consume a split listed in ``forbidden_splits``."""
|
|
570
|
+
if forbidden and split in set(forbidden):
|
|
571
|
+
raise ValueError(
|
|
572
|
+
f"{component} requested split {split!r}, but it is listed in "
|
|
573
|
+
f"forbidden_splits {sorted(set(forbidden))}. Remove the split from "
|
|
574
|
+
f"the component's configuration or lift the restriction."
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
|
|
535
578
|
def split_oot_by_flag(
|
|
536
579
|
data: pd.DataFrame,
|
|
537
580
|
oot_col: str | None,
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/credit_model.py
RENAMED
|
@@ -17,6 +17,8 @@ from ._common import (
|
|
|
17
17
|
merge_dict,
|
|
18
18
|
persist_explain_outputs,
|
|
19
19
|
predict_positive,
|
|
20
|
+
assert_split_allowed,
|
|
21
|
+
resolve_missing_oot,
|
|
20
22
|
safe_to_csv,
|
|
21
23
|
split_oot_by_flag,
|
|
22
24
|
validate_woe_fit_query_columns,
|
|
@@ -88,6 +90,25 @@ class CreditModelPipelineConfig:
|
|
|
88
90
|
backward_params: dict[str, Any] = field(default_factory=dict)
|
|
89
91
|
use_backward_features: bool = True
|
|
90
92
|
|
|
93
|
+
# --- OOT governance (G10/G11/G12) -----------------------------------
|
|
94
|
+
# None means "not set explicitly": the effective value is resolved at
|
|
95
|
+
# run() start — legacy behavior outside candidate_mode, tightened
|
|
96
|
+
# defaults inside it. Explicit values conflicting with
|
|
97
|
+
# candidate_mode=True raise at validation time.
|
|
98
|
+
# NOTE (announced flip): synthesize_missing_oot=None currently resolves
|
|
99
|
+
# to True (legacy OOS-copy stand-in, now with a loud UserWarning);
|
|
100
|
+
# search_eval_splits=None resolves to ["oos", "oot"] and
|
|
101
|
+
# backward_report_splits=None to ["oot"]. The next minor release flips
|
|
102
|
+
# these resolved defaults to False / ["oos"] / [].
|
|
103
|
+
candidate_mode: bool = False
|
|
104
|
+
synthesize_missing_oot: bool | None = None
|
|
105
|
+
evaluation_splits: list[str] | None = None
|
|
106
|
+
forbidden_splits: list[str] = field(default_factory=list)
|
|
107
|
+
search_eval_splits: list[str] | None = None
|
|
108
|
+
search_objective_when_no_oot: str = "max_primary"
|
|
109
|
+
backward_validation_split: str = "oos"
|
|
110
|
+
backward_report_splits: list[str] | None = None
|
|
111
|
+
|
|
91
112
|
optuna_models: list[str] = field(default_factory=lambda: ["lgb", "xgb", "cat"])
|
|
92
113
|
optuna_n_trials: int = 5
|
|
93
114
|
optuna_params: dict[str, Any] = field(default_factory=dict)
|
|
@@ -127,6 +148,7 @@ class CreditModelPipelineResult:
|
|
|
127
148
|
selected_woe_features: list[str] = field(default_factory=list)
|
|
128
149
|
model_paths: dict[str, str] = field(default_factory=dict)
|
|
129
150
|
artifact_paths: dict[str, str] = field(default_factory=dict)
|
|
151
|
+
split_governance: dict[str, Any] = field(default_factory=dict)
|
|
130
152
|
|
|
131
153
|
|
|
132
154
|
class CreditModelPipeline:
|
|
@@ -178,11 +200,15 @@ class CreditModelPipeline:
|
|
|
178
200
|
self.config = config or CreditModelPipelineConfig()
|
|
179
201
|
self.predict_positive_nan_stats: dict[str, dict[str, int]] = {}
|
|
180
202
|
self._validate_gbm_feature_source_config()
|
|
203
|
+
# Resolved again at run() start; kept here so helpers (_split_data,
|
|
204
|
+
# _run_lr_search, ...) work when exercised directly in tests.
|
|
205
|
+
self._governance = self._resolve_split_governance()
|
|
181
206
|
|
|
182
207
|
def run(self, data: pd.DataFrame) -> CreditModelPipelineResult:
|
|
183
208
|
cfg = self.config
|
|
184
209
|
feature_cols = self._resolve_feature_cols(data)
|
|
185
210
|
self._validate_input(data, feature_cols)
|
|
211
|
+
self._governance = self._resolve_split_governance()
|
|
186
212
|
|
|
187
213
|
output_dir = Path(cfg.output_dir)
|
|
188
214
|
if cfg.write_outputs or cfg.write_excel:
|
|
@@ -198,7 +224,7 @@ class CreditModelPipeline:
|
|
|
198
224
|
if cfg.save_models:
|
|
199
225
|
make_dirs(self._model_output_dir(), output_dir / "artifacts")
|
|
200
226
|
|
|
201
|
-
splits = self._split_data(data)
|
|
227
|
+
splits = self._apply_split_governance(self._split_data(data))
|
|
202
228
|
fs_summary, final_features, screening_artifact = self._resolve_feature_selection(
|
|
203
229
|
splits,
|
|
204
230
|
feature_cols,
|
|
@@ -306,6 +332,7 @@ class CreditModelPipeline:
|
|
|
306
332
|
selected_woe_features=list(selected_woe_features),
|
|
307
333
|
model_paths=model_paths,
|
|
308
334
|
artifact_paths=artifact_paths,
|
|
335
|
+
split_governance=dict(self._governance),
|
|
309
336
|
)
|
|
310
337
|
|
|
311
338
|
def _resolve_feature_cols(self, data: pd.DataFrame) -> list[str]:
|
|
@@ -361,6 +388,104 @@ class CreditModelPipeline:
|
|
|
361
388
|
f"extra_eval_datasets[{name!r}] missing warm_start_score_col {cfg.warm_start_score_col!r}"
|
|
362
389
|
)
|
|
363
390
|
|
|
391
|
+
_KNOWN_SPLITS = ("ins", "oos", "oot")
|
|
392
|
+
|
|
393
|
+
def _resolve_split_governance(self) -> dict[str, Any]:
|
|
394
|
+
"""Resolve effective OOT-governance settings (G10/G11/G12).
|
|
395
|
+
|
|
396
|
+
Governance config fields default to None ("not set explicitly") and
|
|
397
|
+
resolve to legacy behavior outside candidate_mode or tightened
|
|
398
|
+
defaults inside it. Explicit values that contradict
|
|
399
|
+
candidate_mode=True raise instead of being silently overridden, and
|
|
400
|
+
the user's config object is never mutated.
|
|
401
|
+
"""
|
|
402
|
+
cfg = self.config
|
|
403
|
+
|
|
404
|
+
def _norm(values: Any, field_name: str) -> list[str]:
|
|
405
|
+
names = [str(v).strip().lower() for v in as_list(values)]
|
|
406
|
+
unknown = sorted(set(names) - set(self._KNOWN_SPLITS))
|
|
407
|
+
if unknown:
|
|
408
|
+
raise ValueError(
|
|
409
|
+
f"{field_name} only supports splits {list(self._KNOWN_SPLITS)}; got {unknown}"
|
|
410
|
+
)
|
|
411
|
+
return names
|
|
412
|
+
|
|
413
|
+
forbidden = set(_norm(cfg.forbidden_splits, "forbidden_splits"))
|
|
414
|
+
if cfg.candidate_mode:
|
|
415
|
+
conflicts = []
|
|
416
|
+
if cfg.synthesize_missing_oot is True:
|
|
417
|
+
conflicts.append("synthesize_missing_oot=True")
|
|
418
|
+
if cfg.evaluation_splits is not None and "oot" in _norm(cfg.evaluation_splits, "evaluation_splits"):
|
|
419
|
+
conflicts.append("evaluation_splits contains 'oot'")
|
|
420
|
+
if cfg.search_eval_splits is not None and "oot" in _norm(cfg.search_eval_splits, "search_eval_splits"):
|
|
421
|
+
conflicts.append("search_eval_splits contains 'oot'")
|
|
422
|
+
if cfg.backward_report_splits is not None and "oot" in _norm(cfg.backward_report_splits, "backward_report_splits"):
|
|
423
|
+
conflicts.append("backward_report_splits contains 'oot'")
|
|
424
|
+
if str(cfg.backward_validation_split).strip().lower() == "oot":
|
|
425
|
+
conflicts.append("backward_validation_split='oot'")
|
|
426
|
+
if conflicts:
|
|
427
|
+
raise ValueError(
|
|
428
|
+
f"candidate_mode=True forbids any OOT consumption, but the config "
|
|
429
|
+
f"explicitly requests it: {conflicts}. Drop candidate_mode or the "
|
|
430
|
+
f"conflicting settings."
|
|
431
|
+
)
|
|
432
|
+
forbidden |= {"oot"}
|
|
433
|
+
|
|
434
|
+
synthesize = cfg.synthesize_missing_oot
|
|
435
|
+
if synthesize is None:
|
|
436
|
+
# Legacy default True (announced flip to False on the next minor
|
|
437
|
+
# release); candidate_mode never synthesizes.
|
|
438
|
+
synthesize = not cfg.candidate_mode
|
|
439
|
+
evaluation_splits = None
|
|
440
|
+
if cfg.evaluation_splits is not None:
|
|
441
|
+
evaluation_splits = _norm(cfg.evaluation_splits, "evaluation_splits")
|
|
442
|
+
elif cfg.candidate_mode:
|
|
443
|
+
evaluation_splits = ["ins", "oos"]
|
|
444
|
+
if cfg.search_eval_splits is not None:
|
|
445
|
+
search_eval = _norm(cfg.search_eval_splits, "search_eval_splits")
|
|
446
|
+
else:
|
|
447
|
+
search_eval = ["oos"] if cfg.candidate_mode else ["oos", "oot"]
|
|
448
|
+
if cfg.backward_report_splits is not None:
|
|
449
|
+
backward_report = _norm(cfg.backward_report_splits, "backward_report_splits")
|
|
450
|
+
else:
|
|
451
|
+
backward_report = [] if cfg.candidate_mode else ["oot"]
|
|
452
|
+
backward_validation = _norm([cfg.backward_validation_split], "backward_validation_split")[0]
|
|
453
|
+
|
|
454
|
+
# Fail fast on explicit forbidden-split consumption.
|
|
455
|
+
if evaluation_splits is not None:
|
|
456
|
+
for name in evaluation_splits:
|
|
457
|
+
assert_split_allowed(name, forbidden, "evaluation_splits")
|
|
458
|
+
for name in search_eval:
|
|
459
|
+
assert_split_allowed(name, forbidden, "search (lr_search/optuna) eval_sets")
|
|
460
|
+
for name in backward_report:
|
|
461
|
+
assert_split_allowed(name, forbidden, "backward test_data_dict")
|
|
462
|
+
assert_split_allowed(backward_validation, forbidden, "backward validation_data")
|
|
463
|
+
|
|
464
|
+
return {
|
|
465
|
+
"candidate_mode": bool(cfg.candidate_mode),
|
|
466
|
+
"synthesize_missing_oot": bool(synthesize),
|
|
467
|
+
"evaluation_splits": evaluation_splits,
|
|
468
|
+
"forbidden_splits": sorted(forbidden),
|
|
469
|
+
"search_eval_splits": search_eval,
|
|
470
|
+
"search_eval_splits_explicit": cfg.search_eval_splits is not None,
|
|
471
|
+
"backward_validation_split": backward_validation,
|
|
472
|
+
"backward_report_splits": backward_report,
|
|
473
|
+
"backward_report_splits_explicit": cfg.backward_report_splits is not None,
|
|
474
|
+
"oot_synthesized": False,
|
|
475
|
+
"oot_withheld": False,
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
def _apply_split_governance(self, splits: dict[str, pd.DataFrame]) -> dict[str, pd.DataFrame]:
|
|
479
|
+
"""Remove forbidden splits from the working splits dict entirely, so
|
|
480
|
+
no downstream stage can consume them even by accident."""
|
|
481
|
+
governed = dict(splits)
|
|
482
|
+
for name in self._governance["forbidden_splits"]:
|
|
483
|
+
if name in governed:
|
|
484
|
+
governed.pop(name)
|
|
485
|
+
if name == "oot":
|
|
486
|
+
self._governance["oot_withheld"] = True
|
|
487
|
+
return governed
|
|
488
|
+
|
|
364
489
|
def _gbm_weight_kwargs(self, train: pd.DataFrame, val: pd.DataFrame) -> dict[str, Any]:
|
|
365
490
|
cfg = self.config
|
|
366
491
|
if not cfg.weight_col:
|
|
@@ -415,7 +540,15 @@ class CreditModelPipeline:
|
|
|
415
540
|
oot = work[lower == "oot"].copy()
|
|
416
541
|
if len(ins) and len(oos):
|
|
417
542
|
if not len(oot):
|
|
418
|
-
|
|
543
|
+
synthesized = resolve_missing_oot(
|
|
544
|
+
oos,
|
|
545
|
+
self._governance["synthesize_missing_oot"],
|
|
546
|
+
"CreditModelPipeline",
|
|
547
|
+
)
|
|
548
|
+
if synthesized is None:
|
|
549
|
+
return {"ins": ins, "oos": oos}
|
|
550
|
+
self._governance["oot_synthesized"] = True
|
|
551
|
+
oot = synthesized
|
|
419
552
|
return {"ins": ins, "oos": oos, "oot": oot}
|
|
420
553
|
if cfg.split_col:
|
|
421
554
|
raise ValueError(f"split_col {cfg.split_col!r} must contain non-empty ins and oos samples")
|
|
@@ -433,7 +566,15 @@ class CreditModelPipeline:
|
|
|
433
566
|
)
|
|
434
567
|
ins, oos = splitter.split_df(ins_oos, target=cfg.target_col)
|
|
435
568
|
if len(oot) == 0:
|
|
436
|
-
|
|
569
|
+
synthesized = resolve_missing_oot(
|
|
570
|
+
oos,
|
|
571
|
+
self._governance["synthesize_missing_oot"],
|
|
572
|
+
"CreditModelPipeline",
|
|
573
|
+
)
|
|
574
|
+
if synthesized is None:
|
|
575
|
+
return {"ins": ins.copy(), "oos": oos.copy()}
|
|
576
|
+
self._governance["oot_synthesized"] = True
|
|
577
|
+
oot = synthesized
|
|
437
578
|
return {"ins": ins.copy(), "oos": oos.copy(), "oot": oot.copy()}
|
|
438
579
|
|
|
439
580
|
def _resolve_screening_artifact(self) -> Any | None:
|
|
@@ -877,18 +1018,42 @@ class CreditModelPipeline:
|
|
|
877
1018
|
try:
|
|
878
1019
|
from Modeling_Tool import BackwardVariableEliminator
|
|
879
1020
|
|
|
1021
|
+
gov = self._governance
|
|
1022
|
+
validation_split = gov["backward_validation_split"]
|
|
1023
|
+
if validation_split not in splits:
|
|
1024
|
+
raise ValueError(
|
|
1025
|
+
f"backward_validation_split {validation_split!r} is not available "
|
|
1026
|
+
f"in this run (available: {sorted(splits)})."
|
|
1027
|
+
)
|
|
1028
|
+
test_data_dict: dict[str, pd.DataFrame] = {}
|
|
1029
|
+
for split_name in gov["backward_report_splits"]:
|
|
1030
|
+
if split_name not in splits:
|
|
1031
|
+
if gov["backward_report_splits_explicit"]:
|
|
1032
|
+
raise ValueError(
|
|
1033
|
+
f"backward_report_splits contains {split_name!r}, which is "
|
|
1034
|
+
f"not available in this run (available: {sorted(splits)})."
|
|
1035
|
+
)
|
|
1036
|
+
continue
|
|
1037
|
+
test_data_dict[split_name] = splits[split_name]
|
|
1038
|
+
user_init = dict(cfg.backward_params.get("init", {}))
|
|
1039
|
+
for split_name in (user_init.get("test_data_dict") or {}):
|
|
1040
|
+
assert_split_allowed(
|
|
1041
|
+
str(split_name).lower(),
|
|
1042
|
+
gov["forbidden_splits"],
|
|
1043
|
+
"backward test_data_dict (backward_params['init'])",
|
|
1044
|
+
)
|
|
880
1045
|
params = merge_dict(
|
|
881
1046
|
{
|
|
882
1047
|
"train_data": splits["ins"],
|
|
883
1048
|
"varlist": feature_cols,
|
|
884
1049
|
"dep": cfg.target_col,
|
|
885
1050
|
"model_type": f"{cfg.backward_model}m" if cfg.backward_model == "lgb" else cfg.backward_model,
|
|
886
|
-
"validation_data": splits[
|
|
887
|
-
"test_data_dict":
|
|
1051
|
+
"validation_data": splits[validation_split],
|
|
1052
|
+
"test_data_dict": test_data_dict,
|
|
888
1053
|
"weight_col": cfg.weight_col,
|
|
889
1054
|
"validation_weight_col": cfg.weight_col,
|
|
890
1055
|
},
|
|
891
|
-
|
|
1056
|
+
user_init,
|
|
892
1057
|
)
|
|
893
1058
|
bwd = BackwardVariableEliminator(**params)
|
|
894
1059
|
run_params = merge_dict(
|
|
@@ -916,6 +1081,79 @@ class CreditModelPipeline:
|
|
|
916
1081
|
except Exception as exc:
|
|
917
1082
|
return pd.DataFrame({"step": ["backward"], "error": [repr(exc)]}), list(feature_cols)
|
|
918
1083
|
|
|
1084
|
+
def _build_search_eval_sets(
|
|
1085
|
+
self,
|
|
1086
|
+
splits: dict[str, pd.DataFrame],
|
|
1087
|
+
*,
|
|
1088
|
+
component: str,
|
|
1089
|
+
) -> dict[str, pd.DataFrame]:
|
|
1090
|
+
"""Build hyperparameter-search eval_sets from search_eval_splits.
|
|
1091
|
+
|
|
1092
|
+
Every requested split passes the forbidden_splits hard gate. A split
|
|
1093
|
+
missing from the run (e.g. no real OOT with synthesis disabled) is an
|
|
1094
|
+
error when search_eval_splits was set explicitly; when the field was
|
|
1095
|
+
left unset, unavailable splits are silently dropped from the resolved
|
|
1096
|
+
legacy default instead.
|
|
1097
|
+
"""
|
|
1098
|
+
gov = self._governance
|
|
1099
|
+
eval_sets: dict[str, pd.DataFrame] = {}
|
|
1100
|
+
for name in gov["search_eval_splits"]:
|
|
1101
|
+
assert_split_allowed(name, gov["forbidden_splits"], component)
|
|
1102
|
+
if name not in splits:
|
|
1103
|
+
if gov["search_eval_splits_explicit"]:
|
|
1104
|
+
raise ValueError(
|
|
1105
|
+
f"{component}: search eval split {name!r} is not available in "
|
|
1106
|
+
f"this run (available: {sorted(splits)}). Drop it from "
|
|
1107
|
+
f"search_eval_splits or provide the split."
|
|
1108
|
+
)
|
|
1109
|
+
continue
|
|
1110
|
+
eval_sets[name] = splits[name]
|
|
1111
|
+
if not eval_sets:
|
|
1112
|
+
raise ValueError(
|
|
1113
|
+
f"{component}: no usable search eval splits "
|
|
1114
|
+
f"(requested {gov['search_eval_splits']}, available {sorted(splits)})."
|
|
1115
|
+
)
|
|
1116
|
+
return eval_sets
|
|
1117
|
+
|
|
1118
|
+
def _default_search_objective_params(self, eval_sets: dict[str, pd.DataFrame]) -> dict[str, Any]:
|
|
1119
|
+
has_oot = "oot" in eval_sets
|
|
1120
|
+
return {
|
|
1121
|
+
"objective": "oot_gap_penalized" if has_oot else str(self.config.search_objective_when_no_oot),
|
|
1122
|
+
"primary_set": "oos",
|
|
1123
|
+
"gap_ref_sets": ["oot"] if has_oot else [],
|
|
1124
|
+
"metric": "auc",
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
def _validate_search_objective_params(
|
|
1128
|
+
self,
|
|
1129
|
+
params: dict[str, Any],
|
|
1130
|
+
eval_sets: dict[str, pd.DataFrame],
|
|
1131
|
+
*,
|
|
1132
|
+
component: str,
|
|
1133
|
+
) -> None:
|
|
1134
|
+
primary = str(params.get("primary_set"))
|
|
1135
|
+
if primary not in eval_sets:
|
|
1136
|
+
raise ValueError(
|
|
1137
|
+
f"{component}: primary_set {primary!r} is not among the search eval "
|
|
1138
|
+
f"sets {sorted(eval_sets)}."
|
|
1139
|
+
)
|
|
1140
|
+
gap_refs = [str(x) for x in as_list(params.get("gap_ref_sets"))]
|
|
1141
|
+
missing = [name for name in gap_refs if name not in eval_sets]
|
|
1142
|
+
if missing:
|
|
1143
|
+
raise ValueError(
|
|
1144
|
+
f"{component}: gap_ref_sets {missing} are not among the search eval "
|
|
1145
|
+
f"sets {sorted(eval_sets)} — the split may be absent from this run "
|
|
1146
|
+
f"or listed in forbidden_splits."
|
|
1147
|
+
)
|
|
1148
|
+
objective = params.get("objective")
|
|
1149
|
+
if not callable(objective) and str(objective) == "oot_gap_penalized" and not gap_refs:
|
|
1150
|
+
raise ValueError(
|
|
1151
|
+
f"{component}: objective='oot_gap_penalized' requires a non-empty "
|
|
1152
|
+
f"gap_ref_sets, but no gap reference split is available in this run. "
|
|
1153
|
+
f"Use objective='max_primary' (see search_objective_when_no_oot) or "
|
|
1154
|
+
f"provide a real OOT split."
|
|
1155
|
+
)
|
|
1156
|
+
|
|
919
1157
|
def _run_lr_search(
|
|
920
1158
|
self,
|
|
921
1159
|
splits: dict[str, pd.DataFrame],
|
|
@@ -945,22 +1183,17 @@ class CreditModelPipeline:
|
|
|
945
1183
|
f"Allowed keys are {sorted(allowed_search_params)}. "
|
|
946
1184
|
"LRMaster.grid_search_params uses holdout eval_sets and does not accept cv."
|
|
947
1185
|
)
|
|
1186
|
+
eval_sets = self._build_search_eval_sets(splits, component="lr_search")
|
|
948
1187
|
params = merge_dict(
|
|
949
|
-
|
|
950
|
-
"objective": "oot_gap_penalized",
|
|
951
|
-
"primary_set": "oos",
|
|
952
|
-
"gap_ref_sets": ["oot"],
|
|
953
|
-
"metric": "auc",
|
|
954
|
-
"refit": False,
|
|
955
|
-
"verbose": False,
|
|
956
|
-
},
|
|
1188
|
+
self._default_search_objective_params(eval_sets),
|
|
957
1189
|
cfg.lr_search_params,
|
|
958
1190
|
)
|
|
1191
|
+
self._validate_search_objective_params(params, eval_sets, component="lr_search")
|
|
959
1192
|
results = lr.grid_search_params(
|
|
960
1193
|
data=splits["ins"],
|
|
961
1194
|
varlist=feature_cols,
|
|
962
1195
|
tgt_name=cfg.target_col,
|
|
963
|
-
eval_sets=
|
|
1196
|
+
eval_sets=eval_sets,
|
|
964
1197
|
param_grid=cfg.lr_search_param_grid,
|
|
965
1198
|
weight_col=cfg.weight_col,
|
|
966
1199
|
eval_weight_col=cfg.weight_col,
|
|
@@ -1055,23 +1288,36 @@ class CreditModelPipeline:
|
|
|
1055
1288
|
continue
|
|
1056
1289
|
splits = input_info["splits"]
|
|
1057
1290
|
feature_cols = list(input_info["features"])
|
|
1291
|
+
user_common = dict(cfg.optuna_params.get("common", {}))
|
|
1292
|
+
user_eval_sets = user_common.get("eval_sets")
|
|
1293
|
+
if user_eval_sets is not None:
|
|
1294
|
+
# Legacy override kept, but every named split still passes the
|
|
1295
|
+
# governance hard gate — forbidden splits cannot sneak back in
|
|
1296
|
+
# through optuna_params.
|
|
1297
|
+
for split_name in user_eval_sets:
|
|
1298
|
+
assert_split_allowed(
|
|
1299
|
+
str(split_name).lower(),
|
|
1300
|
+
self._governance["forbidden_splits"],
|
|
1301
|
+
"optuna_search eval_sets (optuna_params['common'])",
|
|
1302
|
+
)
|
|
1303
|
+
eval_sets = dict(user_eval_sets)
|
|
1304
|
+
else:
|
|
1305
|
+
eval_sets = self._build_search_eval_sets(splits, component="optuna_search")
|
|
1058
1306
|
common = merge_dict(
|
|
1059
1307
|
{
|
|
1060
1308
|
"varlist": feature_cols,
|
|
1061
1309
|
"tgt_name": cfg.target_col,
|
|
1062
|
-
"eval_sets":
|
|
1310
|
+
"eval_sets": eval_sets,
|
|
1063
1311
|
"engine": "optuna",
|
|
1064
|
-
|
|
1065
|
-
"primary_set": "oos",
|
|
1066
|
-
"gap_ref_sets": ["oot"],
|
|
1067
|
-
"metric": "auc",
|
|
1312
|
+
**self._default_search_objective_params(eval_sets),
|
|
1068
1313
|
"n_trials": cfg.optuna_n_trials,
|
|
1069
1314
|
"refit": True,
|
|
1070
1315
|
"verbose": False,
|
|
1071
1316
|
"random_state": cfg.random_state,
|
|
1072
1317
|
},
|
|
1073
|
-
|
|
1318
|
+
user_common,
|
|
1074
1319
|
)
|
|
1320
|
+
self._validate_search_objective_params(common, eval_sets, component="optuna_search")
|
|
1075
1321
|
if self._warm_start_requested_for(name) and name == "cat":
|
|
1076
1322
|
if cfg.warm_start_on_unsupported == "raise":
|
|
1077
1323
|
raise NotImplementedError("CatBoost does not support warm-start init_score")
|
|
@@ -1118,6 +1364,9 @@ class CreditModelPipeline:
|
|
|
1118
1364
|
min_bin_prop=cfg.perf_min_bin_prop,
|
|
1119
1365
|
equal_freq=True,
|
|
1120
1366
|
)
|
|
1367
|
+
allowed = self._governance.get("evaluation_splits")
|
|
1368
|
+
if allowed is not None:
|
|
1369
|
+
splits = {name: df for name, df in splits.items() if name in set(allowed)}
|
|
1121
1370
|
eval_splits = {**splits, **model_extra}
|
|
1122
1371
|
nan_stats: dict[str, int] = {}
|
|
1123
1372
|
for ds_name, df in eval_splits.items():
|
|
@@ -1351,6 +1600,11 @@ class CreditModelPipeline:
|
|
|
1351
1600
|
"warm_start_enabled": bool(cfg.warm_start_enabled and name in warm_start_requested),
|
|
1352
1601
|
"warm_start_score_col": cfg.warm_start_score_col,
|
|
1353
1602
|
"random_state": cfg.random_state,
|
|
1603
|
+
"candidate_mode": self._governance["candidate_mode"],
|
|
1604
|
+
"oot_synthesized": self._governance["oot_synthesized"],
|
|
1605
|
+
"oot_withheld": self._governance["oot_withheld"],
|
|
1606
|
+
"evaluation_splits": self._governance["evaluation_splits"],
|
|
1607
|
+
"forbidden_splits": self._governance["forbidden_splits"],
|
|
1354
1608
|
}
|
|
1355
1609
|
metrics = None
|
|
1356
1610
|
if isinstance(perf_results.get(name), pd.DataFrame):
|
|
@@ -18,6 +18,7 @@ from ._common import (
|
|
|
18
18
|
make_dirs,
|
|
19
19
|
normalize_group_specs,
|
|
20
20
|
normalize_split_values,
|
|
21
|
+
resolve_missing_oot,
|
|
21
22
|
safe_to_csv,
|
|
22
23
|
split_oot_by_flag,
|
|
23
24
|
validate_woe_fit_query_columns,
|
|
@@ -109,6 +110,11 @@ class FeatureValidationPipelineConfig:
|
|
|
109
110
|
selection_enabled: bool = False
|
|
110
111
|
selection_params: dict[str, Any] = field(default_factory=dict)
|
|
111
112
|
weight_col: str | None = None
|
|
113
|
+
# OOT governance (G10): None resolves to the legacy True (missing OOT is
|
|
114
|
+
# replaced by an OOS copy, now with a loud UserWarning). Set False to keep
|
|
115
|
+
# OOT empty instead — every OOT output is then simply absent. The next
|
|
116
|
+
# minor release flips the resolved default to False.
|
|
117
|
+
synthesize_missing_oot: bool | None = None
|
|
112
118
|
|
|
113
119
|
write_outputs: bool = True
|
|
114
120
|
write_excel: bool = True
|
|
@@ -959,6 +965,11 @@ class FeatureValidationPipeline:
|
|
|
959
965
|
if "apply_quarter" in cfg.time_dims and "apply_quarter" not in data.columns:
|
|
960
966
|
data["apply_quarter"] = dt.dt.to_period("Q").astype(str)
|
|
961
967
|
|
|
968
|
+
def _synthesize_missing_oot(self) -> bool:
|
|
969
|
+
"""Resolve the None-default: legacy True until the announced flip."""
|
|
970
|
+
value = self.config.synthesize_missing_oot
|
|
971
|
+
return True if value is None else bool(value)
|
|
972
|
+
|
|
962
973
|
def _split_data(self, data: pd.DataFrame, target_col: str | None) -> dict[str, pd.DataFrame]:
|
|
963
974
|
cfg = self.config
|
|
964
975
|
work = data.copy()
|
|
@@ -973,7 +984,14 @@ class FeatureValidationPipeline:
|
|
|
973
984
|
oot = work[normalized.eq("oot").fillna(False)].copy()
|
|
974
985
|
if len(ins) and len(oos):
|
|
975
986
|
if not len(oot):
|
|
976
|
-
|
|
987
|
+
synthesized = resolve_missing_oot(
|
|
988
|
+
oos,
|
|
989
|
+
self._synthesize_missing_oot(),
|
|
990
|
+
"FeatureValidationPipeline",
|
|
991
|
+
)
|
|
992
|
+
# FVP keeps the empty-OOT representation: downstream
|
|
993
|
+
# stages already guard every OOT consumer with len(oot).
|
|
994
|
+
oot = synthesized if synthesized is not None else oot
|
|
977
995
|
splits = {"ins": ins, "oos": oos, "oot": oot}
|
|
978
996
|
if cfg.split_col:
|
|
979
997
|
reserved = set(splits)
|
|
@@ -1002,7 +1020,13 @@ class FeatureValidationPipeline:
|
|
|
1002
1020
|
else:
|
|
1003
1021
|
ins, oos = self._split_frame(ins_oos, None)
|
|
1004
1022
|
if len(oot) == 0:
|
|
1005
|
-
|
|
1023
|
+
synthesized = resolve_missing_oot(
|
|
1024
|
+
oos,
|
|
1025
|
+
self._synthesize_missing_oot(),
|
|
1026
|
+
"FeatureValidationPipeline",
|
|
1027
|
+
)
|
|
1028
|
+
if synthesized is not None:
|
|
1029
|
+
oot = synthesized
|
|
1006
1030
|
return {"ins": ins.copy(), "oos": oos.copy(), "oot": oot.copy()}
|
|
1007
1031
|
|
|
1008
1032
|
def _split_frame(self, data: pd.DataFrame, target_col: str | None) -> tuple[pd.DataFrame, pd.DataFrame]:
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/field_meta.py
RENAMED
|
@@ -177,6 +177,14 @@ _FIELD_LABELS = {
|
|
|
177
177
|
"backward_model": "逐步回归模型",
|
|
178
178
|
"backward_params": "逐步回归参数",
|
|
179
179
|
"use_backward_features": "使用逐步回归特征",
|
|
180
|
+
"candidate_mode": "候选模式(禁用 OOT)",
|
|
181
|
+
"synthesize_missing_oot": "缺 OOT 时用 OOS 副本代替",
|
|
182
|
+
"evaluation_splits": "评估 split 白名单",
|
|
183
|
+
"forbidden_splits": "禁用 split(硬闸)",
|
|
184
|
+
"search_eval_splits": "调参评估 split",
|
|
185
|
+
"search_objective_when_no_oot": "无 OOT 时调参目标",
|
|
186
|
+
"backward_validation_split": "逐步回归验证 split",
|
|
187
|
+
"backward_report_splits": "逐步回归报告 split",
|
|
180
188
|
"optuna_models": "Optuna 模型",
|
|
181
189
|
"optuna_n_trials": "Optuna 轮数",
|
|
182
190
|
"optuna_params": "Optuna 参数",
|
|
@@ -354,6 +362,8 @@ _FIELD_OPTIONS = {
|
|
|
354
362
|
"warm_start_models": ["lgb", "xgb", "cat"],
|
|
355
363
|
"warm_start_on_unsupported": ["skip", "raise"],
|
|
356
364
|
"feature_selection_mode": ["run", "from_artifact", "skip"],
|
|
365
|
+
"search_objective_when_no_oot": ["max_primary", "oot_gap_penalized"],
|
|
366
|
+
"backward_validation_split": ["ins", "oos", "oot"],
|
|
357
367
|
"prescore_model_type": ["lgb", "xgb", "cat", "lr"],
|
|
358
368
|
"ri_model_type": ["lgb", "xgb", "cat", "lr"],
|
|
359
369
|
"lr_nan_handling": ["fillna_median", "fillna_mean", "fillna_0", "raise"],
|
{supermodelingfactory-0.6.4/SuperModelingFactory.egg-info → supermodelingfactory-0.6.5}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SuperModelingFactory
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.5
|
|
4
4
|
Summary: Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting.
|
|
5
5
|
Home-page: https://github.com/Kyle-J-Sun/SuperModelingFactory
|
|
6
6
|
Author: Kyle Sun
|
|
@@ -265,7 +265,7 @@ em.close_workbook()
|
|
|
265
265
|
|
|
266
266
|
## 版本
|
|
267
267
|
|
|
268
|
-
- **Version**: 0.6.
|
|
268
|
+
- **Version**: 0.6.5
|
|
269
269
|
- **Author**: Jingkai Sun
|
|
270
270
|
|
|
271
271
|
## 许可证
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5/SuperModelingFactory.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SuperModelingFactory
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.5
|
|
4
4
|
Summary: Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting.
|
|
5
5
|
Home-page: https://github.com/Kyle-J-Sun/SuperModelingFactory
|
|
6
6
|
Author: Kyle Sun
|
|
@@ -265,7 +265,7 @@ em.close_workbook()
|
|
|
265
265
|
|
|
266
266
|
## 版本
|
|
267
267
|
|
|
268
|
-
- **Version**: 0.6.
|
|
268
|
+
- **Version**: 0.6.5
|
|
269
269
|
- **Author**: Jingkai Sun
|
|
270
270
|
|
|
271
271
|
## 许可证
|
|
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
|
|
|
12
12
|
|
|
13
13
|
[project]
|
|
14
14
|
name = "SuperModelingFactory"
|
|
15
|
-
version = "0.6.
|
|
15
|
+
version = "0.6.5"
|
|
16
16
|
description = "Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting."
|
|
17
17
|
readme = "README.md"
|
|
18
18
|
requires-python = ">=3.10"
|
|
@@ -15,7 +15,7 @@ def _read(path: str) -> str:
|
|
|
15
15
|
|
|
16
16
|
setup(
|
|
17
17
|
name="SuperModelingFactory",
|
|
18
|
-
version=os.environ.get("SMF_VERSION", "0.6.
|
|
18
|
+
version=os.environ.get("SMF_VERSION", "0.6.5"),
|
|
19
19
|
description="Credit risk modeling factory: WOE binning, scorecards, LightGBM, Excel reporting.",
|
|
20
20
|
long_description=_read("README.md"),
|
|
21
21
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Binning_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Binning_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Json_Data_Converter.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Model_Registry_Tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Parallel_Engine.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/Proc_Compare.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/XOR_Encryptor.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/XOR_Encryptor.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Core/sample_weight_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/Evaluation_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/Evaluation_Tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/Model_Eval_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/Model_Eval_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/evaluate_model.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/evaluate_model.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Eval/weighted_eval_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Explainability/__init__.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Distribution_Tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Feature_Insights.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Feature_Insights.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Feature_Screen.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/PSI_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Feature/Weighted_Screen.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/Backward_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/Backward_Tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Model/GBM_Search_Tool.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/__init__.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/mock_sample.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/orchestrator.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/reject_inference.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/sample_analysis.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Pipeline/score_comparison.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/Reject_Infer.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/Reject_Infer.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/Sample_Split.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/Sample/Sample_Split.pyi
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
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Monotone_Binner.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Plot_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Report_Builder.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/WOE_Report_Builder.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/plot_woe_tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/WOE/plot_woe_tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/Modeling_Tool/ref_font/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/SuperModelingFactory.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/SuperModelingFactory.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{supermodelingfactory-0.6.4 → supermodelingfactory-0.6.5}/SuperModelingFactory.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|