data-advisor 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,515 @@
1
+ """
2
+ codegen.py - Generates copy-pasteable, robust, executable Python scripts for timed exams.
3
+
4
+ Produces clean, PEP 8 compliant, production-grade Pandas, Scipy, and Scikit-Learn code
5
+ tailored specifically to the audited dataset.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, List, Optional
11
+ from data_advisor.visualizer import VisualizationAdvisor
12
+
13
+
14
+ class CodeGenerator:
15
+ """
16
+ Translates dataset scan and rule evaluations into sequential,
17
+ modular, executable Python code blocks and full pipeline scripts.
18
+ """
19
+
20
+ def __init__(self, scan_results: Dict[str, Any], data_source_str: str = "dataset.csv") -> None:
21
+ self.scan = scan_results
22
+ self.source = data_source_str
23
+ self.overview = scan_results.get("overview", {})
24
+ self.missing = scan_results.get("missing_values", {})
25
+ self.dtypes = scan_results.get("data_types", {})
26
+ self.distributions = scan_results.get("distributions", {})
27
+ self.categoricals = scan_results.get("categoricals", {})
28
+ self.target = scan_results.get("target")
29
+ self.hypothesis_tests = scan_results.get("hypothesis_tests", [])
30
+
31
+ # -------------------------------------------------------------------------
32
+ # 1. Environment & Ingestion Block
33
+ # -------------------------------------------------------------------------
34
+ def generate_imports_and_loading(self) -> str:
35
+ return f'''# =============================================================================
36
+ # STEP 1: ENVIRONMENT SETUP & DATA INGESTION
37
+ # =============================================================================
38
+ import numpy as np
39
+ import pandas as pd
40
+ import scipy.stats as stats
41
+ import matplotlib.pyplot as plt
42
+ import seaborn as sns
43
+
44
+ from sklearn.model_selection import train_test_split, cross_val_score, KFold, StratifiedKFold
45
+ from sklearn.preprocessing import StandardScaler, RobustScaler, OneHotEncoder, PowerTransformer
46
+ from sklearn.impute import SimpleImputer, KNNImputer
47
+
48
+ # Set presentation styles
49
+ pd.set_option('display.max_columns', 50)
50
+ pd.set_option('display.width', 1000)
51
+ sns.set_theme(style='whitegrid', palette='muted')
52
+
53
+ # Load the dataset
54
+ DATA_PATH = {repr(self.source)}
55
+ print(f"[*] Ingesting dataset from: {{DATA_PATH}}")
56
+ df = pd.read_csv(DATA_PATH) if DATA_PATH.endswith('.csv') else pd.read_excel(DATA_PATH)
57
+
58
+ print(f"[*] Raw Shape: {{df.shape[0]}} rows, {{df.shape[1]}} columns")
59
+ df.info()
60
+ '''
61
+
62
+ # -------------------------------------------------------------------------
63
+ # 2. Data Cleaning Block
64
+ # -------------------------------------------------------------------------
65
+ def generate_cleaning_code(self) -> str:
66
+ lines = [
67
+ "# =============================================================================",
68
+ "# STEP 2: DATA CLEANING & AUDITING",
69
+ "# =============================================================================",
70
+ ]
71
+
72
+ # Duplicate Handling
73
+ dup_count = self.overview.get("duplicate_rows", 0)
74
+ if dup_count > 0:
75
+ lines.extend([
76
+ f"# Remove {dup_count} duplicated rows",
77
+ "initial_rows = len(df)",
78
+ "df = df.drop_duplicates().reset_index(drop=True)",
79
+ "print(f'[*] Deduplicated dataset: dropped {initial_rows - len(df)} duplicate rows.')",
80
+ "",
81
+ ])
82
+ else:
83
+ lines.extend([
84
+ "# Verification: No duplicate rows detected",
85
+ "assert df.duplicated().sum() == 0, 'Unexpected duplicates found!'",
86
+ "",
87
+ ])
88
+
89
+ # Drop Constant Columns
90
+ constant_cols = self.dtypes.get("constant_columns", [])
91
+ if constant_cols:
92
+ lines.extend([
93
+ f"# Drop zero-variance (constant) columns: {constant_cols}",
94
+ f"df = df.drop(columns={repr(constant_cols)})",
95
+ f'print(f"[*] Dropped constant columns: {constant_cols}")',
96
+ "",
97
+ ])
98
+
99
+ # Drop High-Missing Columns (>60%)
100
+ high_missing_cols = [
101
+ c for c, info in self.missing.items() if info.get("null_pct", 0) >= 60.0
102
+ ]
103
+ if high_missing_cols:
104
+ lines.extend([
105
+ f"# Drop high-sparsity columns (>60% missing): {high_missing_cols}",
106
+ f"df = df.drop(columns={repr(high_missing_cols)})",
107
+ f'print(f"[*] Dropped sparse columns: {high_missing_cols}")',
108
+ "",
109
+ ])
110
+
111
+ # Date conversions & feature extraction
112
+ date_cols = self.dtypes.get("potential_dates", [])
113
+ if date_cols:
114
+ lines.extend([
115
+ "# Parse dates and extract temporal features",
116
+ ])
117
+ for d_col in date_cols:
118
+ lines.extend([
119
+ f"df['{d_col}'] = pd.to_datetime(df['{d_col}'], errors='coerce')",
120
+ f"df['{d_col}_year'] = df['{d_col}'].dt.year",
121
+ f"df['{d_col}_month'] = df['{d_col}'].dt.month",
122
+ f"df['{d_col}_day'] = df['{d_col}'].dt.day",
123
+ f"df['{d_col}_dayofweek'] = df['{d_col}'].dt.dayofweek",
124
+ f"df = df.drop(columns=['{d_col}']) # Drop raw timestamp string",
125
+ ])
126
+ lines.append("")
127
+
128
+ # Missing Value Imputation
129
+ numeric_imputes_median = []
130
+ numeric_imputes_mean = []
131
+ cat_imputes_mode = []
132
+
133
+ for c, info in self.missing.items():
134
+ pct = info.get("null_pct", 0)
135
+ if 0 < pct < 60.0 and c not in high_missing_cols:
136
+ if info.get("is_numeric", False):
137
+ skew = abs(self.distributions.get(c, {}).get("skewness", 0.0))
138
+ if skew > 1.0:
139
+ numeric_imputes_median.append(c)
140
+ else:
141
+ numeric_imputes_mean.append(c)
142
+ else:
143
+ cat_imputes_mode.append(c)
144
+
145
+ if numeric_imputes_median or numeric_imputes_mean or cat_imputes_mode:
146
+ lines.append("# Missing Value Imputation")
147
+ if numeric_imputes_median:
148
+ lines.extend([
149
+ f"# Median imputation for skewed numeric features: {numeric_imputes_median}",
150
+ f"for col in {repr(numeric_imputes_median)}:",
151
+ " df[col] = df[col].fillna(df[col].median())",
152
+ ])
153
+ if numeric_imputes_mean:
154
+ lines.extend([
155
+ f"# Mean imputation for normal numeric features: {numeric_imputes_mean}",
156
+ f"for col in {repr(numeric_imputes_mean)}:",
157
+ " df[col] = df[col].fillna(df[col].mean())",
158
+ ])
159
+ if cat_imputes_mode:
160
+ lines.extend([
161
+ f"# Mode imputation for categorical features: {cat_imputes_mode}",
162
+ f"for col in {repr(cat_imputes_mode)}:",
163
+ " mode_val = df[col].mode()[0] if not df[col].mode().empty else 'Missing'",
164
+ " df[col] = df[col].fillna(mode_val)",
165
+ ])
166
+ lines.extend([
167
+ "print(f'[*] Remaining missing values: {df.isna().sum().sum()}')",
168
+ "",
169
+ ])
170
+
171
+ return "\n".join(lines)
172
+
173
+ # -------------------------------------------------------------------------
174
+ # 3. Distribution Transformations & Outlier Code
175
+ # -------------------------------------------------------------------------
176
+ def generate_transformations_code(self) -> str:
177
+ lines = [
178
+ "# =============================================================================",
179
+ "# STEP 3: DISTRIBUTION TRANSFORMATIONS & OUTLIER HANDLING",
180
+ "# =============================================================================",
181
+ ]
182
+
183
+ has_transforms = False
184
+ for col, dist in self.distributions.items():
185
+ skew = dist.get("skewness", 0.0)
186
+ is_non_neg = dist.get("is_non_negative", True)
187
+ iqr_pct = dist.get("iqr_outlier_pct", 0.0)
188
+ lf = dist.get("lower_fence", 0.0)
189
+ uf = dist.get("upper_fence", 0.0)
190
+
191
+ # High skewness
192
+ if skew > 1.2 and is_non_neg:
193
+ has_transforms = True
194
+ lines.extend([
195
+ f"# Log1p transform for right-skewed column '{col}' (skewness={skew:.2f})",
196
+ f"df['{col}'] = np.log1p(df['{col}'])",
197
+ ])
198
+ elif abs(skew) > 1.2 and not is_non_neg:
199
+ has_transforms = True
200
+ lines.extend([
201
+ f"# Yeo-Johnson transform for skewed column '{col}' containing negative values",
202
+ "pt = PowerTransformer(method='yeo-johnson')",
203
+ f"df['{col}'] = pt.fit_transform(df[['{col}']])",
204
+ ])
205
+
206
+ # Extreme outliers clipping
207
+ if iqr_pct > 5.0:
208
+ has_transforms = True
209
+ lines.extend([
210
+ f"# Winsorize/Clip extreme outliers for '{col}' at IQR fences [{lf:.2f}, {uf:.2f}]",
211
+ f"df['{col}'] = df['{col}'].clip(lower={lf}, upper={uf})",
212
+ ])
213
+
214
+ if not has_transforms:
215
+ lines.append("# No severe skewness or extreme outliers requiring destructive transformation.\n")
216
+ else:
217
+ lines.append("print('[*] Distribution normalization and outlier treatment applied.')\n")
218
+
219
+ return "\n".join(lines)
220
+
221
+ # -------------------------------------------------------------------------
222
+ # 4. Categorical Encoding Block
223
+ # -------------------------------------------------------------------------
224
+ def generate_encoding_code(self) -> str:
225
+ lines = [
226
+ "# =============================================================================",
227
+ "# STEP 4: CATEGORICAL FEATURE ENCODING",
228
+ "# =============================================================================",
229
+ ]
230
+
231
+ target_col = self.target.get("target_column") if self.target else None
232
+ excluded_cols = set(
233
+ list(self.dtypes.get("potential_dates", []))
234
+ + list(self.dtypes.get("constant_columns", []))
235
+ + list(self.dtypes.get("potential_ids", []))
236
+ + [c for c, info in self.missing.items() if info.get("null_pct", 0) >= 60.0]
237
+ )
238
+ if target_col:
239
+ excluded_cols.add(target_col)
240
+
241
+ binary_cols = []
242
+ low_card_cols = []
243
+ high_card_cols = []
244
+
245
+ for col, cat in self.categoricals.items():
246
+ if col in excluded_cols:
247
+ continue
248
+ cardinality = cat.get("cardinality", 0)
249
+ if cardinality == 2:
250
+ binary_cols.append(col)
251
+ elif 3 <= cardinality <= 10:
252
+ low_card_cols.append(col)
253
+ elif cardinality > 10:
254
+ high_card_cols.append(col)
255
+
256
+ if binary_cols:
257
+ lines.extend([
258
+ f"# Binary 0/1 Encoding for binary features: {binary_cols}",
259
+ f"for col in {repr(binary_cols)}:",
260
+ " if col in df.columns:",
261
+ " unique_vals = df[col].dropna().unique()",
262
+ " if len(unique_vals) == 2:",
263
+ " val_map = {unique_vals[0]: 0, unique_vals[1]: 1}",
264
+ " df[col] = df[col].map(val_map)",
265
+ ' print(f"[*] Mapped {col}: {val_map}")',
266
+ "",
267
+ ])
268
+
269
+ if low_card_cols:
270
+ lines.extend([
271
+ f"# One-Hot Encoding for low-cardinality features: {low_card_cols}",
272
+ f"ohe_cols = [c for c in {repr(low_card_cols)} if c in df.columns]",
273
+ "if ohe_cols:",
274
+ " df = pd.get_dummies(df, columns=ohe_cols, drop_first=True, dtype=int)",
275
+ " print(f'[*] Applied One-Hot Encoding on: {ohe_cols}')",
276
+ "",
277
+ ])
278
+
279
+ if high_card_cols:
280
+ lines.extend([
281
+ f"# Frequency Encoding for high-cardinality features: {high_card_cols}",
282
+ f"for col in {repr(high_card_cols)}:",
283
+ " if col in df.columns:",
284
+ " freq_encoding = df[col].value_counts(normalize=True)",
285
+ " df[col] = df[col].map(freq_encoding)",
286
+ ' print(f"[*] Applied Frequency Encoding on {col}")',
287
+ "",
288
+ ])
289
+
290
+ if not (binary_cols or low_card_cols or high_card_cols):
291
+ lines.append("# No categorical columns requiring encoding.\n")
292
+
293
+ return "\n".join(lines)
294
+
295
+ # -------------------------------------------------------------------------
296
+ # 5. Hypothesis Testing Block
297
+ # -------------------------------------------------------------------------
298
+ def generate_hypothesis_testing_code(self) -> str:
299
+ lines = [
300
+ "# =============================================================================",
301
+ "# STEP 5: HYPOTHESIS TESTING & STATISTICAL INFERENCE",
302
+ "# =============================================================================",
303
+ "print('\\n' + '='*70)",
304
+ "print(' STATISTICAL HYPOTHESIS TESTING RESULTS')",
305
+ "print('='*70)",
306
+ "",
307
+ ]
308
+
309
+ if not self.hypothesis_tests:
310
+ lines.append("# No automated hypothesis tests generated.")
311
+ return "\n".join(lines)
312
+
313
+ for idx, test in enumerate(self.hypothesis_tests, 1):
314
+ f1 = test.get("feature_1")
315
+ f2 = test.get("feature_2")
316
+ test_type = test.get("test_type")
317
+ h0 = test.get("h0")
318
+ h1 = test.get("h1")
319
+
320
+ lines.extend([
321
+ f"# Test {idx}: {test_type} ({f1} vs {f2})",
322
+ f"if {repr(f1)} in df.columns and {repr(f2)} in df.columns:",
323
+ f" print('\\n' + '-'*50)",
324
+ f" print({repr(f'[TEST {idx}] {test_type}: {f1} vs {f2}')})",
325
+ f" print({repr(f'H0: {h0}')})",
326
+ f" print({repr(f'H1: {h1}')})",
327
+ ])
328
+
329
+ if "Correlation" in test_type:
330
+ lines.extend([
331
+ f" valid_data = df[[{repr(f1)}, {repr(f2)}]].dropna()",
332
+ f" r, p_val = stats.pearsonr(valid_data[{repr(f1)}], valid_data[{repr(f2)}])",
333
+ f" rho, spearman_pval = stats.spearmanr(valid_data[{repr(f1)}], valid_data[{repr(f2)}])",
334
+ " print(f'Pearson r: {r:.4f} (p-value: {p_val:.4e})')",
335
+ " print(f'Spearman rho: {rho:.4f} (p-value: {spearman_pval:.4e})')",
336
+ " if p_val < 0.05:",
337
+ " print('=> CONCLUSION: Reject H0. Statistically significant correlation detected.')",
338
+ " else:",
339
+ " print('=> CONCLUSION: Fail to reject H0. No significant linear correlation.')",
340
+ ])
341
+ elif "t-test" in test_type or "Mann-Whitney" in test_type:
342
+ lines.extend([
343
+ f" valid_data = df[[{repr(f1)}, {repr(f2)}]].dropna()",
344
+ f" classes = valid_data[{repr(f2)}].unique()",
345
+ f" if len(classes) >= 2:",
346
+ f" g1 = valid_data[valid_data[{repr(f2)}] == classes[0]][{repr(f1)}]",
347
+ f" g2 = valid_data[valid_data[{repr(f2)}] == classes[1]][{repr(f1)}]",
348
+ " t_stat, p_val = stats.ttest_ind(g1, g2, equal_var=False)",
349
+ " u_stat, u_pval = stats.mannwhitneyu(g1, g2, alternative='two-sided')",
350
+ " print(f'Welch t-test: t={t_stat:.4f}, p-value={p_val:.4e}')",
351
+ " print(f'Mann-Whitney U: U={u_stat:.4f}, p-value={u_pval:.4e}')",
352
+ " if p_val < 0.05:",
353
+ " print('=> CONCLUSION: Reject H0. Significant difference between class distributions.')",
354
+ " else:",
355
+ " print('=> CONCLUSION: Fail to reject H0. No significant difference.')",
356
+ ])
357
+ elif "ANOVA" in test_type:
358
+ lines.extend([
359
+ f" valid_data = df[[{repr(f1)}, {repr(f2)}]].dropna()",
360
+ f" classes = valid_data[{repr(f2)}].unique()",
361
+ f" groups = [valid_data[valid_data[{repr(f2)}] == c][{repr(f1)}] for c in classes]",
362
+ " f_stat, p_val = stats.f_oneway(*groups)",
363
+ " print(f'One-way ANOVA: F={f_stat:.4f}, p-value={p_val:.4e}')",
364
+ " if p_val < 0.05:",
365
+ " print('=> CONCLUSION: Reject H0. Group means differ significantly.')",
366
+ " else:",
367
+ " print('=> CONCLUSION: Fail to reject H0. No significant difference in group means.')",
368
+ ])
369
+ elif "Chi-Square" in test_type:
370
+ lines.extend([
371
+ f" valid_data = df[[{repr(f1)}, {repr(f2)}]].dropna()",
372
+ f" contingency_tab = pd.crosstab(valid_data[{repr(f1)}], valid_data[{repr(f2)}])",
373
+ " chi2, p_val, dof, _ = stats.chi2_contingency(contingency_tab)",
374
+ " n_obs = contingency_tab.sum().sum()",
375
+ " min_dim = min(contingency_tab.shape) - 1",
376
+ " cramer_v = np.sqrt(chi2 / (n_obs * min_dim)) if (n_obs * min_dim) > 0 else 0.0",
377
+ " print(f'Chi-Square: chi2={chi2:.4f}, dof={dof}, p-value={p_val:.4e}')",
378
+ " print(f'Cramer V Effect Size: {cramer_v:.4f}')",
379
+ " if p_val < 0.05:",
380
+ " print('=> CONCLUSION: Reject H0. Significant association/dependency between categorical features.')",
381
+ " else:",
382
+ " print('=> CONCLUSION: Fail to reject H0. Variables appear statistically independent.')",
383
+ ])
384
+ lines.append("")
385
+
386
+ return "\n".join(lines)
387
+
388
+ # -------------------------------------------------------------------------
389
+ # 6. Baseline Modeling Block
390
+ # -------------------------------------------------------------------------
391
+ def generate_modeling_code(self) -> str:
392
+ if not self.target:
393
+ return "# No target variable detected; modeling pipeline skipped.\n"
394
+
395
+ target_col = self.target.get("target_column")
396
+ prob_type = self.target.get("problem_type")
397
+ id_cols = list(self.dtypes.get("potential_ids", []))
398
+
399
+ lines = [
400
+ "# =============================================================================",
401
+ f"# STEP 6: MACHINE LEARNING PIPELINE ({prob_type.upper()})",
402
+ "# =============================================================================",
403
+ f"TARGET = {repr(target_col)}",
404
+ f"ID_COLS = {repr(id_cols)}",
405
+ "",
406
+ "# Separate Feature Matrix (X) and Response Vector (y)",
407
+ "drop_cols = [TARGET] + [col for col in ID_COLS if col in df.columns]",
408
+ "X = df.drop(columns=[col for col in drop_cols if col in df.columns])",
409
+ "y = df[TARGET]",
410
+ "",
411
+ "# Align X with only numeric types (already encoded)",
412
+ "X = X.select_dtypes(include=[np.number])",
413
+ "print(f'[*] Modeling Matrix: X shape = {X.shape}, y shape = {y.shape}')",
414
+ "",
415
+ ]
416
+
417
+ if prob_type == "regression":
418
+ lines.extend([
419
+ "from sklearn.linear_model import LinearRegression, Ridge",
420
+ "from sklearn.ensemble import RandomForestRegressor",
421
+ "from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score",
422
+ "",
423
+ "# Train / Test Split (80/20)",
424
+ "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)",
425
+ "",
426
+ "# Feature Scaling with RobustScaler (robust to remaining outliers)",
427
+ "scaler = RobustScaler()",
428
+ "X_train_scaled = scaler.fit_transform(X_train)",
429
+ "X_test_scaled = scaler.transform(X_test)",
430
+ "",
431
+ "# Model Baselines",
432
+ "models = {",
433
+ " 'Linear Regression': LinearRegression(),",
434
+ " 'Ridge Regression (L2)': Ridge(alpha=1.0),",
435
+ " 'Random Forest Regressor': RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1),",
436
+ "}",
437
+ "",
438
+ "print('\\n' + '='*75)",
439
+ "print(f'{\"Model\":<25} | {\"CV R2 (5-Fold)\":<15} | {\"Test RMSE\":<12} | {\"Test R2\":<10}')",
440
+ "print('='*75)",
441
+ "",
442
+ "for name, model in models.items():",
443
+ " # 5-Fold Cross Validation on Training set",
444
+ " cv_scores = cross_val_score(model, X_train_scaled, y_train, cv=5, scoring='r2')",
445
+ " # Train on full train set and evaluate on test set",
446
+ " model.fit(X_train_scaled, y_train)",
447
+ " preds = model.predict(X_test_scaled)",
448
+ " # Calculate metrics",
449
+ " rmse = np.sqrt(mean_squared_error(y_test, preds))",
450
+ " r2 = r2_score(y_test, preds)",
451
+ " mae = mean_absolute_error(y_test, preds)",
452
+ " print(f'{name:<25} | {cv_scores.mean():.4f} (+/-{cv_scores.std():.3f}) | {rmse:<12.4f} | {r2:<10.4f}')",
453
+ "print('='*75)",
454
+ ])
455
+ else:
456
+ # Classification
457
+ is_imbalanced = self.target.get("is_imbalanced", False)
458
+ cw_param = "'balanced'" if is_imbalanced else "None"
459
+
460
+ lines.extend([
461
+ "from sklearn.linear_model import LogisticRegression",
462
+ "from sklearn.ensemble import RandomForestClassifier",
463
+ "from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score",
464
+ "",
465
+ "# Stratified Train / Test Split (preserves class distribution)",
466
+ "X_train, X_test, y_train, y_test = train_test_split(",
467
+ " X, y, test_size=0.20, random_state=42, stratify=y",
468
+ ")",
469
+ "",
470
+ "# Feature Scaling",
471
+ "scaler = StandardScaler()",
472
+ "X_train_scaled = scaler.fit_transform(X_train)",
473
+ "X_test_scaled = scaler.transform(X_test)",
474
+ "",
475
+ "# Classification Models",
476
+ "models = {",
477
+ f" 'Logistic Regression': LogisticRegression(class_weight={cw_param}, max_iter=1000, random_state=42),",
478
+ f" 'Random Forest Classifier': RandomForestClassifier(n_estimators=100, class_weight={cw_param}, random_state=42, n_jobs=-1),",
479
+ "}",
480
+ "",
481
+ "cv_strat = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)",
482
+ "",
483
+ "for name, model in models.items():",
484
+ " print(f'\\n>> Evaluating {name}:')",
485
+ " # 5-Fold Stratified Cross Validation",
486
+ " cv_scores = cross_val_score(model, X_train_scaled, y_train, cv=cv_strat, scoring='f1_weighted')",
487
+ " print(f' 5-Fold CV Weighted F1: {cv_scores.mean():.4f} (+/- {cv_scores.std():.3f})')",
488
+ " # Fit and Predict on Test Set",
489
+ " model.fit(X_train_scaled, y_train)",
490
+ " preds = model.predict(X_test_scaled)",
491
+ " print(' Test Set Classification Report:')",
492
+ " print(classification_report(y_test, preds))",
493
+ " print(' Confusion Matrix:')",
494
+ " print(confusion_matrix(y_test, preds))",
495
+ ])
496
+
497
+ return "\n".join(lines)
498
+
499
+ # -------------------------------------------------------------------------
500
+ # 7. Master Script Assembly
501
+ # -------------------------------------------------------------------------
502
+ def generate_full_script(self) -> str:
503
+ """Assemble all modular blocks into one comprehensive, executable script."""
504
+ vis_advisor = VisualizationAdvisor(self.scan)
505
+ script_parts = [
506
+ self.generate_imports_and_loading(),
507
+ self.generate_cleaning_code(),
508
+ self.generate_transformations_code(),
509
+ self.generate_encoding_code(),
510
+ vis_advisor.generate_all_plots_code(),
511
+ self.generate_hypothesis_testing_code(),
512
+ self.generate_modeling_code(),
513
+ "\nprint('\\n[*] Complete Data Analysis & Modeling Pipeline Finished Successfully.')\n",
514
+ ]
515
+ return "\n\n".join(script_parts)