SuperModelingFactory 0.3.10__tar.gz → 0.3.12__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.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/Feature_Screen.py +137 -6
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/Weighted_Screen.py +31 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/credit_model.py +122 -3
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/__init__.py +1 -1
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/PKG-INFO +1 -1
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/SuperModelingFactory.egg-info/PKG-INFO +1 -1
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/pyproject.toml +1 -1
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/setup.py +1 -1
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/LICENSE +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/MANIFEST.in +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Binning_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Proc_Compare.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/utils.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/GBM_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/LRM_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/_common.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/feature_validation.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/screening_artifact.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/Sample_Split.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Master.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/README.md +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Report/__init__.py +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/SuperModelingFactory.egg-info/SOURCES.txt +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/requirements.txt +0 -0
- {supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/setup.cfg +0 -0
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/Feature_Screen.py
RENAMED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import warnings
|
|
6
5
|
from dataclasses import dataclass, field
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from typing import Any, Mapping
|
|
@@ -10,11 +9,18 @@ from typing import Any, Mapping
|
|
|
10
9
|
import numpy as np
|
|
11
10
|
import pandas as pd
|
|
12
11
|
|
|
12
|
+
from Modeling_Tool.Core.sample_weight_utils import resolve_sample_weight
|
|
13
|
+
|
|
13
14
|
from .Weighted_Screen import (
|
|
14
15
|
WeightedScreenResult,
|
|
16
|
+
_corr_dedup_weighted,
|
|
15
17
|
_legacy_unweighted_screen,
|
|
18
|
+
_psi_from_distributions,
|
|
16
19
|
_resolve_splits,
|
|
17
20
|
_summary_row,
|
|
21
|
+
_weighted_bin_distribution,
|
|
22
|
+
_weighted_iv_from_assigned_bins,
|
|
23
|
+
_weighted_pearson_corr_matrix,
|
|
18
24
|
_weighted_screen_impl,
|
|
19
25
|
)
|
|
20
26
|
|
|
@@ -289,6 +295,128 @@ def _woe_bins_unweighted_screen(
|
|
|
289
295
|
)
|
|
290
296
|
|
|
291
297
|
|
|
298
|
+
def _bins_to_numpy(bins: Any) -> np.ndarray:
|
|
299
|
+
if isinstance(bins, pd.Series):
|
|
300
|
+
return bins.to_numpy(dtype=object)
|
|
301
|
+
return np.asarray(bins, dtype=object)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _weighted_woe_bins_screen(
|
|
305
|
+
splits: dict[str, pd.DataFrame],
|
|
306
|
+
feature_cols: list[str],
|
|
307
|
+
target_col: str,
|
|
308
|
+
weight_col: str,
|
|
309
|
+
config: FeatureScreenConfig,
|
|
310
|
+
*,
|
|
311
|
+
prefit_woe_engine: Any | None = None,
|
|
312
|
+
) -> FeatureScreenResult:
|
|
313
|
+
from Modeling_Tool.WOE.WOE_Adapter import as_woe_engine
|
|
314
|
+
|
|
315
|
+
ins, oos, oot = splits["ins"], splits["oos"], splits["oot"]
|
|
316
|
+
w_ins = resolve_sample_weight(data=ins, weight_col=weight_col, expected_len=len(ins))
|
|
317
|
+
y_ins = ins[target_col].astype(float).to_numpy()
|
|
318
|
+
current = list(feature_cols)
|
|
319
|
+
summary_rows = [_summary_row("initial", len(feature_cols), len(current), None, weight_col)]
|
|
320
|
+
|
|
321
|
+
binner = prefit_woe_engine
|
|
322
|
+
if binner is None:
|
|
323
|
+
binner = fit_screening_woe_engine(
|
|
324
|
+
ins,
|
|
325
|
+
feature_cols,
|
|
326
|
+
target_col,
|
|
327
|
+
woe_engine=config.woe_engine,
|
|
328
|
+
woe_fit_query=config.woe_fit_query,
|
|
329
|
+
woe_params=config.woe_params,
|
|
330
|
+
monotone_woe_params=config.monotone_woe_params,
|
|
331
|
+
categorical_features=config.categorical_features,
|
|
332
|
+
)
|
|
333
|
+
adapter = as_woe_engine(binner)
|
|
334
|
+
|
|
335
|
+
psi_records: list[dict] = []
|
|
336
|
+
if config.psi_enabled:
|
|
337
|
+
for var in feature_cols:
|
|
338
|
+
if var not in ins.columns or ins[var].nunique(dropna=False) <= 1:
|
|
339
|
+
continue
|
|
340
|
+
bins_ins = _bins_to_numpy(adapter.assign_bins(ins, var))
|
|
341
|
+
exp_dist = _weighted_bin_distribution(bins_ins, w_ins, config.content)
|
|
342
|
+
row: dict[str, Any] = {"var": var, "psi_ins_oos": np.nan, "psi_ins_oot": np.nan}
|
|
343
|
+
if "oos" in config.psi_compare_splits and len(oos) > 0:
|
|
344
|
+
w_oos = resolve_sample_weight(data=oos, weight_col=weight_col, expected_len=len(oos))
|
|
345
|
+
bins_oos = _bins_to_numpy(adapter.assign_bins(oos, var))
|
|
346
|
+
act = _weighted_bin_distribution(bins_oos, w_oos, config.content)
|
|
347
|
+
row["psi_ins_oos"] = _psi_from_distributions(exp_dist, act, config.content)
|
|
348
|
+
if "oot" in config.psi_compare_splits and len(oot) > 0:
|
|
349
|
+
w_oot = resolve_sample_weight(data=oot, weight_col=weight_col, expected_len=len(oot))
|
|
350
|
+
bins_oot = _bins_to_numpy(adapter.assign_bins(oot, var))
|
|
351
|
+
act = _weighted_bin_distribution(bins_oot, w_oot, config.content)
|
|
352
|
+
row["psi_ins_oot"] = _psi_from_distributions(exp_dist, act, config.content)
|
|
353
|
+
psi_records.append(row)
|
|
354
|
+
|
|
355
|
+
psi_table = pd.DataFrame(psi_records) if psi_records else pd.DataFrame(
|
|
356
|
+
columns=["var", "psi_ins_oos", "psi_ins_oot"],
|
|
357
|
+
)
|
|
358
|
+
if not psi_table.empty:
|
|
359
|
+
compare_cols = [c for c in ("psi_ins_oos", "psi_ins_oot") if c in psi_table.columns]
|
|
360
|
+
psi_table["psi_max"] = psi_table[compare_cols].max(axis=1, skipna=True)
|
|
361
|
+
keep = psi_table.loc[psi_table["psi_max"] < config.psi_threshold, "var"].tolist()
|
|
362
|
+
n_before = len(current)
|
|
363
|
+
current = [v for v in current if v in keep] or current
|
|
364
|
+
summary_rows.append(_summary_row("psi", n_before, len(current), config.psi_threshold, weight_col))
|
|
365
|
+
else:
|
|
366
|
+
psi_table["psi_max"] = pd.Series(dtype=float)
|
|
367
|
+
else:
|
|
368
|
+
psi_table = pd.DataFrame(columns=["var", "psi_ins_oos", "psi_ins_oot", "psi_max"])
|
|
369
|
+
|
|
370
|
+
iv_records: list[dict] = []
|
|
371
|
+
if config.iv_enabled:
|
|
372
|
+
for var in current:
|
|
373
|
+
if var not in ins.columns or ins[var].nunique(dropna=False) <= 1:
|
|
374
|
+
continue
|
|
375
|
+
bins_ins = _bins_to_numpy(adapter.assign_bins(ins, var))
|
|
376
|
+
x_ins = ins[var].to_numpy(dtype=float)
|
|
377
|
+
iv_val, n_b, miss = _weighted_iv_from_assigned_bins(y_ins, w_ins, bins_ins, x_ins)
|
|
378
|
+
iv_records.append({
|
|
379
|
+
"var": var,
|
|
380
|
+
"iv_weighted": iv_val,
|
|
381
|
+
"n_bins": n_b,
|
|
382
|
+
"missing_rate": miss,
|
|
383
|
+
})
|
|
384
|
+
iv_table = pd.DataFrame(iv_records) if iv_records else pd.DataFrame(
|
|
385
|
+
columns=["var", "iv_weighted", "n_bins", "missing_rate"],
|
|
386
|
+
)
|
|
387
|
+
if not iv_table.empty:
|
|
388
|
+
keep = iv_table.loc[iv_table["iv_weighted"] >= config.iv_threshold, "var"].tolist()
|
|
389
|
+
n_before = len(current)
|
|
390
|
+
current = [v for v in current if v in keep] or current
|
|
391
|
+
summary_rows.append(_summary_row("iv", n_before, len(current), config.iv_threshold, weight_col))
|
|
392
|
+
else:
|
|
393
|
+
iv_table = pd.DataFrame(columns=["var", "iv_weighted", "n_bins", "missing_rate"])
|
|
394
|
+
|
|
395
|
+
corr_dropped = pd.DataFrame(columns=["var_a", "var_b", "corr", "iv_a", "iv_b", "kept", "dropped"])
|
|
396
|
+
if config.corr_enabled and len(current) > 1:
|
|
397
|
+
X = ins[current].astype(float).to_numpy()
|
|
398
|
+
corr = _weighted_pearson_corr_matrix(X, w_ins)
|
|
399
|
+
iv_map = dict(zip(iv_table["var"], iv_table["iv_weighted"])) if not iv_table.empty else {}
|
|
400
|
+
n_before = len(current)
|
|
401
|
+
current, corr_dropped = _corr_dedup_weighted(
|
|
402
|
+
current,
|
|
403
|
+
corr,
|
|
404
|
+
iv_map,
|
|
405
|
+
config.corr_threshold,
|
|
406
|
+
config.corr_max_iterations,
|
|
407
|
+
)
|
|
408
|
+
summary_rows.append(_summary_row("corr", n_before, len(current), config.corr_threshold, weight_col))
|
|
409
|
+
|
|
410
|
+
summary_rows.append(_summary_row("final", len(feature_cols), len(current), None, weight_col))
|
|
411
|
+
return FeatureScreenResult(
|
|
412
|
+
selected_features=list(current),
|
|
413
|
+
iv_table=iv_table,
|
|
414
|
+
psi_table=psi_table,
|
|
415
|
+
corr_dropped=corr_dropped,
|
|
416
|
+
summary=pd.DataFrame(summary_rows),
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
292
420
|
def feature_screen(
|
|
293
421
|
splits: dict[str, pd.DataFrame],
|
|
294
422
|
feature_cols: list[str],
|
|
@@ -303,11 +431,14 @@ def feature_screen(
|
|
|
303
431
|
use_woe_bins = _needs_woe_bins(cfg)
|
|
304
432
|
|
|
305
433
|
if weight_col is not None:
|
|
306
|
-
if use_woe_bins:
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
434
|
+
if use_woe_bins or prefit_woe_engine is not None:
|
|
435
|
+
return _weighted_woe_bins_screen(
|
|
436
|
+
splits,
|
|
437
|
+
feature_cols,
|
|
438
|
+
target_col,
|
|
439
|
+
weight_col,
|
|
440
|
+
cfg,
|
|
441
|
+
prefit_woe_engine=prefit_woe_engine,
|
|
311
442
|
)
|
|
312
443
|
return _weighted_screen_impl(
|
|
313
444
|
splits,
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/Weighted_Screen.py
RENAMED
|
@@ -119,6 +119,37 @@ def _psi_from_distributions(expected: pd.Series, actual: pd.Series, content: flo
|
|
|
119
119
|
return float(((a - e) * np.log(a / e)).sum())
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
def _weighted_iv_from_assigned_bins(
|
|
123
|
+
y: np.ndarray,
|
|
124
|
+
w: np.ndarray,
|
|
125
|
+
bins: np.ndarray,
|
|
126
|
+
x: np.ndarray,
|
|
127
|
+
) -> tuple[float, int, float]:
|
|
128
|
+
total_bad = float(np.sum(w * y))
|
|
129
|
+
total_good = float(np.sum(w * (1.0 - y)))
|
|
130
|
+
if total_bad <= 0 or total_good <= 0:
|
|
131
|
+
missing_rate = 1.0 - float(np.sum(w[np.isfinite(x)])) / float(np.sum(w) or 1.0)
|
|
132
|
+
return 0.0, 0, missing_rate
|
|
133
|
+
|
|
134
|
+
rows = []
|
|
135
|
+
for b in pd.unique(bins):
|
|
136
|
+
m = bins == b
|
|
137
|
+
bad_w = float(np.sum(w[m] * y[m]))
|
|
138
|
+
good_w = float(np.sum(w[m] * (1.0 - y[m])))
|
|
139
|
+
rows.append({
|
|
140
|
+
"bad_pct": bad_w / total_bad,
|
|
141
|
+
"good_pct": good_w / total_good,
|
|
142
|
+
})
|
|
143
|
+
if not rows:
|
|
144
|
+
missing_rate = 1.0 - float(np.sum(w[np.isfinite(x)])) / float(np.sum(w) or 1.0)
|
|
145
|
+
return 0.0, 0, missing_rate
|
|
146
|
+
|
|
147
|
+
stats = pd.DataFrame(rows)
|
|
148
|
+
iv = float(calc_iv(stats, "bad_pct", "good_pct").sum())
|
|
149
|
+
missing_rate = 1.0 - float(np.sum(w[np.isfinite(x)])) / float(np.sum(w) or 1.0)
|
|
150
|
+
return iv, len(rows), missing_rate
|
|
151
|
+
|
|
152
|
+
|
|
122
153
|
def _weighted_iv_for_var(
|
|
123
154
|
x: np.ndarray,
|
|
124
155
|
y: np.ndarray,
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/credit_model.py
RENAMED
|
@@ -97,6 +97,11 @@ class CreditModelPipelineConfig:
|
|
|
97
97
|
perf_pct_bins: int = 10
|
|
98
98
|
perf_min_bin_prop: float = 0.03
|
|
99
99
|
|
|
100
|
+
screening_artifact: Any | None = None
|
|
101
|
+
feature_validation_result: Any | None = None
|
|
102
|
+
feature_selection_mode: Literal["run", "from_artifact", "skip"] = "run"
|
|
103
|
+
reuse_screening_woe: bool = True
|
|
104
|
+
|
|
100
105
|
|
|
101
106
|
@dataclass
|
|
102
107
|
class CreditModelPipelineResult:
|
|
@@ -190,8 +195,12 @@ class CreditModelPipeline:
|
|
|
190
195
|
make_dirs(self._model_output_dir(), output_dir / "artifacts")
|
|
191
196
|
|
|
192
197
|
splits = self._split_data(data)
|
|
193
|
-
fs_summary, final_features = self.
|
|
194
|
-
|
|
198
|
+
fs_summary, final_features, screening_artifact = self._resolve_feature_selection(
|
|
199
|
+
splits,
|
|
200
|
+
feature_cols,
|
|
201
|
+
)
|
|
202
|
+
prefit_woe = screening_artifact.woe_artifacts if screening_artifact and cfg.reuse_screening_woe else None
|
|
203
|
+
woe_artifacts = self._fit_woe(splits, final_features, prefit_woe_artifacts=prefit_woe)
|
|
195
204
|
woe_features = woe_artifacts["woe_features"]
|
|
196
205
|
woe_splits = woe_artifacts["splits"]
|
|
197
206
|
woe_suffix = woe_artifacts.get("woe_suffix", cfg.woe_params.get("woe_suffix", "_woe"))
|
|
@@ -424,6 +433,48 @@ class CreditModelPipeline:
|
|
|
424
433
|
oot = oos.copy()
|
|
425
434
|
return {"ins": ins.copy(), "oos": oos.copy(), "oot": oot.copy()}
|
|
426
435
|
|
|
436
|
+
def _resolve_screening_artifact(self) -> Any | None:
|
|
437
|
+
cfg = self.config
|
|
438
|
+
if cfg.screening_artifact is not None:
|
|
439
|
+
return cfg.screening_artifact
|
|
440
|
+
if cfg.feature_validation_result is not None:
|
|
441
|
+
from .screening_artifact import FeatureScreeningArtifact
|
|
442
|
+
|
|
443
|
+
return FeatureScreeningArtifact.from_fvp_result(
|
|
444
|
+
cfg.feature_validation_result,
|
|
445
|
+
target_col=cfg.target_col,
|
|
446
|
+
)
|
|
447
|
+
return None
|
|
448
|
+
|
|
449
|
+
def _resolve_feature_selection(
|
|
450
|
+
self,
|
|
451
|
+
splits: dict[str, pd.DataFrame],
|
|
452
|
+
feature_cols: list[str],
|
|
453
|
+
) -> tuple[dict[str, Any], list[str], Any | None]:
|
|
454
|
+
cfg = self.config
|
|
455
|
+
artifact = self._resolve_screening_artifact()
|
|
456
|
+
mode = cfg.feature_selection_mode
|
|
457
|
+
if artifact is not None:
|
|
458
|
+
mode = "from_artifact"
|
|
459
|
+
if mode == "from_artifact":
|
|
460
|
+
if artifact is None:
|
|
461
|
+
raise ValueError("feature_selection_mode='from_artifact' requires screening_artifact.")
|
|
462
|
+
artifact.validate_for_cm(target_col=cfg.target_col, weight_col=cfg.weight_col)
|
|
463
|
+
summary = dict(artifact.selection_summary or {})
|
|
464
|
+
summary["from_artifact"] = True
|
|
465
|
+
summary["artifact_source"] = artifact.source
|
|
466
|
+
selected = list(artifact.selected_features) or list(feature_cols)
|
|
467
|
+
return summary, selected, artifact
|
|
468
|
+
if mode == "skip":
|
|
469
|
+
summary = {
|
|
470
|
+
"skipped": True,
|
|
471
|
+
"initial_features": list(feature_cols),
|
|
472
|
+
"final_features": list(feature_cols),
|
|
473
|
+
}
|
|
474
|
+
return summary, list(feature_cols), None
|
|
475
|
+
summary, selected = self._feature_selection(splits, feature_cols)
|
|
476
|
+
return summary, selected, None
|
|
477
|
+
|
|
427
478
|
def _feature_selection(
|
|
428
479
|
self,
|
|
429
480
|
splits: dict[str, pd.DataFrame],
|
|
@@ -509,12 +560,80 @@ class CreditModelPipeline:
|
|
|
509
560
|
transformed[name] = transform_fn(df)
|
|
510
561
|
return transformed
|
|
511
562
|
|
|
512
|
-
def
|
|
563
|
+
def _reuse_screening_woe(
|
|
564
|
+
self,
|
|
565
|
+
splits: dict[str, pd.DataFrame],
|
|
566
|
+
feature_cols: list[str],
|
|
567
|
+
prefit_woe_artifacts: dict[str, Any],
|
|
568
|
+
) -> dict[str, Any] | None:
|
|
569
|
+
from Modeling_Tool.WOE.WOE_Adapter import as_woe_engine
|
|
570
|
+
|
|
571
|
+
cfg = self.config
|
|
572
|
+
by_target = prefit_woe_artifacts.get("by_target", {}) if prefit_woe_artifacts else {}
|
|
573
|
+
item = by_target.get(cfg.target_col)
|
|
574
|
+
if not item:
|
|
575
|
+
return None
|
|
576
|
+
|
|
577
|
+
adapter = item.get("adapter")
|
|
578
|
+
if adapter is None and item.get("engine") is not None:
|
|
579
|
+
woe_suffix = cfg.woe_params.get("woe_suffix", "_woe")
|
|
580
|
+
adapter = as_woe_engine(item["engine"], woe_suffix=woe_suffix)
|
|
581
|
+
if adapter is None:
|
|
582
|
+
return None
|
|
583
|
+
|
|
584
|
+
fitted = set(item.get("features") or [])
|
|
585
|
+
usable = [col for col in feature_cols if col in fitted]
|
|
586
|
+
if not usable:
|
|
587
|
+
return None
|
|
588
|
+
|
|
589
|
+
woe_suffix = cfg.woe_params.get("woe_suffix", "_woe")
|
|
590
|
+
woe_features = [f"{col}{woe_suffix}" for col in usable]
|
|
591
|
+
woe_splits = {
|
|
592
|
+
name: adapter.transform(df, varlist=usable, suffix=woe_suffix)
|
|
593
|
+
for name, df in splits.items()
|
|
594
|
+
}
|
|
595
|
+
extra_eval = self._transform_extra_eval_datasets(
|
|
596
|
+
adapter.transform,
|
|
597
|
+
usable,
|
|
598
|
+
woe_suffix=woe_suffix,
|
|
599
|
+
)
|
|
600
|
+
if cfg.warm_start_enabled and cfg.warm_start_score_col:
|
|
601
|
+
for name, df in woe_splits.items():
|
|
602
|
+
if cfg.warm_start_score_col not in df.columns and cfg.warm_start_score_col in splits[name].columns:
|
|
603
|
+
df[cfg.warm_start_score_col] = splits[name][cfg.warm_start_score_col].to_numpy()
|
|
604
|
+
for name, df in extra_eval.items():
|
|
605
|
+
if cfg.warm_start_score_col not in df.columns and cfg.warm_start_score_col in cfg.extra_eval_datasets[name].columns:
|
|
606
|
+
df[cfg.warm_start_score_col] = cfg.extra_eval_datasets[name][cfg.warm_start_score_col].to_numpy()
|
|
607
|
+
|
|
608
|
+
return {
|
|
609
|
+
"engine": adapter,
|
|
610
|
+
"engine_name": adapter.get_engine_name() if hasattr(adapter, "get_engine_name") else cfg.woe_engine,
|
|
611
|
+
"features": list(usable),
|
|
612
|
+
"woe_features": woe_features,
|
|
613
|
+
"woe_suffix": woe_suffix,
|
|
614
|
+
"splits": woe_splits,
|
|
615
|
+
"extra_eval": extra_eval,
|
|
616
|
+
"woe_table": adapter.get_woe_table(varlist=usable),
|
|
617
|
+
"reused_from_screening": True,
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
def _fit_woe(
|
|
621
|
+
self,
|
|
622
|
+
splits: dict[str, pd.DataFrame],
|
|
623
|
+
feature_cols: list[str],
|
|
624
|
+
*,
|
|
625
|
+
prefit_woe_artifacts: dict[str, Any] | None = None,
|
|
626
|
+
) -> dict[str, Any]:
|
|
513
627
|
from Modeling_Tool import MonotoneWOEBinner, WOE_Master
|
|
514
628
|
from Modeling_Tool.WOE.WOE_Adapter import as_woe_engine
|
|
515
629
|
from Modeling_Tool.WOE.WOE_Master import get_overall_woe_table
|
|
516
630
|
|
|
517
631
|
cfg = self.config
|
|
632
|
+
if prefit_woe_artifacts and cfg.reuse_screening_woe:
|
|
633
|
+
reused = self._reuse_screening_woe(splits, feature_cols, prefit_woe_artifacts)
|
|
634
|
+
if reused is not None:
|
|
635
|
+
return reused
|
|
636
|
+
|
|
518
637
|
woe_suffix = cfg.woe_params.get("woe_suffix", "_woe")
|
|
519
638
|
graph_dir = str(Path(cfg.output_dir) / "figs" / "woe")
|
|
520
639
|
woe_features = [f"{col}{woe_suffix}" for col in feature_cols]
|
|
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
|
|
|
12
12
|
|
|
13
13
|
[project]
|
|
14
14
|
name = "SuperModelingFactory"
|
|
15
|
-
version = "0.3.
|
|
15
|
+
version = "0.3.12"
|
|
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.
|
|
18
|
+
version=os.environ.get("SMF_VERSION", "0.3.12"),
|
|
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.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Binning_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Binning_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Parallel_Engine.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Proc_Compare.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Slope_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/Slope_Tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/XOR_Encryptor.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/XOR_Encryptor.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/kDataFrame.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Core/kDataFrame.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/Evaluation_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/Evaluation_Tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/Model_Eval_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/Model_Eval_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/evaluate_model.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Eval/evaluate_model.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Explainability/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/PSI_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/PSI_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Feature/__init__.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/Backward_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/Backward_Tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/GBM_Search_Tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/GBM_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Model/LRM_Tool.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/__init__.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/_common.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Pipeline/mock_sample.py
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.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/Reject_Infer.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/Reject_Infer.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/Sample_Split.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/Sample_Split.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/Sample/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Adapter.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Adapter.pyi
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Master.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Monotone_Binner.py
RENAMED
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Plot_Tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Report_Builder.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/WOE_Report_Builder.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/plot_woe_tool.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/WOE/plot_woe_tool.pyi
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/ref_font/KaiTi.ttf
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/ref_font/__init__.py
RENAMED
|
File without changes
|
{supermodelingfactory-0.3.10 → supermodelingfactory-0.3.12}/Modeling_Tool/ref_font/simsun.ttc
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|