SuperModelingFactory 0.5.7__tar.gz → 0.5.9__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 (113) hide show
  1. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +23 -2
  2. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/_common.py +6 -0
  3. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/credit_model.py +1 -0
  4. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/feature_validation.py +72 -23
  5. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/score_comparison.py +13 -5
  6. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/screening_artifact.py +29 -6
  7. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/__init__.py +1 -1
  8. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/PKG-INFO +2 -2
  9. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/README.md +1 -1
  10. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/SuperModelingFactory.egg-info/PKG-INFO +2 -2
  11. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/pyproject.toml +1 -1
  12. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/setup.py +1 -1
  13. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/ExcelMaster/ExcelFormatTool.py +0 -0
  14. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/ExcelMaster/ExcelMaster.py +0 -0
  15. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/ExcelMaster/Template.py +0 -0
  16. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/ExcelMaster/Utility.py +0 -0
  17. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/ExcelMaster/__init__.py +0 -0
  18. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/LICENSE +0 -0
  19. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/MANIFEST.in +0 -0
  20. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/Binning_Tool.py +0 -0
  21. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
  22. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
  23. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
  24. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
  25. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
  26. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
  27. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/Proc_Compare.py +0 -0
  28. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/Slope_Tool.py +0 -0
  29. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
  30. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
  31. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
  32. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/__init__.py +0 -0
  33. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/kDataFrame.py +0 -0
  34. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
  35. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
  36. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Core/utils.py +0 -0
  37. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
  38. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
  39. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
  40. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
  41. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Eval/__init__.py +0 -0
  42. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Eval/evaluate_model.py +0 -0
  43. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
  44. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
  45. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
  46. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
  47. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Explainability/__init__.py +0 -0
  48. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
  49. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
  50. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
  51. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
  52. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Feature/Feature_Screen.py +0 -0
  53. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
  54. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
  55. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
  56. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Feature/Weighted_Screen.py +0 -0
  57. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Feature/__init__.py +0 -0
  58. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Model/Backward_Tool.py +0 -0
  59. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
  60. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
  61. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Model/GBM_Tool.py +0 -0
  62. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
  63. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Model/LRM_Tool.py +0 -0
  64. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
  65. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Model/__init__.py +0 -0
  66. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/__init__.py +0 -0
  67. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/field_meta.py +0 -0
  68. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
  69. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
  70. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
  71. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
  72. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
  73. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
  74. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
  75. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
  76. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
  77. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Sample/Sample_Split.py +0 -0
  78. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
  79. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/Sample/__init__.py +0 -0
  80. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
  81. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/UAT/__init__.py +0 -0
  82. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
  83. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
  84. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Master.py +0 -0
  85. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
  86. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
  87. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
  88. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
  89. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
  90. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
  91. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
  92. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
  93. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
  94. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/__init__.py +0 -0
  95. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
  96. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
  97. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/_utils/__init__.py +0 -0
  98. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/_utils/nan_guard.py +0 -0
  99. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/_utils/robust.py +0 -0
  100. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/_utils/sentinels.py +0 -0
  101. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
  102. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
  103. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/ref_font/__init__.py +0 -0
  104. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Modeling_Tool/ref_font/simsun.ttc +0 -0
  105. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Report/Report_Tool.py +0 -0
  106. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/Report/__init__.py +0 -0
  107. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
  108. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
  109. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
  110. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/SuperModelingFactory.egg-info/requires.txt +0 -0
  111. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/SuperModelingFactory.egg-info/top_level.txt +0 -0
  112. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/requirements.txt +0 -0
  113. {supermodelingfactory-0.5.7 → supermodelingfactory-0.5.9}/setup.cfg +0 -0
@@ -64,6 +64,26 @@ def _strip_trailing_semicolon(sql: str) -> str:
64
64
  return sql.strip().rstrip(";").strip()
65
65
 
66
66
 
67
+ def _first_error_line(exc: BaseException, limit: int = 200) -> str:
68
+ """Return ``ExcType: first non-empty message line`` for embedding in a
69
+ re-raised error message, truncated to ``limit`` chars.
70
+
71
+ ODPS errors carry the actionable ``ODPS-XXXXXXX: ...`` description on
72
+ their first line followed by long instance/traceback detail; only the
73
+ first line is worth surfacing inline.
74
+ """
75
+ for line in str(exc).splitlines():
76
+ line = line.strip()
77
+ if line:
78
+ break
79
+ else:
80
+ line = ""
81
+ text = f"{type(exc).__name__}: {line}" if line else type(exc).__name__
82
+ if len(text) > limit:
83
+ return text[:limit] + "..."
84
+ return text
85
+
86
+
67
87
  def _pull_one_chunk(
68
88
  chunk_spec: dict[str, Any],
69
89
  runner: ODPSRunner | None,
@@ -218,8 +238,9 @@ class ParallelODPSManager:
218
238
  self.odps_runner.run_sql(probe_sql, to_df=True, n_process=1)
219
239
  except Exception as exc:
220
240
  raise ValueError(
221
- f"unique_key validation failed for pull SQL. "
222
- f"Check that unique_key={self.config.unique_key!r} is visible in the SQL scope."
241
+ f"unique_key validation failed for pull SQL: {_first_error_line(exc)}. "
242
+ f"Common cause: unique_key={self.config.unique_key!r} not visible in "
243
+ f"the SQL scope; see the chained exception for the full ODPS error."
223
244
  ) from exc
224
245
 
225
246
  def _build_hash_pull_chunks(
@@ -55,6 +55,12 @@ def as_list(value: Any | Iterable[Any] | None, default: list[Any] | None = None)
55
55
  return list(value)
56
56
 
57
57
 
58
+ def normalize_split_values(values: pd.Series) -> pd.Series:
59
+ """Normalize declared split labels for case-insensitive pipeline routing."""
60
+ normalized = values.astype("string").str.strip().str.lower()
61
+ return normalized.mask(normalized.eq(""), pd.NA)
62
+
63
+
58
64
  def normalize_group_specs(
59
65
  value: Any,
60
66
  *,
@@ -446,6 +446,7 @@ class CreditModelPipeline:
446
446
  return FeatureScreeningArtifact.from_fvp_result(
447
447
  cfg.feature_validation_result,
448
448
  target_col=cfg.target_col,
449
+ weight_col=cfg.weight_col,
449
450
  )
450
451
  return None
451
452
 
@@ -17,6 +17,7 @@ from ._common import (
17
17
  as_list,
18
18
  make_dirs,
19
19
  normalize_group_specs,
20
+ normalize_split_values,
20
21
  safe_to_csv,
21
22
  split_oot_by_flag,
22
23
  validate_woe_fit_query_columns,
@@ -132,6 +133,7 @@ class FeatureValidationPipelineResult:
132
133
  selected_features: list[str] = field(default_factory=list)
133
134
  selection_summary: dict[str, Any] = field(default_factory=dict)
134
135
  screening_artifact: Any | None = None
136
+ config_snapshot: dict[str, Any] = field(default_factory=dict)
135
137
 
136
138
 
137
139
  class FeatureValidationPipeline:
@@ -180,6 +182,12 @@ class FeatureValidationPipeline:
180
182
  incumbent_features = self._resolve_incumbent_features(work, new_features)
181
183
  target_cols = [col for col in as_list(cfg.target_cols) if col in work.columns]
182
184
  self._validate_input(work, new_features, incumbent_features, target_cols)
185
+ config_snapshot = self._build_config_snapshot(
186
+ new_features,
187
+ incumbent_features,
188
+ target_cols,
189
+ batch_mode=False,
190
+ )
183
191
 
184
192
  output_dir = Path(cfg.output_dir)
185
193
  if cfg.write_outputs or cfg.write_excel:
@@ -224,6 +232,7 @@ class FeatureValidationPipeline:
224
232
  incumbent_features=incumbent_features,
225
233
  target_cols=target_cols,
226
234
  woe_artifacts=woe_artifacts,
235
+ config_snapshot=config_snapshot,
227
236
  )
228
237
  validation_summary = self._build_validation_summary(
229
238
  data=combined,
@@ -270,6 +279,7 @@ class FeatureValidationPipeline:
270
279
  selected_features=selected_features,
271
280
  selection_summary=selection_summary,
272
281
  screening_artifact=screening_artifact,
282
+ config_snapshot=config_snapshot,
273
283
  )
274
284
 
275
285
  def _resolve_input_type(self, data: pd.DataFrame | str | Path) -> str:
@@ -407,6 +417,7 @@ class FeatureValidationPipeline:
407
417
  selected_features=list(result.selected_features),
408
418
  selection_summary=dict(result.selection_summary or {}),
409
419
  screening_artifact=result.screening_artifact,
420
+ config_snapshot=dict(result.config_snapshot or {}),
410
421
  )
411
422
 
412
423
  def _validate_batch_config(self, new_features: list[str]) -> None:
@@ -496,6 +507,10 @@ class FeatureValidationPipeline:
496
507
  return self._dedupe([col for col in candidates if col and col in header_set])
497
508
 
498
509
  def _stable_split_labels(self, base_df: pd.DataFrame, splits: dict[str, pd.DataFrame]) -> pd.Series:
510
+ cfg = self.config
511
+ if cfg.split_col and cfg.split_col in base_df.columns:
512
+ return normalize_split_values(base_df[cfg.split_col])
513
+
499
514
  labels = pd.Series("ins", index=base_df.index, dtype=object)
500
515
  oos_ids = set(splits.get("oos", pd.DataFrame()).get("_smf_batch_row_id", pd.Series(dtype=int)).tolist())
501
516
  oot_ids = set(splits.get("oot", pd.DataFrame()).get("_smf_batch_row_id", pd.Series(dtype=int)).tolist())
@@ -503,6 +518,11 @@ class FeatureValidationPipeline:
503
518
  row_ids = base_df["_smf_batch_row_id"]
504
519
  labels.loc[row_ids.isin(oos_ids)] = "oos"
505
520
  labels.loc[row_ids.isin(unique_oot_ids)] = "oot"
521
+ for name, frame in splits.items():
522
+ if name in {"ins", "oos", "oot"}:
523
+ continue
524
+ ids = set(frame.get("_smf_batch_row_id", pd.Series(dtype=int)).tolist())
525
+ labels.loc[row_ids.isin(ids)] = name
506
526
  return labels
507
527
 
508
528
  def _merge_batch_results(
@@ -554,6 +574,12 @@ class FeatureValidationPipeline:
554
574
  selected_features = self._dedupe(
555
575
  [feat for res in batch_results for feat in as_list(res.selected_features)]
556
576
  )
577
+ config_snapshot = self._build_config_snapshot(
578
+ new_features,
579
+ incumbent_features,
580
+ target_cols,
581
+ batch_mode=True,
582
+ )
557
583
  selection_summary: dict[str, Any] = {}
558
584
  screening_artifact = None
559
585
  if self.config.selection_enabled and target_cols:
@@ -561,12 +587,7 @@ class FeatureValidationPipeline:
561
587
  "initial_features": list(new_features),
562
588
  "final_features": selected_features,
563
589
  "target_col": target_cols[0],
564
- "config_snapshot": {
565
- "selection_enabled": True,
566
- "selection_params": dict(self.config.selection_params or {}),
567
- "weight_col": self.config.weight_col,
568
- "batch_mode": True,
569
- },
590
+ "config_snapshot": config_snapshot,
570
591
  }
571
592
  if selected_features:
572
593
  from .screening_artifact import FeatureScreeningArtifact
@@ -619,6 +640,7 @@ class FeatureValidationPipeline:
619
640
  selected_features=selected_features,
620
641
  selection_summary=selection_summary,
621
642
  screening_artifact=screening_artifact,
643
+ config_snapshot=config_snapshot,
622
644
  )
623
645
 
624
646
  @staticmethod
@@ -854,6 +876,30 @@ class FeatureValidationPipeline:
854
876
  ]
855
877
  return pd.DataFrame(rows)
856
878
 
879
+ def _build_config_snapshot(
880
+ self,
881
+ new_features: list[str],
882
+ incumbent_features: list[str],
883
+ target_cols: list[str],
884
+ *,
885
+ batch_mode: bool,
886
+ ) -> dict[str, Any]:
887
+ cfg = self.config
888
+ return {
889
+ "selection_enabled": bool(cfg.selection_enabled),
890
+ "selection_params": dict(cfg.selection_params or {}),
891
+ "weight_col": cfg.weight_col,
892
+ "target_col": target_cols[0] if target_cols else None,
893
+ "target_cols": list(target_cols),
894
+ "new_feature_cols": list(new_features),
895
+ "incumbent_feature_cols": list(incumbent_features),
896
+ "woe_engine": cfg.woe_engine,
897
+ "woe_fit_query": cfg.woe_fit_query,
898
+ "woe_params": dict(cfg.woe_params or {}),
899
+ "monotone_woe_params": dict(cfg.monotone_woe_params or {}),
900
+ "batch_mode": bool(batch_mode),
901
+ }
902
+
857
903
  def _validate_input(
858
904
  self,
859
905
  data: pd.DataFrame,
@@ -864,6 +910,8 @@ class FeatureValidationPipeline:
864
910
  cfg = self.config
865
911
  missing = [col for col in [cfg.id_col, cfg.apply_time_col] if col and col not in data.columns]
866
912
  missing += [col for col in new_features + incumbent_features + target_cols if col not in data.columns]
913
+ if cfg.weight_col and cfg.weight_col not in data.columns:
914
+ missing.append(cfg.weight_col)
867
915
  if missing:
868
916
  raise KeyError(f"Missing required columns: {sorted(set(missing))}")
869
917
  if not new_features:
@@ -917,18 +965,20 @@ class FeatureValidationPipeline:
917
965
  raise KeyError(f"Missing split_col {cfg.split_col!r}")
918
966
  if sample_col in work.columns:
919
967
  raw_split = work[sample_col]
920
- lower = raw_split.astype(str).str.strip().str.lower()
921
- if cfg.split_col:
922
- invalid = sorted(set(raw_split.dropna().astype(str).str.strip().str.lower()) - {"ins", "oos", "oot"})
923
- if invalid:
924
- raise ValueError(f"split_col {cfg.split_col!r} only supports ins/oos/oot values, got {invalid}")
925
- ins = work[lower == "ins"].copy()
926
- oos = work[lower == "oos"].copy()
927
- oot = work[lower == "oot"].copy()
968
+ normalized = normalize_split_values(raw_split)
969
+ ins = work[normalized.eq("ins").fillna(False)].copy()
970
+ oos = work[normalized.eq("oos").fillna(False)].copy()
971
+ oot = work[normalized.eq("oot").fillna(False)].copy()
928
972
  if len(ins) and len(oos):
929
973
  if not len(oot):
930
974
  oot = oos.copy()
931
- return {"ins": ins, "oos": oos, "oot": oot}
975
+ splits = {"ins": ins, "oos": oos, "oot": oot}
976
+ if cfg.split_col:
977
+ reserved = set(splits)
978
+ for label in normalized.dropna().drop_duplicates().tolist():
979
+ if label not in reserved:
980
+ splits[str(label)] = work[normalized.eq(label).fillna(False)].copy()
981
+ return splits
932
982
  if cfg.split_col:
933
983
  raise ValueError(f"split_col {cfg.split_col!r} must contain non-empty ins and oos samples")
934
984
 
@@ -1251,6 +1301,7 @@ class FeatureValidationPipeline:
1251
1301
  incumbent_features: list[str],
1252
1302
  target_cols: list[str],
1253
1303
  woe_artifacts: dict[str, Any],
1304
+ config_snapshot: dict[str, Any],
1254
1305
  ) -> tuple[list[str], dict[str, Any], Any | None]:
1255
1306
  from Modeling_Tool.Feature.Feature_Screen import feature_screen
1256
1307
  from .screening_artifact import FeatureScreeningArtifact, screen_result_to_summary
@@ -1274,24 +1325,22 @@ class FeatureValidationPipeline:
1274
1325
  config=screen_cfg,
1275
1326
  prefit_woe_engine=engine,
1276
1327
  )
1277
- config_snapshot = {
1278
- "selection_enabled": True,
1279
- "selection_params": dict(cfg.selection_params or {}),
1328
+ selection_config_snapshot = dict(config_snapshot)
1329
+ selection_config_snapshot.update({
1280
1330
  "missing_rate_threshold": screen_cfg.missing_rate_threshold,
1281
- "weight_col": cfg.weight_col,
1282
1331
  "psi_use_woe_bins": screen_cfg.psi_use_woe_bins,
1283
1332
  "iv_use_woe_bins": screen_cfg.iv_use_woe_bins,
1284
1333
  "corr_use_woe_bins": screen_cfg.corr_use_woe_bins,
1285
1334
  "n_incumbent_features": len(incumbent_features),
1286
- }
1335
+ })
1287
1336
  selection_summary = {
1288
1337
  "initial_features": list(new_features),
1289
1338
  "final_features": list(result.selected_features),
1290
1339
  "target_col": target,
1291
- "config_snapshot": config_snapshot,
1340
+ "config_snapshot": selection_config_snapshot,
1292
1341
  }
1293
1342
  selection_summary.update(screen_result_to_summary(result, new_features))
1294
- selection_summary["config_snapshot"] = config_snapshot
1343
+ selection_summary["config_snapshot"] = selection_config_snapshot
1295
1344
  artifact = FeatureScreeningArtifact.from_screen_result(
1296
1345
  result,
1297
1346
  initial_features=list(new_features),
@@ -1299,7 +1348,7 @@ class FeatureValidationPipeline:
1299
1348
  weight_col=cfg.weight_col,
1300
1349
  woe_artifacts=woe_artifacts,
1301
1350
  source="fvp",
1302
- config_snapshot=config_snapshot,
1351
+ config_snapshot=selection_config_snapshot,
1303
1352
  )
1304
1353
  return list(result.selected_features), selection_summary, artifact
1305
1354
 
@@ -7,7 +7,14 @@ from typing import Any, Callable
7
7
 
8
8
  import pandas as pd
9
9
 
10
- from ._common import as_list, make_dirs, normalize_group_specs, safe_to_csv, write_basic_excel
10
+ from ._common import (
11
+ as_list,
12
+ make_dirs,
13
+ normalize_group_specs,
14
+ normalize_split_values,
15
+ safe_to_csv,
16
+ write_basic_excel,
17
+ )
11
18
 
12
19
  _logger = logging.getLogger(__name__)
13
20
 
@@ -287,6 +294,8 @@ class ScoreComparisonPipeline:
287
294
  data.loc[missing_mask, col] = pd.NA
288
295
  elif cfg.include_missing:
289
296
  data.loc[missing_mask, col] = "[Missing]"
297
+ if cfg.split_col and cfg.split_col in data.columns:
298
+ data[cfg.split_col] = normalize_split_values(data[cfg.split_col])
290
299
 
291
300
  def _resolve_scores(self, data: pd.DataFrame) -> list[str]:
292
301
  cfg = self.config
@@ -314,10 +323,9 @@ class ScoreComparisonPipeline:
314
323
  if missing:
315
324
  raise KeyError(f"Missing required columns: {missing}")
316
325
  if cfg.split_col:
317
- values = data[cfg.split_col].dropna().astype(str).str.strip().str.lower()
318
- invalid = sorted(set(values) - {"ins", "oos", "oot"})
319
- if invalid:
320
- raise ValueError(f"split_col {cfg.split_col!r} only supports ins/oos/oot values, got {invalid}")
326
+ values = normalize_split_values(data[cfg.split_col]).dropna()
327
+ if values.empty:
328
+ raise ValueError(f"split_col {cfg.split_col!r} must contain at least one non-empty value")
321
329
 
322
330
  def _custom_metrics_func(self, sub_df: pd.DataFrame) -> pd.Series:
323
331
  cfg = self.config
@@ -81,24 +81,47 @@ class FeatureScreeningArtifact:
81
81
  result: Any,
82
82
  *,
83
83
  target_col: str | None = None,
84
+ weight_col: str | None = None,
84
85
  ) -> FeatureScreeningArtifact:
85
86
  if getattr(result, "screening_artifact", None) is not None:
86
87
  return result.screening_artifact
88
+ summary = dict(getattr(result, "selection_summary", {}) or {})
89
+ summary_snapshot = dict(summary.get("config_snapshot", {}) or {})
90
+ result_snapshot = dict(getattr(result, "config_snapshot", {}) or {})
91
+ config_snapshot = {**summary_snapshot, **result_snapshot}
87
92
  resolved_target = target_col
88
93
  if not resolved_target:
89
- summary = getattr(result, "selection_summary", {}) or {}
90
94
  resolved_target = summary.get("target_col")
95
+ if not resolved_target:
96
+ resolved_target = config_snapshot.get("target_col")
97
+ if not resolved_target:
98
+ target_cols = list(config_snapshot.get("target_cols") or [])
99
+ resolved_target = target_cols[0] if target_cols else None
91
100
  if not resolved_target:
92
101
  raise ValueError("target_col is required when building artifact from FVP result.")
93
- summary = dict(getattr(result, "selection_summary", {}) or {})
94
- config_snapshot = dict(summary.get("config_snapshot", {}) or {})
102
+ selected_features = list(getattr(result, "selected_features", []) or [])
103
+ if not selected_features and config_snapshot.get("selection_enabled") is False:
104
+ selected_features = list(config_snapshot.get("new_feature_cols") or [])
105
+ if not summary and config_snapshot.get("selection_enabled") is False:
106
+ summary = {
107
+ "initial_features": list(selected_features),
108
+ "final_features": list(selected_features),
109
+ "target_col": str(resolved_target),
110
+ "selection_skipped": True,
111
+ "config_snapshot": config_snapshot,
112
+ }
113
+ resolved_weight_col = (
114
+ config_snapshot["weight_col"]
115
+ if "weight_col" in config_snapshot
116
+ else weight_col
117
+ )
95
118
  return cls(
96
- selected_features=list(getattr(result, "selected_features", []) or []),
119
+ selected_features=selected_features,
97
120
  selection_summary=summary,
98
121
  woe_artifacts=getattr(result, "woe_artifacts", None),
99
122
  source="fvp",
100
123
  target_col=str(resolved_target),
101
- weight_col=config_snapshot.get("weight_col"),
124
+ weight_col=resolved_weight_col,
102
125
  config_snapshot=config_snapshot,
103
126
  created_at=datetime.now(timezone.utc).isoformat(),
104
127
  )
@@ -124,4 +147,4 @@ class FeatureScreeningArtifact:
124
147
  __all__ = [
125
148
  "FeatureScreeningArtifact",
126
149
  "screen_result_to_summary",
127
- ]
150
+ ]
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  __author__ = "Jingkai Sun"
4
- __version__ = "0.5.7"
4
+ __version__ = "0.5.9"
5
5
 
6
6
  from ._utils import SMF_MISSING_BIN
7
7
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SuperModelingFactory
3
- Version: 0.5.7
3
+ Version: 0.5.9
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.5.7
268
+ - **Version**: 0.5.9
269
269
  - **Author**: Jingkai Sun
270
270
 
271
271
  ## 许可证
@@ -208,7 +208,7 @@ em.close_workbook()
208
208
 
209
209
  ## 版本
210
210
 
211
- - **Version**: 0.5.7
211
+ - **Version**: 0.5.9
212
212
  - **Author**: Jingkai Sun
213
213
 
214
214
  ## 许可证
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SuperModelingFactory
3
- Version: 0.5.7
3
+ Version: 0.5.9
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.5.7
268
+ - **Version**: 0.5.9
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.5.7"
15
+ version = "0.5.9"
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.5.7"),
18
+ version=os.environ.get("SMF_VERSION", "0.5.9"),
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",