CoBeDigger commited on
Commit
eb71b92
·
1 Parent(s): e91d5da

fix: track missing Codex files (mlops_bot, tasks, graders) and add app/actors/__init__.py to resolve ModuleNotFoundError

Browse files
app/actors/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Initialize actors package
app/actors/mlops_bot.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ import uuid
8
+ from pathlib import Path
9
+
10
+ import pandas as pd
11
+
12
+
13
+ FEATURE_COLUMNS = [
14
+ "age",
15
+ "salary",
16
+ "credit_score",
17
+ "loan_amount",
18
+ "employment_years",
19
+ ]
20
+
21
+
22
+ class MLOpsBot:
23
+ def _parse_value(self, pattern: str, output: str) -> float | None:
24
+ matches = re.findall(pattern, output, flags=re.IGNORECASE)
25
+ if not matches:
26
+ return None
27
+ try:
28
+ return float(matches[-1])
29
+ except ValueError:
30
+ return None
31
+
32
+ def _run_checks(self, script: str, dataframe: pd.DataFrame) -> tuple[float, float]:
33
+ runs_dir = Path.cwd() / ".actor_runs"
34
+ runs_dir.mkdir(exist_ok=True)
35
+ tmpdir = runs_dir / f"mlops_{uuid.uuid4().hex}"
36
+ tmpdir.mkdir()
37
+
38
+ def cleanup() -> None:
39
+ shutil.rmtree(tmpdir, ignore_errors=True)
40
+
41
+ data_path = tmpdir / "data.csv"
42
+ script_path = tmpdir / "candidate.py"
43
+
44
+ try:
45
+ dataframe.to_csv(data_path, index=False)
46
+ except Exception:
47
+ cleanup()
48
+ return 0.0, 1.0
49
+
50
+ postlude = f"""
51
+ from sklearn.metrics import accuracy_score as _accuracy_score
52
+ _deploy_df = pd.read_csv(r'{data_path.as_posix()}')
53
+ if 'X_test' in globals() and 'y_test' in globals():
54
+ _X_raw = X_test.copy()
55
+ _y = y_test.copy()
56
+ else:
57
+ _X_raw = _deploy_df[{FEATURE_COLUMNS!r}]
58
+ _y = _deploy_df['target']
59
+ _model = globals().get('clf') or globals().get('model') or globals().get('classifier') or globals().get('pipeline')
60
+ if _model is None:
61
+ raise RuntimeError('No trained model found; expected clf, model, classifier, or pipeline.')
62
+ if 'pipeline' in globals() and _model is globals().get('pipeline'):
63
+ _pred = _model.predict(_X_raw)
64
+ else:
65
+ _scaler = globals().get('scaler')
66
+ _X_eval = _scaler.transform(_X_raw) if _scaler is not None else _X_raw
67
+ _pred = _model.predict(_X_eval)
68
+ _eval_df = _X_raw.copy()
69
+ _eval_df['target'] = _y
70
+ _eval_df['_pred'] = _pred
71
+ _group_a = _eval_df[_eval_df['age'] < 40]
72
+ _group_b = _eval_df[_eval_df['age'] >= 40]
73
+ _acc_a = _accuracy_score(_group_a['target'], _group_a['_pred'])
74
+ _acc_b = _accuracy_score(_group_b['target'], _group_b['_pred'])
75
+ print('__FAIRNESS_GAP__:', abs(_acc_a - _acc_b))
76
+ """
77
+ code = (
78
+ "import pandas as pd\n"
79
+ f"df = pd.read_csv(r'{data_path.as_posix()}')\n\n"
80
+ + (script or "")
81
+ + "\n"
82
+ + postlude
83
+ )
84
+ script_path.write_text(code, encoding="utf-8")
85
+
86
+ try:
87
+ proc = subprocess.run(
88
+ [sys.executable, str(script_path)],
89
+ capture_output=True,
90
+ text=True,
91
+ timeout=10,
92
+ check=False,
93
+ cwd=tmpdir,
94
+ )
95
+ except Exception:
96
+ cleanup()
97
+ return 0.0, 1.0
98
+
99
+ cleanup()
100
+
101
+ if proc.returncode != 0 or (proc.stderr or "").strip():
102
+ return 0.0, 1.0
103
+
104
+ stdout = proc.stdout or ""
105
+ acc = self._parse_value(r"Accuracy\s*:\s*([0-9]*\.?[0-9]+)", stdout)
106
+ gap = self._parse_value(r"__FAIRNESS_GAP__\s*:\s*([0-9]*\.?[0-9]+)", stdout)
107
+ return (acc if acc is not None else 0.0), (gap if gap is not None else 1.0)
108
+
109
+ def check_deployment(
110
+ self,
111
+ script: str,
112
+ dataframe: pd.DataFrame,
113
+ deploy_conditions: dict,
114
+ ) -> str:
115
+ threshold = deploy_conditions["accuracy_threshold"]
116
+ max_ms = deploy_conditions["max_inference_ms"]
117
+ max_gap = deploy_conditions["max_fairness_gap"]
118
+ acc, gap = self._run_checks(script, dataframe)
119
+
120
+ if acc < threshold:
121
+ return (
122
+ "REJECTED: Accuracy below threshold.\n"
123
+ f" [FAIL] Accuracy: {acc:.2f} < {threshold}\n"
124
+ " Fix model performance before resubmitting."
125
+ )
126
+
127
+ if gap > max_gap:
128
+ return (
129
+ "REJECTED: Fairness constraint violated.\n"
130
+ f" [PASS] Accuracy: {acc:.2f} >= {threshold}\n"
131
+ f" [FAIL] Fairness gap: {gap:.3f} > {max_gap}\n"
132
+ " Model performs unequally across age groups.\n"
133
+ " Consider class_weight='balanced' or stratified sampling."
134
+ )
135
+
136
+ return (
137
+ "APPROVED: All deployment conditions met.\n"
138
+ f" [PASS] Accuracy: {acc:.2f} >= {threshold}\n"
139
+ f" [PASS] Inference time: 45ms <= {max_ms}ms \n"
140
+ f" [PASS] Fairness gap: {gap:.3f} <= {max_gap}\n"
141
+ " Deploying to production."
142
+ )
app/graders/grader_stage3.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ import uuid
8
+ from pathlib import Path
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+
13
+
14
+ FEATURE_COLUMNS = [
15
+ "age",
16
+ "salary",
17
+ "credit_score",
18
+ "loan_amount",
19
+ "employment_years",
20
+ ]
21
+
22
+
23
+ def _detect_fit_after_split(script: str) -> bool:
24
+ lines = script.split('\n')
25
+ # Strip import lines and comments entirely
26
+ code_lines = [
27
+ l for l in lines
28
+ if not l.strip().startswith('import')
29
+ and not l.strip().startswith('from')
30
+ and not l.strip().startswith('#')
31
+ and l.strip() != ''
32
+ ]
33
+ code_only = '\n'.join(code_lines)
34
+
35
+ fit_pos = code_only.find('scaler.fit')
36
+ split_pos = code_only.find('train_test_split(')
37
+
38
+ # Fix is correct only if train_test_split appears BEFORE scaler.fit in code
39
+ if fit_pos == -1 or split_pos == -1:
40
+ return False
41
+ return split_pos < fit_pos
42
+
43
+
44
+ def _parse_marker(output: str) -> float | None:
45
+ matches = re.findall(r"__HELD_OUT_ACCURACY__\s*:\s*([0-9]*\.?[0-9]+)", output)
46
+ if not matches:
47
+ return None
48
+ try:
49
+ return float(matches[-1])
50
+ except ValueError:
51
+ return None
52
+
53
+
54
+ def grade(script: str, full_df: pd.DataFrame, held_out_X, held_out_y) -> float:
55
+ fit_after_split = _detect_fit_after_split(script or "")
56
+
57
+ runs_dir = Path.cwd() / ".grader_runs"
58
+ runs_dir.mkdir(exist_ok=True)
59
+ tmpdir = runs_dir / f"stage3_{uuid.uuid4().hex}"
60
+ tmpdir.mkdir()
61
+
62
+ def _cleanup() -> None:
63
+ shutil.rmtree(tmpdir, ignore_errors=True)
64
+ data_path = tmpdir / "data.csv"
65
+ heldout_path = tmpdir / "held_out.csv"
66
+ script_path = tmpdir / "candidate.py"
67
+
68
+ try:
69
+ full_df.to_csv(data_path, index=False)
70
+ heldout_df = pd.DataFrame(np.asarray(held_out_X), columns=FEATURE_COLUMNS)
71
+ heldout_df["target"] = np.asarray(held_out_y)
72
+ heldout_df.to_csv(heldout_path, index=False)
73
+ except Exception:
74
+ _cleanup()
75
+ return 0.0
76
+
77
+ postlude = f"""
78
+ from sklearn.metrics import accuracy_score as _accuracy_score
79
+ _held_df = pd.read_csv(r'{heldout_path.as_posix()}')
80
+ _X_held_raw = _held_df[{FEATURE_COLUMNS!r}]
81
+ _y_held = _held_df['target']
82
+ _model = globals().get('clf') or globals().get('model') or globals().get('classifier') or globals().get('pipeline')
83
+ if _model is None:
84
+ raise RuntimeError('No trained model found; expected clf, model, classifier, or pipeline.')
85
+ if 'pipeline' in globals() and _model is globals().get('pipeline'):
86
+ _pred = _model.predict(_X_held_raw)
87
+ else:
88
+ _scaler = globals().get('scaler')
89
+ _X_held = _scaler.transform(_X_held_raw) if _scaler is not None else _X_held_raw
90
+ _pred = _model.predict(_X_held)
91
+ print('__HELD_OUT_ACCURACY__:', _accuracy_score(_y_held, _pred))
92
+ """
93
+ code = (
94
+ "import pandas as pd\n"
95
+ f"df = pd.read_csv(r'{data_path.as_posix()}')\n\n"
96
+ + (script or "")
97
+ + "\n"
98
+ + postlude
99
+ )
100
+ script_path.write_text(code, encoding="utf-8")
101
+
102
+ try:
103
+ proc = subprocess.run(
104
+ [sys.executable, str(script_path)],
105
+ capture_output=True,
106
+ text=True,
107
+ timeout=10,
108
+ check=False,
109
+ )
110
+ except subprocess.TimeoutExpired:
111
+ _cleanup()
112
+ return 0.0
113
+ except Exception:
114
+ _cleanup()
115
+ return 0.0
116
+
117
+ _cleanup()
118
+
119
+ if proc.returncode != 0 or (proc.stderr or "").strip():
120
+ return 0.0
121
+
122
+ held_out_accuracy = _parse_marker(proc.stdout or "")
123
+ if held_out_accuracy is None or held_out_accuracy < 0.60:
124
+ return 0.0
125
+
126
+ score = 0.5
127
+ if fit_after_split:
128
+ score = max(score, 0.3)
129
+ if held_out_accuracy >= 0.75:
130
+ score = 1.0
131
+ elif held_out_accuracy >= 0.70:
132
+ score = 0.75
133
+
134
+ return float(max(0.0, min(1.0, score)))
app/graders/grader_stage4.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ import uuid
8
+ from pathlib import Path
9
+
10
+ import pandas as pd
11
+
12
+
13
+ FEATURE_COLUMNS = [
14
+ "age",
15
+ "salary",
16
+ "credit_score",
17
+ "loan_amount",
18
+ "employment_years",
19
+ ]
20
+
21
+
22
+ def _parse_accuracy(output: str) -> float | None:
23
+ matches = re.findall(r"Accuracy\s*:\s*([0-9]*\.?[0-9]+)", output, flags=re.IGNORECASE)
24
+ if not matches:
25
+ return None
26
+ try:
27
+ return float(matches[-1])
28
+ except ValueError:
29
+ return None
30
+
31
+
32
+ def _parse_fairness_gap(output: str) -> float | None:
33
+ matches = re.findall(r"__FAIRNESS_GAP__\s*:\s*([0-9]*\.?[0-9]+)", output)
34
+ if not matches:
35
+ return None
36
+ try:
37
+ return float(matches[-1])
38
+ except ValueError:
39
+ return None
40
+
41
+
42
+ def grade(script: str, dataframe: pd.DataFrame, deploy_conditions: dict) -> float:
43
+ runs_dir = Path.cwd() / ".grader_runs"
44
+ runs_dir.mkdir(exist_ok=True)
45
+ tmpdir = runs_dir / f"stage4_{uuid.uuid4().hex}"
46
+ tmpdir.mkdir()
47
+
48
+ def _cleanup() -> None:
49
+ shutil.rmtree(tmpdir, ignore_errors=True)
50
+ data_path = tmpdir / "data.csv"
51
+ script_path = tmpdir / "candidate.py"
52
+
53
+ try:
54
+ dataframe.to_csv(data_path, index=False)
55
+ except Exception:
56
+ _cleanup()
57
+ return 0.0
58
+
59
+ postlude = f"""
60
+ from sklearn.metrics import accuracy_score as _accuracy_score
61
+ _deploy_df = pd.read_csv(r'{data_path.as_posix()}')
62
+ if 'X_test' in globals() and 'y_test' in globals():
63
+ _X_raw = X_test.copy()
64
+ _y = y_test.copy()
65
+ else:
66
+ _X_raw = _deploy_df[{FEATURE_COLUMNS!r}]
67
+ _y = _deploy_df['target']
68
+ _model = globals().get('clf') or globals().get('model') or globals().get('classifier') or globals().get('pipeline')
69
+ if _model is None:
70
+ raise RuntimeError('No trained model found; expected clf, model, classifier, or pipeline.')
71
+ if 'pipeline' in globals() and _model is globals().get('pipeline'):
72
+ _pred = _model.predict(_X_raw)
73
+ else:
74
+ _scaler = globals().get('scaler')
75
+ _X_eval = _scaler.transform(_X_raw) if _scaler is not None else _X_raw
76
+ _pred = _model.predict(_X_eval)
77
+ _eval_df = _X_raw.copy()
78
+ _eval_df['target'] = _y
79
+ _eval_df['_pred'] = _pred
80
+ _group_a = _eval_df[_eval_df['age'] < 40]
81
+ _group_b = _eval_df[_eval_df['age'] >= 40]
82
+ _acc_a = _accuracy_score(_group_a['target'], _group_a['_pred'])
83
+ _acc_b = _accuracy_score(_group_b['target'], _group_b['_pred'])
84
+ print('__FAIRNESS_GAP__:', abs(_acc_a - _acc_b))
85
+ """
86
+ code = (
87
+ "import pandas as pd\n"
88
+ f"df = pd.read_csv(r'{data_path.as_posix()}')\n\n"
89
+ + (script or "")
90
+ + "\n"
91
+ + postlude
92
+ )
93
+ script_path.write_text(code, encoding="utf-8")
94
+
95
+ try:
96
+ proc = subprocess.run(
97
+ [sys.executable, str(script_path)],
98
+ capture_output=True,
99
+ text=True,
100
+ timeout=10,
101
+ check=False,
102
+ cwd=tmpdir,
103
+ )
104
+ except subprocess.TimeoutExpired:
105
+ _cleanup()
106
+ return 0.0
107
+ except Exception:
108
+ _cleanup()
109
+ return 0.0
110
+
111
+ _cleanup()
112
+
113
+ if proc.returncode != 0 or (proc.stderr or "").strip():
114
+ return 0.0
115
+
116
+ stdout = proc.stdout or ""
117
+ accuracy = _parse_accuracy(stdout)
118
+ fairness_gap = _parse_fairness_gap(stdout)
119
+ if accuracy is None or fairness_gap is None:
120
+ return 0.2
121
+
122
+ inference_ms = 45
123
+ accuracy_pass = accuracy >= deploy_conditions["accuracy_threshold"]
124
+ inference_pass = inference_ms <= deploy_conditions["max_inference_ms"]
125
+ fairness_pass = fairness_gap <= deploy_conditions["max_fairness_gap"]
126
+
127
+ score = 0.2
128
+ if accuracy_pass:
129
+ score = 0.4
130
+ if fairness_pass:
131
+ score = max(score, 0.7)
132
+ if accuracy_pass and inference_pass and fairness_pass:
133
+ score = 1.0
134
+
135
+ return float(max(0.0, min(1.0, score)))
app/tasks/stage3_eval_validation.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+
5
+ FEATURE_COLUMNS = [
6
+ "age",
7
+ "salary",
8
+ "credit_score",
9
+ "loan_amount",
10
+ "employment_years",
11
+ ]
12
+
13
+
14
+ def _make_data(rng: np.random.Generator, n: int) -> pd.DataFrame:
15
+ age = rng.integers(21, 70, size=n).astype(float)
16
+ salary = np.clip(
17
+ rng.normal(72_000, 22_000, size=n) + (age - 40) * 600,
18
+ 25_000,
19
+ 170_000,
20
+ )
21
+ credit_score = np.clip(
22
+ rng.normal(675, 70, size=n) + (salary - 72_000) / 2_500 - (age - 40) * 0.6,
23
+ 350,
24
+ 850,
25
+ )
26
+ loan_amount = np.clip(
27
+ rng.normal(24_000, 9_000, size=n) - (credit_score - 675) * 25 + (age - 40) * 120,
28
+ 3_000,
29
+ 65_000,
30
+ )
31
+ employment_years = np.clip((age - 22) * 0.45 + rng.normal(0, 4, size=n), 0, 42)
32
+
33
+ logits = (
34
+ (credit_score - 665) * 0.012
35
+ + (salary - 72_000) / 35_000
36
+ - (loan_amount - 24_000) / 18_000
37
+ + employment_years * 0.045
38
+ - (age - 42) * 0.018
39
+ + rng.normal(0, 1.15, size=n)
40
+ )
41
+ target = (logits > 0.15).astype(int)
42
+
43
+ return pd.DataFrame(
44
+ {
45
+ "age": age,
46
+ "salary": salary,
47
+ "credit_score": credit_score,
48
+ "loan_amount": loan_amount,
49
+ "employment_years": employment_years,
50
+ "target": target,
51
+ }
52
+ )
53
+
54
+
55
+ def generate_scenario() -> dict:
56
+ rng = np.random.default_rng(42)
57
+ df = _make_data(rng, 1000)
58
+ held_out_df = _make_data(rng, 200)
59
+
60
+ held_out_X = held_out_df[FEATURE_COLUMNS].to_numpy()
61
+ held_out_y = held_out_df["target"].to_numpy()
62
+
63
+ broken_script = (
64
+ "import pandas as pd\n"
65
+ "from sklearn.ensemble import GradientBoostingClassifier\n"
66
+ "from sklearn.model_selection import train_test_split\n"
67
+ "from sklearn.preprocessing import StandardScaler\n"
68
+ "\n"
69
+ "X = df[['age', 'salary', 'credit_score', 'loan_amount', 'employment_years']].copy()\n"
70
+ "y = df['target']\n"
71
+ "\n"
72
+ "scaler = StandardScaler()\n"
73
+ "X_scaled = scaler.fit_transform(X)\n"
74
+ "X_train, X_test, y_train, y_test = train_test_split(\n"
75
+ " X_scaled, y, test_size=0.2, random_state=42, stratify=y\n"
76
+ ")\n"
77
+ "\n"
78
+ "clf = GradientBoostingClassifier(random_state=42)\n"
79
+ "clf.fit(X_train, y_train)\n"
80
+ "print('Accuracy: 0.98')\n"
81
+ )
82
+
83
+ return {
84
+ "broken_script": broken_script,
85
+ "dataframe": df,
86
+ "held_out_X": held_out_X,
87
+ "held_out_y": held_out_y,
88
+ "full_df": df,
89
+ "initial_error_log": "",
90
+ "task_description": (
91
+ "Model accuracy is 0.98. No errors. But something is wrong. "
92
+ "Investigate the evaluation pipeline and fix any logical bugs."
93
+ ),
94
+ "expected_fix_hint": "move scaler.fit() to after train_test_split()",
95
+ "stage_number": 3,
96
+ }
app/tasks/stage4_deploy_gate.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+
5
+ def generate_scenario() -> dict:
6
+ rng = np.random.default_rng(42)
7
+ n = 1000
8
+
9
+ age = np.concatenate(
10
+ [
11
+ rng.integers(22, 40, size=650),
12
+ rng.integers(40, 70, size=350),
13
+ ]
14
+ ).astype(float)
15
+ rng.shuffle(age)
16
+ older_group = (age >= 40).astype(float)
17
+
18
+ salary = np.clip(
19
+ rng.normal(68_000, 18_000, size=n) + (age - 40) * 900,
20
+ 25_000,
21
+ 180_000,
22
+ )
23
+ credit_score = np.clip(
24
+ rng.normal(665, 65, size=n) + (salary - 68_000) / 3_000 - older_group * 40,
25
+ 350,
26
+ 850,
27
+ )
28
+ loan_amount = np.clip(
29
+ rng.normal(23_000, 9_000, size=n) - (credit_score - 665) * 20 + older_group * 3_000,
30
+ 3_000,
31
+ 70_000,
32
+ )
33
+ employment_years = np.clip((age - 22) * 0.45 + rng.normal(0, 4, size=n), 0, 45)
34
+
35
+ logits = (
36
+ (credit_score - 660) * 0.012
37
+ + (salary - 68_000) / 35_000
38
+ - (loan_amount - 23_000) / 18_000
39
+ + employment_years * 0.035
40
+ - older_group * 0.7
41
+ + rng.normal(0, 0.9, size=n)
42
+ )
43
+ target = (logits > 0.3).astype(int)
44
+
45
+ df = pd.DataFrame(
46
+ {
47
+ "age": age,
48
+ "salary": salary,
49
+ "credit_score": credit_score,
50
+ "loan_amount": loan_amount,
51
+ "employment_years": employment_years,
52
+ "target": target,
53
+ }
54
+ )
55
+
56
+ broken_script = (
57
+ "import pandas as pd\n"
58
+ "from sklearn.linear_model import LogisticRegression\n"
59
+ "from sklearn.metrics import accuracy_score\n"
60
+ "from sklearn.model_selection import train_test_split\n"
61
+ "from sklearn.preprocessing import StandardScaler\n"
62
+ "\n"
63
+ "features = ['age', 'salary', 'credit_score', 'loan_amount', 'employment_years']\n"
64
+ "X = df[features].copy()\n"
65
+ "y = df['target']\n"
66
+ "X_train, X_test, y_train, y_test = train_test_split(\n"
67
+ " X, y, test_size=0.25, random_state=42, stratify=y\n"
68
+ ")\n"
69
+ "\n"
70
+ "scaler = StandardScaler()\n"
71
+ "X_train_scaled = scaler.fit_transform(X_train)\n"
72
+ "X_test_scaled = scaler.transform(X_test)\n"
73
+ "\n"
74
+ "clf = LogisticRegression(max_iter=1000, random_state=42)\n"
75
+ "clf.fit(X_train_scaled, y_train)\n"
76
+ "pred = clf.predict(X_test_scaled)\n"
77
+ "print('Accuracy:', round(accuracy_score(y_test, pred), 2))\n"
78
+ )
79
+
80
+ return {
81
+ "broken_script": broken_script,
82
+ "dataframe": df,
83
+ "deploy_conditions": {
84
+ "accuracy_threshold": 0.75,
85
+ "max_inference_ms": 100,
86
+ "max_fairness_gap": 0.05,
87
+ },
88
+ "initial_error_log": (
89
+ "DEPLOY GATE CHECK:\n"
90
+ "[PASS] Accuracy: 0.77 >= 0.75\n"
91
+ "[PASS] Inference time: 45ms <= 100ms\n"
92
+ "[FAIL] Fairness gap: 0.09 > 0.05 (age groups differ significantly)\n"
93
+ "STATUS: REJECTED \u2014 fix fairness issue before deployment"
94
+ ),
95
+ "task_description": (
96
+ "Deployment rejected. Fix the fairness issue. "
97
+ "The model performs significantly worse on one age group. "
98
+ "All 3 conditions must pass for deployment approval."
99
+ ),
100
+ "expected_fix_hint": (
101
+ "add class_weight='balanced' to classifier OR "
102
+ "add age-group stratified sampling in train_test_split"
103
+ ),
104
+ "stage_number": 4,
105
+ }