SuperModelingFactory 0.5.3__tar.gz → 0.5.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/Parallel_ODPS_Manager.py +221 -50
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/__init__.py +32 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/feature_validation.py +21 -1
- supermodelingfactory-0.5.5/Modeling_Tool/Pipeline/field_meta.py +1124 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/__init__.py +20 -1
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/PKG-INFO +2 -2
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/README.md +1 -1
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/SuperModelingFactory.egg-info/PKG-INFO +2 -2
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/SuperModelingFactory.egg-info/SOURCES.txt +1 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/pyproject.toml +1 -1
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/setup.py +1 -1
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/ExcelMaster/ExcelFormatTool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/ExcelMaster/ExcelMaster.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/ExcelMaster/Template.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/ExcelMaster/Utility.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/ExcelMaster/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/LICENSE +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/MANIFEST.in +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/Binning_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/Binning_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/Check_DuckDB_Compatibility.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/Json_Data_Converter.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/Model_Registry_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/ODPS_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/Parallel_Engine.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/Proc_Compare.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/Slope_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/Slope_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/XOR_Encryptor.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/XOR_Encryptor.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/kDataFrame.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/kDataFrame.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/sample_weight_utils.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Core/utils.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Eval/Evaluation_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Eval/Evaluation_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Eval/Model_Eval_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Eval/Model_Eval_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Eval/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Eval/evaluate_model.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Eval/evaluate_model.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Eval/weighted_eval_utils.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Explainability/Coalition_Structure.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Explainability/Model_Explainer.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Explainability/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Feature/Distribution_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Feature/Distribution_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Feature/Feature_Insights.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Feature/Feature_Insights.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Feature/Feature_Screen.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Feature/PSI_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Feature/PSI_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Feature/Weighted_Screen.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Feature/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Model/Backward_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Model/Backward_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Model/GBM_Search_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Model/GBM_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Model/GBM_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Model/LRM_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Model/LRM_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Model/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/_common.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/credit_model.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/mock_sample.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/orchestrator.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/reject_inference.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/sample_analysis.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/score_comparison.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/score_consistency_uat.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/screening_artifact.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Sample/Distribution_Adaptation.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Sample/Distribution_Adaptation.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Sample/Reject_Infer.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Sample/Reject_Infer.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Sample/Sample_Split.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Sample/Sample_Split.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Sample/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/UAT/UAT_Consistency_Checker.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/UAT/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Adapter.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Adapter.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Master.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Master.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Monotone_Binner.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Plot_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Plot_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Report_Builder.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Report_Builder.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/WOE_Tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/plot_woe_tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/WOE/plot_woe_tool.pyi +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/_utils/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/_utils/nan_guard.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/_utils/robust.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/_utils/sentinels.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/ref_font/KaiTi.ttf +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/ref_font/WeiRuanYaHei.ttf +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/ref_font/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/ref_font/simsun.ttc +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Report/Report_Tool.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Report/__init__.py +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/SuperModelingFactory.egg-info/dependency_links.txt +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/SuperModelingFactory.egg-info/not-zip-safe +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/SuperModelingFactory.egg-info/requires.txt +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/SuperModelingFactory.egg-info/top_level.txt +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/requirements.txt +0 -0
- {supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/setup.cfg +0 -0
|
@@ -17,6 +17,7 @@ from .utils import parse_sql_file
|
|
|
17
17
|
|
|
18
18
|
Backend = Literal["thread", "process", "sequential"]
|
|
19
19
|
WriteMode = Literal["overwrite", "append"]
|
|
20
|
+
PullSplitStrategy = Literal["auto", "hash", "row_number"]
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
@dataclass
|
|
@@ -26,6 +27,10 @@ class ParallelODPSConfig:
|
|
|
26
27
|
n_chunks: int | None = None
|
|
27
28
|
n_jobs: int = 3
|
|
28
29
|
backend: Backend = "thread"
|
|
30
|
+
pull_split_strategy: PullSplitStrategy = "auto"
|
|
31
|
+
row_number_order_by: str | None = None
|
|
32
|
+
row_number_col: str = "__smf_parallel_odps_rn__"
|
|
33
|
+
validate_unique_key: bool = True
|
|
29
34
|
chunk_filter_key: str = "chunk_filter"
|
|
30
35
|
tmp_dir: Path = field(default_factory=lambda: Path("data/_chunks"))
|
|
31
36
|
tmp_table_prefix: str = "tmp_parallel_odps"
|
|
@@ -55,22 +60,23 @@ def _delete_table(runner: ODPSRunner, table_name: str) -> None:
|
|
|
55
60
|
runner.run_sql(f"DROP TABLE IF EXISTS {table_name};", to_df=False)
|
|
56
61
|
|
|
57
62
|
|
|
63
|
+
def _strip_trailing_semicolon(sql: str) -> str:
|
|
64
|
+
return sql.strip().rstrip(";").strip()
|
|
65
|
+
|
|
66
|
+
|
|
58
67
|
def _pull_one_chunk(
|
|
59
|
-
|
|
68
|
+
chunk_spec: dict[str, Any],
|
|
60
69
|
runner: ODPSRunner | None,
|
|
61
|
-
sql_path: str,
|
|
62
|
-
template_kwargs: dict[str, Any],
|
|
63
|
-
chunk_filter_key: str,
|
|
64
|
-
unique_key: str,
|
|
65
|
-
n_chunks: int,
|
|
66
70
|
tmp_dir_str: str,
|
|
67
71
|
) -> dict[str, Any]:
|
|
68
72
|
active_runner = _get_runner(runner)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
chunk_id = int(chunk_spec["chunk"])
|
|
74
|
+
rendered_sql = str(chunk_spec["sql"])
|
|
75
|
+
drop_cols = list(chunk_spec.get("drop_cols") or [])
|
|
72
76
|
|
|
73
77
|
df_chunk = active_runner.run_sql(rendered_sql, to_df=True, n_process=1)
|
|
78
|
+
if drop_cols:
|
|
79
|
+
df_chunk = df_chunk.drop(columns=[col for col in drop_cols if col in df_chunk.columns])
|
|
74
80
|
chunk_path = Path(tmp_dir_str) / f"_pull_chunk_{chunk_id:04d}.csv"
|
|
75
81
|
df_chunk.to_csv(chunk_path, index=False)
|
|
76
82
|
n_rows = len(df_chunk)
|
|
@@ -102,6 +108,7 @@ class ParallelODPSManager:
|
|
|
102
108
|
|
|
103
109
|
_VALID_BACKENDS = {"thread", "process", "sequential"}
|
|
104
110
|
_VALID_WRITE_MODES = {"overwrite", "append"}
|
|
111
|
+
_VALID_PULL_STRATEGIES = {"auto", "hash", "row_number"}
|
|
105
112
|
|
|
106
113
|
def __init__(self, config: ParallelODPSConfig, odps_runner: ODPSRunner | None = None):
|
|
107
114
|
self.config = config
|
|
@@ -112,6 +119,8 @@ class ParallelODPSManager:
|
|
|
112
119
|
cfg = self.config
|
|
113
120
|
if cfg.backend not in self._VALID_BACKENDS:
|
|
114
121
|
raise ValueError(f"backend must be one of {sorted(self._VALID_BACKENDS)}")
|
|
122
|
+
if cfg.pull_split_strategy not in self._VALID_PULL_STRATEGIES:
|
|
123
|
+
raise ValueError(f"pull_split_strategy must be one of {sorted(self._VALID_PULL_STRATEGIES)}")
|
|
115
124
|
if cfg.chunk_size is not None and cfg.chunk_size <= 0:
|
|
116
125
|
raise ValueError("chunk_size must be a positive integer.")
|
|
117
126
|
if cfg.n_chunks is not None and cfg.n_chunks <= 0:
|
|
@@ -122,6 +131,8 @@ class ParallelODPSManager:
|
|
|
122
131
|
raise ValueError("n_jobs must be a positive integer.")
|
|
123
132
|
if not cfg.chunk_filter_key:
|
|
124
133
|
raise ValueError("chunk_filter_key cannot be empty.")
|
|
134
|
+
if not cfg.row_number_col:
|
|
135
|
+
raise ValueError("row_number_col cannot be empty.")
|
|
125
136
|
if not cfg.tmp_table_prefix:
|
|
126
137
|
raise ValueError("tmp_table_prefix cannot be empty.")
|
|
127
138
|
|
|
@@ -131,34 +142,151 @@ class ParallelODPSManager:
|
|
|
131
142
|
def _auto_count_query(self, sql_path: str, template_kwargs: dict[str, Any]) -> str:
|
|
132
143
|
kwargs = dict(template_kwargs)
|
|
133
144
|
kwargs[self.config.chunk_filter_key] = "1=1"
|
|
134
|
-
rendered_sql = parse_sql_file(sql_path=sql_path, **kwargs)
|
|
145
|
+
rendered_sql = _strip_trailing_semicolon(parse_sql_file(sql_path=sql_path, **kwargs))
|
|
135
146
|
return f"SELECT COUNT(1) FROM ({rendered_sql}) __count_src;"
|
|
136
147
|
|
|
137
|
-
def
|
|
148
|
+
def _assert_chunk_filter_placeholder(self, sql_path: str, template_kwargs: dict[str, Any]) -> None:
|
|
149
|
+
sentinel = "__SMF_CHUNK_FILTER_SENTINEL__=1"
|
|
150
|
+
kwargs = dict(template_kwargs)
|
|
151
|
+
kwargs[self.config.chunk_filter_key] = sentinel
|
|
152
|
+
rendered_sql = parse_sql_file(sql_path=sql_path, **kwargs)
|
|
153
|
+
if sentinel not in rendered_sql:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
f"pull SQL template must contain {{{self.config.chunk_filter_key}}}"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def _render_pull_sql(self, sql_path: str, template_kwargs: dict[str, Any], chunk_filter: str) -> str:
|
|
159
|
+
kwargs = dict(template_kwargs)
|
|
160
|
+
kwargs[self.config.chunk_filter_key] = chunk_filter
|
|
161
|
+
return parse_sql_file(sql_path=sql_path, **kwargs)
|
|
162
|
+
|
|
163
|
+
def _resolve_pull_strategy(self) -> str:
|
|
164
|
+
cfg = self.config
|
|
165
|
+
strategy = cfg.pull_split_strategy
|
|
166
|
+
if strategy == "auto":
|
|
167
|
+
return "hash" if cfg.unique_key else "row_number"
|
|
168
|
+
if strategy == "hash" and not cfg.unique_key:
|
|
169
|
+
raise ValueError("unique_key is required when pull_split_strategy='hash'.")
|
|
170
|
+
return strategy
|
|
171
|
+
|
|
172
|
+
def _resolve_pull_n_chunks(
|
|
173
|
+
self,
|
|
174
|
+
count_query: str | None,
|
|
175
|
+
sql_path: str,
|
|
176
|
+
template_kwargs: dict[str, Any],
|
|
177
|
+
strategy: str,
|
|
178
|
+
staging_table: str | None = None,
|
|
179
|
+
) -> int:
|
|
138
180
|
cfg = self.config
|
|
139
|
-
if not cfg.unique_key:
|
|
140
|
-
raise ValueError("unique_key is required for pull().")
|
|
141
181
|
if cfg.n_chunks is not None:
|
|
142
182
|
return cfg.n_chunks
|
|
143
183
|
if cfg.chunk_size is None:
|
|
144
184
|
raise ValueError("chunk_size or n_chunks is required for pull().")
|
|
145
185
|
if count_query is None:
|
|
146
|
-
|
|
186
|
+
if strategy == "row_number":
|
|
187
|
+
if not staging_table:
|
|
188
|
+
raise ValueError("staging_table is required to count row_number pull chunks.")
|
|
189
|
+
count_query = f"SELECT COUNT(1) FROM {staging_table};"
|
|
190
|
+
else:
|
|
191
|
+
count_query = self._auto_count_query(sql_path, template_kwargs)
|
|
147
192
|
count_df = self.odps_runner.run_sql(count_query, to_df=True)
|
|
148
193
|
total_rows = int(count_df.iloc[0, 0])
|
|
149
194
|
return max(1, math.ceil(total_rows / cfg.chunk_size))
|
|
150
195
|
|
|
151
|
-
def
|
|
196
|
+
def _hash_chunk_filter(self, chunk_id: int, n_chunks: int) -> str:
|
|
197
|
+
return f"ABS(HASH({self.config.unique_key})) % {n_chunks} = {chunk_id}"
|
|
198
|
+
|
|
199
|
+
def _validate_hash_pull_sql(
|
|
152
200
|
self,
|
|
153
201
|
sql_path: str,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
202
|
+
template_kwargs: dict[str, Any],
|
|
203
|
+
n_chunks: int,
|
|
204
|
+
) -> None:
|
|
205
|
+
if not self.config.validate_unique_key:
|
|
206
|
+
return
|
|
207
|
+
rendered_sql = self._render_pull_sql(
|
|
208
|
+
sql_path=sql_path,
|
|
209
|
+
template_kwargs=template_kwargs,
|
|
210
|
+
chunk_filter=self._hash_chunk_filter(0, n_chunks),
|
|
211
|
+
)
|
|
212
|
+
probe_sql = (
|
|
213
|
+
"SELECT * FROM (\n"
|
|
214
|
+
f"{_strip_trailing_semicolon(rendered_sql)}\n"
|
|
215
|
+
") __smf_unique_key_probe LIMIT 1;"
|
|
216
|
+
)
|
|
217
|
+
try:
|
|
218
|
+
self.odps_runner.run_sql(probe_sql, to_df=True, n_process=1)
|
|
219
|
+
except Exception as exc:
|
|
220
|
+
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."
|
|
223
|
+
) from exc
|
|
224
|
+
|
|
225
|
+
def _build_hash_pull_chunks(
|
|
226
|
+
self,
|
|
227
|
+
sql_path: str,
|
|
228
|
+
template_kwargs: dict[str, Any],
|
|
229
|
+
n_chunks: int,
|
|
230
|
+
) -> list[dict[str, Any]]:
|
|
231
|
+
return [
|
|
232
|
+
{
|
|
233
|
+
"chunk": chunk_id,
|
|
234
|
+
"sql": self._render_pull_sql(
|
|
235
|
+
sql_path=sql_path,
|
|
236
|
+
template_kwargs=template_kwargs,
|
|
237
|
+
chunk_filter=self._hash_chunk_filter(chunk_id, n_chunks),
|
|
238
|
+
),
|
|
239
|
+
}
|
|
240
|
+
for chunk_id in range(n_chunks)
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
def _row_number_staging_table_name(self, run_id: str) -> str:
|
|
244
|
+
return f"{self.config.tmp_table_prefix}_{run_id}_pull_stage"
|
|
245
|
+
|
|
246
|
+
def _create_row_number_staging_table(
|
|
247
|
+
self,
|
|
248
|
+
sql_path: str,
|
|
249
|
+
template_kwargs: dict[str, Any],
|
|
250
|
+
staging_table: str,
|
|
251
|
+
) -> str:
|
|
158
252
|
cfg = self.config
|
|
159
|
-
|
|
160
|
-
|
|
253
|
+
base_sql = _strip_trailing_semicolon(
|
|
254
|
+
self._render_pull_sql(
|
|
255
|
+
sql_path=sql_path,
|
|
256
|
+
template_kwargs=template_kwargs,
|
|
257
|
+
chunk_filter="1=1",
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
order_by = cfg.row_number_order_by or "1"
|
|
261
|
+
create_sql = (
|
|
262
|
+
f"CREATE TABLE {staging_table} AS\n"
|
|
263
|
+
"SELECT\n"
|
|
264
|
+
f" ROW_NUMBER() OVER (ORDER BY {order_by}) AS {cfg.row_number_col},\n"
|
|
265
|
+
" __smf_base.*\n"
|
|
266
|
+
"FROM (\n"
|
|
267
|
+
f"{base_sql}\n"
|
|
268
|
+
") __smf_base;"
|
|
269
|
+
)
|
|
270
|
+
self.odps_runner.run_sql(create_sql, to_df=False)
|
|
271
|
+
return create_sql
|
|
272
|
+
|
|
273
|
+
def _build_row_number_pull_chunks(self, staging_table: str, n_chunks: int) -> list[dict[str, Any]]:
|
|
274
|
+
row_number_col = self.config.row_number_col
|
|
275
|
+
return [
|
|
276
|
+
{
|
|
277
|
+
"chunk": chunk_id,
|
|
278
|
+
"sql": (
|
|
279
|
+
f"SELECT *\n"
|
|
280
|
+
f"FROM {staging_table}\n"
|
|
281
|
+
f"WHERE ({row_number_col} - 1) % {n_chunks} = {chunk_id};"
|
|
282
|
+
),
|
|
283
|
+
"drop_cols": [row_number_col],
|
|
284
|
+
}
|
|
285
|
+
for chunk_id in range(n_chunks)
|
|
286
|
+
]
|
|
161
287
|
|
|
288
|
+
def _run_pull_chunks(self, chunk_specs: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
289
|
+
cfg = self.config
|
|
162
290
|
engine_cfg = ParallelApplyConfig(
|
|
163
291
|
split_axis="chunk",
|
|
164
292
|
backend=cfg.backend,
|
|
@@ -168,41 +296,83 @@ class ParallelODPSManager:
|
|
|
168
296
|
)
|
|
169
297
|
result = ParallelApplyEngine(engine_cfg).run(
|
|
170
298
|
func=_pull_one_chunk,
|
|
171
|
-
chunks=
|
|
299
|
+
chunks=chunk_specs,
|
|
172
300
|
func_args=(
|
|
173
301
|
self._runner_for_backend(),
|
|
174
|
-
sql_path,
|
|
175
|
-
template_kwargs,
|
|
176
|
-
cfg.chunk_filter_key,
|
|
177
|
-
cfg.unique_key,
|
|
178
|
-
n_chunks,
|
|
179
302
|
str(cfg.tmp_dir),
|
|
180
303
|
),
|
|
181
304
|
)
|
|
182
|
-
|
|
183
305
|
if len(result.errors):
|
|
184
|
-
raise RuntimeError(f"{len(result.errors)}/{
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
"
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
306
|
+
raise RuntimeError(f"{len(result.errors)}/{len(chunk_specs)} ODPS pull chunks failed:\n{result.errors}")
|
|
307
|
+
return sorted(result.output, key=lambda item: item["chunk"])
|
|
308
|
+
|
|
309
|
+
def pull(
|
|
310
|
+
self,
|
|
311
|
+
sql_path: str,
|
|
312
|
+
out_path: str,
|
|
313
|
+
count_query: str | None = None,
|
|
314
|
+
**template_kwargs: Any,
|
|
315
|
+
) -> dict[str, Any]:
|
|
316
|
+
cfg = self.config
|
|
317
|
+
self._assert_chunk_filter_placeholder(sql_path, template_kwargs)
|
|
318
|
+
cfg.tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
319
|
+
strategy = self._resolve_pull_strategy()
|
|
320
|
+
staging_table: str | None = None
|
|
321
|
+
success = False
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
if strategy == "row_number":
|
|
325
|
+
run_id = uuid.uuid4().hex[:12]
|
|
326
|
+
staging_table = self._row_number_staging_table_name(run_id)
|
|
327
|
+
self._create_row_number_staging_table(sql_path, template_kwargs, staging_table)
|
|
328
|
+
n_chunks = self._resolve_pull_n_chunks(
|
|
329
|
+
count_query=count_query,
|
|
330
|
+
sql_path=sql_path,
|
|
331
|
+
template_kwargs=template_kwargs,
|
|
332
|
+
strategy=strategy,
|
|
333
|
+
staging_table=staging_table,
|
|
334
|
+
)
|
|
335
|
+
chunk_specs = self._build_row_number_pull_chunks(staging_table, n_chunks)
|
|
336
|
+
else:
|
|
337
|
+
n_chunks = self._resolve_pull_n_chunks(
|
|
338
|
+
count_query=count_query,
|
|
339
|
+
sql_path=sql_path,
|
|
340
|
+
template_kwargs=template_kwargs,
|
|
341
|
+
strategy=strategy,
|
|
342
|
+
)
|
|
343
|
+
self._validate_hash_pull_sql(sql_path, template_kwargs, n_chunks)
|
|
344
|
+
chunk_specs = self._build_hash_pull_chunks(sql_path, template_kwargs, n_chunks)
|
|
345
|
+
|
|
346
|
+
chunk_summaries = self._run_pull_chunks(chunk_specs)
|
|
347
|
+
final_path = Path(out_path)
|
|
348
|
+
final_path.parent.mkdir(parents=True, exist_ok=True)
|
|
349
|
+
final_path.unlink(missing_ok=True)
|
|
350
|
+
|
|
351
|
+
with open(final_path, "wb") as fout:
|
|
352
|
+
for idx, summary in enumerate(chunk_summaries):
|
|
353
|
+
chunk_file = Path(summary["path"])
|
|
354
|
+
with open(chunk_file, "rb") as fin:
|
|
355
|
+
if idx > 0:
|
|
356
|
+
fin.readline()
|
|
357
|
+
shutil.copyfileobj(fin, fout)
|
|
358
|
+
chunk_file.unlink()
|
|
359
|
+
|
|
360
|
+
success = True
|
|
361
|
+
return {
|
|
362
|
+
"pull_strategy": strategy,
|
|
363
|
+
"staging_table": staging_table,
|
|
364
|
+
"n_chunks": n_chunks,
|
|
365
|
+
"total_rows": sum(item["rows"] for item in chunk_summaries),
|
|
366
|
+
"out_path": str(final_path),
|
|
367
|
+
"per_chunk_rows": [item["rows"] for item in chunk_summaries],
|
|
368
|
+
}
|
|
369
|
+
finally:
|
|
370
|
+
if (
|
|
371
|
+
staging_table
|
|
372
|
+
and cfg.cleanup_tmp
|
|
373
|
+
and (success or not cfg.keep_tmp_on_error)
|
|
374
|
+
):
|
|
375
|
+
_delete_table(self.odps_runner, staging_table)
|
|
206
376
|
|
|
207
377
|
def push(
|
|
208
378
|
self,
|
|
@@ -223,6 +393,7 @@ class ParallelODPSManager:
|
|
|
223
393
|
raise ValueError("push data produced no chunks.")
|
|
224
394
|
|
|
225
395
|
tmp_tables = [spec["tmp_table"] for spec in chunk_specs]
|
|
396
|
+
success = False
|
|
226
397
|
try:
|
|
227
398
|
engine_cfg = ParallelApplyConfig(
|
|
228
399
|
split_axis="chunk",
|
{supermodelingfactory-0.5.3 → supermodelingfactory-0.5.5}/Modeling_Tool/Pipeline/__init__.py
RENAMED
|
@@ -31,6 +31,23 @@ from .feature_validation import (
|
|
|
31
31
|
)
|
|
32
32
|
from .orchestrator import run_modeling_from_validation
|
|
33
33
|
from .screening_artifact import FeatureScreeningArtifact, screen_result_to_summary
|
|
34
|
+
from .field_meta import (
|
|
35
|
+
FieldMeta,
|
|
36
|
+
PipelineRegistryEntry,
|
|
37
|
+
PIPELINE_REGISTRY,
|
|
38
|
+
config_from_dict,
|
|
39
|
+
config_from_yaml,
|
|
40
|
+
config_to_dict,
|
|
41
|
+
config_to_yaml,
|
|
42
|
+
extract_config_schema,
|
|
43
|
+
extract_pipeline_schema,
|
|
44
|
+
extract_schema,
|
|
45
|
+
generate_pipeline_code,
|
|
46
|
+
get_config_field_meta,
|
|
47
|
+
get_pipeline_registry,
|
|
48
|
+
get_pipeline_registry_schema,
|
|
49
|
+
validate_pipeline_config,
|
|
50
|
+
)
|
|
34
51
|
|
|
35
52
|
__all__ = [
|
|
36
53
|
"RejectInferencePipeline",
|
|
@@ -57,4 +74,19 @@ __all__ = [
|
|
|
57
74
|
"FeatureScreeningArtifact",
|
|
58
75
|
"screen_result_to_summary",
|
|
59
76
|
"run_modeling_from_validation",
|
|
77
|
+
"FieldMeta",
|
|
78
|
+
"PipelineRegistryEntry",
|
|
79
|
+
"PIPELINE_REGISTRY",
|
|
80
|
+
"get_pipeline_registry",
|
|
81
|
+
"get_pipeline_registry_schema",
|
|
82
|
+
"get_config_field_meta",
|
|
83
|
+
"extract_config_schema",
|
|
84
|
+
"extract_pipeline_schema",
|
|
85
|
+
"extract_schema",
|
|
86
|
+
"config_to_dict",
|
|
87
|
+
"config_from_dict",
|
|
88
|
+
"config_to_yaml",
|
|
89
|
+
"config_from_yaml",
|
|
90
|
+
"validate_pipeline_config",
|
|
91
|
+
"generate_pipeline_code",
|
|
60
92
|
]
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import gc
|
|
4
4
|
import logging
|
|
5
|
+
import warnings
|
|
5
6
|
from dataclasses import dataclass, field, replace
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, Literal
|
|
@@ -33,6 +34,7 @@ class FeatureValidationPipelineConfig:
|
|
|
33
34
|
|
|
34
35
|
input_type: Literal["auto", "dataframe", "csv"] = "auto"
|
|
35
36
|
csv_read_kwargs: dict[str, Any] = field(default_factory=dict)
|
|
37
|
+
enable_batch: bool = False
|
|
36
38
|
feature_batch_size: int | None = None
|
|
37
39
|
feature_batches: list[list[str]] | None = None
|
|
38
40
|
batch_base_cols: list[str] | None = None
|
|
@@ -150,10 +152,19 @@ class FeatureValidationPipeline:
|
|
|
150
152
|
|
|
151
153
|
def run(self, data: pd.DataFrame | str | Path) -> FeatureValidationPipelineResult:
|
|
152
154
|
input_type = self._resolve_input_type(data)
|
|
155
|
+
if self.config.enable_batch and input_type != "csv":
|
|
156
|
+
raise ValueError("enable_batch=True currently requires CSV input.")
|
|
153
157
|
if input_type == "csv":
|
|
154
158
|
csv_path = Path(data)
|
|
155
159
|
if self._is_batch_mode():
|
|
156
160
|
return self._run_csv_batches(csv_path)
|
|
161
|
+
if self._has_batch_config():
|
|
162
|
+
warnings.warn(
|
|
163
|
+
"feature_batch_size/feature_batches are configured but enable_batch=False; "
|
|
164
|
+
"FeatureValidationPipeline will read the CSV in full and ignore batch settings.",
|
|
165
|
+
RuntimeWarning,
|
|
166
|
+
stacklevel=2,
|
|
167
|
+
)
|
|
157
168
|
return self._run_dataframe(self._read_csv(csv_path))
|
|
158
169
|
if not isinstance(data, pd.DataFrame):
|
|
159
170
|
raise TypeError("data must be a pandas DataFrame when input_type='dataframe'.")
|
|
@@ -268,10 +279,18 @@ class FeatureValidationPipeline:
|
|
|
268
279
|
return cfg.input_type
|
|
269
280
|
return "dataframe" if isinstance(data, pd.DataFrame) else "csv"
|
|
270
281
|
|
|
271
|
-
def
|
|
282
|
+
def _has_batch_config(self) -> bool:
|
|
272
283
|
cfg = self.config
|
|
273
284
|
return bool(cfg.feature_batches) or cfg.feature_batch_size is not None
|
|
274
285
|
|
|
286
|
+
def _is_batch_mode(self) -> bool:
|
|
287
|
+
cfg = self.config
|
|
288
|
+
if not cfg.enable_batch:
|
|
289
|
+
return False
|
|
290
|
+
if not self._has_batch_config():
|
|
291
|
+
raise ValueError("enable_batch=True requires feature_batch_size or feature_batches.")
|
|
292
|
+
return True
|
|
293
|
+
|
|
275
294
|
def _read_csv(self, csv_path: Path, usecols: list[str] | None = None, nrows: int | None = None) -> pd.DataFrame:
|
|
276
295
|
if not csv_path.exists():
|
|
277
296
|
raise FileNotFoundError(f"CSV file not found: {csv_path}")
|
|
@@ -325,6 +344,7 @@ class FeatureValidationPipeline:
|
|
|
325
344
|
cfg,
|
|
326
345
|
output_dir=str(batch_dir),
|
|
327
346
|
input_type="dataframe",
|
|
347
|
+
enable_batch=False,
|
|
328
348
|
feature_batch_size=None,
|
|
329
349
|
feature_batches=None,
|
|
330
350
|
new_feature_cols=list(batch_features),
|