SuperModelingFactory 0.3.4__tar.gz → 0.3.6__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 (104) hide show
  1. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Pipeline/_common.py +108 -0
  2. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Pipeline/credit_model.py +106 -6
  3. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Pipeline/feature_validation.py +17 -1
  4. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/__init__.py +1 -1
  5. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/PKG-INFO +7 -4
  6. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/README.md +6 -3
  7. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/SuperModelingFactory.egg-info/PKG-INFO +7 -4
  8. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/pyproject.toml +1 -1
  9. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/setup.py +1 -1
  10. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/ExcelMaster/ExcelFormatTool.py +0 -0
  11. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/ExcelMaster/ExcelMaster.py +0 -0
  12. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/ExcelMaster/Template.py +0 -0
  13. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/ExcelMaster/Utility.py +0 -0
  14. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/ExcelMaster/__init__.py +0 -0
  15. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/LICENSE +0 -0
  16. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/MANIFEST.in +0 -0
  17. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/Binning_Tool.py +0 -0
  18. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
  19. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
  20. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
  21. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
  22. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
  23. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
  24. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
  25. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/Proc_Compare.py +0 -0
  26. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/Slope_Tool.py +0 -0
  27. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
  28. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
  29. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
  30. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/__init__.py +0 -0
  31. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/kDataFrame.py +0 -0
  32. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
  33. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
  34. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Core/utils.py +0 -0
  35. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
  36. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
  37. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
  38. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
  39. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Eval/__init__.py +0 -0
  40. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Eval/evaluate_model.py +0 -0
  41. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
  42. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
  43. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
  44. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
  45. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Explainability/__init__.py +0 -0
  46. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
  47. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
  48. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
  49. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
  50. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
  51. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
  52. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
  53. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Feature/__init__.py +0 -0
  54. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Model/Backward_Tool.py +0 -0
  55. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
  56. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
  57. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Model/GBM_Tool.py +0 -0
  58. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
  59. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Model/LRM_Tool.py +0 -0
  60. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
  61. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Model/__init__.py +0 -0
  62. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Pipeline/__init__.py +0 -0
  63. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
  64. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
  65. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
  66. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
  67. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
  68. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
  69. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
  70. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
  71. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
  72. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Sample/Sample_Split.py +0 -0
  73. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
  74. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/Sample/__init__.py +0 -0
  75. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
  76. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/UAT/__init__.py +0 -0
  77. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
  78. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
  79. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Master.py +0 -0
  80. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
  81. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
  82. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
  83. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
  84. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
  85. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
  86. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
  87. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
  88. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
  89. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/__init__.py +0 -0
  90. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
  91. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
  92. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
  93. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
  94. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/ref_font/__init__.py +0 -0
  95. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Modeling_Tool/ref_font/simsun.ttc +0 -0
  96. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Report/Report_Tool.py +0 -0
  97. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/Report/__init__.py +0 -0
  98. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
  99. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
  100. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
  101. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/SuperModelingFactory.egg-info/requires.txt +0 -0
  102. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/SuperModelingFactory.egg-info/top_level.txt +0 -0
  103. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/requirements.txt +0 -0
  104. {supermodelingfactory-0.3.4 → supermodelingfactory-0.3.6}/setup.cfg +0 -0
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
+ import re
4
5
  from dataclasses import fields, is_dataclass
5
6
  from pathlib import Path
6
7
  from typing import Any, Iterable, Mapping
@@ -8,6 +9,36 @@ from typing import Any, Iterable, Mapping
8
9
  import numpy as np
9
10
  import pandas as pd
10
11
 
12
+ _QUERY_COLUMN_RE = re.compile(r"(?<!['\"])\b([A-Za-z_][\w]*)\b(?![\'\"])")
13
+ _QUERY_RESERVED = frozenset(
14
+ {
15
+ "and",
16
+ "or",
17
+ "not",
18
+ "True",
19
+ "False",
20
+ "None",
21
+ "in",
22
+ "is",
23
+ "where",
24
+ "if",
25
+ "else",
26
+ "lambda",
27
+ "abs",
28
+ "all",
29
+ "any",
30
+ "max",
31
+ "min",
32
+ "sum",
33
+ "len",
34
+ "round",
35
+ "float",
36
+ "int",
37
+ "str",
38
+ "bool",
39
+ }
40
+ )
41
+
11
42
 
12
43
  def make_dirs(*paths: str | os.PathLike | None) -> None:
13
44
  for path in paths:
@@ -29,6 +60,83 @@ def merge_dict(base: Mapping[str, Any] | None, override: Mapping[str, Any] | Non
29
60
  return merged
30
61
 
31
62
 
63
+ def query_referenced_columns(expr: str) -> set[str]:
64
+ """Return identifier-like column names referenced in a pandas query expression."""
65
+ if not expr or not str(expr).strip():
66
+ return set()
67
+ return {
68
+ match.group(1)
69
+ for match in _QUERY_COLUMN_RE.finditer(str(expr))
70
+ if match.group(1) not in _QUERY_RESERVED
71
+ }
72
+
73
+
74
+ def validate_woe_fit_query_syntax(data: pd.DataFrame, query: str) -> None:
75
+ """Raise ValueError when a woe_fit_query expression is syntactically invalid."""
76
+ if not query:
77
+ return
78
+ sample = data.head(min(100, len(data)))
79
+ if sample.empty:
80
+ raise ValueError("woe_fit_query cannot be validated on an empty dataset.")
81
+ try:
82
+ sample.query(query, engine="python")
83
+ except SyntaxError as exc:
84
+ raise ValueError(f"Invalid woe_fit_query syntax: {query!r}") from exc
85
+ except Exception as exc:
86
+ raise ValueError(f"woe_fit_query failed on sample data: {query!r} ({exc})") from exc
87
+
88
+
89
+ def validate_woe_fit_query_columns(
90
+ query: str,
91
+ available_cols: Iterable[str],
92
+ *,
93
+ context: str = "dataset",
94
+ ) -> None:
95
+ """Raise KeyError when query references columns missing from available_cols."""
96
+ if not query:
97
+ return
98
+ available = set(available_cols)
99
+ missing = sorted(query_referenced_columns(query) - available)
100
+ if missing:
101
+ raise KeyError(
102
+ f"woe_fit_query references missing columns in {context}: {missing}. "
103
+ "Add them to the input data, batch_base_cols, or population_dims."
104
+ )
105
+
106
+
107
+ def apply_woe_fit_query(
108
+ train: pd.DataFrame,
109
+ query: str | None,
110
+ *,
111
+ target: str | None = None,
112
+ ) -> tuple[pd.DataFrame, dict[str, Any] | None]:
113
+ """Filter WOE fit rows via pandas query and return an audit row for refine_summary."""
114
+ if not query:
115
+ return train, None
116
+ n_before = len(train)
117
+ try:
118
+ filtered = train.query(query, engine="python").copy()
119
+ except Exception as exc:
120
+ audit = {
121
+ "target": target,
122
+ "step": "fit_filter",
123
+ "status": "error",
124
+ "query": query,
125
+ "n_before": n_before,
126
+ "error": repr(exc),
127
+ }
128
+ return train, audit
129
+ audit = {
130
+ "target": target,
131
+ "step": "fit_filter",
132
+ "status": "ok",
133
+ "query": query,
134
+ "n_before": n_before,
135
+ "n_after": len(filtered),
136
+ }
137
+ return filtered, audit
138
+
139
+
32
140
  def dataclass_to_dict(obj: Any) -> dict[str, Any]:
33
141
  if not is_dataclass(obj):
34
142
  return dict(obj)
@@ -9,11 +9,14 @@ import pandas as pd
9
9
 
10
10
  from ._common import (
11
11
  add_dataset_with_optional_weight,
12
+ apply_woe_fit_query,
12
13
  as_list,
13
14
  make_dirs,
14
15
  merge_dict,
15
16
  predict_positive,
16
17
  safe_to_csv,
18
+ validate_woe_fit_query_columns,
19
+ validate_woe_fit_query_syntax,
17
20
  write_basic_excel,
18
21
  )
19
22
 
@@ -26,6 +29,7 @@ class CreditModelPipelineConfig:
26
29
  split_col: str | None = None
27
30
  sample_col: str = "sample_ind"
28
31
  oot_col: str | None = "oot_flag"
32
+ weight_col: str | None = None
29
33
  random_state: int = 42
30
34
  write_outputs: bool = True
31
35
  write_excel: bool = True
@@ -47,6 +51,8 @@ class CreditModelPipelineConfig:
47
51
  }
48
52
  )
49
53
  woe_engine: str = "equal_freq"
54
+ woe_fit_query: str | None = None
55
+ extra_eval_datasets: dict[str, pd.DataFrame] | None = None
50
56
  woe_params: dict[str, Any] = field(
51
57
  default_factory=lambda: {"nbins": 10, "equal_freq": True, "min_bin_prop": 0.05}
52
58
  )
@@ -207,7 +213,11 @@ class CreditModelPipeline:
207
213
  models = self._train_models(model_inputs)
208
214
 
209
215
  optuna_results = self._run_optuna(model_inputs)
210
- perf_results = self._evaluate_models(model_inputs, models)
216
+ perf_results = self._evaluate_models(
217
+ model_inputs,
218
+ models,
219
+ woe_artifacts.get("extra_eval"),
220
+ )
211
221
  explain_outputs = self._run_explainability(model_inputs, models)
212
222
  model_paths: dict[str, str] = {}
213
223
  artifact_paths: dict[str, str] = {}
@@ -283,6 +293,8 @@ class CreditModelPipeline:
283
293
  excluded.add(cfg.split_col)
284
294
  if cfg.oot_col:
285
295
  excluded.add(cfg.oot_col)
296
+ if cfg.weight_col:
297
+ excluded.add(cfg.weight_col)
286
298
  numeric_cols = data.select_dtypes(include=[np.number]).columns
287
299
  return [col for col in numeric_cols if col not in excluded]
288
300
 
@@ -292,6 +304,12 @@ class CreditModelPipeline:
292
304
  missing = [col for col in missing if col not in data.columns]
293
305
  if missing:
294
306
  raise KeyError(f"Missing required columns: {missing}")
307
+ if cfg.weight_col:
308
+ if cfg.weight_col not in data.columns:
309
+ raise KeyError(f"Missing weight_col {cfg.weight_col!r}")
310
+ from Modeling_Tool.Core.sample_weight_utils import resolve_sample_weight
311
+
312
+ resolve_sample_weight(data=data, weight_col=cfg.weight_col, expected_len=len(data))
295
313
  if cfg.warm_start_enabled:
296
314
  if not cfg.warm_start_score_col:
297
315
  raise ValueError("warm_start_score_col is required when warm_start_enabled=True")
@@ -301,6 +319,38 @@ class CreditModelPipeline:
301
319
  raise ValueError("warm_start_score_type must be 'probability' or 'log_odds'")
302
320
  if cfg.warm_start_on_unsupported not in {"skip", "raise"}:
303
321
  raise ValueError("warm_start_on_unsupported must be 'skip' or 'raise'")
322
+ if cfg.woe_fit_query:
323
+ validate_woe_fit_query_columns(cfg.woe_fit_query, data.columns, context="input data")
324
+ validate_woe_fit_query_syntax(data, cfg.woe_fit_query)
325
+ if cfg.extra_eval_datasets:
326
+ reserved = {"ins", "oos", "oot"}
327
+ conflicts = sorted(set(cfg.extra_eval_datasets) & reserved)
328
+ if conflicts:
329
+ raise ValueError(
330
+ f"extra_eval_datasets names cannot conflict with split names {sorted(reserved)}: {conflicts}"
331
+ )
332
+ for name, extra_df in cfg.extra_eval_datasets.items():
333
+ if cfg.target_col not in extra_df.columns:
334
+ raise KeyError(f"extra_eval_datasets[{name!r}] missing target_col {cfg.target_col!r}")
335
+ if cfg.warm_start_enabled and cfg.warm_start_score_col and cfg.warm_start_score_col not in extra_df.columns:
336
+ raise KeyError(
337
+ f"extra_eval_datasets[{name!r}] missing warm_start_score_col {cfg.warm_start_score_col!r}"
338
+ )
339
+
340
+ def _gbm_weight_kwargs(self, train: pd.DataFrame, val: pd.DataFrame) -> dict[str, Any]:
341
+ cfg = self.config
342
+ if not cfg.weight_col:
343
+ return {}
344
+ from Modeling_Tool.Core.sample_weight_utils import resolve_sample_weight
345
+
346
+ kwargs: dict[str, Any] = {}
347
+ train_sw = resolve_sample_weight(data=train, weight_col=cfg.weight_col, expected_len=len(train))
348
+ if train_sw is not None:
349
+ kwargs["sample_weight"] = train_sw
350
+ eval_sw = resolve_sample_weight(data=val, weight_col=cfg.weight_col, expected_len=len(val))
351
+ if eval_sw is not None:
352
+ kwargs["eval_sample_weight"] = eval_sw
353
+ return kwargs
304
354
 
305
355
  def _validate_gbm_feature_source_config(self) -> None:
306
356
  source_cfg = self.config.gbm_feature_source
@@ -442,6 +492,24 @@ class CreditModelPipeline:
442
492
  summary["final_features"] = list(current)
443
493
  return summary, list(current)
444
494
 
495
+ def _transform_extra_eval_datasets(
496
+ self,
497
+ transform_fn: Any,
498
+ feature_cols: list[str],
499
+ *,
500
+ woe_suffix: str | None = None,
501
+ ) -> dict[str, pd.DataFrame]:
502
+ cfg = self.config
503
+ if not cfg.extra_eval_datasets:
504
+ return {}
505
+ transformed: dict[str, pd.DataFrame] = {}
506
+ for name, df in cfg.extra_eval_datasets.items():
507
+ if woe_suffix is not None:
508
+ transformed[name] = transform_fn(df, varlist=feature_cols, suffix=woe_suffix)
509
+ else:
510
+ transformed[name] = transform_fn(df)
511
+ return transformed
512
+
445
513
  def _fit_woe(self, splits: dict[str, pd.DataFrame], feature_cols: list[str]) -> dict[str, Any]:
446
514
  from Modeling_Tool import MonotoneWOEBinner, WOE_Master
447
515
  from Modeling_Tool.WOE.WOE_Adapter import as_woe_engine
@@ -451,6 +519,11 @@ class CreditModelPipeline:
451
519
  woe_suffix = cfg.woe_params.get("woe_suffix", "_woe")
452
520
  graph_dir = str(Path(cfg.output_dir) / "figs" / "woe")
453
521
  woe_features = [f"{col}{woe_suffix}" for col in feature_cols]
522
+ fit_ins, _ = apply_woe_fit_query(
523
+ splits["ins"],
524
+ cfg.woe_fit_query,
525
+ target=cfg.target_col,
526
+ )
454
527
 
455
528
  if cfg.woe_engine.lower() == "monotone":
456
529
  params = merge_dict(
@@ -462,7 +535,7 @@ class CreditModelPipeline:
462
535
  cfg.monotone_woe_params,
463
536
  )
464
537
  binner = MonotoneWOEBinner(**params)
465
- binner.fit(splits["ins"], chi2_binning=bool(cfg.monotone_woe_params.get("chi2_binning", False)))
538
+ binner.fit(fit_ins, chi2_binning=bool(cfg.monotone_woe_params.get("chi2_binning", False)))
466
539
  if cfg.write_outputs and cfg.plot_outputs:
467
540
  binner.plot_woe_graph(graph_path=str(Path(cfg.output_dir) / "figs" / "mono_woe"))
468
541
  adapter = as_woe_engine(binner, woe_suffix=woe_suffix)
@@ -472,9 +545,14 @@ class CreditModelPipeline:
472
545
  }
473
546
  woe_table = adapter.get_woe_table(varlist=feature_cols)
474
547
  engine = adapter
548
+ extra_eval = self._transform_extra_eval_datasets(
549
+ adapter.transform,
550
+ feature_cols,
551
+ woe_suffix=woe_suffix,
552
+ )
475
553
  else:
476
554
  master = WOE_Master(
477
- train_data=splits["ins"],
555
+ train_data=fit_ins,
478
556
  varlist=feature_cols,
479
557
  dep=cfg.target_col,
480
558
  graph_save_dir=graph_dir,
@@ -494,13 +572,18 @@ class CreditModelPipeline:
494
572
  dirname="overall",
495
573
  varlist=feature_cols,
496
574
  )
497
- woe_table = get_overall_woe_table(master, splits["ins"], varlist=feature_cols)
575
+ woe_table = get_overall_woe_table(master, fit_ins, varlist=feature_cols)
498
576
  engine = master
577
+ extra_eval = self._transform_extra_eval_datasets(master.transform, feature_cols)
499
578
 
500
579
  if cfg.warm_start_enabled and cfg.warm_start_score_col:
501
580
  for name, df in woe_splits.items():
502
581
  if cfg.warm_start_score_col not in df.columns:
503
582
  df[cfg.warm_start_score_col] = splits[name][cfg.warm_start_score_col].to_numpy()
583
+ for name, df in extra_eval.items():
584
+ if cfg.warm_start_score_col not in df.columns:
585
+ source = cfg.extra_eval_datasets[name]
586
+ df[cfg.warm_start_score_col] = source[cfg.warm_start_score_col].to_numpy()
504
587
 
505
588
  return {
506
589
  "engine": engine,
@@ -509,6 +592,7 @@ class CreditModelPipeline:
509
592
  "woe_features": woe_features,
510
593
  "woe_suffix": woe_suffix,
511
594
  "splits": woe_splits,
595
+ "extra_eval": extra_eval,
512
596
  "woe_table": woe_table,
513
597
  }
514
598
 
@@ -614,6 +698,7 @@ class CreditModelPipeline:
614
698
  val_data=val,
615
699
  val_varlist=feature_cols,
616
700
  val_tgt_name=cfg.target_col,
701
+ weight_col=cfg.weight_col,
617
702
  )
618
703
  models[name] = (lr, getattr(lr, "model", lr), list(feature_cols))
619
704
  elif name in {"lgb", "xgb", "cat"}:
@@ -629,6 +714,7 @@ class CreditModelPipeline:
629
714
  valx=val[feature_cols],
630
715
  valy=val[cfg.target_col].astype(int),
631
716
  init_score=init_score,
717
+ **self._gbm_weight_kwargs(train, val),
632
718
  )
633
719
  raw = gbm._model.model if hasattr(gbm, "_model") else gbm
634
720
  models[name] = (gbm, raw, list(feature_cols))
@@ -651,6 +737,8 @@ class CreditModelPipeline:
651
737
  "model_type": f"{cfg.backward_model}m" if cfg.backward_model == "lgb" else cfg.backward_model,
652
738
  "validation_data": splits["oos"],
653
739
  "test_data_dict": {"oot": splits["oot"]},
740
+ "weight_col": cfg.weight_col,
741
+ "validation_weight_col": cfg.weight_col,
654
742
  },
655
743
  cfg.backward_params.get("init", {}),
656
744
  )
@@ -711,6 +799,8 @@ class CreditModelPipeline:
711
799
  tgt_name=cfg.target_col,
712
800
  eval_sets={"oos": splits["oos"], "oot": splits["oot"]},
713
801
  param_grid=cfg.lr_search_param_grid,
802
+ weight_col=cfg.weight_col,
803
+ eval_weight_col=cfg.weight_col,
714
804
  **params,
715
805
  )
716
806
  self._lr_best_params = dict(getattr(lr, "best_params_", {}) or {})
@@ -831,6 +921,8 @@ class CreditModelPipeline:
831
921
  data=splits["ins"],
832
922
  search_space=search_spaces[name],
833
923
  fit_kwargs=fit_kwargs or None,
924
+ weight_col=cfg.weight_col,
925
+ eval_weight_col=cfg.weight_col,
834
926
  **common,
835
927
  )
836
928
  except Exception as exc:
@@ -841,13 +933,20 @@ class CreditModelPipeline:
841
933
  self,
842
934
  model_inputs: dict[str, dict[str, Any]],
843
935
  models: dict[str, tuple[Any, Any, list[str]]],
936
+ extra_eval_splits: dict[str, pd.DataFrame] | None = None,
844
937
  ) -> dict[str, pd.DataFrame]:
845
938
  from Modeling_Tool import PerformanceEvaluator
846
939
 
847
940
  cfg = self.config
848
941
  results = {}
942
+ extra_eval_splits = extra_eval_splits or {}
849
943
  for name, (wrapper, _, feature_cols) in models.items():
850
944
  splits = model_inputs[name]["splits"]
945
+ source = str(model_inputs[name].get("source", "woe")).lower()
946
+ if source == "raw" and cfg.extra_eval_datasets:
947
+ model_extra = {key: df.copy() for key, df in cfg.extra_eval_datasets.items()}
948
+ else:
949
+ model_extra = extra_eval_splits
851
950
  evaluator = PerformanceEvaluator(
852
951
  tgt_name=cfg.target_col,
853
952
  scr_name=f"pred_{name}",
@@ -855,10 +954,11 @@ class CreditModelPipeline:
855
954
  min_bin_prop=cfg.perf_min_bin_prop,
856
955
  equal_freq=True,
857
956
  )
858
- for ds_name, df in splits.items():
957
+ eval_splits = {**splits, **model_extra}
958
+ for ds_name, df in eval_splits.items():
859
959
  scored = df.copy()
860
960
  scored[f"pred_{name}"] = self._predict_model_positive(name, wrapper, scored, feature_cols)
861
- add_dataset_with_optional_weight(evaluator, ds_name, scored)
961
+ add_dataset_with_optional_weight(evaluator, ds_name, scored, weight_col=cfg.weight_col)
862
962
  fig_save_path = None
863
963
  if cfg.write_outputs and cfg.plot_outputs:
864
964
  fig_save_path = str(Path(cfg.output_dir) / "figs" / "perf" / f"perf_{name}.png")
@@ -8,7 +8,14 @@ from typing import Any, Literal
8
8
  import numpy as np
9
9
  import pandas as pd
10
10
 
11
- from ._common import as_list, make_dirs, safe_to_csv
11
+ from ._common import (
12
+ apply_woe_fit_query,
13
+ as_list,
14
+ make_dirs,
15
+ safe_to_csv,
16
+ validate_woe_fit_query_columns,
17
+ validate_woe_fit_query_syntax,
18
+ )
12
19
 
13
20
 
14
21
  @dataclass
@@ -47,6 +54,7 @@ class FeatureValidationPipelineConfig:
47
54
  )
48
55
 
49
56
  woe_enabled: bool = True
57
+ woe_fit_query: str | None = None
50
58
  woe_engine: str = "monotone"
51
59
  woe_params: dict[str, Any] = field(
52
60
  default_factory=lambda: {"nbins": 10, "equal_freq": True, "min_bin_prop": 0.05}
@@ -257,6 +265,8 @@ class FeatureValidationPipeline:
257
265
  self._validate_batch_config(new_features)
258
266
  feature_batches = self._make_feature_batches(new_features)
259
267
  base_cols = self._resolve_batch_base_cols(header, incumbent_features, target_cols)
268
+ if cfg.woe_fit_query:
269
+ validate_woe_fit_query_columns(cfg.woe_fit_query, base_cols, context="CSV batch base columns")
260
270
 
261
271
  base_df = self._read_csv(csv_path, usecols=base_cols).copy()
262
272
  base_df["_smf_batch_row_id"] = np.arange(len(base_df))
@@ -777,6 +787,9 @@ class FeatureValidationPipeline:
777
787
  raise ValueError("psi_reference_dataset must be one of ins/oos/oot/external")
778
788
  if cfg.psi_reference_dataset == "external" and cfg.psi_reference_data is None:
779
789
  raise ValueError("psi_reference_data is required when psi_reference_dataset='external'")
790
+ if cfg.woe_fit_query:
791
+ validate_woe_fit_query_columns(cfg.woe_fit_query, data.columns, context="input data")
792
+ validate_woe_fit_query_syntax(data, cfg.woe_fit_query)
780
793
 
781
794
  def _resolve_new_features(self, data: pd.DataFrame) -> list[str]:
782
795
  cfg = self.config
@@ -979,6 +992,9 @@ class FeatureValidationPipeline:
979
992
  refine_rows = []
980
993
  for target in target_cols:
981
994
  train = splits["ins"][splits["ins"][target].notna()].copy()
995
+ train, fit_filter_row = apply_woe_fit_query(train, cfg.woe_fit_query, target=target)
996
+ if fit_filter_row is not None:
997
+ refine_rows.append(fit_filter_row)
982
998
  if len(train) < max(10, cfg.min_group_size):
983
999
  refine_rows.append({"target": target, "step": "fit", "status": "skipped_min_group_size", "n": len(train)})
984
1000
  continue
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  __author__ = "Jingkai Sun"
4
- __version__ = "0.3.4"
4
+ __version__ = "0.3.6"
5
5
 
6
6
  # Core module - base data structures
7
7
  from .Core import (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SuperModelingFactory
3
- Version: 0.3.4
3
+ Version: 0.3.6
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
@@ -114,7 +114,10 @@ SuperModelingFactory/
114
114
  │ ├── Feature/ # 特征分析:分布偏移、PSI、相关性过滤
115
115
  │ ├── Model/ # 模型训练:LR、LightGBM、XGBoost、变量选择
116
116
  │ ├── Eval/ # 模型评估:Gains 表、ROC/KS、性能汇总
117
- └── Sample/ # 样本管理:切分、分层、拒绝推断、分布适配
117
+ ├── Sample/ # 样本管理:切分、分层、拒绝推断、分布适配
118
+ │ ├── Explainability/ # 模型解释:SHAP、LIME、PDP、ICE、ALE、Owen
119
+ │ ├── Pipeline/ # 一键流水线:特征验收、信用建模、拒绝推断等
120
+ │ └── UAT/ # 线上线下分数一致性校验
118
121
  ├── ExcelMaster/ # Excel 报告引擎
119
122
  │ ├── ExcelFormatTool.py # 格式定义(50+ 预设单元格格式)
120
123
  │ ├── ExcelMaster.py # 核心引擎(光标流式写入、图表、条件格式)
@@ -262,9 +265,9 @@ em.close_workbook()
262
265
 
263
266
  ## 版本
264
267
 
265
- - **Version**: 0.3.4
268
+ - **Version**: 0.3.6
266
269
  - **Author**: Jingkai Sun
267
270
 
268
271
  ## 许可证
269
272
 
270
- 内部项目,仅供团队使用。
273
+ 本项目采用 **Business Source License 1.1**(Change Date **2030-06-24**)。详见文首 [许可证](#许可证) 一节与 [LICENSE](LICENSE) 全文。
@@ -57,7 +57,10 @@ SuperModelingFactory/
57
57
  │ ├── Feature/ # 特征分析:分布偏移、PSI、相关性过滤
58
58
  │ ├── Model/ # 模型训练:LR、LightGBM、XGBoost、变量选择
59
59
  │ ├── Eval/ # 模型评估:Gains 表、ROC/KS、性能汇总
60
- └── Sample/ # 样本管理:切分、分层、拒绝推断、分布适配
60
+ ├── Sample/ # 样本管理:切分、分层、拒绝推断、分布适配
61
+ │ ├── Explainability/ # 模型解释:SHAP、LIME、PDP、ICE、ALE、Owen
62
+ │ ├── Pipeline/ # 一键流水线:特征验收、信用建模、拒绝推断等
63
+ │ └── UAT/ # 线上线下分数一致性校验
61
64
  ├── ExcelMaster/ # Excel 报告引擎
62
65
  │ ├── ExcelFormatTool.py # 格式定义(50+ 预设单元格格式)
63
66
  │ ├── ExcelMaster.py # 核心引擎(光标流式写入、图表、条件格式)
@@ -205,9 +208,9 @@ em.close_workbook()
205
208
 
206
209
  ## 版本
207
210
 
208
- - **Version**: 0.3.4
211
+ - **Version**: 0.3.6
209
212
  - **Author**: Jingkai Sun
210
213
 
211
214
  ## 许可证
212
215
 
213
- 内部项目,仅供团队使用。
216
+ 本项目采用 **Business Source License 1.1**(Change Date **2030-06-24**)。详见文首 [许可证](#许可证) 一节与 [LICENSE](LICENSE) 全文。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SuperModelingFactory
3
- Version: 0.3.4
3
+ Version: 0.3.6
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
@@ -114,7 +114,10 @@ SuperModelingFactory/
114
114
  │ ├── Feature/ # 特征分析:分布偏移、PSI、相关性过滤
115
115
  │ ├── Model/ # 模型训练:LR、LightGBM、XGBoost、变量选择
116
116
  │ ├── Eval/ # 模型评估:Gains 表、ROC/KS、性能汇总
117
- └── Sample/ # 样本管理:切分、分层、拒绝推断、分布适配
117
+ ├── Sample/ # 样本管理:切分、分层、拒绝推断、分布适配
118
+ │ ├── Explainability/ # 模型解释:SHAP、LIME、PDP、ICE、ALE、Owen
119
+ │ ├── Pipeline/ # 一键流水线:特征验收、信用建模、拒绝推断等
120
+ │ └── UAT/ # 线上线下分数一致性校验
118
121
  ├── ExcelMaster/ # Excel 报告引擎
119
122
  │ ├── ExcelFormatTool.py # 格式定义(50+ 预设单元格格式)
120
123
  │ ├── ExcelMaster.py # 核心引擎(光标流式写入、图表、条件格式)
@@ -262,9 +265,9 @@ em.close_workbook()
262
265
 
263
266
  ## 版本
264
267
 
265
- - **Version**: 0.3.4
268
+ - **Version**: 0.3.6
266
269
  - **Author**: Jingkai Sun
267
270
 
268
271
  ## 许可证
269
272
 
270
- 内部项目,仅供团队使用。
273
+ 本项目采用 **Business Source License 1.1**(Change Date **2030-06-24**)。详见文首 [许可证](#许可证) 一节与 [LICENSE](LICENSE) 全文。
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
12
12
 
13
13
  [project]
14
14
  name = "SuperModelingFactory"
15
- version = "0.3.4"
15
+ version = "0.3.6"
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.3.4"),
18
+ version=os.environ.get("SMF_VERSION", "0.3.6"),
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",