aimodelshare 0.3.7__py3-none-any.whl → 0.3.94__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.
Files changed (36) hide show
  1. aimodelshare/moral_compass/__init__.py +51 -2
  2. aimodelshare/moral_compass/api_client.py +92 -4
  3. aimodelshare/moral_compass/apps/__init__.py +36 -16
  4. aimodelshare/moral_compass/apps/ai_consequences.py +98 -88
  5. aimodelshare/moral_compass/apps/bias_detective_ca.py +2722 -0
  6. aimodelshare/moral_compass/apps/bias_detective_en.py +2722 -0
  7. aimodelshare/moral_compass/apps/bias_detective_part1.py +2722 -0
  8. aimodelshare/moral_compass/apps/bias_detective_part2.py +2465 -0
  9. aimodelshare/moral_compass/apps/bias_detective_part_es.py +2722 -0
  10. aimodelshare/moral_compass/apps/ethical_revelation.py +237 -147
  11. aimodelshare/moral_compass/apps/fairness_fixer.py +1839 -859
  12. aimodelshare/moral_compass/apps/fairness_fixer_ca.py +1869 -0
  13. aimodelshare/moral_compass/apps/fairness_fixer_en.py +1869 -0
  14. aimodelshare/moral_compass/apps/fairness_fixer_es.py +1869 -0
  15. aimodelshare/moral_compass/apps/judge.py +130 -143
  16. aimodelshare/moral_compass/apps/justice_equity_upgrade.py +793 -831
  17. aimodelshare/moral_compass/apps/justice_equity_upgrade_ca.py +815 -0
  18. aimodelshare/moral_compass/apps/justice_equity_upgrade_en.py +815 -0
  19. aimodelshare/moral_compass/apps/justice_equity_upgrade_es.py +815 -0
  20. aimodelshare/moral_compass/apps/mc_integration_helpers.py +227 -745
  21. aimodelshare/moral_compass/apps/model_building_app_ca.py +4399 -0
  22. aimodelshare/moral_compass/apps/model_building_app_ca_final.py +3899 -0
  23. aimodelshare/moral_compass/apps/model_building_app_en.py +4167 -0
  24. aimodelshare/moral_compass/apps/model_building_app_en_final.py +3869 -0
  25. aimodelshare/moral_compass/apps/model_building_app_es.py +4351 -0
  26. aimodelshare/moral_compass/apps/model_building_app_es_final.py +3899 -0
  27. aimodelshare/moral_compass/apps/model_building_game.py +4211 -935
  28. aimodelshare/moral_compass/apps/moral_compass_challenge.py +195 -95
  29. aimodelshare/moral_compass/apps/what_is_ai.py +126 -117
  30. aimodelshare/moral_compass/challenge.py +98 -17
  31. {aimodelshare-0.3.7.dist-info → aimodelshare-0.3.94.dist-info}/METADATA +1 -1
  32. {aimodelshare-0.3.7.dist-info → aimodelshare-0.3.94.dist-info}/RECORD +35 -19
  33. aimodelshare/moral_compass/apps/bias_detective.py +0 -714
  34. {aimodelshare-0.3.7.dist-info → aimodelshare-0.3.94.dist-info}/WHEEL +0 -0
  35. {aimodelshare-0.3.7.dist-info → aimodelshare-0.3.94.dist-info}/licenses/LICENSE +0 -0
  36. {aimodelshare-0.3.7.dist-info → aimodelshare-0.3.94.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,2722 @@
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ import time
5
+ from typing import Tuple, Optional, List
6
+
7
+ # --- 1. CONFIGURATION ---
8
+ DEFAULT_API_URL = "https://b22q73wp50.execute-api.us-east-1.amazonaws.com/dev"
9
+ ORIGINAL_PLAYGROUND_URL = "https://cf3wdpkg0d.execute-api.us-east-1.amazonaws.com/prod/m"
10
+ TABLE_ID = "m-mc"
11
+ TOTAL_COURSE_TASKS = 20 # Score calculated against full course
12
+ LOCAL_TEST_SESSION_ID = None
13
+
14
+
15
+ # --- 2. SETUP & DEPENDENCIES ---
16
+ def install_dependencies():
17
+ packages = ["gradio>=5.0.0", "aimodelshare", "pandas"]
18
+ for package in packages:
19
+ subprocess.check_call([sys.executable, "-m", "pip", "install", package])
20
+
21
+
22
+ try:
23
+ import gradio as gr
24
+ import pandas as pd
25
+ from aimodelshare.playground import Competition
26
+ from aimodelshare.moral_compass import MoralcompassApiClient
27
+ from aimodelshare.aws import get_token_from_session, _get_username_from_token
28
+ except ImportError:
29
+ print("📦 Installing dependencies...")
30
+ install_dependencies()
31
+ import gradio as gr
32
+ import pandas as pd
33
+ from aimodelshare.playground import Competition
34
+ from aimodelshare.moral_compass import MoralcompassApiClient
35
+ from aimodelshare.aws import get_token_from_session, _get_username_from_token
36
+
37
+ # --- 3. AUTH & HISTORY HELPERS ---
38
+ def _try_session_based_auth(request: "gr.Request") -> Tuple[bool, Optional[str], Optional[str]]:
39
+ try:
40
+ session_id = request.query_params.get("sessionid") if request else None
41
+ if not session_id and LOCAL_TEST_SESSION_ID:
42
+ session_id = LOCAL_TEST_SESSION_ID
43
+ if not session_id:
44
+ return False, None, None
45
+ token = get_token_from_session(session_id)
46
+ if not token:
47
+ return False, None, None
48
+ username = _get_username_from_token(token)
49
+ if not username:
50
+ return False, None, None
51
+ return True, username, token
52
+ except Exception:
53
+ return False, None, None
54
+
55
+
56
+ def fetch_user_history(username, token):
57
+ default_acc = 0.0
58
+ default_team = "Team-Unassigned"
59
+ try:
60
+ playground = Competition(ORIGINAL_PLAYGROUND_URL)
61
+ df = playground.get_leaderboard(token=token)
62
+ if df is None or df.empty:
63
+ return default_acc, default_team
64
+ if "username" in df.columns and "accuracy" in df.columns:
65
+ user_rows = df[df["username"] == username]
66
+ if not user_rows.empty:
67
+ best_acc = user_rows["accuracy"].max()
68
+ if "timestamp" in user_rows.columns and "Team" in user_rows.columns:
69
+ try:
70
+ user_rows = user_rows.copy()
71
+ user_rows["timestamp"] = pd.to_datetime(
72
+ user_rows["timestamp"], errors="coerce"
73
+ )
74
+ user_rows = user_rows.sort_values("timestamp", ascending=False)
75
+ found_team = user_rows.iloc[0]["Team"]
76
+ if pd.notna(found_team) and str(found_team).strip():
77
+ default_team = str(found_team).strip()
78
+ except Exception:
79
+ pass
80
+ return float(best_acc), default_team
81
+ except Exception:
82
+ pass
83
+ return default_acc, default_team
84
+
85
+ # --- 4. MODULE DEFINITIONS (APP 1: 0-10) ---
86
+ MODULES = [
87
+ # --- MODULE 0: THE HOOK (Mission Dossier) ---
88
+ {
89
+ "id": 0,
90
+ "title": "Mission Dossier",
91
+ "html": """
92
+ <div class="scenario-box">
93
+ <div class="slide-body">
94
+ <h2 class="slide-title" style="margin-bottom:25px; text-align:center; font-size: 2.2rem;">🕵️ MISSION DOSSIER</h2>
95
+
96
+ <div style="display:grid; grid-template-columns: 1fr 1fr; gap:25px; margin-bottom:30px; align-items:stretch;">
97
+ <div style="background:var(--background-fill-secondary); padding:20px; border-radius:12px; border:1px solid var(--border-color-primary);">
98
+ <div style="margin-bottom:15px;">
99
+ <div style="font-size:0.9rem; font-weight:800; color:var(--body-text-color-subdued); letter-spacing:1px;">YOUR ROLE</div>
100
+ <div style="font-size:1.3rem; font-weight:700; color:var(--color-accent);">Lead Bias Detective</div>
101
+ </div>
102
+ <div>
103
+ <div style="font-size:0.9rem; font-weight:800; color:var(--body-text-color-subdued); letter-spacing:1px;">YOUR TARGET</div>
104
+ <div style="font-size:1.3rem; font-weight:700;">"Compas" AI Algorithm</div>
105
+ <div style="font-size:1.0rem; margin-top:5px; opacity:0.8;">Used by judges to decide bail.</div>
106
+ </div>
107
+ </div>
108
+ <div style="background:rgba(239,68,68,0.08); padding:20px; border-radius:12px; border:2px solid #fca5a5; display:flex; flex-direction:column; justify-content:center;">
109
+ <div style="font-size:0.9rem; font-weight:800; color:#b91c1c; letter-spacing:1px;">🚨 THE THREAT</div>
110
+ <div style="font-size:1.15rem; font-weight:600; line-height:1.4; color:#7f1d1d;">
111
+ The model is 92% accurate, but we suspect a <strong>hidden systematic bias</strong>.
112
+ <br><br>
113
+ Your goal: Expose flaws before this model is deployed nationwide.
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ <hr style="opacity:0.2; margin:25px 0;">
119
+
120
+ <p style="text-align:center; font-weight:800; color:var(--body-text-color-subdued); margin-bottom:20px; font-size:1.0rem; letter-spacing:1px;">
121
+ 👇 CLICK CARDS TO UNLOCK INTEL
122
+ </p>
123
+
124
+ <div style="display:grid; gap:20px;">
125
+ <details class="evidence-card" style="background:white; border:2px solid #e5e7eb; border-left: 6px solid #ef4444; padding:0; border-radius:8px; overflow:hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.05);">
126
+ <summary style="padding:20px; font-weight:800; font-size:1.2rem; color:#1f2937; cursor:pointer; list-style:none; display:flex; align-items:center; justify-content:space-between; background:rgba(254,242,242,0.5);">
127
+ <div style="display:flex; align-items:center; gap:15px;">
128
+ <span style="font-size:1.8rem;">⚠️</span>
129
+ <span>RISK: The "Ripple Effect"</span>
130
+ </div>
131
+ <span style="font-size:0.9rem; color:#ef4444; text-transform:uppercase;">Click to Simulate</span>
132
+ </summary>
133
+ <div style="padding:25px; border-top:1px solid #e5e7eb;">
134
+ <div style="display:flex; gap:30px; align-items:center;">
135
+ <div style="font-size:3.5rem; line-height:1;">🌊</div>
136
+ <div>
137
+ <div style="font-weight:900; font-size:2.0rem; color:#ef4444; line-height:1;">15,000+</div>
138
+ <div style="font-weight:700; font-size:1.1rem; color:#374151; margin-bottom:5px;">Cases Processed Per Year</div>
139
+ <div style="font-size:1.1rem; color:#4b5563; line-height:1.5;">
140
+ A human makes a mistake once. This AI will repeat the same bias <strong>15,000+ times a year</strong>.
141
+ <br>If we don't fix it, we automate unfairness at a massive scale.
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </details>
147
+
148
+ <details class="evidence-card" style="background:white; border:2px solid #e5e7eb; border-left: 6px solid #22c55e; padding:0; border-radius:8px; overflow:hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.05);">
149
+ <summary style="padding:20px; font-weight:800; font-size:1.2rem; color:#1f2937; cursor:pointer; list-style:none; display:flex; align-items:center; justify-content:space-between; background:rgba(240,253,244,0.5);">
150
+ <div style="display:flex; align-items:center; gap:15px;">
151
+ <span style="font-size:1.8rem;">🧭</span>
152
+ <span>OBJECTIVE: How to Win</span>
153
+ </div>
154
+ <span style="font-size:0.9rem; color:#15803d; text-transform:uppercase;">Click to Calculate</span>
155
+ </summary>
156
+ <div style="padding:25px; border-top:1px solid #e5e7eb;">
157
+ <div style="text-align:center; margin-bottom:20px;">
158
+ <div style="font-size:1.4rem; font-weight:800; background:#f3f4f6; padding:15px; border-radius:10px; display:inline-block;">
159
+ <span style="color:#6366f1;">[ Accuracy ]</span>
160
+ <span style="color:#9ca3af; margin:0 10px;">×</span>
161
+ <span style="color:#22c55e;">[ Ethical Progress % ]</span>
162
+ <span style="color:#9ca3af; margin:0 10px;">=</span>
163
+ SCORE
164
+ </div>
165
+ </div>
166
+ <div style="display:grid; grid-template-columns: 1fr 1fr; gap:20px;">
167
+ <div style="padding:15px; background:#fef2f2; border:2px solid #fecaca; border-radius:10px; text-align:center;">
168
+ <div style="font-weight:700; color:#b91c1c; margin-bottom:5px;">Scenario A: Ignored Ethics</div>
169
+ <div style="font-size:0.95rem;">High Accuracy (95%)</div>
170
+ <div style="font-size:0.95rem;">0% Ethics</div>
171
+ <div style="margin-top:10px; border-top:1px solid #fecaca; padding-top:5px;">
172
+ <div style="font-size:0.8rem; text-transform:uppercase; color:#7f1d1d;">Final Score</div>
173
+ <div style="font-size:2.5rem; font-weight:900; color:#ef4444;">0</div>
174
+ </div>
175
+ </div>
176
+ <div style="padding:15px; background:#f0fdf4; border:2px solid #bbf7d0; border-radius:10px; text-align:center;">
177
+ <div style="font-weight:700; color:#15803d; margin-bottom:5px;">Scenario B: True Detective</div>
178
+ <div style="font-size:0.95rem;">Good Accuracy (92%)</div>
179
+ <div style="font-size:0.95rem;">100% Ethics</div>
180
+ <div style="margin-top:10px; border-top:1px solid #bbf7d0; padding-top:5px;">
181
+ <div style="font-size:0.8rem; text-transform:uppercase; color:#14532d;">Final Score</div>
182
+ <div style="font-size:2.5rem; font-weight:900; color:#22c55e;">92</div>
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ </details>
188
+ </div>
189
+
190
+ <div style="text-align:center; margin-top:35px; padding:20px; background:linear-gradient(to right, rgba(99,102,241,0.1), rgba(16,185,129,0.1)); border-radius:12px; border:2px solid var(--color-accent);">
191
+ <p style="font-size:1.15rem; font-weight:800; color:var(--color-accent); margin-bottom:5px;">
192
+ 🚀 MISSION START
193
+ </p>
194
+ <p style="font-size:1.05rem; margin:0;">
195
+ Answer the question below to receive your first <strong>Moral Compass Score boost</strong>.
196
+ <br>Then click <strong>Next</strong> to start the investigation.
197
+ </p>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ """,
202
+ },
203
+
204
+ # --- MODULE 1: THE MAP (Mission Roadmap) ---
205
+ {
206
+ "id": 1,
207
+ "title": "Mission Roadmap",
208
+ "html": """
209
+ <div class="scenario-box">
210
+ <div class="slide-body">
211
+
212
+ <h2 class="slide-title" style="text-align:center; margin-bottom:15px;">🗺️ MISSION ROADMAP</h2>
213
+
214
+ <p style="font-size:1.1rem; max-width:800px; margin:0 auto 25px auto; text-align:center;">
215
+ <strong>Your mission is clear:</strong> Uncover the bias hiding inside the
216
+ AI system before it hurts real people. If you cannot find bias, we cannot fix it.
217
+ </p>
218
+
219
+ <div class="ai-risk-container" style="background:white; border:none; padding:0;">
220
+
221
+ <div style="display:grid; grid-template-columns: 1fr 1fr; gap:20px;">
222
+
223
+ <div style="border: 3px solid #3b82f6; background: #eff6ff; border-radius: 12px; padding: 20px; position: relative; box-shadow: 0 4px 12px rgba(59,130,246,0.25);">
224
+ <div style="position:absolute; top:-15px; left:15px; background:#3b82f6; color:white; padding:4px 16px; border-radius:20px; font-weight:800; font-size:0.9rem; letter-spacing:1px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">STEP 1: RULES</div>
225
+ <div style="font-size:3rem; margin-top:10px; margin-bottom:5px;">📜</div>
226
+ <div style="font-weight:800; font-size:1.2rem; color:#1e3a8a; margin-bottom:5px;">Establish the Rules</div>
227
+ <div style="font-size:1.0rem; color:#1e40af; font-weight:500; line-height:1.4;">
228
+ Define the ethical standard: <strong>Justice & Equity</strong>. What specifically counts as bias in this investigation?
229
+ </div>
230
+ </div>
231
+
232
+ <div style="border: 3px solid #14b8a6; background: #f0fdfa; border-radius: 12px; padding: 20px; position: relative; box-shadow: 0 4px 10px rgba(20, 184, 166, 0.15);">
233
+ <div style="position:absolute; top:-15px; left:15px; background:#14b8a6; color:white; padding:4px 16px; border-radius:20px; font-weight:800; font-size:0.9rem; letter-spacing:1px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">STEP 2: DATA EVIDENCE</div>
234
+ <div style="font-size:3rem; margin-top:10px; margin-bottom:5px;">🔍</div>
235
+ <div style="font-weight:800; font-size:1.2rem; color:#134e4a; margin-bottom:5px;">Input Data Forensics</div>
236
+ <div style="font-size:1.0rem; color:#0f766e; font-weight:500; line-height:1.4;">
237
+ Scan the <strong>Input Data</strong> for historical injustice, representation gaps, and exclusion bias.
238
+ </div>
239
+ </div>
240
+
241
+ <div style="border: 3px solid #8b5cf6; background: #f5f3ff; border-radius: 12px; padding: 20px; position: relative; box-shadow: 0 4px 10px rgba(139, 92, 246, 0.15);">
242
+ <div style="position:absolute; top:-15px; left:15px; background:#8b5cf6; color:white; padding:4px 16px; border-radius:20px; font-weight:800; font-size:0.9rem; letter-spacing:1px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">STEP 3: TEST ERROR</div>
243
+ <div style="font-size:3rem; margin-top:10px; margin-bottom:5px;">🎯</div>
244
+ <div style="font-weight:800; font-size:1.2rem; color:#4c1d95; margin-bottom:5px;">Output Error Testing</div>
245
+ <div style="font-size:1.0rem; color:#6d28d9; font-weight:500; line-height:1.4;">
246
+ Test the Model's predictions. Prove that mistakes (False Alarms) are <strong>unequal</strong> across groups.
247
+ </div>
248
+ </div>
249
+
250
+ <div style="border: 3px solid #f97316; background: #fff7ed; border-radius: 12px; padding: 20px; position: relative; box-shadow: 0 4px 10px rgba(249, 115, 22, 0.15);">
251
+ <div style="position:absolute; top:-15px; left:15px; background:#f97316; color:white; padding:4px 16px; border-radius:20px; font-weight:800; font-size:0.9rem; letter-spacing:1px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">STEP 4: REPORT IMPACT</div>
252
+ <div style="font-size:3rem; margin-top:10px; margin-bottom:5px;">⚖️</div>
253
+ <div style="font-weight:800; font-size:1.2rem; color:#7c2d12; margin-bottom:5px;">The Final Report</div>
254
+ <div style="font-size:1.0rem; color:#c2410c; font-weight:500; line-height:1.4;">
255
+ Diagnose systematic harm and issue your final recommendation to the court: <strong>Deploy AI System or Pause to Repair.</strong>
256
+ </div>
257
+ </div>
258
+
259
+ </div>
260
+ </div>
261
+
262
+
263
+ <div style="text-align:center; margin-top:35px; padding:20px; background:linear-gradient(to right, rgba(99,102,241,0.1), rgba(16,185,129,0.1)); border-radius:12px; border:2px solid var(--color-accent);">
264
+ <p style="font-size:1.15rem; font-weight:800; color:var(--color-accent); margin-bottom:5px;">
265
+ 🚀 CONTINUE MISSION
266
+ </p>
267
+ <p style="font-size:1.05rem; margin:0;">
268
+ Answer the question below to receive your next <strong>Moral Compass Score boost</strong>.
269
+ <br>Then click <strong>Next</strong> to continue the investigation.
270
+ </p>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ """,
275
+ },
276
+
277
+ # --- MODULE 2: RULES (Interactive) ---
278
+ {
279
+ "id": 2,
280
+ "title": "Step 1: Learn the Rules",
281
+ "html": """
282
+ <div class="scenario-box">
283
+ <div class="tracker-container">
284
+ <div class="tracker-step active">1. RULES</div>
285
+ <div class="tracker-step">2. EVIDENCE</div>
286
+ <div class="tracker-step">3. ERROR</div>
287
+ <div class="tracker-step">4. VERDICT</div>
288
+ </div>
289
+
290
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;">
291
+ <h2 class="slide-title" style="margin:0;">STEP 1: LEARN THE RULES</h2>
292
+ <div style="font-size:2rem;">⚖️</div>
293
+ </div>
294
+
295
+ <div class="slide-body">
296
+
297
+ <div style="background:#eff6ff; border-left:4px solid #3b82f6; padding:15px; margin-bottom:20px; border-radius:4px;">
298
+ <p style="margin:0; font-size:1.05rem; line-height:1.5;">
299
+ <strong>Justice & Equity: Your Primary Rule.</strong><br>
300
+ Ethics isn't abstract here—it’s our field guide for action. We rely on expert advice from the Catalan Observatory for Ethics in AI <strong>OEIAC (UdG)</strong> to ensure AI systems are fair.
301
+ While they have defined 7 core principles of safe AI, our intel suggests this specific case involves a violation of <strong>Justice and Equity</strong>.
302
+ </p>
303
+ </div>
304
+
305
+ <div style="text-align:center; margin-bottom:20px;">
306
+ <p style="font-size:1rem; font-weight:700; color:#2563eb; background:#eff6ff; display:inline-block; padding:6px 16px; border-radius:20px; border:1px solid #bfdbfe;">
307
+ 👇 Click on each card below to reveal what counts as bias
308
+ </p>
309
+ </div>
310
+
311
+ <p style="text-align:center; font-weight:700; color:var(--body-text-color-subdued); margin-bottom:10px; font-size:0.9rem; letter-spacing:1px;">
312
+ 🧩 JUSTICE & EQUITY: WHAT COUNTS AS BIAS?
313
+ </p>
314
+
315
+ <div class="ai-risk-container" style="background:#f8fafc; border:none; padding:0;">
316
+ <div style="display:grid; grid-template-columns:repeat(3, 1fr); gap:15px;">
317
+
318
+ <details style="cursor:pointer; background:white; padding:15px; border-radius:10px; border:1px solid #bfdbfe; box-shadow:0 2px 5px rgba(0,0,0,0.05);">
319
+ <summary style="list-style:none; font-weight:800; color:#2563eb; text-align:center; font-size:1.0rem;">
320
+ <div style="font-size:2rem; margin-bottom:5px;">📊</div>
321
+ Representation Bias
322
+ </summary>
323
+ <div style="margin-top:12px; font-size:0.95rem; color:#334155; border-top:1px solid #e2e8f0; padding-top:10px; line-height:1.4;">
324
+ <strong>Definition:</strong> Compares the dataset distribution to the actual real-world distribution.
325
+ <br><br>
326
+ If one group appears far less (e.g., only 10% of cases are Group A, but they are 71% of the population) or far more than reality, the AI likely learns biased patterns.
327
+ </div>
328
+ </details>
329
+
330
+ <details style="cursor:pointer; background:white; padding:15px; border-radius:10px; border:1px solid #fca5a5; box-shadow:0 2px 5px rgba(0,0,0,0.05);">
331
+ <summary style="list-style:none; font-weight:800; color:#dc2626; text-align:center; font-size:1.0rem;">
332
+ <div style="font-size:2rem; margin-bottom:5px;">🎯</div>
333
+ Error Gaps
334
+ </summary>
335
+ <div style="margin-top:12px; font-size:0.95rem; color:#334155; border-top:1px solid #e2e8f0; padding-top:10px; line-height:1.4;">
336
+ <strong>Definition:</strong> Checks for AI prediction mistakes by subgroup (e.g., False Positive Rate for Group A vs. Group B).
337
+ <br><br>
338
+ Higher error for a group indicates risk for unfair treatment, showing the model may be less trustworthy for that specific group.
339
+ </div>
340
+ </details>
341
+
342
+ <details style="cursor:pointer; background:white; padding:15px; border-radius:10px; border:1px solid #bbf7d0; box-shadow:0 2px 5px rgba(0,0,0,0.05);">
343
+ <summary style="list-style:none; font-weight:800; color:#16a34a; text-align:center; font-size:1.0rem;">
344
+ <div style="font-size:2rem; margin-bottom:5px;">⛓️</div>
345
+ Outcome Disparities
346
+ </summary>
347
+ <div style="margin-top:12px; font-size:0.95rem; color:#334155; border-top:1px solid #e2e8f0; padding-top:10px; line-height:1.4;">
348
+ <strong>Definition:</strong> Looks for worse real-world results after AI predictions (e.g., harsher sentencing).
349
+ <br><br>
350
+ Bias isn’t just numbers—it changes real-world outcomes for people.
351
+ </div>
352
+ </details>
353
+ </div>
354
+ </div>
355
+
356
+ <hr style="opacity:0.2; margin:25px 0;">
357
+
358
+ <details class="hint-box" style="margin-top:0; cursor:pointer;">
359
+ <summary style="font-weight:700; color:#64748b;">🧭 Reference: Other AI Ethics Principles (OEIAC)</summary>
360
+ <div style="margin-top:15px; font-size:0.9rem; display:grid; grid-template-columns: 1fr 1fr; gap:15px;">
361
+ <div>
362
+ <strong>Transparency &amp; Explainability</strong><br>Ensure the AI's reasoning and final judgment are clear so decisions can be inspected and people can appeal.<br>
363
+ <strong>Security &amp; Non-maleficence</strong><br>Minimize harmful mistakes and always have a solid plan for system failure.<br>
364
+ <strong>Responsibility &amp; Accountability</strong><br>Assign clear owners for the AI and maintain a detailed record of decisions (audit trail).
365
+ </div>
366
+ <div>
367
+ <strong>Autonomy</strong><br>Provide individuals with clear appeals processes and alternatives to the AI's decision.<br>
368
+ <strong>Privacy</strong><br>Use only necessary data and always justify any need to use sensitive attributes.<br>
369
+ <strong>Sustainability</strong><br>Avoid long-term harm to society or the environment (e.g., massive energy use or market destabilization).
370
+ </div>
371
+ </div>
372
+ </details>
373
+
374
+
375
+ <div style="text-align:center; margin-top:35px; padding:20px; background:linear-gradient(to right, rgba(99,102,241,0.1), rgba(16,185,129,0.1)); border-radius:12px; border:2px solid var(--color-accent);">
376
+ <p style="font-size:1.15rem; font-weight:800; color:var(--color-accent); margin-bottom:5px;">
377
+ 🚀 RULES BRIEFING COMPLETE: CONTINUE MISSION
378
+ </p>
379
+ <p style="font-size:1.05rem; margin:0;">
380
+ Answer the question below to receive your next <strong>Moral Compass Score boost</strong>.
381
+ <br>Then click <strong>Next</strong> to continue your mission.
382
+ </p>
383
+ </div>
384
+ </div>
385
+ </div>
386
+ """
387
+ },
388
+
389
+ {
390
+ "id": 3,
391
+ "title": "Step 2: Pattern Recognition",
392
+ "html": """
393
+ <div class="scenario-box">
394
+ <div class="tracker-container">
395
+ <div class="tracker-step completed">1. RULES</div>
396
+ <div class="tracker-step active">2. EVIDENCE</div>
397
+ <div class="tracker-step">3. ERROR</div>
398
+ <div class="tracker-step">4. VERDICT</div>
399
+ </div>
400
+
401
+ <div class="slide-body">
402
+ <h2 class="slide-title" style="margin:0;">STEP 2: SEARCH FOR THE EVIDENCE</h2>
403
+
404
+ <div style="text-align:center; margin-bottom:20px;">
405
+
406
+ <h2 class="slide-title" style="margin-top:10px; color:#0c4a6e;">The Hunt for Biased Demographic Patterns</h2>
407
+ <p style="font-size:1.1rem; max-width:820px; margin:0 auto; color:#334155;">
408
+ An AI is only as fair as the data it learns from. If the input data distorts reality, the AI will likely distort justice.
409
+ <br>The first step is to hunt for patterns that reveal <strong>Representation Bias.</strong> To find representation bias we must inspect the <strong>Demographics.</strong>.
410
+ </p>
411
+ </div>
412
+
413
+ <div style="background:white; border:2px solid #e2e8f0; border-radius:16px; padding:25px; margin-bottom:20px; box-shadow: 0 4px 15px rgba(0,0,0,0.05);">
414
+
415
+ <div style="display:flex; align-items:center; gap:10px; margin-bottom:15px; border-bottom:1px solid #f1f5f9; padding-bottom:10px;">
416
+ <div style="font-size:1.5rem;">🚩</div>
417
+ <div>
418
+ <strong style="color:#0ea5e9; font-size:1.1rem; text-transform:uppercase; letter-spacing:1px;">PATTERN: "THE DISTORTED MIRROR"</strong>
419
+ <div style="font-size:0.9rem; color:#64748b;">(Representation Bias in Protected Groups)</div>
420
+ </div>
421
+ </div>
422
+
423
+ <div style="display:grid; grid-template-columns: 1fr 1fr; gap:30px;">
424
+
425
+ <div>
426
+ <p style="font-size:1rem; color:#334155; line-height:1.6;">
427
+ <strong>The Concept:</strong> Ideally, a dataset should look like a "Mirror" of the real population.
428
+ If a group makes up 50% of the population, they should generally make up ~50% of the data.
429
+ </p>
430
+ <p style="font-size:1rem; color:#334155; line-height:1.6;">
431
+ <strong>The Red Flag:</strong> Look for <strong>Drastic Imbalances</strong> in Protected Characteristics (Race, Gender, Age).
432
+ </p>
433
+ <ul style="font-size:0.95rem; color:#475569; margin-top:10px; padding-left:20px; line-height:1.5;">
434
+ <li><strong>Over-Representation:</strong> One group has a "Giant Bar" (e.g., 80% of arrest records are Men). The AI learns to target this group.</li>
435
+ <li><strong>Under-Representation:</strong> One group is missing or tiny. The AI fails to learn accurate patterns for them.</li>
436
+ </ul>
437
+ </div>
438
+
439
+ <div style="background:#f8fafc; padding:20px; border-radius:12px; border:1px solid #e2e8f0; display:flex; flex-direction:column; justify-content:center;">
440
+
441
+ <div style="margin-bottom:20px;">
442
+ <div style="font-size:0.85rem; font-weight:700; color:#64748b; margin-bottom:5px;">REALITY (The Population)</div>
443
+ <div style="display:flex; width:100%; height:24px; border-radius:4px; overflow:hidden;">
444
+ <div style="width:33%; background:#94a3b8; display:flex; align-items:center; justify-content:center; color:white; font-size:0.75rem;">Group A</div>
445
+ <div style="width:34%; background:#64748b; display:flex; align-items:center; justify-content:center; color:white; font-size:0.75rem;">Group B</div>
446
+ <div style="width:33%; background:#475569; display:flex; align-items:center; justify-content:center; color:white; font-size:0.75rem;">Group C</div>
447
+ </div>
448
+ </div>
449
+
450
+ <div>
451
+ <div style="font-size:0.85rem; font-weight:700; color:#0c4a6e; margin-bottom:5px;">THE TRAINING DATA (The Distorted Mirror)</div>
452
+ <div style="display:flex; width:100%; height:24px; border-radius:4px; overflow:hidden;">
453
+ <div style="width:80%; background:linear-gradient(90deg, #f43f5e, #be123c); display:flex; align-items:center; justify-content:center; color:white; font-size:0.75rem; font-weight:700;">GROUP A (80%)</div>
454
+ <div style="width:10%; background:#e2e8f0;"></div>
455
+ <div style="width:10%; background:#cbd5e1;"></div>
456
+ </div>
457
+ <div style="font-size:0.8rem; color:#be123c; margin-top:5px; font-weight:600;">
458
+ ⚠️ Alert: Group A is massively over-represented.
459
+ </div>
460
+ </div>
461
+
462
+ </div>
463
+ </div>
464
+ </div>
465
+
466
+ <div style="margin-bottom: 25px; padding: 0 10px;">
467
+ <p style="font-size:1.1rem; color:#1e293b; line-height:1.5;">
468
+ <strong>🕵️ Your Next Step:</strong> You must enter the Data Forensics Lab and check the data for specific demographic categories. If the patterns look like the "Distorted Mirror" above, the data is likely unsafe.
469
+ </p>
470
+ </div>
471
+
472
+ <details style="margin-bottom:30px; cursor:pointer; background:#f8fafc; border:1px solid #e2e8f0; border-radius:8px; padding:12px;">
473
+ <summary style="font-weight:700; color:#64748b; font-size:0.95rem;">🧭 Reference: How do AI datasets become biased?</summary>
474
+ <div style="margin-top:12px; font-size:0.95rem; color:#475569; line-height:1.5; padding:0 5px;">
475
+ <p style="margin-bottom:10px;"><strong>Example:</strong> When a dataset is built from <strong>historical arrest records</strong>.</p>
476
+ <p>Systemic over-policing in specific neighborhoods could distort the counts in the dataset for attributes like <strong>Race or Income</strong>.
477
+ The AI then learns this distortion as "truth."</p>
478
+ </div>
479
+ </details>
480
+
481
+ <div style="text-align:center; margin-top:35px; padding:20px; background:linear-gradient(to right, rgba(99,102,241,0.1), rgba(16,185,129,0.1)); border-radius:12px; border:2px solid var(--color-accent);">
482
+ <p style="font-size:1.15rem; font-weight:800; color:var(--color-accent); margin-bottom:5px;">
483
+ 🚀 EVIDENCE PATTERNS ESTABLISHED: CONTINUE MISSION
484
+ </p>
485
+ <p style="font-size:1.05rem; margin:0;">
486
+ Answer the question below to receive your next <strong>Moral Compass Score boost</strong>.
487
+ <br>Then click <strong>Next</strong> to begin <strong>analyzing evidence in the Data Forensics Lab.</strong>
488
+ </p>
489
+ </div>
490
+ </div>
491
+ </div>
492
+ """
493
+ },
494
+
495
+ # --- MODULE 4: DATA FORENSICS LAB (The Action) ---
496
+ {
497
+ "id": 4, # Re-indexed from 3
498
+ "title": "Step 2: Data Forensics Lab",
499
+ "html": """
500
+ <div class="scenario-box">
501
+ <div class="tracker-container">
502
+ <div class="tracker-step completed">1. RULES</div>
503
+ <div class="tracker-step active">2. EVIDENCE</div>
504
+ <div class="tracker-step">3. ERROR</div>
505
+ <div class="tracker-step">4. VERDICT</div>
506
+ </div>
507
+
508
+ <h2 class="slide-title" style="margin:0;">STEP 2: SEARCH FOR THE EVIDENCE</h2>
509
+
510
+ <div style="text-align:center; margin-bottom:20px;">
511
+
512
+ <h2 class="slide-title" style="margin-top:10px; color:#0c4a6e;">The Data Forensics Lab</h2>
513
+ <div class="slide-body">
514
+
515
+ <p style="text-align:center; max-width:700px; margin:0 auto 15px auto; font-size:1.1rem;">
516
+ Search for evidence of Representation Bias.
517
+ Compare the **Real World** population against the AI's **Input Data**.
518
+ <br>Does the AI "see" the world as it truly is or do you see evidence of distorted representation?
519
+ </p>
520
+
521
+ <div style="text-align:center; margin-bottom:20px;">
522
+ <p style="font-size:1rem; font-weight:700; color:#2563eb; background:#eff6ff; display:inline-block; padding:6px 16px; border-radius:20px; border:1px solid #bfdbfe;">
523
+ 👇 Click to scan each demographic category to reveal the evidence
524
+ </p>
525
+ </div>
526
+
527
+ <div style="margin-top:20px;">
528
+ <input type="radio" id="scan-race" name="scan-tabs" class="scan-radio" checked>
529
+ <input type="radio" id="scan-gender" name="scan-tabs" class="scan-radio">
530
+ <input type="radio" id="scan-age" name="scan-tabs" class="scan-radio">
531
+
532
+ <div class="forensic-tabs" style="display:flex; justify-content:center; gap:10px; margin-bottom:0;">
533
+ <label for="scan-race" class="tab-label-styled" style="flex:1; text-align:center;">SCAN: RACE</label>
534
+ <label for="scan-gender" class="tab-label-styled" style="flex:1; text-align:center;">SCAN: GENDER</label>
535
+ <label for="scan-age" class="tab-label-styled" style="flex:1; text-align:center;">SCAN: AGE</label>
536
+ </div>
537
+
538
+ <div class="scan-content" style="border-top: 3px solid var(--color-accent);">
539
+
540
+ <div class="scan-pane pane-race">
541
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; background:#1e293b; color:white; padding:10px 15px; border-radius:6px;">
542
+ <span style="font-family:monospace; letter-spacing:1px;">SCANNING: RACIAL DISTRIBUTION</span>
543
+ <span style="color:#ef4444; font-weight:bold; animation: blink 1.5s infinite;">⚠️ ANOMALY DETECTED</span>
544
+ </div>
545
+
546
+ <div style="display:grid; grid-template-columns: 1fr 0.2fr 1fr; align-items:center; gap:10px;">
547
+
548
+ <div style="text-align:center; background:white; padding:15px; border-radius:8px; border:1px solid #bfdbfe;">
549
+ <div style="font-size:0.9rem; font-weight:700; color:#64748b; letter-spacing:1px;">REAL WORLD</div>
550
+ <div style="font-size:2rem; font-weight:900; color:#3b82f6; margin:5px 0;">28%</div>
551
+ <div style="font-size:0.9rem; margin-bottom:10px;">African-American Population</div>
552
+ <div style="display:grid; grid-template-columns:repeat(4, 1fr); gap:4px; max-width:80px; margin:0 auto;">
553
+ <span style="color:#3b82f6;">●</span><span style="color:#3b82f6;">●</span><span style="color:#3b82f6;">●</span><span style="color:#e2e8f0;">●</span>
554
+ <span style="color:#e2e8f0;">●</span><span style="color:#e2e8f0;">●</span><span style="color:#e2e8f0;">●</span><span style="color:#e2e8f0;">●</span>
555
+ <span style="color:#e2e8f0;">●</span><span style="color:#e2e8f0;">●</span><span style="color:#e2e8f0;">●</span><span style="color:#e2e8f0;">●</span>
556
+ </div>
557
+ </div>
558
+
559
+ <div style="text-align:center; font-size:1.5rem; color:#94a3b8;">👉</div>
560
+
561
+ <div style="text-align:center; background:#fef2f2; padding:15px; border-radius:8px; border:2px solid #ef4444;">
562
+ <div style="font-size:0.9rem; font-weight:700; color:#b91c1c; letter-spacing:1px;">INPUT DATA</div>
563
+ <div style="font-size:2rem; font-weight:900; color:#ef4444; margin:5px 0;">51%</div>
564
+ <div style="font-size:0.9rem; margin-bottom:10px;">African-American Records</div>
565
+ <div style="display:grid; grid-template-columns:repeat(4, 1fr); gap:4px; max-width:80px; margin:0 auto;">
566
+ <span style="color:#ef4444;">●</span><span style="color:#ef4444;">●</span><span style="color:#ef4444;">●</span><span style="color:#ef4444;">●</span>
567
+ <span style="color:#ef4444;">●</span><span style="color:#ef4444;">●</span><span style="color:#e2e8f0;">●</span><span style="color:#e2e8f0;">●</span>
568
+ <span style="color:#e2e8f0;">●</span><span style="color:#e2e8f0;">●</span><span style="color:#e2e8f0;">●</span><span style="color:#e2e8f0;">●</span>
569
+ </div>
570
+ </div>
571
+
572
+ </div>
573
+
574
+ <div class="hint-box" style="margin-top:20px; border-left:4px solid #ef4444; background:white;">
575
+ <div style="font-weight:800; color:#ef4444; font-size:1.0rem;">❌ EVIDENCE LOGGED: Race Representation Bias</div>
576
+ <div style="font-size:0.95rem; margin-top:5px;">
577
+ The AI is **over-exposed** to this group (51% vs 28%). It may learn to associate "High Risk" with this demographic simply because it sees them more often in arrest records.
578
+ </div>
579
+ </div>
580
+ </div>
581
+
582
+ <div class="scan-pane pane-gender">
583
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; background:#1e293b; color:white; padding:10px 15px; border-radius:6px;">
584
+ <span style="font-family:monospace; letter-spacing:1px;">SCANNING: GENDER BALANCE</span>
585
+ <span style="color:#ef4444; font-weight:bold; animation: blink 1.5s infinite;">⚠️ DATA GAP FOUND</span>
586
+ </div>
587
+ <div style="display:grid; grid-template-columns: 1fr 1fr; gap:20px;">
588
+ <div style="text-align:center; padding:20px; background:white; border-radius:8px; border:1px solid #e2e8f0;">
589
+ <div style="font-size:4rem; line-height:1;">♂️</div>
590
+ <div style="font-size:2.2rem; font-weight:900; color:#3b82f6;">81%</div>
591
+ <div style="font-weight:700; color:#64748b;">MALE</div>
592
+ <div style="font-size:0.85rem; color:#16a34a; font-weight:600; margin-top:5px;">✅ Well Represented</div>
593
+ </div>
594
+ <div style="text-align:center; padding:20px; background:#fff1f2; border-radius:8px; border:2px solid #fda4af;">
595
+ <div style="font-size:4rem; line-height:1; opacity:0.5;">♀️</div>
596
+ <div style="font-size:2.2rem; font-weight:900; color:#e11d48;">19%</div>
597
+ <div style="font-weight:700; color:#9f1239;">FEMALE</div>
598
+ <div style="font-size:0.85rem; color:#e11d48; font-weight:600; margin-top:5px;">⚠️ Insufficient Data</div>
599
+ </div>
600
+ </div>
601
+ <div class="hint-box" style="margin-top:20px; border-left:4px solid #ef4444; background:white;">
602
+ <div style="font-weight:800; color:#ef4444; font-size:1.0rem;">❌ EVIDENCE LOGGED: Gender Representation Bias</div>
603
+ <div style="font-size:0.95rem; margin-top:5px;">
604
+ Women are a "minority class" in this dataset even though they typically make up 50% of the true population. The model will likely struggle to learn accurate patterns for them, leading to **higher error rates** for female defendants.
605
+ </div>
606
+ </div>
607
+ </div>
608
+
609
+ <div class="scan-pane pane-age">
610
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; background:#1e293b; color:white; padding:10px 15px; border-radius:6px;">
611
+ <span style="font-family:monospace; letter-spacing:1px;">SCANNING: AGE DISTRIBUTION</span>
612
+ <span style="color:#ef4444; font-weight:bold; animation: blink 1.5s infinite;">⚠️ DISTRIBUTION SPIKE</span>
613
+ </div>
614
+
615
+ <div style="padding:20px; background:white; border-radius:8px; border:1px solid #e2e8f0; height:200px; display:flex; align-items:flex-end; justify-content:space-around;">
616
+
617
+ <div style="width:20%; text-align:center; display:flex; flex-direction:column; justify-content:flex-end; height:100%;">
618
+ <div style="font-weight:700; color:#64748b; margin-bottom:5px;">Low</div>
619
+ <div style="height:60px; background:#cbd5e1; border-radius:4px 4px 0 0; width:100%;"></div>
620
+ <div style="margin-top:10px; font-size:0.85rem; font-weight:700; color:#334155;">Younger (<25)</div>
621
+ </div>
622
+
623
+ <div style="width:35%; text-align:center; display:flex; flex-direction:column; justify-content:flex-end; height:100%;">
624
+ <div style="font-weight:700; color:#ef4444; margin-bottom:5px;">HIGH</div>
625
+ <div style="height:120px; background:#ef4444; border-radius:4px 4px 0 0; width:100%; box-shadow:0 4px 10px rgba(239,68,68,0.3);"></div>
626
+ <div style="margin-top:10px; font-size:0.9rem; font-weight:800; color:#b91c1c;">25-45 (BUBBLE)</div>
627
+ </div>
628
+
629
+ <div style="width:20%; text-align:center; display:flex; flex-direction:column; justify-content:flex-end; height:100%;">
630
+ <div style="font-weight:700; color:#64748b; margin-bottom:5px;">Low</div>
631
+ <div style="height:50px; background:#cbd5e1; border-radius:4px 4px 0 0; width:100%;"></div>
632
+ <div style="margin-top:10px; font-size:0.85rem; font-weight:700; color:#334155;">Older (>45)</div>
633
+ </div>
634
+
635
+ </div>
636
+
637
+ <div class="hint-box" style="margin-top:20px; border-left:4px solid #ef4444; background:white;">
638
+ <div style="font-weight:800; color:#ef4444; font-size:1.0rem;">❌ EVIDENCE LOGGED: Age Representation Bias</div>
639
+ <div style="font-size:0.95rem; margin-top:5px;">
640
+ The data is concentrated in the 25-45 age "Bubble." The model has a **blind spot** for younger and older people, meaning predictions for those groups will be unreliable (Generalization Error).
641
+ </div>
642
+ </div>
643
+ </div>
644
+ </div>
645
+ </div>
646
+
647
+ <div style="text-align:center; margin-top:35px; padding:20px; background:linear-gradient(to right, rgba(99,102,241,0.1), rgba(16,185,129,0.1)); border-radius:12px; border:2px solid var(--color-accent);">
648
+ <p style="font-size:1.15rem; font-weight:800; color:var(--color-accent); margin-bottom:5px;">
649
+ 🚀 REPRESENTATION BIAS EVIDENCE ESTABLISHED: CONTINUE MISSION
650
+ </p>
651
+ <p style="font-size:1.05rem; margin:0;">
652
+ Answer the question below to receive your next <strong>Moral Compass Score boost</strong>.
653
+ <br>Then click <strong>Next</strong> to <strong>summarize your data forensic lab findings.</strong>
654
+ </p>
655
+ </div>
656
+
657
+ </div>
658
+ </div>
659
+ """,
660
+ },
661
+
662
+ # --- MODULE 4: EVIDENCE REPORT (Input Flaws) ---
663
+ {
664
+ "id": 4,
665
+ "title": "Evidence Report: Input Flaws",
666
+ "html": """
667
+ <div class="scenario-box">
668
+ <div class="tracker-container">
669
+ <div class="tracker-step completed">✓ RULES</div>
670
+ <div class="tracker-step completed">✓ EVIDENCE</div>
671
+ <div class="tracker-step active">3. ERROR</div>
672
+ <div class="tracker-step">4. VERDICT</div>
673
+ </div>
674
+ <h2 class="slide-title" style="font-size:1.6rem; text-align:center; margin-bottom:15px;">Data Forensics Report: Input Flaws</h2>
675
+ <div class="ai-risk-container" style="border: 2px solid #ef4444; background: rgba(239,68,68,0.05); padding: 20px;">
676
+ <h4 style="margin-top:0; font-size:1.2rem; color:#b91c1c; text-align:center;">📋 EVIDENCE SUMMARY</h4>
677
+ <table style="width: 100%; border-collapse: collapse; margin-top: 15px;">
678
+ <thead>
679
+ <tr style="background: rgba(239,68,68,0.1); border-bottom: 2px solid #ef4444;">
680
+ <th style="padding: 8px; text-align: left;">SECTOR</th>
681
+ <th style="padding: 8px; text-align: left;">FINDING</th>
682
+ <th style="padding: 8px; text-align: left;">IMPACT</th>
683
+ </tr>
684
+ </thead>
685
+ <tbody>
686
+ <tr style="border-bottom: 1px solid var(--border-color-primary);">
687
+ <td style="padding: 8px; font-weight:700;">Race</td>
688
+ <td style="padding: 8px;">Over-represented (51%)</td>
689
+ <td style="padding: 8px; color:#b91c1c;">Risk of Increased Prediction Error</td>
690
+ </tr>
691
+ <tr style="border-bottom: 1px solid var(--border-color-primary);">
692
+ <td style="padding: 8px; font-weight:700;">Gender</td>
693
+ <td style="padding: 8px;">Under-represented (19%)</td>
694
+ <td style="padding: 8px; color:#b91c1c;">Risk of Increased Prediction Error</td>
695
+ </tr>
696
+ <tr>
697
+ <td style="padding: 8px; font-weight:700;">Age</td>
698
+ <td style="padding: 8px;">Excluded Groups (Under 25/Over 45)</td>
699
+ <td style="padding: 8px; color:#b91c1c;">Risk of Increased Prediction Error</td>
700
+ </tr>
701
+ </tbody>
702
+ </table>
703
+ </div>
704
+
705
+
706
+ <div style="text-align:center; margin-top:35px; padding:20px; background:linear-gradient(to right, rgba(99,102,241,0.1), rgba(16,185,129,0.1)); border-radius:12px; border:2px solid var(--color-accent);">
707
+ <p style="font-size:1.15rem; font-weight:800; color:var(--color-accent); margin-bottom:5px;">
708
+ 🚀 NEXT: INVESTIGATE ERRORS IN OUTPUTS - CONTINUE MISSION
709
+ </p>
710
+ <p style="font-size:1.05rem; margin:0;">
711
+ Answer the question below to receive your next <strong>Moral Compass Score boost</strong>.
712
+ <br>Click <strong>Next</strong> to proceed to **Step 3** to find proof of actual harm: **The Error Gaps**.
713
+ </p>
714
+ </div>
715
+ </div>
716
+ </div>
717
+ """
718
+ },
719
+
720
+ # --- MODULE 5: INTRO TO PREDICTION ERROR ---
721
+ {
722
+ "id": 5,
723
+ "title": "Part II: Step 3 — Proving the Prediction Error",
724
+ "html": """
725
+ <div class="scenario-box">
726
+ <div class="tracker-container">
727
+ <div class="tracker-step completed">1. RULES</div>
728
+ <div class="tracker-step completed">2. EVIDENCE</div>
729
+ <div class="tracker-step active">3. ERROR</div>
730
+ <div class="tracker-step">4. VERDICT</div>
731
+ </div>
732
+
733
+ <div class="slide-body">
734
+ <h2 class="slide-title" style="margin:0;">STEP 3: EVALUATE PREDICTION ERRORS</h2>
735
+
736
+ <div style="text-align:center; margin-bottom:20px;">
737
+ <h2 class="slide-title" style="margin-top:10px; color:#be123c;">The Hunt For Prediction Errors</h2>
738
+ <p style="font-size:1.1rem; max-width:820px; margin:0 auto; color:#334155;">
739
+ We found evidence that the Input Data is biased. Now we must investigate if this bias has influenced the <strong>Model's Decisions</strong>.
740
+ <br>We are looking for the second Red Flag from our Rulebook: <strong>Error Gaps</strong>.
741
+ </p>
742
+ </div>
743
+
744
+ <div style="background:white; border:2px solid #e2e8f0; border-radius:16px; padding:25px; margin-bottom:25px; box-shadow: 0 4px 15px rgba(0,0,0,0.05);">
745
+
746
+ <div style="display:flex; align-items:center; gap:10px; margin-bottom:15px; border-bottom:1px solid #f1f5f9; padding-bottom:10px;">
747
+ <div style="font-size:1.5rem;">🚩</div>
748
+ <div>
749
+ <strong style="color:#be123c; font-size:1.1rem; text-transform:uppercase; letter-spacing:1px;">PATTERN: "THE DOUBLE STANDARD"</strong>
750
+ <div style="font-size:0.9rem; color:#64748b;">(Unequal Impact of Mistakes)</div>
751
+ </div>
752
+ </div>
753
+
754
+ <div style="display:grid; grid-template-columns: 1fr 1fr; gap:30px;">
755
+
756
+ <div>
757
+ <p style="font-size:1rem; color:#334155; line-height:1.6; margin-top:0;">
758
+ <strong>The Concept:</strong> A model's prediction shapes a person's future. When it makes a mistake, real people suffer.
759
+ </p>
760
+
761
+ <div style="margin-top:15px; margin-bottom:15px;">
762
+ <div style="background:#fff1f2; padding:12px; border-radius:8px; border:1px solid #fda4af; margin-bottom:10px;">
763
+ <div style="font-weight:700; color:#9f1239; margin-bottom:4px; font-size:0.95rem;">⚠️ TYPE 1: FALSE ALARMS</div>
764
+ <div style="font-size:0.9rem; color:#881337; line-height:1.4;">Labeling a low-risk person as <strong>High Risk</strong>.</div>
765
+ <div style="font-size:0.85rem; font-weight:700; color:#be123c; margin-top:4px;">Harm: Unfair Detention.</div>
766
+ </div>
767
+
768
+ <div style="background:#f0f9ff; padding:12px; border-radius:8px; border:1px solid #bae6fd;">
769
+ <div style="font-weight:700; color:#0369a1; margin-bottom:4px; font-size:0.95rem;">⚠️ TYPE 2: MISSED WARNINGS</div>
770
+ <div style="font-size:0.9rem; color:#075985; line-height:1.4;">Labeling a high-risk person as <strong>Low Risk</strong>.</div>
771
+ <div style="font-size:0.85rem; font-weight:700; color:#0284c7; margin-top:4px;">Harm: Public Safety Risk.</div>
772
+ </div>
773
+ </div>
774
+
775
+ <div style="background:#fff1f2; color:#be123c; padding:10px; border-radius:6px; font-size:0.9rem; border-left:4px solid #db2777; margin-top:15px;">
776
+ <strong>Key Clue:</strong> Look for a significant gap in the <strong>False Alarm Rate</strong>. If Group A is flagged incorrectly substantially more than Group B, that is an Error Gap.
777
+ </div>
778
+ </div>
779
+
780
+ <div style="background:#f8fafc; padding:20px; border-radius:12px; border:1px solid #e2e8f0; display:flex; flex-direction:column; justify-content:center;">
781
+
782
+ <div style="text-align:center; margin-bottom:10px; font-weight:700; color:#334155; font-size:0.9rem;">
783
+ "FALSE ALARMS" (Innocent People Flagged Risky)
784
+ </div>
785
+
786
+ <div style="margin-bottom:15px;">
787
+ <div style="display:flex; justify-content:space-between; font-size:0.8rem; font-weight:700; color:#9f1239; margin-bottom:4px;">
788
+ <span>GROUP A (Targeted)</span>
789
+ <span>60% ERROR</span>
790
+ </div>
791
+ <div style="width:100%; background:#e2e8f0; height:12px; border-radius:10px; overflow:hidden;">
792
+ <div style="width:60%; background:#db2777; height:100%;"></div>
793
+ </div>
794
+ </div>
795
+
796
+ <div>
797
+ <div style="display:flex; justify-content:space-between; font-size:0.8rem; font-weight:700; color:#64748b; margin-bottom:4px;">
798
+ <span>GROUP B (Baseline)</span>
799
+ <span>30% ERROR</span>
800
+ </div>
801
+ <div style="width:100%; background:#e2e8f0; height:12px; border-radius:10px; overflow:hidden;">
802
+ <div style="width:30%; background:#94a3b8; height:100%;"></div>
803
+ </div>
804
+ </div>
805
+
806
+ <div style="text-align:center; margin-top:15px; font-size:0.85rem; color:#db2777; font-weight:700; background:#fff1f2; padding:5px; border-radius:4px;">
807
+ ⚠️ GAP DETECTED: +30 Percentage Point Difference
808
+ </div>
809
+
810
+ </div>
811
+ </div>
812
+ </div>
813
+
814
+ <details style="margin-bottom:25px; cursor:pointer; background:#fff1f2; border:1px solid #fda4af; border-radius:8px; padding:12px;">
815
+ <summary style="font-weight:700; color:#9f1239; font-size:0.95rem;">🔬 The Hypothesis: How is Representation Bias connected to Prediction Error?</summary>
816
+ <div style="margin-top:12px; font-size:0.95rem; color:#881337; line-height:1.5; padding:0 5px;">
817
+ <p style="margin-bottom:10px;"><strong>Connect the dots:</strong> In Step 2, we found that the input data overrepresented specific groups.</p>
818
+ <p><strong>The Theory:</strong> Because the AI saw these groups more often in arrest records, the data structure may lead the model to make group-specific prediction mistakes. The model may generate more <strong>False Alarms</strong> for innocent people from these groups at a much higher rate.</p>
819
+ </div>
820
+ </details>
821
+
822
+ <div style="text-align:center; margin-top:35px; padding:20px; background:linear-gradient(to right, rgba(219,39,119,0.1), rgba(251,113,133,0.1)); border-radius:12px; border:2px solid #fecdd3;">
823
+ <p style="font-size:1.15rem; font-weight:800; color:#9f1239; margin-bottom:5px;">
824
+ 🚀 ERROR PATTERN ESTABLISHED: CONTINUE MISSION
825
+ </p>
826
+ <p style="font-size:1.05rem; margin:0; color:#881337;">
827
+ Answer the question below to confirm your target.
828
+ <br>Then click <strong>Next</strong> to open the <strong>Prediction Error Lab</strong> and test the False Alarm Rates.
829
+ </p>
830
+ </div>
831
+
832
+ </div>
833
+ </div>
834
+ """
835
+ },
836
+
837
+ # --- MODULE 6: RACE ERROR GAP LAB ---
838
+ # --- MODULE 6: PREDICTION ERROR LAB ---
839
+ # --- MODULE 6: THE RACE ERROR GAP LAB ---
840
+ {
841
+ "id": 6,
842
+ "title": "Step 3: The Race Error Gap Lab",
843
+ "html": """
844
+ <div class="scenario-box">
845
+ <div class="tracker-container">
846
+ <div class="tracker-step completed">1. RULES</div>
847
+ <div class="tracker-step completed">2. EVIDENCE</div>
848
+ <div class="tracker-step active">3. ERROR</div>
849
+ <div class="tracker-step">4. VERDICT</div>
850
+ </div>
851
+
852
+ <div class="slide-body">
853
+ <h2 class="slide-title" style="margin:0;">STEP 3: ANALYZE THE PREDICTION ERROR GAP</h2>
854
+
855
+ <div style="text-align:center; margin-bottom:20px;">
856
+ <h2 class="slide-title" style="margin-top:10px; color:#be123c;">The Prediction Error Lab - Race Analysis</h2>
857
+ <p style="font-size:1.1rem; max-width:820px; margin:0 auto; color:#334155;">
858
+ We suspected the model is generating unfair amounts of prediction errors for specific groups. Now, we run the analysis.
859
+ <br>Click to reveal the error rates below. Do AI mistakes fall equally across white and black defendants?
860
+ </p>
861
+ </div>
862
+
863
+ <div style="display:grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom:25px;">
864
+
865
+ <div class="ai-risk-container" style="padding:0; border:2px solid #ef4444; overflow:hidden; border-radius:12px; box-shadow: 0 4px 12px rgba(239, 68, 68, 0.1);">
866
+ <div style="background:#fff1f2; padding:15px; text-align:center; border-bottom:1px solid #fda4af;">
867
+ <h3 style="margin:0; font-size:1.25rem; color:#b91c1c;">📡 SCAN 1: FALSE ALARMS</h3>
868
+ <p style="font-size:0.9rem; margin:5px 0 0 0; color:#9f1239;">(Innocent people wrongly flagged as "High Risk")</p>
869
+ </div>
870
+
871
+ <details style="cursor:pointer; background:white;">
872
+ <summary style="list-style:none; padding:20px; font-weight:800; text-align:center; color:#ef4444; font-size:1.1rem; transition:background 0.2s;">
873
+ 👇 CLICK TO REVEAL DATA
874
+ </summary>
875
+ <div style="padding:0 20px 25px 20px; text-align:center; border-top:1px solid #fecdd3;">
876
+
877
+ <div style="display:flex; justify-content:center; gap:30px; margin-bottom:20px;">
878
+ <div style="text-align:center;">
879
+ <div style="font-size:2.5rem; font-weight:900; color:#ef4444; line-height:1;">45%</div>
880
+ <div style="font-size:0.85rem; font-weight:700; color:#7f1d1d; margin-top:5px;">AFRICAN-AMERICAN</div>
881
+ </div>
882
+ <div style="width:1px; background:#e5e7eb;"></div>
883
+ <div style="text-align:center;">
884
+ <div style="font-size:2.5rem; font-weight:900; color:#3b82f6; line-height:1;">23%</div>
885
+ <div style="font-size:0.85rem; font-weight:700; color:#1e3a8a; margin-top:5px;">WHITE</div>
886
+ </div>
887
+ </div>
888
+
889
+ <div class="hint-box" style="border-left:4px solid #ef4444; background:#fff1f2; text-align:left;">
890
+ <div style="font-weight:800; color:#b91c1c; font-size:0.95rem;">❌ VERDICT: PUNITIVE BIAS</div>
891
+ <div style="font-size:0.9rem; color:#9f1239; margin-top:3px;">
892
+ Black defendants are nearly <strong>twice as likely</strong> to be wrongly labeled as dangerous compared to White defendants.
893
+ </div>
894
+ </div>
895
+
896
+ </div>
897
+ </details>
898
+ </div>
899
+
900
+ <div class="ai-risk-container" style="padding:0; border:2px solid #3b82f6; overflow:hidden; border-radius:12px; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1);">
901
+ <div style="background:#eff6ff; padding:15px; text-align:center; border-bottom:1px solid #bfdbfe;">
902
+ <h3 style="margin:0; font-size:1.25rem; color:#1e40af;">📡 SCAN 2: MISSED WARNINGS</h3>
903
+ <p style="font-size:0.9rem; margin:5px 0 0 0; color:#1e3a8a;">(Risky people wrongly labeled as "Safe")</p>
904
+ </div>
905
+
906
+ <details style="cursor:pointer; background:white;">
907
+ <summary style="list-style:none; padding:20px; font-weight:800; text-align:center; color:#3b82f6; font-size:1.1rem; transition:background 0.2s;">
908
+ 👇 CLICK TO REVEAL DATA
909
+ </summary>
910
+ <div style="padding:0 20px 25px 20px; text-align:center; border-top:1px solid #dbeafe;">
911
+
912
+ <div style="display:flex; justify-content:center; gap:30px; margin-bottom:20px;">
913
+ <div style="text-align:center;">
914
+ <div style="font-size:2.5rem; font-weight:900; color:#ef4444; line-height:1;">28%</div>
915
+ <div style="font-size:0.85rem; font-weight:700; color:#7f1d1d; margin-top:5px;">AFRICAN-AMERICAN</div>
916
+ </div>
917
+ <div style="width:1px; background:#e5e7eb;"></div>
918
+ <div style="text-align:center;">
919
+ <div style="font-size:2.5rem; font-weight:900; color:#3b82f6; line-height:1;">48%</div>
920
+ <div style="font-size:0.85rem; font-weight:700; color:#1e3a8a; margin-top:5px;">WHITE</div>
921
+ </div>
922
+ </div>
923
+
924
+ <div class="hint-box" style="border-left:4px solid #3b82f6; background:#eff6ff; text-align:left;">
925
+ <div style="font-weight:800; color:#1e40af; font-size:0.95rem;">❌ VERDICT: LENIENCY BIAS</div>
926
+ <div style="font-size:0.9rem; color:#1e3a8a; margin-top:3px;">
927
+ White defendants who re-offend are much more likely to be <strong>missed</strong> by the system than Black defendants.
928
+ </div>
929
+ </div>
930
+
931
+ </div>
932
+ </details>
933
+ </div>
934
+ </div>
935
+
936
+ <div style="text-align:center; margin-top:20px; padding:20px; background:linear-gradient(to right, rgba(219,39,119,0.1), rgba(251,113,133,0.1)); border-radius:12px; border:2px solid #fecdd3;">
937
+ <p style="font-size:1.15rem; font-weight:800; color:#9f1239; margin-bottom:5px;">
938
+ 🚀 RACIAL ERROR GAP CONFIRMED
939
+ </p>
940
+ <p style="font-size:1.05rem; margin:0; color:#881337;">
941
+ We have demonstrated the model has a "Double Standard" for race.
942
+ <br>Answer the question below to certify your findings, then proceed to <strong>Step 4: Analyze Gender, Age, and Geography Gaps in Error.</strong>
943
+ </p>
944
+ </div>
945
+
946
+ </div>
947
+ </div>
948
+ """
949
+ },
950
+
951
+ # --- MODULE 7: GENERALIZATION SCAN LAB ---
952
+ # --- MODULE 7: GENERALIZATION & PROXY SCAN ---
953
+ {
954
+ "id": 7,
955
+ "title": "Step 3: Generalization Scan Lab",
956
+ "html": """
957
+ <div class="scenario-box">
958
+ <div class="tracker-container">
959
+ <div class="tracker-step completed">1. RULES</div>
960
+ <div class="tracker-step completed">2. EVIDENCE</div>
961
+ <div class="tracker-step active">3. ERROR</div>
962
+ <div class="tracker-step">4. VERDICT</div>
963
+ </div>
964
+
965
+ <div class="slide-body">
966
+ <h2 class="slide-title" style="margin:0;">STEP 3: ANALYZE THE PREDICTION ERROR GAP</h2>
967
+
968
+ <div style="text-align:center; margin-bottom:20px;">
969
+ <h2 class="slide-title" style="margin-top:10px; color:#b91c1c;">Gender, Age, and Geography Error Scans</h2>
970
+ <p style="font-size:1.1rem; max-width:820px; margin:0 auto; color:#334155;">
971
+ We revealed the Racial Error Gap. But bias hides in other places too.
972
+ <br>Use the scanner below to check for gender and age <strong>Representation Errors</strong> (due to data gaps) and <strong>Proxy Bias</strong> (hidden variables).
973
+ </p>
974
+ </div>
975
+
976
+ <div style="margin-top:20px;">
977
+ <input type="radio" id="scan-gender-err" name="error-tabs" class="scan-radio" checked>
978
+ <input type="radio" id="scan-age-err" name="error-tabs" class="scan-radio">
979
+ <input type="radio" id="scan-geo-err" name="error-tabs" class="scan-radio">
980
+
981
+ <div class="forensic-tabs" style="display:flex; justify-content:center; gap:10px; margin-bottom:0;">
982
+ <label for="scan-gender-err" class="tab-label-styled" style="flex:1; text-align:center; border-color:#fda4af; color:#9f1239;">SCAN: GENDER</label>
983
+ <label for="scan-age-err" class="tab-label-styled" style="flex:1; text-align:center; border-color:#fda4af; color:#9f1239;">SCAN: AGE</label>
984
+ <label for="scan-geo-err" class="tab-label-styled" style="flex:1; text-align:center; border-color:#fda4af; color:#9f1239;">SCAN: GEOGRAPHY</label>
985
+ </div>
986
+
987
+ <div class="scan-content" style="border-top: 3px solid #db2777;">
988
+
989
+ <div class="scan-pane pane-gender-err">
990
+ <div style="background:#fff1f2; padding:15px; text-align:center; border-bottom:1px solid #fda4af; margin-bottom:15px;">
991
+ <h3 style="margin:0; font-size:1.2rem; color:#b91c1c;">📡 GENDER SCAN: PREDICTION ERROR</h3>
992
+ <p style="font-size:0.9rem; margin:5px 0 0 0; color:#9f1239;">(Does the "Data Gap" lead to more mistakes?)</p>
993
+ </div>
994
+
995
+ <details style="cursor:pointer; background:white; border:1px solid #e2e8f0; border-radius:8px; overflow:hidden;">
996
+ <summary style="list-style:none; padding:15px; font-weight:800; text-align:center; color:#db2777; font-size:1.05rem; background:#fff1f2;">
997
+ 👇 CLICK TO REVEAL FALSE ALARM RATES
998
+ </summary>
999
+ <div style="padding:20px;">
1000
+
1001
+ <div style="margin-bottom:20px;">
1002
+ <div style="display:flex; justify-content:space-between; margin-bottom:5px;">
1003
+ <span style="font-weight:700; color:#9f1239;">WOMEN (The Minority Class)</span>
1004
+ <span style="font-weight:700; color:#9f1239;">32% Error</span>
1005
+ </div>
1006
+ <div style="width:100%; background:#e2e8f0; height:18px; border-radius:4px; overflow:hidden;">
1007
+ <div style="width:32%; background:#db2777; height:100%;"></div>
1008
+ </div>
1009
+ </div>
1010
+
1011
+ <div style="margin-bottom:20px;">
1012
+ <div style="display:flex; justify-content:space-between; margin-bottom:5px;">
1013
+ <span style="font-weight:700; color:#64748b;">MEN (Well Represented)</span>
1014
+ <span style="font-weight:700; color:#64748b;">18% Error</span>
1015
+ </div>
1016
+ <div style="width:100%; background:#e2e8f0; height:18px; border-radius:4px; overflow:hidden;">
1017
+ <div style="width:18%; background:#94a3b8; height:100%;"></div>
1018
+ </div>
1019
+ </div>
1020
+
1021
+ <div class="hint-box" style="border-left:4px solid #db2777; background:#fff1f2;">
1022
+ <div style="font-weight:800; color:#b91c1c;">❌ VERDICT: BLIND SPOT CONFIRMED</div>
1023
+ <div style="font-size:0.95rem; margin-top:5px;">
1024
+ Because the model has less data on women, it is "guessing" more often.
1025
+ This high error rate is most likely the result of the <strong>Data Gap</strong> we found in Step 2.
1026
+ </div>
1027
+ </div>
1028
+ </div>
1029
+ </details>
1030
+ </div>
1031
+
1032
+ <div class="scan-pane pane-age-err">
1033
+ <div style="background:#fff1f2; padding:15px; text-align:center; border-bottom:1px solid #fda4af; margin-bottom:15px;">
1034
+ <h3 style="margin:0; font-size:1.2rem; color:#b91c1c;">📡 AGE SCAN: PREDICTION ERROR</h3>
1035
+ <p style="font-size:0.9rem; margin:5px 0 0 0; color:#9f1239;">(Does the model fail outside the "25-45" bubble?)</p>
1036
+ </div>
1037
+
1038
+ <details style="cursor:pointer; background:white; border:1px solid #e2e8f0; border-radius:8px; overflow:hidden;">
1039
+ <summary style="list-style:none; padding:15px; font-weight:800; text-align:center; color:#db2777; font-size:1.05rem; background:#fff1f2;">
1040
+ 👇 CLICK TO REVEAL FALSE ALARM RATES
1041
+ </summary>
1042
+ <div style="padding:20px;">
1043
+
1044
+ <div style="display:flex; align-items:flex-end; justify-content:space-around; height:100px; margin-bottom:15px; padding-bottom:10px; border-bottom:1px solid #e2e8f0;">
1045
+ <div style="text-align:center; width:25%;">
1046
+ <div style="font-size:0.8rem; font-weight:700; color:#ef4444; margin-bottom:2px;">33%</div>
1047
+ <div style="height:60px; background:#ef4444; width:100%; border-radius:4px 4px 0 0;"></div>
1048
+ <div style="font-size:0.75rem; font-weight:700; margin-top:5px;"<Less than 25</div>
1049
+ </div>
1050
+ <div style="text-align:center; width:25%;">
1051
+ <div style="font-size:0.8rem; font-weight:700; color:#16a34a; margin-bottom:2px;">18%</div>
1052
+ <div style="height:30px; background:#16a34a; width:100%; border-radius:4px 4px 0 0;"></div>
1053
+ <div style="font-size:0.75rem; font-weight:700; margin-top:5px;">25-45</div>
1054
+ </div>
1055
+ <div style="text-align:center; width:25%;">
1056
+ <div style="font-size:0.8rem; font-weight:700; color:#ef4444; margin-bottom:2px;">27%</div>
1057
+ <div style="height:50px; background:#ef4444; width:100%; border-radius:4px 4px 0 0;"></div>
1058
+ <div style="font-size:0.75rem; font-weight:700; margin-top:5px;">Greater than 45</div>
1059
+ </div>
1060
+ </div>
1061
+
1062
+ <div class="hint-box" style="border-left:4px solid #db2777; background:#fff1f2;">
1063
+ <div style="font-weight:800; color:#b91c1c;">❌ VERDICT: THE "U-SHAPED" FAILURE</div>
1064
+ <div style="font-size:0.95rem; margin-top:5px;">
1065
+ The model works well for the "Bubble" (25-45) with more data but fails significantly for the less than 25 and greater than 45 age categories.
1066
+ It cannot accurately predict risk for age groups it hasn't studied enough.
1067
+ </div>
1068
+ </div>
1069
+ </div>
1070
+ </details>
1071
+ </div>
1072
+
1073
+ <div class="scan-pane pane-geo-err">
1074
+ <div style="background:#fff1f2; padding:15px; text-align:center; border-bottom:1px solid #fda4af; margin-bottom:15px;">
1075
+ <h3 style="margin:0; font-size:1.2rem; color:#b91c1c;">📡 GEOGRAPHY SCAN: THE PROXY CHECK</h3>
1076
+ <p style="font-size:0.9rem; margin:5px 0 0 0; color:#9f1239;">(Is "Zip Code" creating a racial double standard?)</p>
1077
+ </div>
1078
+
1079
+ <details style="cursor:pointer; background:white; border:1px solid #e2e8f0; border-radius:8px; overflow:hidden;">
1080
+ <summary style="list-style:none; padding:15px; font-weight:800; text-align:center; color:#db2777; font-size:1.05rem; background:#fff1f2;">
1081
+ 👇 CLICK TO REVEAL FALSE ALARM RATES
1082
+ </summary>
1083
+ <div style="padding:20px;">
1084
+
1085
+ <div style="margin-bottom:20px;">
1086
+ <div style="display:flex; justify-content:space-between; margin-bottom:5px;">
1087
+ <span style="font-weight:700; color:#9f1239;">URBAN ZONES (High Minority Pop.)</span>
1088
+ <span style="font-weight:700; color:#9f1239;">58% Error</span>
1089
+ </div>
1090
+ <div style="width:100%; background:#e2e8f0; height:18px; border-radius:4px; overflow:hidden;">
1091
+ <div style="width:58%; background:#db2777; height:100%;"></div>
1092
+ </div>
1093
+ </div>
1094
+
1095
+ <div style="margin-bottom:20px;">
1096
+ <div style="display:flex; justify-content:space-between; margin-bottom:5px;">
1097
+ <span style="font-weight:700; color:#64748b;">RURAL ZONES</span>
1098
+ <span style="font-weight:700; color:#64748b;">22% Error</span>
1099
+ </div>
1100
+ <div style="width:100%; background:#e2e8f0; height:18px; border-radius:4px; overflow:hidden;">
1101
+ <div style="width:22%; background:#94a3b8; height:100%;"></div>
1102
+ </div>
1103
+ </div>
1104
+
1105
+ <div class="hint-box" style="border-left:4px solid #db2777; background:#fff1f2;">
1106
+ <div style="font-weight:800; color:#b91c1c;">❌ VERDICT: PROXY (HIDDEN RELATIONSHIP) BIAS CONFIRMED</div>
1107
+ <div style="font-size:0.95rem; margin-top:5px;">
1108
+ The error rate in Urban Zones is massive (58%).
1109
+ Even if "Race" was removed, the model is using <strong>Location</strong> to target the same groups.
1110
+ It is treating "City Resident" as a synonym for "High Risk."
1111
+ </div>
1112
+ </div>
1113
+ </div>
1114
+ </details>
1115
+ </div>
1116
+
1117
+ </div>
1118
+ </div>
1119
+
1120
+ <div style="text-align:center; margin-top:25px; padding:20px; background:linear-gradient(to right, rgba(219,39,119,0.1), rgba(251,113,133,0.1)); border-radius:12px; border:2px solid #fecdd3;">
1121
+ <p style="font-size:1.15rem; font-weight:800; color:#9f1239; margin-bottom:5px;">
1122
+ 🚀 ALL SYSTEMS SCANNED
1123
+ </p>
1124
+ <p style="font-size:1.05rem; margin:0; color:#881337;">
1125
+ You have collected all the forensic evidence. The bias is systemic.
1126
+ <br>Click <strong>Next</strong> to make your final recommendation about the AI system.
1127
+ </p>
1128
+ </div>
1129
+
1130
+ </div>
1131
+ </div>
1132
+ """
1133
+ },
1134
+ # --- MODULE 8: PREDICTION AUDIT SUMMARY ---
1135
+ {
1136
+ "id": 8,
1137
+ "title": "Step 3: Audit Report Summary",
1138
+ "html": """
1139
+ <div class="scenario-box">
1140
+ <div class="tracker-container">
1141
+ <div class="tracker-step completed">1. RULES</div>
1142
+ <div class="tracker-step completed">2. EVIDENCE</div>
1143
+ <div class="tracker-step active">3. ERROR</div>
1144
+ <div class="tracker-step">4. VERDICT</div>
1145
+ </div>
1146
+
1147
+ <div class="slide-body">
1148
+ <h2 class="slide-title" style="margin:0;">STEP 3: AUDIT REPORT SUMMARY</h2>
1149
+
1150
+ <div style="text-align:center; margin-bottom:25px;">
1151
+ <h2 class="slide-title" style="margin-top:10px; color:#b91c1c;">Final Prediction Analysis</h2>
1152
+ <p style="font-size:1.1rem; max-width:820px; margin:0 auto; color:#334155;">
1153
+ Review your forensic logs. You have uncovered systemic failures across multiple dimensions.
1154
+ <br>This evidence shows the model violates the core principle of <strong>Justice & Fairness</strong>.
1155
+ </p>
1156
+ </div>
1157
+
1158
+ <div style="display:grid; grid-template-columns: 1fr 1fr; gap:25px; margin-bottom:30px;">
1159
+
1160
+ <div style="background:#fff1f2; border:2px solid #ef4444; border-radius:12px; padding:20px; box-shadow: 0 4px 10px rgba(239,68,68,0.1);">
1161
+ <div style="display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #fda4af; padding-bottom:10px; margin-bottom:15px;">
1162
+ <strong style="color:#9f1239; font-size:1.1rem;">🚨 PRIMARY THREAT</strong>
1163
+ <span style="background:#ef4444; color:white; font-size:0.75rem; font-weight:800; padding:4px 8px; border-radius:4px;">CONFIRMED</span>
1164
+ </div>
1165
+ <h3 style="margin:0 0 10px 0; color:#b91c1c; font-size:1.25rem;">Racial Double Standard</h3>
1166
+ <p style="font-size:0.95rem; color:#7f1d1d; line-height:1.5;">
1167
+ <strong>The Evidence:</strong> African-American defendants face a <strong>45% False Alarm Rate</strong> (vs. 23% for White defendants).
1168
+ </p>
1169
+ <div style="background:white; padding:10px; border-radius:6px; border:1px solid #fda4af; margin-top:10px;">
1170
+ <strong style="color:#ef4444; font-size:0.9rem;">The Impact:</strong> Punitive Bias. Innocent people are being wrongly flagged for detention at 2x the rate of others.
1171
+ </div>
1172
+ </div>
1173
+
1174
+ <div style="background:white; border:2px solid #e2e8f0; border-radius:12px; padding:20px; box-shadow: 0 4px 10px rgba(0,0,0,0.05);">
1175
+ <div style="display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #e2e8f0; padding-bottom:10px; margin-bottom:15px;">
1176
+ <strong style="color:#475569; font-size:1.1rem;">📍 PROXY FAILURE</strong>
1177
+ <span style="background:#f59e0b; color:white; font-size:0.75rem; font-weight:800; padding:4px 8px; border-radius:4px;">DETECTED</span>
1178
+ </div>
1179
+ <h3 style="margin:0 0 10px 0; color:#334155; font-size:1.25rem;">Geographic Discrimination</h3>
1180
+ <p style="font-size:0.95rem; color:#475569; line-height:1.5;">
1181
+ <strong>The Evidence:</strong> Urban Zones show a massive <strong>58% Error Rate</strong>.
1182
+ </p>
1183
+ <div style="background:#f8fafc; padding:10px; border-radius:6px; border:1px solid #e2e8f0; margin-top:10px;">
1184
+ <strong style="color:#64748b; font-size:0.9rem;">The Mechanism:</strong> Although "Race" was hidden, the AI used "Zip Code" as a loophole to target the same communities.
1185
+ </div>
1186
+ </div>
1187
+
1188
+ <div style="grid-column: span 2; background:#f0f9ff; border:2px solid #bae6fd; border-radius:12px; padding:20px;">
1189
+ <div style="display:flex; align-items:center; gap:10px; margin-bottom:10px;">
1190
+ <span style="font-size:1.5rem;">📉</span>
1191
+ <h3 style="margin:0; color:#0369a1; font-size:1.2rem;">Secondary Failure: Prediction Errors Due to Represenation Bias</h3>
1192
+ </div>
1193
+ <p style="font-size:1rem; color:#334155; margin-bottom:0;">
1194
+ <strong>The Evidence:</strong> High instability in predictions for <strong>Women and Younger/Older Age Groups</strong>.
1195
+ <br>
1196
+ <span style="color:#0284c7; font-size:0.95rem;"><strong>Why?</strong> The input data lacked sufficient examples for these groups (The Distorted Mirror), causing the model to "guess" rather than learn.</span>
1197
+ </p>
1198
+ </div>
1199
+
1200
+ </div>
1201
+
1202
+
1203
+ <div style="text-align:center; margin-top:25px; padding:20px; background:linear-gradient(to right, rgba(219,39,119,0.1), rgba(251,113,133,0.1)); border-radius:12px; border:2px solid #fecdd3;">
1204
+ <p style="font-size:1.15rem; font-weight:800; color:#9f1239; margin-bottom:5px;">
1205
+ 🚀 INVESTIGATION CASE FILE CLOSED. FINAL EVIDENCE LOCKED.
1206
+ </p>
1207
+ <p style="font-size:1.05rem; margin:0; color:#881337;">
1208
+ You have successfully investigated the Inputs Data and the Output Errors.
1209
+ <br>Answer the question below to boost your Moral Compass score. Then click <strong>Next</strong> to file your final report about the AI system.
1210
+ </p>
1211
+ </div>
1212
+ </div>
1213
+ </div>
1214
+ """
1215
+ },
1216
+
1217
+ # --- MODULE 8: FINAL ERROR REPORT ---
1218
+ # --- MODULE 9: FINAL VERDICT & REPORT GENERATION ---
1219
+ {
1220
+ "id": 9,
1221
+ "title": "Step 4: The Final Verdict",
1222
+ "html": """
1223
+ <div class="scenario-box">
1224
+ <div class="tracker-container">
1225
+ <div class="tracker-step completed">1. RULES</div>
1226
+ <div class="tracker-step completed">2. EVIDENCE</div>
1227
+ <div class="tracker-step completed">3. ERROR</div>
1228
+ <div class="tracker-step active">4. VERDICT</div>
1229
+ </div>
1230
+
1231
+ <div class="slide-body">
1232
+ <h2 class="slide-title" style="margin:0;">STEP 4: FILE YOUR FINAL REPORT</h2>
1233
+
1234
+ <div style="text-align:center; margin-bottom:20px;">
1235
+ <h2 class="slide-title" style="margin-top:10px; color:#0f766e;">Assemble The Case File</h2>
1236
+ <p style="font-size:1.1rem; max-width:820px; margin:0 auto; color:#334155;">
1237
+ You have completed the audit. Now you must build the final report for the court and other stakeholders.
1238
+ <br><strong>Select the valid findings below</strong> to add them to the official record. Be careful—do not include false evidence.
1239
+ </p>
1240
+ </div>
1241
+
1242
+ <div style="display:grid; grid-template-columns: 1fr 1fr; gap:15px; margin-bottom:30px;">
1243
+
1244
+ <details style="background:white; border:2px solid #e2e8f0; border-radius:8px; overflow:hidden; cursor:pointer; box-shadow:0 2px 5px rgba(0,0,0,0.05);">
1245
+ <summary style="list-style:none; padding:15px; font-weight:700; color:#334155; display:flex; align-items:center; gap:10px;">
1246
+ <div style="background:#e2e8f0; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center;">+</div>
1247
+ Finding: "The Distorted Mirror"
1248
+ </summary>
1249
+ <div style="background:#f0fdf4; padding:15px; border-top:1px solid #bbf7d0; color:#166534;">
1250
+ <strong style="color:#15803d;">✅ ADDED TO REPORT</strong>
1251
+ <p style="margin:5px 0 0 0; font-size:0.9rem;">Confirmed: The Input Data incorrectly over-represents specific demographic groups likely due in part to historical bias.</p>
1252
+ </div>
1253
+ </details>
1254
+
1255
+ <details style="background:white; border:2px solid #e2e8f0; border-radius:8px; overflow:hidden; cursor:pointer; box-shadow:0 2px 5px rgba(0,0,0,0.05);">
1256
+ <summary style="list-style:none; padding:15px; font-weight:700; color:#334155; display:flex; align-items:center; gap:10px;">
1257
+ <div style="background:#e2e8f0; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center;">+</div>
1258
+ Finding: "Malicious Programmer Intent"
1259
+ </summary>
1260
+ <div style="background:#fef2f2; padding:15px; border-top:1px solid #fecaca; color:#991b1b;">
1261
+ <strong style="color:#b91c1c;">❌ REJECTED</strong>
1262
+ <p style="margin:5px 0 0 0; font-size:0.9rem;">Incorrect. We found no evidence of malicious code. The bias came from the <em>data</em> and <em>proxies</em>, not the programmer's personality.</p>
1263
+ </div>
1264
+ </details>
1265
+
1266
+ <details style="background:white; border:2px solid #e2e8f0; border-radius:8px; overflow:hidden; cursor:pointer; box-shadow:0 2px 5px rgba(0,0,0,0.05);">
1267
+ <summary style="list-style:none; padding:15px; font-weight:700; color:#334155; display:flex; align-items:center; gap:10px;">
1268
+ <div style="background:#e2e8f0; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center;">+</div>
1269
+ Finding: "Racial Double Standard"
1270
+ </summary>
1271
+ <div style="background:#f0fdf4; padding:15px; border-top:1px solid #bbf7d0; color:#166534;">
1272
+ <strong style="color:#15803d;">✅ ADDED TO REPORT</strong>
1273
+ <p style="margin:5px 0 0 0; font-size:0.9rem;">Confirmed: African-American defendants suffer a 2x higher False Alarm rate than White defendants.</p>
1274
+ </div>
1275
+ </details>
1276
+
1277
+ <details style="background:white; border:2px solid #e2e8f0; border-radius:8px; overflow:hidden; cursor:pointer; box-shadow:0 2px 5px rgba(0,0,0,0.05);">
1278
+ <summary style="list-style:none; padding:15px; font-weight:700; color:#334155; display:flex; align-items:center; gap:10px;">
1279
+ <div style="background:#e2e8f0; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center;">+</div>
1280
+ Finding: "Proxy Variable Leakage"
1281
+ </summary>
1282
+ <div style="background:#f0fdf4; padding:15px; border-top:1px solid #bbf7d0; color:#166534;">
1283
+ <strong style="color:#15803d;">✅ ADDED TO REPORT</strong>
1284
+ <p style="margin:5px 0 0 0; font-size:0.9rem;">Confirmed: "Zip Code" is functioning as a proxy for Race, reintroducing bias even when variables like Race are removed.</p>
1285
+ </div>
1286
+ </details>
1287
+
1288
+ <details style="background:white; border:2px solid #e2e8f0; border-radius:8px; overflow:hidden; cursor:pointer; box-shadow:0 2px 5px rgba(0,0,0,0.05);">
1289
+ <summary style="list-style:none; padding:15px; font-weight:700; color:#334155; display:flex; align-items:center; gap:10px;">
1290
+ <div style="background:#e2e8f0; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center;">+</div>
1291
+ Finding: "Hardware Calculation Error"
1292
+ </summary>
1293
+ <div style="background:#fef2f2; padding:15px; border-top:1px solid #fecaca; color:#991b1b;">
1294
+ <strong style="color:#b91c1c;">❌ REJECTED</strong>
1295
+ <p style="margin:5px 0 0 0; font-size:0.9rem;">Irrelevant. The servers are working fine. The math is correct; the <em>patterns</em> it learned are unfair.</p>
1296
+ </div>
1297
+ </details>
1298
+
1299
+ <details style="background:white; border:2px solid #e2e8f0; border-radius:8px; overflow:hidden; cursor:pointer; box-shadow:0 2px 5px rgba(0,0,0,0.05);">
1300
+ <summary style="list-style:none; padding:15px; font-weight:700; color:#334155; display:flex; align-items:center; gap:10px;">
1301
+ <div style="background:#e2e8f0; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center;">+</div>
1302
+ Finding: "Generalization Blind Spots"
1303
+ </summary>
1304
+ <div style="background:#f0fdf4; padding:15px; border-top:1px solid #bbf7d0; color:#166534;">
1305
+ <strong style="color:#15803d;">✅ ADDED TO REPORT</strong>
1306
+ <p style="margin:5px 0 0 0; font-size:0.9rem;">Confirmed: Lack of data for Women, Younger, and Older defendants creates unreliable predictions.</p>
1307
+ </div>
1308
+ </details>
1309
+
1310
+ </div>
1311
+
1312
+ <div style="background:#f8fafc; border-top:2px solid #e2e8f0; padding:25px; text-align:center; border-radius:0 0 12px 12px; margin-top:-15px;">
1313
+ <h3 style="margin-top:0; color:#1e293b;">⚖️ SUBMIT YOUR RECOMMENDATION (By using the Moral Compass Question below these cards.)</h3>
1314
+ <p style="font-size:1.05rem; margin-bottom:20px; color:#475569;">
1315
+ Based on the evidence filed above, what is your official recommendation regarding this AI system?
1316
+ </p>
1317
+
1318
+ <div style="display:flex; justify-content:center; gap:20px; flex-wrap:wrap;">
1319
+ <div style="background:white; border:1px solid #cbd5e1; padding:15px 25px; border-radius:8px; cursor:pointer; max-width:250px; opacity:0.8; box-shadow:0 2px 4px rgba(0,0,0,0.05);">
1320
+ <div style="font-size:2rem; margin-bottom:10px;">✅</div>
1321
+ <div style="font-weight:700; color:#166534; margin-bottom:5px;">CERTIFY AS SAFE</div>
1322
+ <div style="font-size:0.85rem; color:#475569;">The biases are minor technicalities. Continue using the system.</div>
1323
+ </div>
1324
+
1325
+ <div style="background:white; border:2px solid #ef4444; padding:15px 25px; border-radius:8px; cursor:pointer; max-width:250px; box-shadow:0 4px 12px rgba(239,68,68,0.2);">
1326
+ <div style="font-size:2rem; margin-bottom:10px;">🚨</div>
1327
+ <div style="font-weight:700; color:#b91c1c; margin-bottom:5px;">RED NOTICE: PAUSE & FIX</div>
1328
+ <div style="font-size:0.85rem; color:#7f1d1d;">The system violates Justice & Equity principles. Halt immediately.</div>
1329
+ </div>
1330
+ </div>
1331
+ </div>
1332
+
1333
+ <div style="text-align:center; margin-top:30px;">
1334
+ <p style="font-size:0.95rem; color:#64748b;">
1335
+ Select your final recommendation below to officially file your report and complete your investigation.
1336
+ </p>
1337
+ </div>
1338
+
1339
+ </div>
1340
+ </div>
1341
+ """
1342
+ },
1343
+
1344
+
1345
+ # --- MODULE 10: PROMOTION ---
1346
+ # --- MODULE 10: MISSION ACCOMPLISHED ---
1347
+ {
1348
+ "id": 10,
1349
+ "title": "Mission Accomplished: Promotion Unlocked",
1350
+ "html": """
1351
+ <div class="scenario-box">
1352
+ <div class="tracker-container">
1353
+ <div class="tracker-step completed">✓ RULES</div>
1354
+ <div class="tracker-step completed">✓ EVIDENCE</div>
1355
+ <div class="tracker-step completed">✓ ERROR</div>
1356
+ <div class="tracker-step completed">✓ VERDICT</div>
1357
+ </div>
1358
+
1359
+ <div class="slide-body">
1360
+
1361
+ <div style="text-align:center; margin-bottom:25px;">
1362
+ <h2 class="slide-title" style="margin-top:10px; color:#15803d;">🎉 MISSION ACCOMPLISHED</h2>
1363
+ <p style="font-size:1.1rem; max-width:820px; margin:0 auto; color:#334155;">
1364
+ Report Filed. The court has accepted your recommendation to <strong>PAUSE</strong> the system.
1365
+ </p>
1366
+ </div>
1367
+
1368
+ <div style="background:#f0fdf4; border:2px solid #22c55e; border-radius:12px; padding:20px; margin-bottom:30px; text-align:center; box-shadow: 0 4px 15px rgba(34, 197, 94, 0.1);">
1369
+ <div style="font-size:1.2rem; font-weight:800; color:#15803d; letter-spacing:1px; text-transform:uppercase;">
1370
+ ✅ DECISION VALIDATED
1371
+ </div>
1372
+ <p style="font-size:1.05rem; color:#166534; margin:10px 0 0 0;">
1373
+ You chose the responsible path. That decision required evidence, judgment, and a deep commitment to the principle of <strong>Justice & Equity</strong>.
1374
+ </p>
1375
+ </div>
1376
+
1377
+ <div style="background:linear-gradient(135deg, #eff6ff 0%, #f0fdfa 100%); border:2px solid #0ea5e9; border-radius:16px; padding:0; overflow:hidden; box-shadow: 0 10px 25px rgba(0,0,0,0.05);">
1378
+
1379
+ <div style="background:#0ea5e9; padding:15px; text-align:center; color:white;">
1380
+ <h3 style="margin:0; font-size:1.3rem; letter-spacing:1px;">🎖️ PROMOTION UNLOCKED</h3>
1381
+ <div style="font-size:0.9rem; opacity:0.9;">LEVEL UP: FROM DETECTIVE TO BUILDER</div>
1382
+ </div>
1383
+
1384
+ <div style="padding:25px;">
1385
+ <p style="text-align:center; font-size:1.1rem; color:#334155; margin-bottom:20px;">
1386
+ Exposing bias is only the first half of the mission. Now that you have the evidence, the real work begins.
1387
+ <br><strong>You are trading your Magnifying Glass for a Wrench.</strong>
1388
+ </p>
1389
+
1390
+ <div style="background:white; border-radius:12px; padding:20px; border:1px solid #bae6fd;">
1391
+ <h4 style="margin-top:0; color:#0369a1; text-align:center; margin-bottom:15px;">🎓 NEW ROLE: FAIRNESS ENGINEER</h4>
1392
+
1393
+ <ul style="list-style:none; padding:0; margin:0; font-size:1rem; color:#475569;">
1394
+ <li style="margin-bottom:12px; display:flex; gap:10px; align-items:start;">
1395
+ <span>🔧</span>
1396
+ <span><strong>Your Task 1:</strong> Dismantle the "Proxy Variables" (Remove Zip Code bias).</span>
1397
+ </li>
1398
+ <li style="margin-bottom:12px; display:flex; gap:10px; align-items:start;">
1399
+ <span>📊</span>
1400
+ <span><strong>Your Task 2:</strong> Fix the "Distorted Mirror" by redesigning the data strategy.</span>
1401
+ </li>
1402
+ <li style="display:flex; gap:10px; align-items:start;">
1403
+ <span>🗺️</span>
1404
+ <span><strong>Your Task 3:</strong> Build an ethical roadmap for continuous monitoring.</span>
1405
+ </li>
1406
+ </ul>
1407
+ </div>
1408
+ </div>
1409
+ </div>
1410
+
1411
+ <div style="text-align:center; margin-top:30px;">
1412
+ <p style="font-size:1.1rem; font-weight:600; color:#475569;">
1413
+ 👉 Your next mission starts in <strong>Activity 8: The Fairness Fixer</strong>.
1414
+ <br>
1415
+ <span style="font-size:0.95rem; font-weight:400;"><strong>Scroll down to the next app</strong> to conclude this audit and begin the repairs.</span>
1416
+ </p>
1417
+ </div>
1418
+
1419
+ </div>
1420
+ </div>
1421
+ """,
1422
+ },
1423
+ ]
1424
+ # --- 5. INTERACTIVE CONTENT CONFIGURATION (APP 1) ---
1425
+ QUIZ_CONFIG = {
1426
+ 0: {
1427
+ "t": "t1",
1428
+ # Added bold incentive text to the question
1429
+ "q": "🚀 **First Score Opportunity:** Why do we multiply your Accuracy by Ethical Progress? (Answer correctly to earn your first Moral Compass Score boost!)",
1430
+ "o": [
1431
+ "A) Because simple accuracy ignores potential bias and harm.",
1432
+ "B) To make the leaderboard math more complicated.",
1433
+ "C) Accuracy is the only metric that actually matters.",
1434
+ ],
1435
+ "a": "A) Because simple accuracy ignores potential bias and harm.",
1436
+ # Updated success message to confirm the 'win'
1437
+ "success": "<strong>Score Unlocked!</strong> Calibration complete. You are now officially on the leaderboard.",
1438
+ },
1439
+ 1: {
1440
+ "t": "t2",
1441
+ "q": "What is the best first step before you start examining the model's data?",
1442
+ "o": [
1443
+ "Jump straight into the data and look for patterns.",
1444
+ "Learn the rules that define what counts as bias.",
1445
+ "Let the model explain its own decisions.",
1446
+ ],
1447
+ "a": "Learn the rules that define what counts as bias.",
1448
+ "success": "Briefing complete. You’re starting your investigation with the right rules in mind.",
1449
+ },
1450
+ 2: {
1451
+ "t": "t3",
1452
+ "q": "What does Justice & Equity require?",
1453
+ "o": [
1454
+ "Explain model decisions",
1455
+ "Checking group level prediction errors to prevent systematic harm",
1456
+ "Minimize error rate",
1457
+ ],
1458
+ "a": "Checking group level prediction errors to prevent systematic harm",
1459
+ "success": "Protocol Active. You are now auditing for Justice & Fairness.",
1460
+ },
1461
+ 3: {
1462
+ "t": "t4",
1463
+ "q": "Detective, we suspect the input data is a 'Distorted Mirror' of reality. To confirm if Representation Bias exists, what is your primary forensic target?",
1464
+ "o": [
1465
+ "A) I need to read the judge's personal diary entries.",
1466
+ "B) I need to check if the computer is plugged in correctly.",
1467
+ "C) I need to compare the Demographic Distributions (Race/Gender) in the data against real-world population statistics.",
1468
+ ],
1469
+ "a": "C) I need to compare the Demographic Distributions (Race/Gender) in the data against real-world population statistics.",
1470
+ "success": "Target Acquired. You are ready to enter the Data Forensics Lab.",
1471
+ },
1472
+ 4: {
1473
+ "t": "t5",
1474
+ "q": "Forensic Analysis Review: You flagged the Gender data as a 'Data Gap' (only 19% Female). According to your evidence log, what is the specific technical risk for this group?",
1475
+ "o": [
1476
+ "A) The model will have a 'Blind Spot' because it hasn't seen enough examples to learn accurate patterns.",
1477
+ "B) The AI will automatically target this group due to historical over-policing.",
1478
+ "C) The model will default to the 'Real World' statistics to fill in the missing numbers.",
1479
+ ],
1480
+ "a": "A) The model will have a 'Blind Spot' because it hasn't seen enough examples to learn accurate patterns.",
1481
+ "success": "Evidence Locked. You understand that 'Missing Data' creates blind spots, making predictions for that group less reliable.",
1482
+ },
1483
+ # --- QUESTION 4 (Evidence Report Summary) ---
1484
+ 5: {
1485
+ "t": "t6",
1486
+ "q": "Detective, review your Evidence Summary table. You found instances of both Over-representation (Race) and Under-representation (Gender/Age). What is your general conclusion about how Representation Bias affects the AI?",
1487
+ "o": [
1488
+ "A) It confirms the dataset is neutral, as the 'Over' and 'Under' categories mathematically cancel each other out.",
1489
+ "B) It creates a 'Risk of Increased Prediction Error' in BOTH directions—whether a group is exaggerated or ignored, the AI's view of reality is warped.",
1490
+ "C) It only creates risk when data is missing (Under-represented); having extra data (Over-represented) actually makes the model more accurate.",
1491
+ ],
1492
+ "a": "B) It creates a 'Risk of Increased Prediction Error' in BOTH directions—whether a group is exaggerated or ignored, the AI's view of reality is warped.",
1493
+ "success": "Conclusion Verified. Distorted data—whether inflated or missing—can lead to distorted justice.",
1494
+ },
1495
+ 6: {
1496
+ "t": "t7",
1497
+ "q": "Detective, you are hunting for the 'Double Standard' pattern. Which specific piece of evidence represents this Red Flag?",
1498
+ "o": [
1499
+ "A) The model makes zero mistakes for any group.",
1500
+ "B) One group suffers from a significantly higher 'False Alarm' rate than another group.",
1501
+ "C) The input data contains more men than women.",
1502
+ ],
1503
+ "a": "B) One group suffers from a significantly higher 'False Alarm' rate than another group.",
1504
+ "success": "Pattern Confirmed. When the error rate is lopsided, it's a Double Standard.",
1505
+ },
1506
+ # --- QUESTION 6 (Race Error Gap) ---
1507
+ 7: {
1508
+ "t": "t8",
1509
+ "q": "Review your data log. What did the 'False Alarm' scan reveal about the treatment of African-American defendants?",
1510
+ "o": [
1511
+ "A) They are treated exactly the same as White defendants.",
1512
+ "B) They are missed by the system more often (Leniency Bias).",
1513
+ "C) They are nearly twice as likely to be wrongly flagged as 'High Risk' (Punitive Bias).",
1514
+ ],
1515
+ "a": "C) They are nearly twice as likely to be wrongly flagged as 'High Risk' (Punitive Bias).",
1516
+ "success": "Evidence Logged. The system is punishing innocent people based on race.",
1517
+ },
1518
+
1519
+ # --- QUESTION 7 (Generalization & Proxy Scan) ---
1520
+ 8: {
1521
+ "t": "t9",
1522
+ "q": "The Geography Scan showed a massive error rate in Urban Zones. What does this prove about 'Zip Codes'?",
1523
+ "o": [
1524
+ "A) Zip Codes are acting as a 'Proxy Variable' to target specific groups, even if variables like Race are removed from the dataset.",
1525
+ "B) The AI is simply bad at reading maps and location data.",
1526
+ "C) People in cities naturally generate more computer errors than people in rural areas.",
1527
+ ],
1528
+ "a": "A) Zip Codes are acting as a 'Proxy Variable' to target specific groups, even if variables like Race are removed from the dataset.",
1529
+ "success": "Proxy Identified. Hiding a variable doesn't work if you leave a proxy behind.",
1530
+ },
1531
+
1532
+ # --- QUESTION 8 (Audit Summary) ---
1533
+ 9: {
1534
+ "t": "t10",
1535
+ "q": "You have closed the case file. Which of the following is CONFIRMED as the 'Primary Threat' in your final report?",
1536
+ "o": [
1537
+ "A) A Racial Double Standard where innocent Black defendants are penalized twice as often.",
1538
+ "B) Malicious code written by hackers to break the system.",
1539
+ "C) A hardware failure in the server room causing random math errors.",
1540
+ ],
1541
+ "a": "A) A Racial Double Standard where innocent Black defendants are penalized twice as often.",
1542
+ "success": "Threat Assessed. The bias is confirmed and documented.",
1543
+ },
1544
+
1545
+ # --- QUESTION 9 (Final Verdict) ---
1546
+ 10: {
1547
+ "t": "t11",
1548
+ "q": "Based on the severe violations of Justice & Equity found in your audit, what is your final recommendation to the court?",
1549
+ "o": [
1550
+ "A) CERTIFY: The system is mostly fine, minor glitches are acceptable.",
1551
+ "B) RED NOTICE: Pause the system for repairs immediately because it is unsafe and biased.",
1552
+ "C) WARNING: Only use the AI on weekends when crime is lower.",
1553
+ ],
1554
+ "a": "B) RED NOTICE: Pause the system for repairs immediately because it is unsafe and biased.",
1555
+ "success": "Verdict Delivered. You successfully stopped a harmful system.",
1556
+ },
1557
+ }
1558
+
1559
+
1560
+ # --- 6. SCENARIO CONFIG (for Module 0) ---
1561
+ SCENARIO_CONFIG = {
1562
+ "Criminal risk prediction": {
1563
+ "q": (
1564
+ "A system predicts who might reoffend.\n"
1565
+ "Why isn’t accuracy alone enough?"
1566
+ ),
1567
+ "summary": "Even tiny bias can repeat across thousands of bail/sentencing calls — real lives, real impact.",
1568
+ "a": "Accuracy can look good overall while still being unfair to specific groups affected by the model.",
1569
+ "rationale": "Bias at scale means one pattern can hurt many people quickly. We must check subgroup fairness, not just the top-line score."
1570
+ },
1571
+ "Loan approval system": {
1572
+ "q": (
1573
+ "A model decides who gets a loan.\n"
1574
+ "What’s the biggest risk if it learns from biased history?"
1575
+ ),
1576
+ "summary": "Some groups get blocked over and over, shutting down chances for housing, school, and stability.",
1577
+ "a": "It can repeatedly deny the same groups, copying old patterns and locking out opportunity.",
1578
+ "rationale": "If past approvals were unfair, the model can mirror that and keep doors closed — not just once, but repeatedly."
1579
+ },
1580
+ "College admissions screening": {
1581
+ "q": (
1582
+ "A tool ranks college applicants using past admissions data.\n"
1583
+ "What’s the main fairness risk?"
1584
+ ),
1585
+ "summary": "It can favor the same profiles as before, overlooking great candidates who don’t ‘match’ history.",
1586
+ "a": "It can amplify past preferences and exclude talented students who don’t fit the old mold.",
1587
+ "rationale": "Models trained on biased patterns can miss potential. We need checks to ensure diverse, fair selection."
1588
+ }
1589
+ }
1590
+
1591
+ # --- 7. SLIDE 3 RIPPLE EFFECT SLIDER HELPER ---
1592
+ def simulate_ripple_effect_cases(cases_per_year):
1593
+ try:
1594
+ c = float(cases_per_year)
1595
+ except (TypeError, ValueError):
1596
+ c = 0.0
1597
+ c_int = int(c)
1598
+ if c_int <= 0:
1599
+ message = (
1600
+ "If the system isn't used on any cases, its bias can't hurt anyone yet — "
1601
+ "but once it goes live, each biased decision can scale quickly."
1602
+ )
1603
+ elif c_int < 5000:
1604
+ message = (
1605
+ f"Even at <strong>{c_int}</strong> cases per year, a biased model can quietly "
1606
+ "affect hundreds of people over time."
1607
+ )
1608
+ elif c_int < 15000:
1609
+ message = (
1610
+ f"At around <strong>{c_int}</strong> cases per year, a biased model could unfairly label "
1611
+ "thousands of people as 'high risk.'"
1612
+ )
1613
+ else:
1614
+ message = (
1615
+ f"At <strong>{c_int}</strong> cases per year, one flawed algorithm can shape the futures "
1616
+ "of an entire region — turning hidden bias into thousands of unfair decisions."
1617
+ )
1618
+
1619
+ return f"""
1620
+ <div class="hint-box interactive-block">
1621
+ <p style="margin-bottom:4px; font-size:1.05rem;">
1622
+ <strong>Estimated cases processed per year:</strong> {c_int}
1623
+ </p>
1624
+ <p style="margin-bottom:0; font-size:1.05rem;">
1625
+ {message}
1626
+ </p>
1627
+ </div>
1628
+ """
1629
+
1630
+ # --- 7b. STATIC SCENARIOS RENDERER (Module 0) ---
1631
+ def render_static_scenarios():
1632
+ cards = []
1633
+ for name, cfg in SCENARIO_CONFIG.items():
1634
+ q_html = cfg["q"].replace("\\n", "<br>")
1635
+ cards.append(f"""
1636
+ <div class="hint-box" style="margin-top:12px;">
1637
+ <div style="font-weight:700; font-size:1.05rem;">📘 {name}</div>
1638
+ <p style="margin:8px 0 6px 0;">{q_html}</p>
1639
+ <p style="margin:0;"><strong>Key takeaway:</strong> {cfg["a"]}</p>
1640
+ <p style="margin:6px 0 0 0; color:var(--body-text-color-subdued);">{cfg["f_correct"]}</p>
1641
+ </div>
1642
+ """)
1643
+ return "<div class='interactive-block'>" + "".join(cards) + "</div>"
1644
+
1645
+ def render_scenario_card(name: str):
1646
+ cfg = SCENARIO_CONFIG.get(name)
1647
+ if not cfg:
1648
+ return "<div class='hint-box'>Select a scenario to view details.</div>"
1649
+ q_html = cfg["q"].replace("\n", "<br>")
1650
+ return f"""
1651
+ <div class="scenario-box">
1652
+ <h3 class="slide-title" style="font-size:1.4rem; margin-bottom:8px;">📘 {name}</h3>
1653
+ <div class="slide-body">
1654
+ <div class="hint-box">
1655
+ <p style="margin:0 0 6px 0; font-size:1.05rem;">{q_html}</p>
1656
+ <p style="margin:0 0 6px 0;"><strong>Key takeaway:</strong> {cfg['a']}</p>
1657
+ <p style="margin:0; color:var(--body-text-color-subdued);">{cfg['rationale']}</p>
1658
+ </div>
1659
+ </div>
1660
+ </div>
1661
+ """
1662
+
1663
+ def render_scenario_buttons():
1664
+ # Stylized, high-contrast buttons optimized for 17–20 age group
1665
+ btns = []
1666
+ for name in SCENARIO_CONFIG.keys():
1667
+ btns.append(gr.Button(
1668
+ value=f"🎯 {name}",
1669
+ variant="primary",
1670
+ elem_classes=["scenario-choice-btn"]
1671
+ ))
1672
+ return btns
1673
+
1674
+ # --- 8. LEADERBOARD & API LOGIC ---
1675
+ def get_leaderboard_data(client, username, team_name, local_task_list=None, override_score=None):
1676
+ try:
1677
+ resp = client.list_users(table_id=TABLE_ID, limit=500)
1678
+ users = resp.get("users", [])
1679
+
1680
+ # 1. OPTIMISTIC UPDATE
1681
+ if override_score is not None:
1682
+ found = False
1683
+ for u in users:
1684
+ if u.get("username") == username:
1685
+ u["moralCompassScore"] = override_score
1686
+ found = True
1687
+ break
1688
+ if not found:
1689
+ users.append(
1690
+ {"username": username, "moralCompassScore": override_score, "teamName": team_name}
1691
+ )
1692
+
1693
+ # 2. SORT with new score
1694
+ users_sorted = sorted(
1695
+ users, key=lambda x: float(x.get("moralCompassScore", 0) or 0), reverse=True
1696
+ )
1697
+
1698
+ my_user = next((u for u in users_sorted if u.get("username") == username), None)
1699
+ score = float(my_user.get("moralCompassScore", 0) or 0) if my_user else 0.0
1700
+ rank = users_sorted.index(my_user) + 1 if my_user else 0
1701
+
1702
+ completed_task_ids = (
1703
+ local_task_list
1704
+ if local_task_list is not None
1705
+ else (my_user.get("completedTaskIds", []) if my_user else [])
1706
+ )
1707
+
1708
+ team_map = {}
1709
+ for u in users:
1710
+ t = u.get("teamName")
1711
+ s = float(u.get("moralCompassScore", 0) or 0)
1712
+ if t:
1713
+ if t not in team_map:
1714
+ team_map[t] = {"sum": 0, "count": 0}
1715
+ team_map[t]["sum"] += s
1716
+ team_map[t]["count"] += 1
1717
+ teams_sorted = []
1718
+ for t, d in team_map.items():
1719
+ teams_sorted.append({"team": t, "avg": d["sum"] / d["count"]})
1720
+ teams_sorted.sort(key=lambda x: x["avg"], reverse=True)
1721
+ my_team = next((t for t in teams_sorted if t["team"] == team_name), None)
1722
+ team_rank = teams_sorted.index(my_team) + 1 if my_team else 0
1723
+ return {
1724
+ "score": score,
1725
+ "rank": rank,
1726
+ "team_rank": team_rank,
1727
+ "all_users": users_sorted,
1728
+ "all_teams": teams_sorted,
1729
+ "completed_task_ids": completed_task_ids,
1730
+ }
1731
+ except Exception:
1732
+ return None
1733
+
1734
+
1735
+ def ensure_table_and_get_data(username, token, team_name, task_list_state=None):
1736
+ if not username or not token:
1737
+ return None, username
1738
+ os.environ["MORAL_COMPASS_API_BASE_URL"] = DEFAULT_API_URL
1739
+ client = MoralcompassApiClient(api_base_url=DEFAULT_API_URL, auth_token=token)
1740
+ try:
1741
+ client.get_table(TABLE_ID)
1742
+ except Exception:
1743
+ try:
1744
+ client.create_table(
1745
+ table_id=TABLE_ID,
1746
+ display_name="LMS",
1747
+ playground_url="https://example.com",
1748
+ )
1749
+ except Exception:
1750
+ pass
1751
+ return get_leaderboard_data(client, username, team_name, task_list_state), username
1752
+
1753
+
1754
+ def trigger_api_update(
1755
+ username, token, team_name, module_id, user_real_accuracy, task_list_state, append_task_id=None
1756
+ ):
1757
+ if not username or not token:
1758
+ return None, None, username, task_list_state
1759
+ os.environ["MORAL_COMPASS_API_BASE_URL"] = DEFAULT_API_URL
1760
+ client = MoralcompassApiClient(api_base_url=DEFAULT_API_URL, auth_token=token)
1761
+
1762
+ acc = float(user_real_accuracy) if user_real_accuracy is not None else 0.0
1763
+
1764
+ # 1. Update Lists
1765
+ old_task_list = list(task_list_state) if task_list_state else []
1766
+ new_task_list = list(old_task_list)
1767
+ if append_task_id and append_task_id not in new_task_list:
1768
+ new_task_list.append(append_task_id)
1769
+ try:
1770
+ new_task_list.sort(
1771
+ key=lambda x: int(x[1:]) if x.startswith("t") and x[1:].isdigit() else 0
1772
+ )
1773
+ except Exception:
1774
+ pass
1775
+
1776
+ # 2. Write to Server
1777
+ tasks_completed = len(new_task_list)
1778
+ client.update_moral_compass(
1779
+ table_id=TABLE_ID,
1780
+ username=username,
1781
+ team_name=team_name,
1782
+ metrics={"accuracy": acc},
1783
+ tasks_completed=tasks_completed,
1784
+ total_tasks=TOTAL_COURSE_TASKS,
1785
+ primary_metric="accuracy",
1786
+ completed_task_ids=new_task_list,
1787
+ )
1788
+
1789
+ # 3. Calculate Scores Locally (Simulate Before/After)
1790
+ old_score_calc = acc * (len(old_task_list) / TOTAL_COURSE_TASKS)
1791
+ new_score_calc = acc * (len(new_task_list) / TOTAL_COURSE_TASKS)
1792
+
1793
+ # 4. Get Data with Override to force rank re-calculation
1794
+ prev_data = get_leaderboard_data(
1795
+ client, username, team_name, old_task_list, override_score=old_score_calc
1796
+ )
1797
+ lb_data = get_leaderboard_data(
1798
+ client, username, team_name, new_task_list, override_score=new_score_calc
1799
+ )
1800
+
1801
+ return prev_data, lb_data, username, new_task_list
1802
+
1803
+ # --- 9. SUCCESS MESSAGE RENDERER (approved version) ---
1804
+ # --- 8. SUCCESS MESSAGE / DASHBOARD RENDERING ---
1805
+ def generate_success_message(prev, curr, specific_text):
1806
+ old_score = float(prev.get("score", 0) or 0) if prev else 0.0
1807
+ new_score = float(curr.get("score", 0) or 0)
1808
+ diff_score = new_score - old_score
1809
+
1810
+ old_rank = prev.get("rank", "–") if prev else "–"
1811
+ new_rank = curr.get("rank", "–")
1812
+
1813
+ # Are ranks integers? If yes, we can reason about direction.
1814
+ ranks_are_int = isinstance(old_rank, int) and isinstance(new_rank, int)
1815
+ rank_diff = old_rank - new_rank if ranks_are_int else 0 # positive => rank improved
1816
+
1817
+ # --- STYLE SELECTION -------------------------------------------------
1818
+ # First-time score: special "on the board" moment
1819
+ if old_score == 0 and new_score > 0:
1820
+ style_key = "first"
1821
+ else:
1822
+ if ranks_are_int:
1823
+ if rank_diff >= 3:
1824
+ style_key = "major" # big rank jump
1825
+ elif rank_diff > 0:
1826
+ style_key = "climb" # small climb
1827
+ elif diff_score > 0 and new_rank == old_rank:
1828
+ style_key = "solid" # better score, same rank
1829
+ else:
1830
+ style_key = "tight" # leaderboard shifted / no visible rank gain
1831
+ else:
1832
+ # When we can't trust rank as an int, lean on score change
1833
+ style_key = "solid" if diff_score > 0 else "tight"
1834
+
1835
+ # --- TEXT + CTA BY STYLE --------------------------------------------
1836
+ card_class = "profile-card success-card"
1837
+
1838
+ if style_key == "first":
1839
+ card_class += " first-score"
1840
+ header_emoji = "🎉"
1841
+ header_title = "You're Officially on the Board!"
1842
+ summary_line = (
1843
+ "You just earned your first Moral Compass Score — you're now part of the global rankings."
1844
+ )
1845
+ cta_line = "Scroll down to take your next step and start climbing."
1846
+ elif style_key == "major":
1847
+ header_emoji = "🔥"
1848
+ header_title = "Major Moral Compass Boost!"
1849
+ summary_line = (
1850
+ "Your decision made a big impact — you just moved ahead of other participants."
1851
+ )
1852
+ cta_line = "Scroll down to take on your next challenge and keep the boost going."
1853
+ elif style_key == "climb":
1854
+ header_emoji = "🚀"
1855
+ header_title = "You're Climbing the Leaderboard"
1856
+ summary_line = "Nice work — you edged out a few other participants."
1857
+ cta_line = "Scroll down to continue your investigation and push even higher."
1858
+ elif style_key == "tight":
1859
+ header_emoji = "📊"
1860
+ header_title = "The Leaderboard Is Shifting"
1861
+ summary_line = (
1862
+ "Other teams are moving too. You'll need a few more strong decisions to stand out."
1863
+ )
1864
+ cta_line = "Take on the next question to strengthen your position."
1865
+ else: # "solid"
1866
+ header_emoji = "✅"
1867
+ header_title = "Progress Logged"
1868
+ summary_line = "Your ethical insight increased your Moral Compass Score."
1869
+ cta_line = "Try the next scenario to break into the next tier."
1870
+
1871
+ # --- SCORE / RANK LINES ---------------------------------------------
1872
+
1873
+ # First-time: different wording (no previous score)
1874
+ if style_key == "first":
1875
+ score_line = f"🧭 Score: <strong>{new_score:.3f}</strong>"
1876
+ if ranks_are_int:
1877
+ rank_line = f"🏅 Initial Rank: <strong>#{new_rank}</strong>"
1878
+ else:
1879
+ rank_line = f"🏅 Initial Rank: <strong>#{new_rank}</strong>"
1880
+ else:
1881
+ score_line = (
1882
+ f"🧭 Score: {old_score:.3f} → <strong>{new_score:.3f}</strong> "
1883
+ f"(+{diff_score:.3f})"
1884
+ )
1885
+
1886
+ if ranks_are_int:
1887
+ if old_rank == new_rank:
1888
+ rank_line = f"📊 Rank: <strong>#{new_rank}</strong> (holding steady)"
1889
+ elif rank_diff > 0:
1890
+ rank_line = (
1891
+ f"📈 Rank: #{old_rank} → <strong>#{new_rank}</strong> "
1892
+ f"(+{rank_diff} places)"
1893
+ )
1894
+ else:
1895
+ rank_line = (
1896
+ f"🔻 Rank: #{old_rank} → <strong>#{new_rank}</strong> "
1897
+ f"({rank_diff} places)"
1898
+ )
1899
+ else:
1900
+ rank_line = f"📊 Rank: <strong>#{new_rank}</strong>"
1901
+
1902
+ # --- HTML COMPOSITION -----------------------------------------------
1903
+ return f"""
1904
+ <div class="{card_class}">
1905
+ <div class="success-header">
1906
+ <div>
1907
+ <div class="success-title">{header_emoji} {header_title}</div>
1908
+ <div class="success-summary">{summary_line}</div>
1909
+ </div>
1910
+ <div class="success-delta">
1911
+ +{diff_score:.3f}
1912
+ </div>
1913
+ </div>
1914
+
1915
+ <div class="success-metrics">
1916
+ <div class="success-metric-line">{score_line}</div>
1917
+ <div class="success-metric-line">{rank_line}</div>
1918
+ </div>
1919
+
1920
+ <div class="success-body">
1921
+ <p class="success-body-text">{specific_text}</p>
1922
+ <p class="success-cta">{cta_line}</p>
1923
+ </div>
1924
+ </div>
1925
+ """
1926
+
1927
+ # --- 10. DASHBOARD & LEADERBOARD RENDERERS ---
1928
+ def render_top_dashboard(data, module_id):
1929
+ display_score = 0.0
1930
+ count_completed = 0
1931
+ rank_display = "–"
1932
+ team_rank_display = "–"
1933
+ if data:
1934
+ display_score = float(data.get("score", 0.0))
1935
+ rank_display = f"#{data.get('rank', '–')}"
1936
+ team_rank_display = f"#{data.get('team_rank', '–')}"
1937
+ count_completed = len(data.get("completed_task_ids", []) or [])
1938
+ progress_pct = min(100, int((count_completed / TOTAL_COURSE_TASKS) * 100))
1939
+ return f"""
1940
+ <div class="summary-box">
1941
+ <div class="summary-box-inner">
1942
+ <div class="summary-metrics">
1943
+ <div style="text-align:center;">
1944
+ <div class="label-text">Moral Compass Score</div>
1945
+ <div class="score-text-primary">🧭 {display_score:.3f}</div>
1946
+ </div>
1947
+ <div class="divider-vertical"></div>
1948
+ <div style="text-align:center;">
1949
+ <div class="label-text">Team Rank</div>
1950
+ <div class="score-text-team">{team_rank_display}</div>
1951
+ </div>
1952
+ <div class="divider-vertical"></div>
1953
+ <div style="text-align:center;">
1954
+ <div class="label-text">Global Rank</div>
1955
+ <div class="score-text-global">{rank_display}</div>
1956
+ </div>
1957
+ </div>
1958
+ <div class="summary-progress">
1959
+ <div class="progress-label">Mission Progress: {progress_pct}%</div>
1960
+ <div class="progress-bar-bg">
1961
+ <div class="progress-bar-fill" style="width:{progress_pct}%;"></div>
1962
+ </div>
1963
+ </div>
1964
+ </div>
1965
+ </div>
1966
+ """
1967
+
1968
+
1969
+ def render_leaderboard_card(data, username, team_name):
1970
+ team_rows = ""
1971
+ user_rows = ""
1972
+ if data and data.get("all_teams"):
1973
+ for i, t in enumerate(data["all_teams"]):
1974
+ cls = "row-highlight-team" if t["team"] == team_name else "row-normal"
1975
+ team_rows += (
1976
+ f"<tr class='{cls}'><td style='padding:8px;text-align:center;'>{i+1}</td>"
1977
+ f"<td style='padding:8px;'>{t['team']}</td>"
1978
+ f"<td style='padding:8px;text-align:right;'>{t['avg']:.3f}</td></tr>"
1979
+ )
1980
+ if data and data.get("all_users"):
1981
+ for i, u in enumerate(data["all_users"]):
1982
+ cls = "row-highlight-me" if u.get("username") == username else "row-normal"
1983
+ sc = float(u.get("moralCompassScore", 0))
1984
+ if u.get("username") == username and data.get("score") != sc:
1985
+ sc = data.get("score")
1986
+ user_rows += (
1987
+ f"<tr class='{cls}'><td style='padding:8px;text-align:center;'>{i+1}</td>"
1988
+ f"<td style='padding:8px;'>{u.get('username','')}</td>"
1989
+ f"<td style='padding:8px;text-align:right;'>{sc:.3f}</td></tr>"
1990
+ )
1991
+ return f"""
1992
+ <div class="scenario-box leaderboard-card">
1993
+ <h3 class="slide-title" style="margin-bottom:10px;">📊 Live Standings</h3>
1994
+ <div class="lb-tabs">
1995
+ <input type="radio" id="lb-tab-team" name="lb-tabs" checked>
1996
+ <label for="lb-tab-team" class="lb-tab-label">🏆 Team</label>
1997
+ <input type="radio" id="lb-tab-user" name="lb-tabs">
1998
+ <label for="lb-tab-user" class="lb-tab-label">👤 Individual</label>
1999
+ <div class="lb-tab-panels">
2000
+ <div class="lb-panel panel-team">
2001
+ <div class='table-container'>
2002
+ <table class='leaderboard-table'>
2003
+ <thead>
2004
+ <tr><th>Rank</th><th>Team</th><th style='text-align:right;'>Avg 🧭</th></tr>
2005
+ </thead>
2006
+ <tbody>{team_rows}</tbody>
2007
+ </table>
2008
+ </div>
2009
+ </div>
2010
+ <div class="lb-panel panel-user">
2011
+ <div class='table-container'>
2012
+ <table class='leaderboard-table'>
2013
+ <thead>
2014
+ <tr><th>Rank</th><th>Agent</th><th style='text-align:right;'>Score 🧭</th></tr>
2015
+ </thead>
2016
+ <tbody>{user_rows}</tbody>
2017
+ </table>
2018
+ </div>
2019
+ </div>
2020
+ </div>
2021
+ </div>
2022
+ </div>
2023
+ """
2024
+
2025
+ def check_audit_report_selection(selected_biases: List[str]) -> Tuple[str, str]:
2026
+ # Define the correct findings (matching the choices defined in the front-end)
2027
+ CORRECT_FINDINGS = [
2028
+ "Choice A: Punitive Bias (Race): AA defendants were twice as likely to be falsely labeled 'High Risk.'",
2029
+ "Choice B: Generalization (Gender): The model made more False Alarm errors for women than for men.",
2030
+ "Choice C: Leniency Pattern (Race): White defendants who re-offended were more likely to be labeled 'Low Risk.'",
2031
+ "Choice E: Proxy Bias (Geography): Location acted as a proxy, doubling False Alarms in high-density areas.",
2032
+ ]
2033
+
2034
+ # Define the incorrect finding
2035
+ INCORRECT_FINDING = "Choice D: FALSE STATEMENT: The model achieved an equal False Negative Rate (FNR) across all races."
2036
+
2037
+ # Separate correct from incorrect selections
2038
+ correctly_selected = [s for s in selected_biases if s in CORRECT_FINDINGS]
2039
+ incorrectly_selected = [s for s in selected_biases if s == INCORRECT_FINDING]
2040
+
2041
+ # Check if any correct finding was missed
2042
+ missed_correct = [s for s in CORRECT_FINDINGS if s not in selected_biases]
2043
+
2044
+ # --- Generate Feedback ---
2045
+ feedback_html = ""
2046
+ if incorrectly_selected:
2047
+ feedback_html = f"<div class='hint-box' style='border-left:4px solid #ef4444; color:#b91c1c;'>❌ ERROR: The statement '{INCORRECT_FINDING.split(':')[0]}' is NOT a true finding. Check your lab results and try again.</div>"
2048
+ elif missed_correct:
2049
+ feedback_html = f"<div class='hint-box' style='border-left:4px solid #f97316; color:#f97316;'>⚠️ INCOMPLETE: You missed {len(missed_correct)} piece(s) of key evidence. Your final report must be complete.</div>"
2050
+ elif len(selected_biases) == len(CORRECT_FINDINGS):
2051
+ feedback_html = "<div class='hint-box' style='border-left:4px solid #22c55e; color:#16a34a;'>✅ EVIDENCE SECURED: This is a complete and accurate diagnosis of the model's systematic failure.</div>"
2052
+ else:
2053
+ feedback_html = "<div class='hint-box' style='border-left:4px solid var(--color-accent);'>Gathering evidence...</div>"
2054
+
2055
+ # --- Build Markdown Report Preview ---
2056
+ if not correctly_selected:
2057
+ report_markdown = "Select the evidence cards above to start drafting your report. (The draft report will appear here.)"
2058
+ else:
2059
+ lines = []
2060
+ lines.append("### 🧾 Draft Audit Report")
2061
+ lines.append("\n**Findings of Systemic Error:**")
2062
+
2063
+ # Map short findings to the markdown report
2064
+ finding_map = {
2065
+ "Choice A": "Punitive Bias (Race): The model is twice as harsh on AA defendants.",
2066
+ "Choice B": "Generalization (Gender): Higher False Alarm errors for women.",
2067
+ "Choice C": "Leniency Pattern (Race): More missed warnings for White defendants.",
2068
+ "Choice E": "Proxy Bias (Geography): Location acts as a stand-in for race/class.",
2069
+ }
2070
+
2071
+ for i, choice in enumerate(CORRECT_FINDINGS):
2072
+ if choice in correctly_selected:
2073
+ short_key = choice.split(':')[0]
2074
+ lines.append(f"{i+1}. {finding_map[short_key]}")
2075
+
2076
+ if len(correctly_selected) == len(CORRECT_FINDINGS) and not incorrectly_selected:
2077
+ lines.append("\n**CONCLUSION:** The evidence proves the system creates unequal harm and violates Justice & Equity.")
2078
+
2079
+ report_markdown = "\n".join(lines)
2080
+
2081
+ return report_markdown, feedback_html
2082
+
2083
+ # --- 11. CSS ---
2084
+ css = """
2085
+ /* Layout + containers */
2086
+ .summary-box {
2087
+ background: var(--block-background-fill);
2088
+ padding: 20px;
2089
+ border-radius: 12px;
2090
+ border: 1px solid var(--border-color-primary);
2091
+ margin-bottom: 20px;
2092
+ box-shadow: 0 4px 12px rgba(0,0,0,0.06);
2093
+ }
2094
+ .summary-box-inner { display: flex; align-items: center; justify-content: space-between; gap: 30px; }
2095
+ .summary-metrics { display: flex; gap: 30px; align-items: center; }
2096
+ .summary-progress { width: 560px; max-width: 100%; }
2097
+
2098
+ /* Scenario cards */
2099
+ .scenario-box {
2100
+ padding: 24px;
2101
+ border-radius: 14px;
2102
+ background: var(--block-background-fill);
2103
+ border: 1px solid var(--border-color-primary);
2104
+ margin-bottom: 22px;
2105
+ box-shadow: 0 6px 18px rgba(0,0,0,0.08);
2106
+ }
2107
+ .slide-title { margin-top: 0; font-size: 1.9rem; font-weight: 800; }
2108
+ .slide-body { font-size: 1.12rem; line-height: 1.65; }
2109
+
2110
+ /* Hint boxes */
2111
+ .hint-box {
2112
+ padding: 12px;
2113
+ border-radius: 10px;
2114
+ background: var(--background-fill-secondary);
2115
+ border: 1px solid var(--border-color-primary);
2116
+ margin-top: 10px;
2117
+ font-size: 0.98rem;
2118
+ }
2119
+
2120
+ /* Success / profile card */
2121
+ .profile-card.success-card {
2122
+ padding: 20px;
2123
+ border-radius: 14px;
2124
+ border-left: 6px solid #22c55e;
2125
+ background: linear-gradient(135deg, rgba(34,197,94,0.08), var(--block-background-fill));
2126
+ margin-top: 16px;
2127
+ box-shadow: 0 4px 18px rgba(0,0,0,0.08);
2128
+ font-size: 1.04rem;
2129
+ line-height: 1.55;
2130
+ }
2131
+ .profile-card.first-score {
2132
+ border-left-color: #facc15;
2133
+ background: linear-gradient(135deg, rgba(250,204,21,0.18), var(--block-background-fill));
2134
+ }
2135
+ .success-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 8px; }
2136
+ .success-title { font-size: 1.26rem; font-weight: 900; color: #16a34a; }
2137
+ .success-summary { font-size: 1.06rem; color: var(--body-text-color-subdued); margin-top: 4px; }
2138
+ .success-delta { font-size: 1.5rem; font-weight: 800; color: #16a34a; }
2139
+ .success-metrics { margin-top: 10px; padding: 10px 12px; border-radius: 10px; background: var(--background-fill-secondary); font-size: 1.06rem; }
2140
+ .success-metric-line { margin-bottom: 4px; }
2141
+ .success-body { margin-top: 10px; font-size: 1.06rem; }
2142
+ .success-body-text { margin: 0 0 6px 0; }
2143
+ .success-cta { margin: 4px 0 0 0; font-weight: 700; font-size: 1.06rem; }
2144
+
2145
+ /* Numbers + labels */
2146
+ .score-text-primary { font-size: 2.05rem; font-weight: 900; color: var(--color-accent); }
2147
+ .score-text-team { font-size: 2.05rem; font-weight: 900; color: #60a5fa; }
2148
+ .score-text-global { font-size: 2.05rem; font-weight: 900; }
2149
+ .label-text { font-size: 0.82rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #6b7280; }
2150
+
2151
+ /* Progress bar */
2152
+ .progress-bar-bg { width: 100%; height: 10px; background: #e5e7eb; border-radius: 6px; overflow: hidden; margin-top: 8px; }
2153
+ .progress-bar-fill { height: 100%; background: var(--color-accent); transition: width 280ms ease; }
2154
+
2155
+ /* Leaderboard tabs + tables */
2156
+ .leaderboard-card input[type="radio"] { display: none; }
2157
+ .lb-tab-label {
2158
+ display: inline-block; padding: 8px 16px; margin-right: 8px; border-radius: 20px;
2159
+ cursor: pointer; border: 1px solid var(--border-color-primary); font-weight: 700; font-size: 0.94rem;
2160
+ }
2161
+ #lb-tab-team:checked + label, #lb-tab-user:checked + label {
2162
+ background: var(--color-accent); color: white; border-color: var(--color-accent);
2163
+ box-shadow: 0 3px 8px rgba(99,102,241,0.25);
2164
+ }
2165
+ .lb-panel { display: none; margin-top: 10px; }
2166
+ #lb-tab-team:checked ~ .lb-tab-panels .panel-team { display: block; }
2167
+ #lb-tab-user:checked ~ .lb-tab-panels .panel-user { display: block; }
2168
+ .table-container { height: 320px; overflow-y: auto; border: 1px solid var(--border-color-primary); border-radius: 10px; }
2169
+ .leaderboard-table { width: 100%; border-collapse: collapse; }
2170
+ .leaderboard-table th {
2171
+ position: sticky; top: 0; background: var(--background-fill-secondary);
2172
+ padding: 10px; text-align: left; border-bottom: 2px solid var(--border-color-primary);
2173
+ font-weight: 800;
2174
+ }
2175
+ .leaderboard-table td { padding: 10px; border-bottom: 1px solid var(--border-color-primary); }
2176
+ .row-highlight-me, .row-highlight-team { background: rgba(96,165,250,0.18); font-weight: 700; }
2177
+
2178
+ /* Containers */
2179
+ .ai-risk-container { margin-top: 16px; padding: 16px; background: var(--body-background-fill); border-radius: 10px; border: 1px solid var(--border-color-primary); }
2180
+
2181
+ /* Interactive blocks (text size tuned for 17–20 age group) */
2182
+ .interactive-block { font-size: 1.06rem; }
2183
+ .interactive-block .hint-box { font-size: 1.02rem; }
2184
+ .interactive-text { font-size: 1.06rem; }
2185
+
2186
+ /* Radio sizes */
2187
+ .scenario-radio-large label { font-size: 1.06rem; }
2188
+ .quiz-radio-large label { font-size: 1.06rem; }
2189
+
2190
+ /* Small utility */
2191
+ .divider-vertical { width: 1px; height: 48px; background: var(--border-color-primary); opacity: 0.6; }
2192
+
2193
+ /* Navigation loading overlay */
2194
+ #nav-loading-overlay {
2195
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
2196
+ background: color-mix(in srgb, var(--body-background-fill) 95%, transparent);
2197
+ z-index: 9999; display: none; flex-direction: column; align-items: center;
2198
+ justify-content: center; opacity: 0; transition: opacity 0.3s ease;
2199
+ }
2200
+ .nav-spinner {
2201
+ width: 50px; height: 50px; border: 5px solid var(--border-color-primary);
2202
+ border-top: 5px solid var(--color-accent); border-radius: 50%;
2203
+ animation: nav-spin 1s linear infinite; margin-bottom: 20px;
2204
+ }
2205
+ @keyframes nav-spin {
2206
+ 0% { transform: rotate(0deg); }
2207
+ 100% { transform: rotate(360deg); }
2208
+ }
2209
+ #nav-loading-text {
2210
+ font-size: 1.3rem; font-weight: 600; color: var(--color-accent);
2211
+ }
2212
+ @media (prefers-color-scheme: dark) {
2213
+ #nav-loading-overlay { background: rgba(15, 23, 42, 0.9); }
2214
+ .nav-spinner { border-color: rgba(148, 163, 184, 0.4); border-top-color: var(--color-accent); }
2215
+ }
2216
+ /* Add these new classes to your existing CSS block (Section 11) */
2217
+
2218
+ /* --- PROGRESS TRACKER STYLES --- */
2219
+ .tracker-container {
2220
+ display: flex;
2221
+ justify-content: space-around;
2222
+ align-items: center;
2223
+ margin-bottom: 25px;
2224
+ background: var(--background-fill-secondary);
2225
+ padding: 10px 0;
2226
+ border-radius: 8px;
2227
+ border: 1px solid var(--border-color-primary);
2228
+ }
2229
+ .tracker-step {
2230
+ text-align: center;
2231
+ font-weight: 700;
2232
+ font-size: 0.85rem;
2233
+ padding: 5px 10px;
2234
+ border-radius: 4px;
2235
+ color: var(--body-text-color-subdued);
2236
+ transition: all 0.3s ease;
2237
+ }
2238
+ .tracker-step.completed {
2239
+ color: #10b981; /* Green */
2240
+ background: rgba(16, 185, 129, 0.1);
2241
+ }
2242
+ .tracker-step.active {
2243
+ color: var(--color-accent); /* Primary Hue */
2244
+ background: var(--color-accent-soft);
2245
+ box-shadow: 0 0 5px rgba(99, 102, 241, 0.3);
2246
+ }
2247
+
2248
+ /* --- FORENSICS TAB STYLES --- */
2249
+ .forensic-tabs {
2250
+ display: flex;
2251
+ border-bottom: 2px solid var(--border-color-primary);
2252
+ margin-bottom: 0;
2253
+ }
2254
+ .tab-label-styled {
2255
+ padding: 10px 15px;
2256
+ cursor: pointer;
2257
+ font-weight: 700;
2258
+ font-size: 0.95rem;
2259
+ color: var(--body-text-color-subdued);
2260
+ border-bottom: 2px solid transparent;
2261
+ margin-bottom: -2px; /* Align with border */
2262
+ transition: color 0.2s ease;
2263
+ }
2264
+
2265
+ /* Hide the radio buttons */
2266
+ .scan-radio { display: none; }
2267
+
2268
+ /* Content panel styling */
2269
+ .scan-content {
2270
+ background: var(--body-background-fill); /* Light gray or similar */
2271
+ padding: 20px;
2272
+ border-radius: 0 8px 8px 8px;
2273
+ border: 1px solid var(--border-color-primary);
2274
+ min-height: 350px;
2275
+ position: relative;
2276
+ }
2277
+
2278
+ /* Hide all panes by default */
2279
+ .scan-pane { display: none; }
2280
+
2281
+ /* Show active tab content */
2282
+ #scan-race:checked ~ .scan-content .pane-race,
2283
+ #scan-gender:checked ~ .scan-content .pane-gender,
2284
+ #scan-age:checked ~ .scan-content .pane-age {
2285
+ display: block;
2286
+ }
2287
+
2288
+ /* Highlight active tab label */
2289
+ #scan-race:checked ~ .forensic-tabs label[for="scan-race"],
2290
+ #scan-gender:checked ~ .forensic-tabs label[for="scan-gender"],
2291
+ #scan-age:checked ~ .forensic-tabs label[for="scan-age"] {
2292
+ color: var(--color-accent);
2293
+ border-bottom-color: var(--color-accent);
2294
+ }
2295
+
2296
+ /* Utility for danger color */
2297
+ :root {
2298
+ --color-danger-light: rgba(239, 68, 68, 0.1);
2299
+ --color-accent-light: rgba(99, 102, 241, 0.15); /* Reusing accent color for general bars */
2300
+ }
2301
+ /* --- NEW SELECTORS FOR MODULE 8 (Generalization Scan Lab) --- */
2302
+
2303
+ /* Show active tab content in Module 8 */
2304
+ #scan-gender-err:checked ~ .scan-content .pane-gender-err,
2305
+ #scan-age-err:checked ~ .scan-content .pane-age-err,
2306
+ #scan-geo-err:checked ~ .scan-content .pane-geo-err {
2307
+ display: block;
2308
+ }
2309
+
2310
+ /* Highlight active tab label in Module 8 */
2311
+ #scan-gender-err:checked ~ .forensic-tabs label[for="scan-gender-err"],
2312
+ #scan-age-err:checked ~ .forensic-tabs label[for="scan-age-err"],
2313
+ #scan-geo-err:checked ~ .forensic-tabs label[for="scan-geo-err"] {
2314
+ color: var(--color-accent);
2315
+ border-bottom-color: var(--color-accent);
2316
+ }
2317
+
2318
+ /* If you used .data-scan-tabs instead of .forensic-tabs in Module 8 HTML,
2319
+ the selectors above need to target the parent container correctly.
2320
+ Assuming you used the structure from the draft: */
2321
+
2322
+ .data-scan-tabs input[type="radio"]:checked + .tab-label-styled {
2323
+ color: var(--color-accent);
2324
+ border-bottom-color: var(--color-accent);
2325
+ }
2326
+
2327
+ .data-scan-tabs .scan-content .scan-pane {
2328
+ display: none;
2329
+ }
2330
+ .data-scan-tabs #scan-gender-err:checked ~ .scan-content .pane-gender-err,
2331
+ .data-scan-tabs #scan-age-err:checked ~ .scan-content .pane-age-err,
2332
+ .data-scan-tabs #scan-geo-err:checked ~ .scan-content .pane-geo-err {
2333
+ display: block;
2334
+ }
2335
+ """
2336
+
2337
+ # --- 12. HELPER: SLIDER FOR MORAL COMPASS SCORE (MODULE 0) ---
2338
+ def simulate_moral_compass_score(acc, progress_pct):
2339
+ try:
2340
+ acc_val = float(acc)
2341
+ except (TypeError, ValueError):
2342
+ acc_val = 0.0
2343
+ try:
2344
+ prog_val = float(progress_pct)
2345
+ except (TypeError, ValueError):
2346
+ prog_val = 0.0
2347
+
2348
+ score = acc_val * (prog_val / 100.0)
2349
+ return f"""
2350
+ <div class="hint-box interactive-block">
2351
+ <p style="margin-bottom:4px; font-size:1.05rem;">
2352
+ <strong>Your current accuracy (from the leaderboard):</strong> {acc_val:.3f}
2353
+ </p>
2354
+ <p style="margin-bottom:4px; font-size:1.05rem;">
2355
+ <strong>Simulated Ethical Progress %:</strong> {prog_val:.0f}%
2356
+ </p>
2357
+ <p style="margin-bottom:0; font-size:1.08rem;">
2358
+ <strong>Simulated Moral Compass Score:</strong> 🧭 {score:.3f}
2359
+ </p>
2360
+ </div>
2361
+ """
2362
+
2363
+
2364
+ # --- 13. APP FACTORY (APP 1) ---
2365
+ def create_bias_detective_part1_app(theme_primary_hue: str = "indigo"):
2366
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue=theme_primary_hue), css=css) as demo:
2367
+ # States
2368
+ username_state = gr.State(value=None)
2369
+ token_state = gr.State(value=None)
2370
+ team_state = gr.State(value=None)
2371
+ module0_done = gr.State(value=False)
2372
+ accuracy_state = gr.State(value=0.0)
2373
+ task_list_state = gr.State(value=[])
2374
+
2375
+ # --- TOP ANCHOR & LOADING OVERLAY FOR NAVIGATION ---
2376
+ gr.HTML("<div id='app_top_anchor' style='height:0;'></div>")
2377
+ gr.HTML("<div id='nav-loading-overlay'><div class='nav-spinner'></div><span id='nav-loading-text'>Loading...</span></div>")
2378
+
2379
+ # --- LOADING VIEW ---
2380
+ with gr.Column(visible=True, elem_id="app-loader") as loader_col:
2381
+ gr.HTML(
2382
+ "<div style='text-align:center; padding:100px;'>"
2383
+ "<h2>🕵️‍♀️ Authenticating...</h2>"
2384
+ "<p>Syncing Moral Compass Data...</p>"
2385
+ "</div>"
2386
+ )
2387
+
2388
+ # --- MAIN APP VIEW ---
2389
+ with gr.Column(visible=False) as main_app_col:
2390
+ # Title
2391
+ #gr.Markdown("# 🕵️‍♀️ Bias Detective: Part 1 - Data Forensics")
2392
+
2393
+ # Top summary dashboard (progress bar & score)
2394
+ out_top = gr.HTML()
2395
+
2396
+ # Dynamic modules container
2397
+ module_ui_elements = {}
2398
+ quiz_wiring_queue = []
2399
+
2400
+ # --- DYNAMIC MODULE GENERATION ---
2401
+ for i, mod in enumerate(MODULES):
2402
+ with gr.Column(
2403
+ elem_id=f"module-{i}",
2404
+ elem_classes=["module-container"],
2405
+ visible=(i == 0),
2406
+ ) as mod_col:
2407
+ # Core slide HTML
2408
+ gr.HTML(mod["html"])
2409
+
2410
+
2411
+
2412
+ # --- QUIZ CONTENT FOR MODULES WITH QUIZ_CONFIG ---
2413
+ if i in QUIZ_CONFIG:
2414
+ q_data = QUIZ_CONFIG[i]
2415
+ gr.Markdown(f"### 🧠 {q_data['q']}")
2416
+ radio = gr.Radio(
2417
+ choices=q_data["o"],
2418
+ label="Select Answer:",
2419
+ elem_classes=["quiz-radio-large"],
2420
+ )
2421
+ feedback = gr.HTML("")
2422
+ quiz_wiring_queue.append((i, radio, feedback))
2423
+
2424
+ # --- NAVIGATION BUTTONS ---
2425
+ with gr.Row():
2426
+ btn_prev = gr.Button("⬅️ Previous", visible=(i > 0))
2427
+ next_label = (
2428
+ "Next ▶️"
2429
+ if i < len(MODULES) - 1
2430
+ else "🎉 You Have Completed Part 1!! (Please Proceed to the Next Activity)"
2431
+ )
2432
+ btn_next = gr.Button(next_label, variant="primary")
2433
+
2434
+ module_ui_elements[i] = (mod_col, btn_prev, btn_next)
2435
+
2436
+ # Leaderboard card appears AFTER content & interactions
2437
+ leaderboard_html = gr.HTML()
2438
+
2439
+ # --- WIRING: QUIZ LOGIC ---
2440
+ for mod_id, radio_comp, feedback_comp in quiz_wiring_queue:
2441
+
2442
+ def quiz_logic_wrapper(
2443
+ user,
2444
+ tok,
2445
+ team,
2446
+ acc_val,
2447
+ task_list,
2448
+ ans,
2449
+ mid=mod_id,
2450
+ ):
2451
+ cfg = QUIZ_CONFIG[mid]
2452
+ if ans == cfg["a"]:
2453
+ prev, curr, _, new_tasks = trigger_api_update(
2454
+ user, tok, team, mid, acc_val, task_list, cfg["t"]
2455
+ )
2456
+ msg = generate_success_message(prev, curr, cfg["success"])
2457
+ return (
2458
+ render_top_dashboard(curr, mid),
2459
+ render_leaderboard_card(curr, user, team),
2460
+ msg,
2461
+ new_tasks,
2462
+ )
2463
+ else:
2464
+ return (
2465
+ gr.update(),
2466
+ gr.update(),
2467
+ "<div class='hint-box' style='border-color:red;'>"
2468
+ "❌ Incorrect. Review the evidence above.</div>",
2469
+ task_list,
2470
+ )
2471
+
2472
+ radio_comp.change(
2473
+ fn=quiz_logic_wrapper,
2474
+ inputs=[
2475
+ username_state,
2476
+ token_state,
2477
+ team_state,
2478
+ accuracy_state,
2479
+ task_list_state,
2480
+ radio_comp,
2481
+ ],
2482
+ outputs=[out_top, leaderboard_html, feedback_comp, task_list_state],
2483
+ )
2484
+
2485
+ # --- GLOBAL LOAD HANDLER ---
2486
+ def handle_load(req: gr.Request):
2487
+ success, user, token = _try_session_based_auth(req)
2488
+ team = "Team-Unassigned"
2489
+ acc = 0.0
2490
+ fetched_tasks: List[str] = []
2491
+
2492
+ if success and user and token:
2493
+ acc, fetched_team = fetch_user_history(user, token)
2494
+ os.environ["MORAL_COMPASS_API_BASE_URL"] = DEFAULT_API_URL
2495
+ client = MoralcompassApiClient(
2496
+ api_base_url=DEFAULT_API_URL, auth_token=token
2497
+ )
2498
+
2499
+ # Simple team assignment helper
2500
+ def get_or_assign_team(client_obj, username_val):
2501
+ try:
2502
+ user_data = client_obj.get_user(
2503
+ table_id=TABLE_ID, username=username_val
2504
+ )
2505
+ except Exception:
2506
+ user_data = None
2507
+ if user_data and isinstance(user_data, dict):
2508
+ if user_data.get("teamName"):
2509
+ return user_data["teamName"]
2510
+ return "team-a"
2511
+
2512
+ exist_team = get_or_assign_team(client, user)
2513
+ if fetched_team != "Team-Unassigned":
2514
+ team = fetched_team
2515
+ elif exist_team != "team-a":
2516
+ team = exist_team
2517
+ else:
2518
+ team = "team-a"
2519
+
2520
+ try:
2521
+ user_stats = client.get_user(table_id=TABLE_ID, username=user)
2522
+ except Exception:
2523
+ user_stats = None
2524
+
2525
+ if user_stats:
2526
+ if isinstance(user_stats, dict):
2527
+ fetched_tasks = user_stats.get("completedTaskIds") or []
2528
+ else:
2529
+ fetched_tasks = getattr(
2530
+ user_stats, "completed_task_ids", []
2531
+ ) or []
2532
+
2533
+ # Sync baseline moral compass record
2534
+ try:
2535
+ client.update_moral_compass(
2536
+ table_id=TABLE_ID,
2537
+ username=user,
2538
+ team_name=team,
2539
+ metrics={"accuracy": acc},
2540
+ tasks_completed=len(fetched_tasks),
2541
+ total_tasks=TOTAL_COURSE_TASKS,
2542
+ primary_metric="accuracy",
2543
+ completed_task_ids=fetched_tasks,
2544
+ )
2545
+ time.sleep(1.0)
2546
+ except Exception:
2547
+ pass
2548
+
2549
+ data, _ = ensure_table_and_get_data(
2550
+ user, token, team, fetched_tasks
2551
+ )
2552
+ return (
2553
+ user,
2554
+ token,
2555
+ team,
2556
+ False,
2557
+ render_top_dashboard(data, 0),
2558
+ render_leaderboard_card(data, user, team),
2559
+ acc,
2560
+ fetched_tasks,
2561
+ gr.update(visible=False),
2562
+ gr.update(visible=True),
2563
+ )
2564
+
2565
+ # Auth failed / no session
2566
+ return (
2567
+ None,
2568
+ None,
2569
+ None,
2570
+ False,
2571
+ "<div class='hint-box'>⚠️ Auth Failed. Please launch from the course link.</div>",
2572
+ "",
2573
+ 0.0,
2574
+ [],
2575
+ gr.update(visible=False),
2576
+ gr.update(visible=True),
2577
+ )
2578
+
2579
+ # Attach load event
2580
+ demo.load(
2581
+ handle_load,
2582
+ None,
2583
+ [
2584
+ username_state,
2585
+ token_state,
2586
+ team_state,
2587
+ module0_done,
2588
+ out_top,
2589
+ leaderboard_html,
2590
+ accuracy_state,
2591
+ task_list_state,
2592
+ loader_col,
2593
+ main_app_col,
2594
+ ],
2595
+ )
2596
+
2597
+ # --- JAVASCRIPT HELPER FOR NAVIGATION ---
2598
+ def nav_js(target_id: str, message: str) -> str:
2599
+ """Generate JavaScript for smooth navigation with loading overlay."""
2600
+ return f"""
2601
+ ()=>{{
2602
+ try {{
2603
+ const overlay = document.getElementById('nav-loading-overlay');
2604
+ const messageEl = document.getElementById('nav-loading-text');
2605
+ if(overlay && messageEl) {{
2606
+ messageEl.textContent = '{message}';
2607
+ overlay.style.display = 'flex';
2608
+ setTimeout(() => {{ overlay.style.opacity = '1'; }}, 10);
2609
+ }}
2610
+ const startTime = Date.now();
2611
+ setTimeout(() => {{
2612
+ const anchor = document.getElementById('app_top_anchor');
2613
+ if(anchor) anchor.scrollIntoView({{behavior:'smooth', block:'start'}});
2614
+ }}, 40);
2615
+ const targetId = '{target_id}';
2616
+ const pollInterval = setInterval(() => {{
2617
+ const elapsed = Date.now() - startTime;
2618
+ const target = document.getElementById(targetId);
2619
+ const isVisible = target && target.offsetParent !== null &&
2620
+ window.getComputedStyle(target).display !== 'none';
2621
+ if((isVisible && elapsed >= 1200) || elapsed > 7000) {{
2622
+ clearInterval(pollInterval);
2623
+ if(overlay) {{
2624
+ overlay.style.opacity = '0';
2625
+ setTimeout(() => {{ overlay.style.display = 'none'; }}, 300);
2626
+ }}
2627
+ }}
2628
+ }}, 90);
2629
+ }} catch(e) {{ console.warn('nav-js error', e); }}
2630
+ }}
2631
+ """
2632
+
2633
+ # --- NAVIGATION BETWEEN MODULES ---
2634
+ for i in range(len(MODULES)):
2635
+ curr_col, prev_btn, next_btn = module_ui_elements[i]
2636
+
2637
+ # Previous button
2638
+ if i > 0:
2639
+ prev_col = module_ui_elements[i - 1][0]
2640
+ prev_target_id = f"module-{i-1}"
2641
+
2642
+ def make_prev_handler(p_col, c_col, target_id):
2643
+ def navigate_prev():
2644
+ # First yield: hide current, show nothing (transition state)
2645
+ yield gr.update(visible=False), gr.update(visible=False)
2646
+ # Second yield: show previous, hide current
2647
+ yield gr.update(visible=True), gr.update(visible=False)
2648
+ return navigate_prev
2649
+
2650
+ prev_btn.click(
2651
+ fn=make_prev_handler(prev_col, curr_col, prev_target_id),
2652
+ outputs=[prev_col, curr_col],
2653
+ js=nav_js(prev_target_id, "Loading..."),
2654
+ )
2655
+
2656
+ # Next button
2657
+ if i < len(MODULES) - 1:
2658
+ next_col = module_ui_elements[i + 1][0]
2659
+ next_target_id = f"module-{i+1}"
2660
+
2661
+ def make_next_handler(c_col, n_col, next_idx):
2662
+ def wrapper_next(user, tok, team, tasks):
2663
+ data, _ = ensure_table_and_get_data(user, tok, team, tasks)
2664
+ dash_html = render_top_dashboard(data, next_idx)
2665
+ return dash_html
2666
+ return wrapper_next
2667
+
2668
+ def make_nav_generator(c_col, n_col):
2669
+ def navigate_next():
2670
+ # First yield: hide current, show nothing (transition state)
2671
+ yield gr.update(visible=False), gr.update(visible=False)
2672
+ # Second yield: hide current, show next
2673
+ yield gr.update(visible=False), gr.update(visible=True)
2674
+ return navigate_next
2675
+
2676
+ next_btn.click(
2677
+ fn=make_next_handler(curr_col, next_col, i + 1),
2678
+ inputs=[username_state, token_state, team_state, task_list_state],
2679
+ outputs=[out_top],
2680
+ js=nav_js(next_target_id, "Loading..."),
2681
+ ).then(
2682
+ fn=make_nav_generator(curr_col, next_col),
2683
+ outputs=[curr_col, next_col],
2684
+ )
2685
+
2686
+ return demo
2687
+
2688
+
2689
+
2690
+
2691
+ def launch_bias_detective_part1_app(
2692
+ share: bool = False,
2693
+ server_name: str = "0.0.0.0",
2694
+ server_port: int = 8080,
2695
+ theme_primary_hue: str = "indigo",
2696
+ **kwargs
2697
+ ) -> None:
2698
+ """
2699
+ Launch the Bias Detective V2 app.
2700
+
2701
+ Args:
2702
+ share: Whether to create a public link
2703
+ server_name: Server hostname
2704
+ server_port: Server port
2705
+ theme_primary_hue: Primary color hue
2706
+ **kwargs: Additional Gradio launch arguments
2707
+ """
2708
+ app = create_bias_detective_part1_app(theme_primary_hue=theme_primary_hue)
2709
+ app.launch(
2710
+ share=share,
2711
+ server_name=server_name,
2712
+ server_port=server_port,
2713
+ **kwargs
2714
+ )
2715
+
2716
+
2717
+ # ============================================================================
2718
+ # Main Entry Point
2719
+ # ============================================================================
2720
+
2721
+ if __name__ == "__main__":
2722
+ launch_bias_detective_part1_app(share=False, debug=True, height=1000)