aimodelshare 0.3.7__py3-none-any.whl → 0.4.71__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.
- aimodelshare/moral_compass/__init__.py +51 -2
- aimodelshare/moral_compass/api_client.py +92 -4
- aimodelshare/moral_compass/apps/__init__.py +36 -16
- aimodelshare/moral_compass/apps/ai_consequences.py +98 -88
- aimodelshare/moral_compass/apps/bias_detective_ca.py +2722 -0
- aimodelshare/moral_compass/apps/bias_detective_en.py +2722 -0
- aimodelshare/moral_compass/apps/bias_detective_part1.py +2722 -0
- aimodelshare/moral_compass/apps/bias_detective_part2.py +2465 -0
- aimodelshare/moral_compass/apps/bias_detective_part_es.py +2722 -0
- aimodelshare/moral_compass/apps/ethical_revelation.py +237 -147
- aimodelshare/moral_compass/apps/fairness_fixer.py +1839 -859
- aimodelshare/moral_compass/apps/fairness_fixer_ca.py +1869 -0
- aimodelshare/moral_compass/apps/fairness_fixer_en.py +1869 -0
- aimodelshare/moral_compass/apps/fairness_fixer_es.py +1869 -0
- aimodelshare/moral_compass/apps/judge.py +130 -143
- aimodelshare/moral_compass/apps/justice_equity_upgrade.py +793 -831
- aimodelshare/moral_compass/apps/justice_equity_upgrade_ca.py +815 -0
- aimodelshare/moral_compass/apps/justice_equity_upgrade_en.py +815 -0
- aimodelshare/moral_compass/apps/justice_equity_upgrade_es.py +815 -0
- aimodelshare/moral_compass/apps/mc_integration_helpers.py +227 -745
- aimodelshare/moral_compass/apps/model_building_app_ca.py +4544 -0
- aimodelshare/moral_compass/apps/model_building_app_ca_final.py +3899 -0
- aimodelshare/moral_compass/apps/model_building_app_en.py +4290 -0
- aimodelshare/moral_compass/apps/model_building_app_en_final.py +3869 -0
- aimodelshare/moral_compass/apps/model_building_app_es.py +4362 -0
- aimodelshare/moral_compass/apps/model_building_app_es_final.py +3899 -0
- aimodelshare/moral_compass/apps/model_building_game.py +4211 -935
- aimodelshare/moral_compass/apps/moral_compass_challenge.py +195 -95
- aimodelshare/moral_compass/apps/what_is_ai.py +126 -117
- aimodelshare/moral_compass/challenge.py +98 -17
- {aimodelshare-0.3.7.dist-info → aimodelshare-0.4.71.dist-info}/METADATA +1 -1
- {aimodelshare-0.3.7.dist-info → aimodelshare-0.4.71.dist-info}/RECORD +35 -19
- aimodelshare/moral_compass/apps/bias_detective.py +0 -714
- {aimodelshare-0.3.7.dist-info → aimodelshare-0.4.71.dist-info}/WHEEL +0 -0
- {aimodelshare-0.3.7.dist-info → aimodelshare-0.4.71.dist-info}/licenses/LICENSE +0 -0
- {aimodelshare-0.3.7.dist-info → aimodelshare-0.4.71.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,2465 @@
|
|
|
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 = 19
|
|
12
|
+
LOCAL_TEST_SESSION_ID = None
|
|
13
|
+
|
|
14
|
+
# --- 2. SETUP & DEPENDENCIES ---
|
|
15
|
+
def install_dependencies():
|
|
16
|
+
packages = ["gradio>=5.0.0", "aimodelshare", "pandas"]
|
|
17
|
+
for package in packages:
|
|
18
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import gradio as gr
|
|
22
|
+
import pandas as pd
|
|
23
|
+
from aimodelshare.playground import Competition
|
|
24
|
+
from aimodelshare.moral_compass import MoralcompassApiClient
|
|
25
|
+
from aimodelshare.aws import get_token_from_session, _get_username_from_token
|
|
26
|
+
except ImportError:
|
|
27
|
+
print("📦 Installing dependencies...")
|
|
28
|
+
install_dependencies()
|
|
29
|
+
import gradio as gr
|
|
30
|
+
import pandas as pd
|
|
31
|
+
from aimodelshare.playground import Competition
|
|
32
|
+
from aimodelshare.moral_compass import MoralcompassApiClient
|
|
33
|
+
from aimodelshare.aws import get_token_from_session, _get_username_from_token
|
|
34
|
+
|
|
35
|
+
# --- 3. AUTH & HISTORY HELPERS ---
|
|
36
|
+
def _try_session_based_auth(request: "gr.Request") -> Tuple[bool, Optional[str], Optional[str]]:
|
|
37
|
+
try:
|
|
38
|
+
session_id = request.query_params.get("sessionid") if request else None
|
|
39
|
+
if not session_id and LOCAL_TEST_SESSION_ID: session_id = LOCAL_TEST_SESSION_ID
|
|
40
|
+
if not session_id: return False, None, None
|
|
41
|
+
token = get_token_from_session(session_id)
|
|
42
|
+
if not token: return False, None, None
|
|
43
|
+
username = _get_username_from_token(token)
|
|
44
|
+
if not username: return False, None, None
|
|
45
|
+
return True, username, token
|
|
46
|
+
except Exception: return False, None, None
|
|
47
|
+
|
|
48
|
+
def fetch_user_history(username, token):
|
|
49
|
+
default_acc = 0.0; default_team = "Team-Unassigned"
|
|
50
|
+
try:
|
|
51
|
+
playground = Competition(ORIGINAL_PLAYGROUND_URL)
|
|
52
|
+
df = playground.get_leaderboard(token=token)
|
|
53
|
+
if df is None or df.empty: return default_acc, default_team
|
|
54
|
+
if "username" in df.columns and "accuracy" in df.columns:
|
|
55
|
+
user_rows = df[df["username"] == username]
|
|
56
|
+
if not user_rows.empty:
|
|
57
|
+
best_acc = user_rows["accuracy"].max()
|
|
58
|
+
if "timestamp" in user_rows.columns and "Team" in user_rows.columns:
|
|
59
|
+
try:
|
|
60
|
+
user_rows = user_rows.copy()
|
|
61
|
+
user_rows["timestamp"] = pd.to_datetime(user_rows["timestamp"], errors="coerce")
|
|
62
|
+
user_rows = user_rows.sort_values("timestamp", ascending=False)
|
|
63
|
+
found_team = user_rows.iloc[0]["Team"]
|
|
64
|
+
if pd.notna(found_team) and str(found_team).strip(): default_team = str(found_team).strip()
|
|
65
|
+
except Exception: pass
|
|
66
|
+
return float(best_acc), default_team
|
|
67
|
+
except Exception: pass
|
|
68
|
+
return default_acc, default_team
|
|
69
|
+
|
|
70
|
+
# --- 4. API & LEADERBOARD LOGIC ---
|
|
71
|
+
def get_or_assign_team(client, username):
|
|
72
|
+
try:
|
|
73
|
+
resp = client.list_users(table_id=TABLE_ID, limit=500)
|
|
74
|
+
u = next((u for u in resp.get("users", []) if u.get("username") == username), None)
|
|
75
|
+
return u.get("teamName") if u else "team-a"
|
|
76
|
+
except: return "team-a"
|
|
77
|
+
|
|
78
|
+
def get_leaderboard_data(client, username, team_name, local_list=None, override_score=None):
|
|
79
|
+
try:
|
|
80
|
+
resp = client.list_users(table_id=TABLE_ID, limit=500)
|
|
81
|
+
users = resp.get("users", [])
|
|
82
|
+
|
|
83
|
+
# Optimistic Score Patch
|
|
84
|
+
if override_score is not None:
|
|
85
|
+
found = False
|
|
86
|
+
for u in users:
|
|
87
|
+
if u.get("username") == username:
|
|
88
|
+
u["moralCompassScore"] = override_score; found = True; break
|
|
89
|
+
if not found: users.append({"username": username, "moralCompassScore": override_score, "teamName": team_name})
|
|
90
|
+
|
|
91
|
+
users_sorted = sorted(users, key=lambda x: float(x.get("moralCompassScore", 0) or 0), reverse=True)
|
|
92
|
+
my_user = next((u for u in users_sorted if u.get("username") == username), None)
|
|
93
|
+
score = float(my_user.get("moralCompassScore", 0) or 0) if my_user else 0.0
|
|
94
|
+
rank = users_sorted.index(my_user) + 1 if my_user else 0
|
|
95
|
+
completed = local_list if local_list is not None else (my_user.get("completedTaskIds", []) if my_user else [])
|
|
96
|
+
|
|
97
|
+
team_map = {}
|
|
98
|
+
for u in users:
|
|
99
|
+
t = u.get("teamName"); s = float(u.get("moralCompassScore", 0) or 0)
|
|
100
|
+
if t:
|
|
101
|
+
if t not in team_map: team_map[t] = {"sum": 0, "count": 0}
|
|
102
|
+
team_map[t]["sum"] += s; team_map[t]["count"] += 1
|
|
103
|
+
teams_sorted = []
|
|
104
|
+
for t, d in team_map.items(): teams_sorted.append({"team": t, "avg": d["sum"] / d["count"]})
|
|
105
|
+
teams_sorted.sort(key=lambda x: x["avg"], reverse=True)
|
|
106
|
+
my_team = next((t for t in teams_sorted if t['team'] == team_name), None)
|
|
107
|
+
team_rank = teams_sorted.index(my_team) + 1 if my_team else 0
|
|
108
|
+
|
|
109
|
+
return {"score": score, "rank": rank, "team_rank": team_rank, "all_users": users_sorted, "all_teams": teams_sorted, "completed_task_ids": completed}
|
|
110
|
+
except Exception: return None
|
|
111
|
+
|
|
112
|
+
def ensure_table_and_get_data(username, token, team_name, task_list_state=None):
|
|
113
|
+
if not username or not token: return None, username
|
|
114
|
+
os.environ["MORAL_COMPASS_API_BASE_URL"] = DEFAULT_API_URL
|
|
115
|
+
client = MoralcompassApiClient(api_base_url=DEFAULT_API_URL, auth_token=token)
|
|
116
|
+
try: client.get_table(TABLE_ID)
|
|
117
|
+
except:
|
|
118
|
+
try: client.create_table(table_id=TABLE_ID, display_name="LMS", playground_url="https://example.com")
|
|
119
|
+
except: pass
|
|
120
|
+
return get_leaderboard_data(client, username, team_name, task_list_state), username
|
|
121
|
+
|
|
122
|
+
def trigger_api_update(username, token, team_name, module_id, user_real_accuracy, task_list_state, append_task_id=None):
|
|
123
|
+
if not username or not token: return None, None, username, task_list_state
|
|
124
|
+
os.environ["MORAL_COMPASS_API_BASE_URL"] = DEFAULT_API_URL
|
|
125
|
+
client = MoralcompassApiClient(api_base_url=DEFAULT_API_URL, auth_token=token)
|
|
126
|
+
|
|
127
|
+
acc = float(user_real_accuracy) if user_real_accuracy is not None else 0.0
|
|
128
|
+
|
|
129
|
+
old_task_list = list(task_list_state) if task_list_state else []
|
|
130
|
+
new_task_list = list(old_task_list)
|
|
131
|
+
if append_task_id and append_task_id not in new_task_list:
|
|
132
|
+
new_task_list.append(append_task_id)
|
|
133
|
+
try: new_task_list.sort(key=lambda x: int(x[1:]) if x.startswith('t') and x[1:].isdigit() else 0)
|
|
134
|
+
except: pass
|
|
135
|
+
|
|
136
|
+
tasks_completed = len(new_task_list)
|
|
137
|
+
client.update_moral_compass(table_id=TABLE_ID, username=username, team_name=team_name, metrics={"accuracy": acc}, tasks_completed=tasks_completed, total_tasks=TOTAL_COURSE_TASKS, primary_metric="accuracy", completed_task_ids=new_task_list)
|
|
138
|
+
|
|
139
|
+
old_score_calc = acc * (len(old_task_list) / TOTAL_COURSE_TASKS)
|
|
140
|
+
new_score_calc = acc * (len(new_task_list) / TOTAL_COURSE_TASKS)
|
|
141
|
+
|
|
142
|
+
prev_data = get_leaderboard_data(client, username, team_name, old_task_list, override_score=old_score_calc)
|
|
143
|
+
lb_data = get_leaderboard_data(client, username, team_name, new_task_list, override_score=new_score_calc)
|
|
144
|
+
return prev_data, lb_data, username, new_task_list
|
|
145
|
+
|
|
146
|
+
def reset_user_progress(username, token, team_name, acc):
|
|
147
|
+
if not username or not token: return []
|
|
148
|
+
os.environ["MORAL_COMPASS_API_BASE_URL"] = DEFAULT_API_URL
|
|
149
|
+
client = MoralcompassApiClient(api_base_url=DEFAULT_API_URL, auth_token=token)
|
|
150
|
+
print(f"🔄 Resetting progress for {username}...")
|
|
151
|
+
client.update_moral_compass(table_id=TABLE_ID, username=username, team_name=team_name, metrics={"accuracy": acc}, tasks_completed=0, total_tasks=TOTAL_COURSE_TASKS, primary_metric="accuracy", completed_task_ids=[])
|
|
152
|
+
time.sleep(1.0)
|
|
153
|
+
return []
|
|
154
|
+
|
|
155
|
+
# --- 5. CONTENT MODULES ---
|
|
156
|
+
MODULES = [
|
|
157
|
+
{
|
|
158
|
+
"id": 0, "title": "Part 2 Intro",
|
|
159
|
+
"html": """
|
|
160
|
+
<div class="scenario-box">
|
|
161
|
+
<h2 class="slide-title">🕵️♀️ PART 2: THE ALGORITHMIC AUDIT</h2>
|
|
162
|
+
<div class="slide-body">
|
|
163
|
+
|
|
164
|
+
<!-- STATUS BADGE -->
|
|
165
|
+
<div style="display:flex; justify-content:center; margin-bottom:18px;">
|
|
166
|
+
<div style="display:inline-flex; align-items:center; gap:10px; padding:10px 18px; border-radius:999px; background:var(--background-fill-secondary); border:1px solid var(--border-color-primary); font-size:0.95rem; text-transform:uppercase; letter-spacing:0.08em; font-weight:700;">
|
|
167
|
+
<span style="font-size:1.1rem;">⚡</span>
|
|
168
|
+
<span>STATUS: DATA FORENSICS COMPLETE</span>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<!-- ROADMAP RECAP (from App 1) -->
|
|
173
|
+
<div class="ai-risk-container" style="margin:0 auto 22px auto; max-width:780px; padding:16px; border:1px solid var(--border-color-primary); border-radius:10px;">
|
|
174
|
+
<h4 style="margin-top:0; font-size:1.05rem; text-align:center;">🗺️ Your Investigation Roadmap</h4>
|
|
175
|
+
<div style="display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; margin-top:12px;">
|
|
176
|
+
|
|
177
|
+
<div class="hint-box" style="margin-top:0;">
|
|
178
|
+
<div style="font-weight:700;">1. Learn the Rules</div>
|
|
179
|
+
<div style="font-size:0.85rem; color:var(--body-text-color-subdued);">✔ Completed</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div class="hint-box" style="margin-top:0;">
|
|
183
|
+
<div style="font-weight:700;">2. Collect Evidence</div>
|
|
184
|
+
<div style="font-size:0.85rem; color:var(--body-text-color-subdued);">✔ Completed</div>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #3b82f6; background:rgba(59,130,246,0.08);">
|
|
188
|
+
<div style="font-weight:700; color:#1d4ed8;">3. Prove the Prediction Error</div>
|
|
189
|
+
<div style="font-size:0.85rem; color:var(--body-text-color-subdued);">⬅ You are here</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div class="hint-box" style="margin-top:0;">
|
|
193
|
+
<div style="font-weight:700;">4. Diagnose Harm</div>
|
|
194
|
+
<div style="font-size:0.85rem; color:var(--body-text-color-subdued);">Coming Soon</div>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- TRANSITION NARRATIVE -->
|
|
201
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 14px auto; text-align:center;">
|
|
202
|
+
Welcome back, Detective. In Part 1, you uncovered powerful evidence: the <strong>input data</strong>
|
|
203
|
+
feeding this model was distorted by history and unequal sampling.
|
|
204
|
+
</p>
|
|
205
|
+
|
|
206
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 18px auto; text-align:center;">
|
|
207
|
+
But corrupted data is only <em>half</em> the case. Now comes the decisive moment in any AI audit:
|
|
208
|
+
testing whether these distorted inputs have produced <strong>unfair outputs</strong> — unequal predictions
|
|
209
|
+
that change real lives.
|
|
210
|
+
</p>
|
|
211
|
+
|
|
212
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 22px auto; text-align:center;">
|
|
213
|
+
In Part 2, you will compare the model’s predictions against reality, group by group.
|
|
214
|
+
This is where you expose <strong>false positives</strong>, <strong>false negatives</strong>, and the
|
|
215
|
+
hidden <strong>error gaps</strong> that reveal whether the system is treating people unfairly.
|
|
216
|
+
</p>
|
|
217
|
+
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
"""
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"id": 1, "title": "Why outputs matter",
|
|
225
|
+
"html": """
|
|
226
|
+
<div class="scenario-box">
|
|
227
|
+
<h2 class="slide-title">🎯 WHY OUTPUTS MATTER</h2>
|
|
228
|
+
<div class="slide-body">
|
|
229
|
+
|
|
230
|
+
<!-- Badge -->
|
|
231
|
+
<div style="display:flex; justify-content:center; margin-bottom:18px;">
|
|
232
|
+
<div style="display:inline-flex; align-items:center; gap:10px; padding:10px 18px;
|
|
233
|
+
border-radius:999px; background:var(--background-fill-secondary);
|
|
234
|
+
border:1px solid var(--border-color-primary); font-size:0.95rem;
|
|
235
|
+
text-transform:uppercase; letter-spacing:0.08em; font-weight:700;">
|
|
236
|
+
<span style="font-size:1.1rem;">🎛️</span>
|
|
237
|
+
<span>FOCUS: MODEL OUTPUTS</span>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<!-- Core framing -->
|
|
242
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 16px auto; text-align:center;">
|
|
243
|
+
In Part 1, you uncovered distortions in the <strong>input data</strong>. But biased data doesn’t
|
|
244
|
+
automatically prove the model’s <em>decisions</em> are unfair.
|
|
245
|
+
</p>
|
|
246
|
+
|
|
247
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 24px auto; text-align:center;">
|
|
248
|
+
To protect people — and society — we must test the <strong>outputs</strong>.
|
|
249
|
+
When an AI model makes a prediction, that prediction can directly shape someone’s future.
|
|
250
|
+
</p>
|
|
251
|
+
|
|
252
|
+
<!-- Visual box: consequences -->
|
|
253
|
+
<div class="ai-risk-container" style="margin:22px auto; max-width:780px; padding:20px;
|
|
254
|
+
border-radius:12px; background:rgba(59,130,246,0.08); border:1px solid rgba(59,130,246,0.25);">
|
|
255
|
+
<h4 style="margin-top:0; font-size:1.2rem; text-align:center;">🔎 Why Outputs Shape Justice</h4>
|
|
256
|
+
<p style="font-size:1rem; text-align:center; margin-bottom:12px;">
|
|
257
|
+
A model’s prediction doesn’t just describe risk — it can <strong>change real decisions</strong>.
|
|
258
|
+
</p>
|
|
259
|
+
|
|
260
|
+
<ul style="font-size:0.98rem; max-width:700px; margin:0 auto; padding-left:20px;">
|
|
261
|
+
<li><strong>High risk score →</strong> denied bail, longer detention, fewer opportunities.</li>
|
|
262
|
+
<li><strong>Low risk score →</strong> early release, access to programs, second chances.</li>
|
|
263
|
+
</ul>
|
|
264
|
+
|
|
265
|
+
<p style="font-size:1rem; text-align:center; margin:12px 0;">
|
|
266
|
+
And mistakes go both ways:
|
|
267
|
+
</p>
|
|
268
|
+
|
|
269
|
+
<ul style="font-size:0.98rem; max-width:700px; margin:0 auto; padding-left:20px;">
|
|
270
|
+
<li><strong>False alarms</strong> keep low-risk people locked up — harming families and communities.</li>
|
|
271
|
+
<li><strong>Missed warnings</strong> can release someone who may commit another crime — harming public safety.</li>
|
|
272
|
+
</ul>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<!-- Interactive cards: click to reveal examples -->
|
|
276
|
+
<div class="ai-risk-container" style="margin:18px auto; max-width:780px;">
|
|
277
|
+
<h4 style="margin-top:0; font-size:1.15rem; text-align:center;">🗂️ Evidence Cards: How Outputs Change Lives</h4>
|
|
278
|
+
<p style="font-size:0.95rem; text-align:center; margin:6px 0 14px 0; color:var(--body-text-color-subdued);">
|
|
279
|
+
Click each card to reveal what can happen when an AI model gets it wrong.
|
|
280
|
+
</p>
|
|
281
|
+
|
|
282
|
+
<div style="display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:12px;">
|
|
283
|
+
|
|
284
|
+
<!-- Card 1: Human impact (false alarm) -->
|
|
285
|
+
<details class="hint-box" style="margin-top:0;">
|
|
286
|
+
<summary style="display:flex; align-items:center; justify-content:space-between; font-weight:800; cursor:pointer;">
|
|
287
|
+
<span>🧑⚖️ Card 1: False Alarm</span>
|
|
288
|
+
<span style="font-size:0.8rem; font-weight:700; opacity:0.8;">Click to reveal</span>
|
|
289
|
+
</summary>
|
|
290
|
+
<div style="font-size:0.95rem; margin-top:10px;">
|
|
291
|
+
A young person with a low real risk gets a <strong>high risk score</strong>.
|
|
292
|
+
The judge sees the number and decides to keep them in jail.
|
|
293
|
+
</div>
|
|
294
|
+
<div style="font-size:0.9rem; color:var(--body-text-color-subdued); margin-top:6px;">
|
|
295
|
+
They lose their job, miss school, and their family struggles without them — even though the model was wrong.
|
|
296
|
+
This is the cost of <strong>too many false alarms</strong>.
|
|
297
|
+
</div>
|
|
298
|
+
</details>
|
|
299
|
+
|
|
300
|
+
<!-- Card 2: Public safety impact (missed warning) -->
|
|
301
|
+
<details class="hint-box" style="margin-top:0;">
|
|
302
|
+
<summary style="display:flex; align-items:center; justify-content:space-between; font-weight:800; cursor:pointer;">
|
|
303
|
+
<span>🌍 Card 2: Missed Warning</span>
|
|
304
|
+
<span style="font-size:0.8rem; font-weight:700; opacity:0.8;">Click to reveal</span>
|
|
305
|
+
</summary>
|
|
306
|
+
<div style="font-size:0.95rem; margin-top:10px;">
|
|
307
|
+
Someone with a high real risk gets a <strong>low risk score</strong>.
|
|
308
|
+
They are released early because the system says they are “safe”.
|
|
309
|
+
</div>
|
|
310
|
+
<div style="font-size:0.9rem; color:var(--body-text-color-subdued); margin-top:6px;">
|
|
311
|
+
If they go on to commit another crime, people in the community are harmed,
|
|
312
|
+
and trust in the justice system and AI tools drops.
|
|
313
|
+
This is the danger of <strong>missed warnings</strong>.
|
|
314
|
+
</div>
|
|
315
|
+
</details>
|
|
316
|
+
|
|
317
|
+
<!-- Card 3: Justice & Equity tradeoff -->
|
|
318
|
+
<details class="hint-box" style="margin-top:0;">
|
|
319
|
+
<summary style="display:flex; align-items:center; justify-content:space-between; font-weight:800; cursor:pointer;">
|
|
320
|
+
<span>⚖️ Card 3: Unequal Mistakes</span>
|
|
321
|
+
<span style="font-size:0.8rem; font-weight:700; opacity:0.8;">Click to reveal</span>
|
|
322
|
+
</summary>
|
|
323
|
+
<div style="font-size:0.95rem; margin-top:10px;">
|
|
324
|
+
Now imagine these errors are not random: one group gets <strong>more false alarms</strong>,
|
|
325
|
+
another gets <strong>more missed warnings</strong>.
|
|
326
|
+
</div>
|
|
327
|
+
<div style="font-size:0.9rem; color:var(--body-text-color-subdued); margin-top:6px;">
|
|
328
|
+
The result? Some communities are over-punished, others are under-protected.
|
|
329
|
+
The AI system doesn’t just make mistakes — it can make society <strong>less just</strong>.
|
|
330
|
+
</div>
|
|
331
|
+
</details>
|
|
332
|
+
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
|
|
336
|
+
<!-- Societal lens -->
|
|
337
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 18px auto; text-align:center;">
|
|
338
|
+
This is why fairness experts warn that AI can make society more just — or less just —
|
|
339
|
+
depending on whether its <strong>mistakes fall equally</strong> across groups.
|
|
340
|
+
</p>
|
|
341
|
+
|
|
342
|
+
<p style="font-size:1.05rem; max-width:760px; margin:0 auto 22px auto; text-align:center;">
|
|
343
|
+
A biased model doesn’t just harm individuals.
|
|
344
|
+
It can also <strong>distort public safety</strong> by being too strict on some people and too lenient with others.
|
|
345
|
+
</p>
|
|
346
|
+
|
|
347
|
+
<!-- Justice & Equity callout -->
|
|
348
|
+
<div class="hint-box" style="margin-top:20px; border-left:4px solid #ef4444;">
|
|
349
|
+
<div style="font-weight:800;">Justice & Equity Check</div>
|
|
350
|
+
<div style="font-size:0.95rem;">
|
|
351
|
+
A system threatens Justice & Equity when one group receives more <strong>false alarms</strong>,
|
|
352
|
+
more <strong>missed warnings</strong>, or more <strong>harmful outcomes</strong>.
|
|
353
|
+
This doesn’t just break fairness — it weakens trust and safety for everyone.
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
<!-- Next move -->
|
|
358
|
+
<div style="text-align:center; margin-top:24px; padding:14px;
|
|
359
|
+
background:rgba(59,130,246,0.08); border-radius:10px;">
|
|
360
|
+
<p style="font-size:1.05rem; margin:0;">
|
|
361
|
+
<strong>Your next task:</strong> Test whether the model treats all groups fairly.
|
|
362
|
+
You’ll compare predictions vs reality to see whose futures are being shaped — or distorted.
|
|
363
|
+
</p>
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
|
|
369
|
+
"""
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"id": 2, "title": "HOW WE KNOW WHEN AI IS WRONG",
|
|
373
|
+
"html": """
|
|
374
|
+
<div class="scenario-box">
|
|
375
|
+
<h2 class="slide-title">⏳ THE POWER OF HINDSIGHT</h2>
|
|
376
|
+
<div class="slide-body">
|
|
377
|
+
|
|
378
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 22px auto; text-align:center;">
|
|
379
|
+
How do we know when the AI is wrong if we can’t open its code?
|
|
380
|
+
Simple — we compare what the model <em>predicted</em> with what <em>actually happened</em>.
|
|
381
|
+
</p>
|
|
382
|
+
|
|
383
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 22px auto; text-align:center;">
|
|
384
|
+
Investigative journalists at <strong>ProPublica</strong> collected public records on over 7,000 defendants.
|
|
385
|
+
This gives us a rare advantage: the real-world outcomes — also called the <strong>ground truth</strong> —
|
|
386
|
+
that let us check the AI’s homework.
|
|
387
|
+
</p>
|
|
388
|
+
|
|
389
|
+
<div class="ai-risk-container">
|
|
390
|
+
<div style="display:grid; gap:14px; margin-top:16px;">
|
|
391
|
+
|
|
392
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #ef4444;">
|
|
393
|
+
<div style="font-weight:bold; color:#ef4444;">1. The Prediction (What the AI expected)</div>
|
|
394
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
395
|
+
The model’s guess about the future (e.g., “High Risk”).
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #22c55e;">
|
|
400
|
+
<div style="font-weight:bold; color:#22c55e;">2. What Actually Happened (the Ground Truth)</div>
|
|
401
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
402
|
+
The real outcome in the world (e.g., “Did Not Re-offend”).
|
|
403
|
+
This is the answer key we use to check whether the AI was right.
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
|
|
410
|
+
<p style="font-size:1.05rem; max-width:760px; margin:22px auto 10px auto; text-align:center;">
|
|
411
|
+
When the prediction doesn’t match what happened in real life,
|
|
412
|
+
<strong>that’s a mistake</strong> — a false alarm or a missed warning.
|
|
413
|
+
</p>
|
|
414
|
+
|
|
415
|
+
<!-- Hands-on interactive example -->
|
|
416
|
+
<div class="ai-risk-container" style="margin-top:18px;">
|
|
417
|
+
<h4 style="margin-top:0; font-size:1.15rem; text-align:center;">🧪 Try It Yourself: Did the AI Get It Right?</h4>
|
|
418
|
+
<p style="font-size:0.95rem; text-align:center; margin:6px 0 14px 0; color:var(--body-text-color-subdued);">
|
|
419
|
+
Look at each case file. Decide: <strong>Was the AI correct?</strong>
|
|
420
|
+
If not, was it a <strong>false alarm</strong> or a <strong>missed warning</strong>?
|
|
421
|
+
Click to reveal the answer.
|
|
422
|
+
</p>
|
|
423
|
+
|
|
424
|
+
<div style="display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:12px;">
|
|
425
|
+
|
|
426
|
+
<!-- Case 1 -->
|
|
427
|
+
<details class="hint-box" style="margin-top:0;">
|
|
428
|
+
<summary style="display:flex; flex-direction:column; gap:6px; cursor:pointer;">
|
|
429
|
+
<div style="display:flex; align-items:center; justify-content:space-between; font-weight:800;">
|
|
430
|
+
<span>📁 Case #1</span>
|
|
431
|
+
<span style="font-size:0.8rem; font-weight:700; opacity:0.8;">Click to reveal</span>
|
|
432
|
+
</div>
|
|
433
|
+
<div style="font-size:0.9rem; color:var(--body-text-color-subdued);">
|
|
434
|
+
Prediction: <strong>High Risk</strong><br>
|
|
435
|
+
Real Outcome: <strong>Did Not Re-offend</strong>
|
|
436
|
+
</div>
|
|
437
|
+
</summary>
|
|
438
|
+
<div style="font-size:0.95rem; margin-top:10px;">
|
|
439
|
+
❌ The AI was <strong>wrong</strong>.
|
|
440
|
+
This is a <strong>false alarm</strong> (false positive).
|
|
441
|
+
</div>
|
|
442
|
+
<div style="font-size:0.9rem; color:var(--body-text-color-subdued); margin-top:6px;">
|
|
443
|
+
A low-risk person was treated as “dangerous.”
|
|
444
|
+
They may have been kept in jail longer or denied opportunities unfairly.
|
|
445
|
+
</div>
|
|
446
|
+
</details>
|
|
447
|
+
|
|
448
|
+
<!-- Case 2 -->
|
|
449
|
+
<details class="hint-box" style="margin-top:0;">
|
|
450
|
+
<summary style="display:flex; flex-direction:column; gap:6px; cursor:pointer;">
|
|
451
|
+
<div style="display:flex; align-items:center; justify-content:space-between; font-weight:800;">
|
|
452
|
+
<span>📁 Case #2</span>
|
|
453
|
+
<span style="font-size:0.8rem; font-weight:700; opacity:0.8;">Click to reveal</span>
|
|
454
|
+
</div>
|
|
455
|
+
<div style="font-size:0.9rem; color:var(--body-text-color-subdued);">
|
|
456
|
+
Prediction: <strong>Low Risk</strong><br>
|
|
457
|
+
Real Outcome: <strong>Re-offended</strong>
|
|
458
|
+
</div>
|
|
459
|
+
</summary>
|
|
460
|
+
<div style="font-size:0.95rem; margin-top:10px;">
|
|
461
|
+
❌ The AI was <strong>wrong</strong>.
|
|
462
|
+
This is a <strong>missed warning</strong> (false negative).
|
|
463
|
+
</div>
|
|
464
|
+
<div style="font-size:0.9rem; color:var(--body-text-color-subdued); margin-top:6px;">
|
|
465
|
+
Someone who was actually high risk was treated as “safe,”
|
|
466
|
+
which can harm people in the community and weaken trust.
|
|
467
|
+
</div>
|
|
468
|
+
</details>
|
|
469
|
+
|
|
470
|
+
<!-- Case 3 -->
|
|
471
|
+
<details class="hint-box" style="margin-top:0;">
|
|
472
|
+
<summary style="display:flex; flex-direction:column; gap:6px; cursor:pointer;">
|
|
473
|
+
<div style="display:flex; align-items:center; justify-content:space-between; font-weight:800;">
|
|
474
|
+
<span>📁 Case #3</span>
|
|
475
|
+
<span style="font-size:0.8rem; font-weight:700; opacity:0.8;">Click to reveal</span>
|
|
476
|
+
</div>
|
|
477
|
+
<div style="font-size:0.9rem; color:var(--body-text-color-subdued);">
|
|
478
|
+
Prediction: <strong>High Risk</strong><br>
|
|
479
|
+
Real Outcome: <strong>Re-offended</strong>
|
|
480
|
+
</div>
|
|
481
|
+
</summary>
|
|
482
|
+
<div style="font-size:0.95rem; margin-top:10px;">
|
|
483
|
+
✅ The AI was <strong>correct</strong>.
|
|
484
|
+
This is a <strong>true positive</strong>.
|
|
485
|
+
</div>
|
|
486
|
+
<div style="font-size:0.9rem; color:var(--body-text-color-subdued); margin-top:6px;">
|
|
487
|
+
The model’s high-risk flag matched reality.
|
|
488
|
+
Correct predictions like this are useful — as long as errors aren’t concentrated on certain groups.
|
|
489
|
+
</div>
|
|
490
|
+
</details>
|
|
491
|
+
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
|
|
495
|
+
<p style="font-size:1.05rem; max-width:760px; margin:22px auto 0 auto; text-align:center;">
|
|
496
|
+
Next, you’ll stop looking at single cases and start scanning <strong>patterns</strong>:
|
|
497
|
+
which groups get more false alarms or missed warnings from the model.
|
|
498
|
+
</p>
|
|
499
|
+
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
"""
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
"id": 3, "title": "Analysis: False Positives",
|
|
508
|
+
"html": """
|
|
509
|
+
<div class="scenario-box">
|
|
510
|
+
<h2 class="slide-title">📡 OUTPUT SCAN: SEARCHING FOR UNEQUAL MISTAKES</h2>
|
|
511
|
+
<div class="slide-body">
|
|
512
|
+
|
|
513
|
+
<p style="font-size:1.05rem; max-width:800px; margin:0 auto 18px auto; text-align:center;">
|
|
514
|
+
You’ve seen individual cases where the AI made false alarms and missed warnings.
|
|
515
|
+
Now it’s time to look for <strong>patterns</strong> across groups.
|
|
516
|
+
A fair system should not make <em>more mistakes</em> for one group than another.
|
|
517
|
+
</p>
|
|
518
|
+
|
|
519
|
+
<div class="ai-risk-container" style="margin-top:10px;">
|
|
520
|
+
<h4 style="margin-top:0; font-size:1.2rem; text-align:center;">🧠 What an Output Error Scan Looks For</h4>
|
|
521
|
+
|
|
522
|
+
<div style="display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-top:16px;">
|
|
523
|
+
|
|
524
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #ef4444;">
|
|
525
|
+
<div style="font-weight:800;">1. False Alarm Rate</div>
|
|
526
|
+
<div style="font-size:0.95rem; color:var(--body-text-color-subdued);">
|
|
527
|
+
How often the model labels someone “High Risk” when they actually did <em>not</em> re-offend.
|
|
528
|
+
</div>
|
|
529
|
+
<div style="font-size:0.92rem; margin-top:6px;">
|
|
530
|
+
High false alarms mean more people may face detention or punishment unfairly.
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
|
|
534
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #3b82f6;">
|
|
535
|
+
<div style="font-weight:800;">2. Missed Warning Rate</div>
|
|
536
|
+
<div style="font-size:0.95rem; color:var(--body-text-color-subdued);">
|
|
537
|
+
How often the model labels someone “Low Risk” when they actually re-offend.
|
|
538
|
+
</div>
|
|
539
|
+
<div style="font-size:0.92rem; margin-top:6px;">
|
|
540
|
+
High missed warnings can harm communities and reduce trust in the justice system.
|
|
541
|
+
</div>
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
|
|
545
|
+
<p style="font-size:1.05rem; max-width:780px; margin:20px auto 14px auto; text-align:center;">
|
|
546
|
+
If one group receives more false alarms or more missed warnings,
|
|
547
|
+
the system may violate <strong>Justice & Equity</strong>.
|
|
548
|
+
</p>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<!-- FIRST REAL OUTPUT SCAN: False Alarms by Race -->
|
|
552
|
+
<div class="ai-risk-container" style="margin-top:30px; padding:22px; border-width:2px;">
|
|
553
|
+
|
|
554
|
+
<h3 style="margin-top:0; font-size:1.35rem; text-align:center;">
|
|
555
|
+
📡 FIRST SCAN: FALSE ALARMS BY RACE
|
|
556
|
+
</h3>
|
|
557
|
+
|
|
558
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 18px auto; text-align:center;">
|
|
559
|
+
You’ll now scan for <strong>False Alarms</strong> — cases where the AI marked someone as “High Risk”
|
|
560
|
+
even though they <em>did not</em> re-offend. Click the scan below to reveal what you found.
|
|
561
|
+
</p>
|
|
562
|
+
|
|
563
|
+
<!-- INTERACTIVE SCAN -->
|
|
564
|
+
<details style="border:1px solid var(--border-color-primary); border-radius:12px; overflow:hidden; margin-top:10px;">
|
|
565
|
+
<summary style="list-style:none; cursor:pointer; padding:14px 18px; font-weight:800;
|
|
566
|
+
text-align:center; background:var(--background-fill-secondary);">
|
|
567
|
+
📡 SCAN: False Alarms by Race — Click to reveal analysis
|
|
568
|
+
</summary>
|
|
569
|
+
|
|
570
|
+
<!-- EVERYTHING BELOW IS REVEALED AFTER CLICK -->
|
|
571
|
+
<div style="text-align:center; padding:24px;">
|
|
572
|
+
|
|
573
|
+
<!-- Title -->
|
|
574
|
+
<h4 style="margin-top:0; font-size:1.25rem; margin-bottom:20px;">
|
|
575
|
+
📊 False Alarm Rate (Incorrect High-Risk Flags)
|
|
576
|
+
</h4>
|
|
577
|
+
|
|
578
|
+
<!-- CLEAN, ALIGNED BAR CHART -->
|
|
579
|
+
<div style="display:flex; justify-content:center; gap:40px;">
|
|
580
|
+
|
|
581
|
+
<!-- African-American Bar -->
|
|
582
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
583
|
+
<div style="font-size:2rem; font-weight:800; color:#ef4444; margin-bottom:6px;">
|
|
584
|
+
45%
|
|
585
|
+
</div>
|
|
586
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
587
|
+
width:80px; height:180px; background:#fee2e2; border-radius:8px;
|
|
588
|
+
overflow:hidden; border:1px solid #fca5a5;">
|
|
589
|
+
<div style="background:#ef4444; height:45%; width:100%;"></div>
|
|
590
|
+
</div>
|
|
591
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">
|
|
592
|
+
African-American
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
|
|
596
|
+
<!-- White Bar -->
|
|
597
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
598
|
+
<div style="font-size:2rem; font-weight:800; color:#3b82f6; margin-bottom:6px;">
|
|
599
|
+
23%
|
|
600
|
+
</div>
|
|
601
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
602
|
+
width:80px; height:180px; background:#dbeafe; border-radius:8px;
|
|
603
|
+
overflow:hidden; border:1px solid #93c5fd;">
|
|
604
|
+
<div style="background:#3b82f6; height:23%; width:100%;"></div>
|
|
605
|
+
</div>
|
|
606
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">
|
|
607
|
+
White
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
</div>
|
|
612
|
+
|
|
613
|
+
<p style="font-size:0.95rem; max-width:760px; margin:20px auto 0 auto;
|
|
614
|
+
color:var(--body-text-color-subdued);">
|
|
615
|
+
African-American defendants received nearly <strong>twice as many</strong> false alarms as White defendants.
|
|
616
|
+
</p>
|
|
617
|
+
|
|
618
|
+
<!-- DETECTIVE ANALYSIS -->
|
|
619
|
+
<div class="hint-box" style="background:rgba(239, 68, 68, 0.08); margin-top:24px; border-left:4px solid #ef4444;">
|
|
620
|
+
<h4 style="margin-top:0;">🔍 Detective's Analysis</h4>
|
|
621
|
+
|
|
622
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
623
|
+
This scan reveals a major imbalance: the AI is producing <strong>nearly twice as many false alarms</strong>
|
|
624
|
+
for African-American defendants as for White defendants. These are people labeled “High Risk”
|
|
625
|
+
even though they <em>did not</em> re-offend.
|
|
626
|
+
</p>
|
|
627
|
+
|
|
628
|
+
<p style="font-size:0.98rem; margin-bottom:12px;">
|
|
629
|
+
As a detective, this is where you’d ask:
|
|
630
|
+
<strong>“If the system is wrong, who pays the price for the mistake?”</strong>
|
|
631
|
+
In this case, one group consistently receives harsher mistakes — and that pattern has a name.
|
|
632
|
+
</p>
|
|
633
|
+
|
|
634
|
+
<!-- Punitive Bias Definition -->
|
|
635
|
+
<div style="background:white; border:1px solid #ef4444; border-radius:8px; padding:14px; margin:14px 0;">
|
|
636
|
+
<h4 style="margin:0; color:#ef4444; font-size:1.05rem;">⚠️ What You Just Found: Punitive Bias</h4>
|
|
637
|
+
<p style="font-size:0.95rem; margin:8px 0 0 0;">
|
|
638
|
+
<strong>Punitive Bias</strong> happens when an AI makes mistakes that unfairly label certain groups as
|
|
639
|
+
“more dangerous,” causing harsher outcomes — even when those individuals did nothing wrong.
|
|
640
|
+
</p>
|
|
641
|
+
<p style="font-size:0.9rem; margin:8px 0 0 0; color:var(--body-text-color-subdued);">
|
|
642
|
+
These mistakes aren’t random. One group gets <em>more false alarms</em>, <em>more harsh labels</em>,
|
|
643
|
+
and <em>more punishment</em>. That’s a serious warning of a Justice & Equity failure.
|
|
644
|
+
</p>
|
|
645
|
+
</div>
|
|
646
|
+
|
|
647
|
+
<p style="font-size:0.95rem; margin-top:8px;">
|
|
648
|
+
The takeaway: this model isn’t just inaccurate — it is <strong>inaccurate in a way that targets one group more than others</strong>.
|
|
649
|
+
That means the harm is not evenly shared.
|
|
650
|
+
</p>
|
|
651
|
+
</div>
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
<!-- TRANSITION TO NEXT SCAN -->
|
|
655
|
+
<div style="text-align:center; margin-top:26px; padding:14px; background:rgba(59,130,246,0.08); border-radius:10px;">
|
|
656
|
+
<p style="font-size:1.05rem; margin:0;">
|
|
657
|
+
<strong>Next:</strong> Punitive Bias is only half the story.
|
|
658
|
+
What about mistakes that make the system <em>too lenient</em>?
|
|
659
|
+
Let’s scan for <strong>False Negatives</strong> — cases where the model missed real danger.
|
|
660
|
+
</p>
|
|
661
|
+
</div>
|
|
662
|
+
|
|
663
|
+
</div> <!-- end of revealed content -->
|
|
664
|
+
|
|
665
|
+
</details>
|
|
666
|
+
|
|
667
|
+
</div>
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
"""
|
|
671
|
+
},
|
|
672
|
+
|
|
673
|
+
{
|
|
674
|
+
"id": 4, "title": "Analysis: Missed Risk",
|
|
675
|
+
"html": """
|
|
676
|
+
<div class="scenario-box">
|
|
677
|
+
<h2 class="slide-title">⚖️ THE OTHER SIDE OF ERROR: MISSED RISK</h2>
|
|
678
|
+
<div class="slide-body">
|
|
679
|
+
|
|
680
|
+
<!-- Step badge -->
|
|
681
|
+
<div style="display:flex; justify-content:center; margin-bottom:18px;">
|
|
682
|
+
<div style="display:inline-flex; align-items:center; gap:10px; padding:10px 18px;
|
|
683
|
+
border-radius:999px; background:var(--background-fill-secondary);
|
|
684
|
+
border:1px solid var(--border-color-primary); font-size:0.95rem; font-weight:800;">
|
|
685
|
+
<span style="font-size:1.1rem;">📋</span>
|
|
686
|
+
<span>STEP 3: PROVE THE PREDICTION ERROR — Part 2</span>
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
|
|
690
|
+
<!-- Framing text -->
|
|
691
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 14px auto; text-align:center;">
|
|
692
|
+
You’ve already uncovered <strong>Punitive Bias</strong> — the model is more likely to
|
|
693
|
+
<em>wrongly label</em> African-American defendants as “High Risk.”
|
|
694
|
+
</p>
|
|
695
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 18px auto; text-align:center;">
|
|
696
|
+
But that’s only half the story. Now we ask the mirror question:
|
|
697
|
+
<strong>Who does the model go too easy on?</strong>
|
|
698
|
+
</p>
|
|
699
|
+
|
|
700
|
+
<div class="ai-risk-container" style="margin-top:8px;">
|
|
701
|
+
<h4 style="margin-top:0; font-size:1.15rem; text-align:center;">🧩 What We’re Looking For Now</h4>
|
|
702
|
+
<p style="font-size:1.0rem; max-width:780px; margin:0 auto 8px auto; text-align:center;">
|
|
703
|
+
In AI fairness, we don’t just look at who is punished more. We also look at who the system
|
|
704
|
+
<strong>lets off the hook</strong> — even when they later cause harm.
|
|
705
|
+
</p>
|
|
706
|
+
<p style="font-size:1.0rem; max-width:780px; margin:0 auto; text-align:center; color:var(--body-text-color-subdued);">
|
|
707
|
+
These cases are called <strong>False Negatives</strong>: people labeled “Low Risk” by the AI
|
|
708
|
+
who actually <em>did re-offend</em> in the real world.
|
|
709
|
+
</p>
|
|
710
|
+
</div>
|
|
711
|
+
|
|
712
|
+
<!-- Simple explanatory analogy -->
|
|
713
|
+
<div class="hint-box" style="margin-top:16px;">
|
|
714
|
+
<div style="font-weight:800;">🔁 Two Types of Dangerous Mistakes</div>
|
|
715
|
+
<div style="font-size:0.98rem; margin-top:4px;">
|
|
716
|
+
You can think of the model like a security checkpoint:
|
|
717
|
+
</div>
|
|
718
|
+
<ul style="font-size:0.95rem; margin:8px 0 0 18px; padding:0;">
|
|
719
|
+
<li><strong>False Positive (False Alarm):</strong> Stopping an innocent person as if they were dangerous.</li>
|
|
720
|
+
<li><strong>False Negative (Missed Risk):</strong> Letting a dangerous person walk through unchecked.</li>
|
|
721
|
+
</ul>
|
|
722
|
+
<p style="font-size:0.96rem; margin-top:10px;">
|
|
723
|
+
Justice & Equity means checking <em>both</em>: Who is unfairly stopped, and who is unfairly waved through.
|
|
724
|
+
</p>
|
|
725
|
+
</div>
|
|
726
|
+
|
|
727
|
+
<!-- INTERACTIVE SCAN: FALSE NEGATIVES BY RACE -->
|
|
728
|
+
<div class="ai-risk-container" style="margin-top:24px; padding:22px; border-width:2px;">
|
|
729
|
+
<h3 style="margin-top:0; font-size:1.3rem; text-align:center;">
|
|
730
|
+
📡 SECOND SCAN: MISSED RISK BY RACE
|
|
731
|
+
</h3>
|
|
732
|
+
|
|
733
|
+
<p style="font-size:1.02rem; max-width:780px; margin:0 auto 16px auto; text-align:center;">
|
|
734
|
+
Now you’ll scan for <strong>False Negatives</strong> — people the AI marked as “Low Risk” who
|
|
735
|
+
<em>did</em> go on to re-offend. Click the scan to reveal what you found in the COMPAS data.
|
|
736
|
+
</p>
|
|
737
|
+
|
|
738
|
+
<details style="border:1px solid var(--border-color-primary); border-radius:12px; overflow:hidden; margin-top:10px;">
|
|
739
|
+
<summary style="list-style:none; cursor:pointer; padding:14px 18px; font-weight:800;
|
|
740
|
+
text-align:center; background:var(--background-fill-secondary);">
|
|
741
|
+
📡 SCAN: Missed Risk by Race — Click to reveal analysis
|
|
742
|
+
</summary>
|
|
743
|
+
|
|
744
|
+
<!-- Revealed content -->
|
|
745
|
+
<div style="text-align:center; padding:24px;">
|
|
746
|
+
|
|
747
|
+
<h4 style="margin-top:0; font-size:1.2rem; margin-bottom:18px;">
|
|
748
|
+
📊 False Negative Rate (Missed High-Risk Cases)
|
|
749
|
+
</h4>
|
|
750
|
+
|
|
751
|
+
<p style="font-size:0.95rem; max-width:780px; margin:0 auto 18px auto;">
|
|
752
|
+
Among people who <strong>did re-offend</strong>, how often did the AI incorrectly label them as
|
|
753
|
+
“Low Risk”?
|
|
754
|
+
</p>
|
|
755
|
+
|
|
756
|
+
<!-- Vertical bar chart -->
|
|
757
|
+
<div style="display:flex; justify-content:center; gap:40px;">
|
|
758
|
+
|
|
759
|
+
<!-- African-American bar -->
|
|
760
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
761
|
+
<div style="font-size:1.8rem; font-weight:800; color:#ef4444; margin-bottom:6px;">
|
|
762
|
+
28%
|
|
763
|
+
</div>
|
|
764
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
765
|
+
width:80px; height:180px; background:#fee2e2; border-radius:8px;
|
|
766
|
+
overflow:hidden; border:1px solid #fca5a5;">
|
|
767
|
+
<div style="background:#ef4444; height:28%; width:100%;"></div>
|
|
768
|
+
</div>
|
|
769
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">
|
|
770
|
+
African-American
|
|
771
|
+
</div>
|
|
772
|
+
</div>
|
|
773
|
+
|
|
774
|
+
<!-- White bar -->
|
|
775
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
776
|
+
<div style="font-size:1.8rem; font-weight:800; color:#3b82f6; margin-bottom:6px;">
|
|
777
|
+
48%
|
|
778
|
+
</div>
|
|
779
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
780
|
+
width:80px; height:180px; background:#dbeafe; border-radius:8px;
|
|
781
|
+
overflow:hidden; border:1px solid #93c5fd;">
|
|
782
|
+
<div style="background:#3b82f6; height:48%; width:100%;"></div>
|
|
783
|
+
</div>
|
|
784
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">
|
|
785
|
+
White
|
|
786
|
+
</div>
|
|
787
|
+
</div>
|
|
788
|
+
|
|
789
|
+
</div>
|
|
790
|
+
|
|
791
|
+
<p style="font-size:0.95rem; max-width:760px; margin:20px auto 0 auto;
|
|
792
|
+
color:var(--body-text-color-subdued);">
|
|
793
|
+
In this dataset, White defendants who went on to re-offend were <strong>much more likely</strong>
|
|
794
|
+
to be labeled “Low Risk” than African-American defendants who re-offended.
|
|
795
|
+
</p>
|
|
796
|
+
|
|
797
|
+
<!-- Detective analysis -->
|
|
798
|
+
<div class="hint-box" style="background:rgba(59,130,246,0.08); margin-top:24px; border-left:4px solid #3b82f6;">
|
|
799
|
+
<h4 style="margin-top:0;">🔍 Detective's Analysis</h4>
|
|
800
|
+
|
|
801
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
802
|
+
Earlier, you found <strong>Punitive Bias</strong>: more harsh mistakes for African-American defendants
|
|
803
|
+
(False Alarms). Now you’ve found the flip side:
|
|
804
|
+
the model is <strong>more likely to underestimate risk</strong> for White defendants.
|
|
805
|
+
</p>
|
|
806
|
+
|
|
807
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
808
|
+
This pattern is sometimes called a <strong>leniency pattern</strong>: the system gives one group
|
|
809
|
+
<em>more second chances</em>, even when those people are statistically more likely to re-offend.
|
|
810
|
+
</p>
|
|
811
|
+
|
|
812
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
813
|
+
Put together, this means:
|
|
814
|
+
</p>
|
|
815
|
+
<ul style="font-size:0.95rem; margin:0 0 8px 18px; padding:0;">
|
|
816
|
+
<li><strong>More false harshness</strong> for one group (Punitive Bias).</li>
|
|
817
|
+
<li><strong>More false leniency</strong> for another group (leniency pattern / Missed Risk).</li>
|
|
818
|
+
</ul>
|
|
819
|
+
|
|
820
|
+
<p style="font-size:0.96rem; margin-top:8px;">
|
|
821
|
+
The system isn’t just “a bit wrong.” It is wrong in a way that <strong>shifts both punishment and
|
|
822
|
+
protection unequally</strong>. That’s a serious Justice & Equity concern.
|
|
823
|
+
</p>
|
|
824
|
+
</div>
|
|
825
|
+
|
|
826
|
+
<!-- Optional: small bonus tip -->
|
|
827
|
+
<details style="margin:22px 0 0 0; border:1px solid var(--border-color-primary); border-radius:10px; overflow:hidden;">
|
|
828
|
+
<summary style="
|
|
829
|
+
list-style:none;
|
|
830
|
+
cursor:pointer;
|
|
831
|
+
padding:10px 16px;
|
|
832
|
+
display:flex;
|
|
833
|
+
align-items:center;
|
|
834
|
+
justify-content:space-between;
|
|
835
|
+
gap:10px;
|
|
836
|
+
background:rgba(59,130,246,0.08);
|
|
837
|
+
font-weight:800;
|
|
838
|
+
font-size:0.9rem;">
|
|
839
|
+
<span>
|
|
840
|
+
🕵️ BONUS DETECTIVE TIP: Justice vs. Safety — what balance do you want?
|
|
841
|
+
</span>
|
|
842
|
+
<span style="font-size:0.85rem; opacity:0.8;">Click to reveal</span>
|
|
843
|
+
</summary>
|
|
844
|
+
<div style="padding:14px 16px 18px 16px; background:rgba(15,23,42,0.02);">
|
|
845
|
+
<p style="font-size:0.96rem; margin-top:0;">
|
|
846
|
+
Some people argue: “As long as we catch dangerous people, it’s fine.” But your scan shows something deeper:
|
|
847
|
+
<strong>who is protected and who is punished depends on how the errors are distributed.</strong>
|
|
848
|
+
</p>
|
|
849
|
+
<ul style="font-size:0.94rem; margin:8px 0 0 18px; padding:0;">
|
|
850
|
+
<li>If False Alarms target one group → <strong>unfair punishment.</strong></li>
|
|
851
|
+
<li>If Missed Risk favors another group → <strong>unfair protection.</strong></li>
|
|
852
|
+
</ul>
|
|
853
|
+
<p style="font-size:0.96rem; margin-top:10px;">
|
|
854
|
+
As a Bias Detective, your job is not to make the model perfect — it’s to make sure its mistakes
|
|
855
|
+
<strong>don’t systematically favor or hurt specific groups</strong>.
|
|
856
|
+
</p>
|
|
857
|
+
</div>
|
|
858
|
+
</details>
|
|
859
|
+
|
|
860
|
+
</div> <!-- end revealed content -->
|
|
861
|
+
|
|
862
|
+
</details>
|
|
863
|
+
</div>
|
|
864
|
+
|
|
865
|
+
<!-- Short transition to next step -->
|
|
866
|
+
<div style="text-align:center; margin-top:24px; padding:14px; background:rgba(59,130,246,0.1); border-radius:10px;">
|
|
867
|
+
<p style="font-size:1.04rem; margin:0; font-weight:600;">
|
|
868
|
+
Next, compare these errors across <strong>Gender → Age → Geography</strong> to before you build and submit your full audit report.
|
|
869
|
+
</p>
|
|
870
|
+
</div>
|
|
871
|
+
|
|
872
|
+
</div>
|
|
873
|
+
</div>
|
|
874
|
+
|
|
875
|
+
"""
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
"id": 5, "title": "Analysis: Gender",
|
|
879
|
+
"html": """
|
|
880
|
+
<div class="scenario-box">
|
|
881
|
+
<h2 class="slide-title">⚠️ EVIDENCE FOUND: GENERALIZATION BIAS (GENDER)</h2>
|
|
882
|
+
<div class="slide-body">
|
|
883
|
+
|
|
884
|
+
<!-- Step badge -->
|
|
885
|
+
<div style="display:flex; justify-content:center; margin-bottom:18px;">
|
|
886
|
+
<div style="display:inline-flex; align-items:center; gap:10px; padding:10px 18px;
|
|
887
|
+
border-radius:999px; background:var(--background-fill-secondary);
|
|
888
|
+
border:1px solid var(--border-color-primary); font-size:0.95rem; font-weight:800;">
|
|
889
|
+
<span style="font-size:1.1rem;">📋</span>
|
|
890
|
+
<span>STEP 3: PROVE THE PREDICTION ERROR — Gender Scan</span>
|
|
891
|
+
</div>
|
|
892
|
+
</div>
|
|
893
|
+
|
|
894
|
+
<!-- Framing text -->
|
|
895
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 14px auto; text-align:center;">
|
|
896
|
+
Earlier, you discovered that the COMPAS dataset is <strong>81% male</strong> and only <strong>19% female</strong>.
|
|
897
|
+
Now we’ll see what that imbalance does to the model’s behavior.
|
|
898
|
+
</p>
|
|
899
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 18px auto; text-align:center;">
|
|
900
|
+
We’re looking for <strong>Generalization Bias</strong> — when the AI learns patterns mostly from one group
|
|
901
|
+
and then <em>copies</em> those patterns onto another group where they don’t really fit.
|
|
902
|
+
</p>
|
|
903
|
+
|
|
904
|
+
<!-- 92% Accuracy Trap -->
|
|
905
|
+
<div class="hint-box" style="margin-top:10px;">
|
|
906
|
+
<div style="font-weight:800;">🎭 The “92% Accuracy” Trap</div>
|
|
907
|
+
|
|
908
|
+
<p style="font-size:0.98rem; margin-top:6px;">
|
|
909
|
+
Imagine the COMPAS engineering team proudly reports:
|
|
910
|
+
<br><strong>“Our model is 92% accurate.”</strong>
|
|
911
|
+
</p>
|
|
912
|
+
|
|
913
|
+
<p style="font-size:0.98rem; margin-top:8px;">
|
|
914
|
+
Sounds impressive, right? But that <strong>92% is an average</strong> over the whole dataset —
|
|
915
|
+
and we already know that most of that dataset is men.
|
|
916
|
+
</p>
|
|
917
|
+
|
|
918
|
+
<p style="font-size:0.98rem; margin-top:8px;">
|
|
919
|
+
That means the “92% accuracy” number mostly reflects how well the model does on
|
|
920
|
+
<strong>men’s cases</strong>. The accuracy for women could be much worse, but you’d never
|
|
921
|
+
see that from the headline number alone.
|
|
922
|
+
</p>
|
|
923
|
+
|
|
924
|
+
<p style="font-size:0.98rem; margin-top:8px;">
|
|
925
|
+
A system can look “92% accurate overall” while still making
|
|
926
|
+
<strong>dangerously bad mistakes for a smaller group</strong> it barely saw in training.
|
|
927
|
+
</p>
|
|
928
|
+
|
|
929
|
+
<p style="font-size:0.96rem; margin-top:10px; color:var(--body-text-color-subdued);">
|
|
930
|
+
Your job as a Bias Detective is to <strong>break that average apart</strong> and see who the
|
|
931
|
+
model is really failing.
|
|
932
|
+
</p>
|
|
933
|
+
</div>
|
|
934
|
+
|
|
935
|
+
<!-- INTERACTIVE SCAN: ERROR PATTERN BY GENDER -->
|
|
936
|
+
<div class="ai-risk-container" style="margin-top:22px; padding:22px; border-width:2px;">
|
|
937
|
+
<h3 style="margin-top:0; font-size:1.25rem; text-align:center;">
|
|
938
|
+
📡 GENDER SCAN: WHO GETS MISTAKENLY FLAGGED?
|
|
939
|
+
</h3>
|
|
940
|
+
|
|
941
|
+
<p style="font-size:1.0rem; max-width:780px; margin:0 auto 16px auto; text-align:center;">
|
|
942
|
+
Now you’ll scan for how often the AI <strong>over-predicts risk</strong> for men vs women.
|
|
943
|
+
Think of it as asking: <strong>“Whose behavior is the model bad at reading?”</strong>
|
|
944
|
+
</p>
|
|
945
|
+
|
|
946
|
+
<details style="border:1px solid var(--border-color-primary); border-radius:12px; overflow:hidden; margin-top:10px;">
|
|
947
|
+
<summary style="list-style:none; cursor:pointer; padding:14px 18px; font-weight:800;
|
|
948
|
+
text-align:center; background:var(--background-fill-secondary);">
|
|
949
|
+
📡 SCAN: High-Risk Errors by Gender — Click to reveal analysis
|
|
950
|
+
</summary>
|
|
951
|
+
|
|
952
|
+
<!-- Revealed content -->
|
|
953
|
+
<div style="text-align:center; padding:24px;">
|
|
954
|
+
|
|
955
|
+
<h4 style="margin-top:0; font-size:1.15rem; margin-bottom:14px;">
|
|
956
|
+
📊 Example Scan: Incorrect “High Risk” Flags on Less Serious Charges
|
|
957
|
+
</h4>
|
|
958
|
+
|
|
959
|
+
<p style="font-size:0.95rem; max-width:780px; margin:0 auto 18px auto;">
|
|
960
|
+
Imagine your scan finds this pattern for people with <strong>less serious (non-violent) charges</strong>:
|
|
961
|
+
how often does the AI wrongly label them as “High Risk”?
|
|
962
|
+
</p>
|
|
963
|
+
|
|
964
|
+
<!-- Simple vertical bar chart -->
|
|
965
|
+
<div style="display:flex; justify-content:center; gap:40px; margin-top:10px;">
|
|
966
|
+
|
|
967
|
+
<!-- Men bar -->
|
|
968
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
969
|
+
<div style="font-size:1.6rem; font-weight:800; color:#3b82f6; margin-bottom:6px;">
|
|
970
|
+
18%
|
|
971
|
+
</div>
|
|
972
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
973
|
+
width:80px; height:180px; background:#dbeafe; border-radius:8px;
|
|
974
|
+
overflow:hidden; border:1px solid #93c5fd;">
|
|
975
|
+
<div style="background:#3b82f6; height:18%; width:100%;"></div>
|
|
976
|
+
</div>
|
|
977
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">
|
|
978
|
+
Men
|
|
979
|
+
</div>
|
|
980
|
+
<div style="margin-top:4px; font-size:0.85rem; color:var(--body-text-color-subdued);">
|
|
981
|
+
Wrong “High Risk” label
|
|
982
|
+
</div>
|
|
983
|
+
</div>
|
|
984
|
+
|
|
985
|
+
<!-- Women bar -->
|
|
986
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
987
|
+
<div style="font-size:1.6rem; font-weight:800; color:#ef4444; margin-bottom:6px;">
|
|
988
|
+
32%
|
|
989
|
+
</div>
|
|
990
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
991
|
+
width:80px; height:180px; background:#fee2e2; border-radius:8px;
|
|
992
|
+
overflow:hidden; border:1px solid #fca5a5;">
|
|
993
|
+
<div style="background:#ef4444; height:32%; width:100%;"></div>
|
|
994
|
+
</div>
|
|
995
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">
|
|
996
|
+
Women
|
|
997
|
+
</div>
|
|
998
|
+
<div style="margin-top:4px; font-size:0.85rem; color:var(--body-text-color-subdued);">
|
|
999
|
+
Wrong “High Risk” label
|
|
1000
|
+
</div>
|
|
1001
|
+
</div>
|
|
1002
|
+
|
|
1003
|
+
</div>
|
|
1004
|
+
|
|
1005
|
+
<p style="font-size:0.93rem; max-width:760px; margin:20px auto 0 auto;
|
|
1006
|
+
color:var(--body-text-color-subdued);">
|
|
1007
|
+
In this kind of pattern, women with similar, less serious charges are
|
|
1008
|
+
<strong>more likely</strong> to be mistakenly treated as “High Risk” than men.
|
|
1009
|
+
</p>
|
|
1010
|
+
|
|
1011
|
+
<!-- Detective analysis -->
|
|
1012
|
+
<div class="hint-box" style="background:rgba(239,68,68,0.08); margin-top:22px; border-left:4px solid #ef4444;">
|
|
1013
|
+
<h4 style="margin-top:0;">🔍 Detective's Analysis</h4>
|
|
1014
|
+
|
|
1015
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
1016
|
+
The model was trained mostly on men. So when it sees women’s cases, it often
|
|
1017
|
+
<strong>copies male patterns onto them</strong> — even when women’s real re-offense patterns are different.
|
|
1018
|
+
</p>
|
|
1019
|
+
|
|
1020
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
1021
|
+
This is an example of <strong>generalization bias</strong>:
|
|
1022
|
+
the AI takes what it learned from one group and wrongly generalizes it to another group
|
|
1023
|
+
it doesn’t really understand.
|
|
1024
|
+
</p>
|
|
1025
|
+
|
|
1026
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
1027
|
+
The “92% accuracy” claim hides this:
|
|
1028
|
+
</p>
|
|
1029
|
+
<ul style="font-size:0.95rem; margin:0 0 8px 18px; padding:0;">
|
|
1030
|
+
<li>The model might be doing <strong>okay for men</strong> (the majority).</li>
|
|
1031
|
+
<li>But for women, it makes <strong>more bad calls</strong> — flagging them as “High Risk”
|
|
1032
|
+
when their actual risk is lower.</li>
|
|
1033
|
+
</ul>
|
|
1034
|
+
|
|
1035
|
+
<p style="font-size:0.96rem; margin-top:8px;">
|
|
1036
|
+
To judges or the public, the system looks “92% accurate.” But to the women
|
|
1037
|
+
being misclassified, the system feels <strong>unfair, over-cautious, and mistrustful</strong>.
|
|
1038
|
+
</p>
|
|
1039
|
+
</div>
|
|
1040
|
+
|
|
1041
|
+
</div> <!-- end revealed content -->
|
|
1042
|
+
|
|
1043
|
+
</details>
|
|
1044
|
+
</div>
|
|
1045
|
+
|
|
1046
|
+
<!-- Short transition -->
|
|
1047
|
+
<div style="text-align:center; margin-top:24px; padding:14px; background:rgba(59,130,246,0.1); border-radius:10px;">
|
|
1048
|
+
<p style="font-size:1.04rem; margin:0; font-weight:600;">
|
|
1049
|
+
Next, you’ll repeat this logic for <strong>Age</strong> and <strong>Geography</strong> to see
|
|
1050
|
+
whether the model’s “overall accuracy” is hiding more unfair patterns.
|
|
1051
|
+
</p>
|
|
1052
|
+
</div>
|
|
1053
|
+
|
|
1054
|
+
</div>
|
|
1055
|
+
</div>
|
|
1056
|
+
|
|
1057
|
+
"""
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
"id": 6, "title": "Age Scan",
|
|
1061
|
+
"html": """
|
|
1062
|
+
<div class="scenario-box">
|
|
1063
|
+
<h2 class="slide-title">⚠️ EVIDENCE FOUND: AGE BLIND SPOTS</h2>
|
|
1064
|
+
<div class="slide-body">
|
|
1065
|
+
|
|
1066
|
+
<!-- Step badge -->
|
|
1067
|
+
<div style="display:flex; justify-content:center; margin-bottom:18px;">
|
|
1068
|
+
<div style="display:inline-flex; align-items:center; gap:10px; padding:10px 18px;
|
|
1069
|
+
border-radius:999px; background:var(--background-fill-secondary);
|
|
1070
|
+
border:1px solid var(--border-color-primary); font-size:0.95rem; font-weight:800;">
|
|
1071
|
+
<span style="font-size:1.1rem;">📋</span>
|
|
1072
|
+
<span>STEP 3: PROVE THE PREDICTION ERROR — Age Scan</span>
|
|
1073
|
+
</div>
|
|
1074
|
+
</div>
|
|
1075
|
+
|
|
1076
|
+
<!-- Framing text -->
|
|
1077
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 14px auto; text-align:center;">
|
|
1078
|
+
You’ve analyzed Race and Gender. Now we examine <strong>Age</strong> — one of the strongest predictors of real-world recidivism.
|
|
1079
|
+
</p>
|
|
1080
|
+
|
|
1081
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 18px auto; text-align:center;">
|
|
1082
|
+
Criminology shows that <strong>risk generally drops as people get older</strong>.
|
|
1083
|
+
But the COMPAS dataset you scanned earlier is <strong>heavily concentrated in ages 25–45</strong>.
|
|
1084
|
+
That imbalance creates a new kind of error pattern.
|
|
1085
|
+
</p>
|
|
1086
|
+
|
|
1087
|
+
<!-- Age Accuracy Trap -->
|
|
1088
|
+
<div class="hint-box" style="margin-top:10px;">
|
|
1089
|
+
<div style="font-weight:800;">🎭 Why Age Matters — and How Accuracy Lies Again</div>
|
|
1090
|
+
|
|
1091
|
+
<p style="font-size:0.98rem; margin-top:6px;">
|
|
1092
|
+
Suppose the COMPAS team proudly reports:
|
|
1093
|
+
<br><strong>“Our model is 92% accurate at predicting reoffending.”</strong>
|
|
1094
|
+
</p>
|
|
1095
|
+
|
|
1096
|
+
<p style="font-size:0.98rem; margin-top:8px;">
|
|
1097
|
+
But here’s the trick: <strong>most of the dataset is 25–45 years old</strong>.
|
|
1098
|
+
The model learns their patterns extremely well.
|
|
1099
|
+
</p>
|
|
1100
|
+
|
|
1101
|
+
<p style="font-size:0.98rem; margin-top:8px;">
|
|
1102
|
+
That means the “92% accuracy” mostly describes <em>middle-aged predictions</em>, not the whole population.
|
|
1103
|
+
</p>
|
|
1104
|
+
|
|
1105
|
+
<p style="font-size:0.98rem; margin-top:8px;">
|
|
1106
|
+
For younger adults (< 25) and older adults (> 45), the model has <strong>far fewer training examples</strong>,
|
|
1107
|
+
so its predictions can become much less reliable — while still looking perfect in the headline number.
|
|
1108
|
+
</p>
|
|
1109
|
+
|
|
1110
|
+
<p style="font-size:0.96rem; margin-top:10px; color:var(--body-text-color-subdued);">
|
|
1111
|
+
Your job is to uncover where the AI struggles — especially when the stakes involve sentencing or pretrial release.
|
|
1112
|
+
</p>
|
|
1113
|
+
</div>
|
|
1114
|
+
|
|
1115
|
+
<!-- INTERACTIVE SCAN: AGE ERROR PATTERN -->
|
|
1116
|
+
<div class="ai-risk-container" style="margin-top:22px; padding:22px; border-width:2px;">
|
|
1117
|
+
<h3 style="margin-top:0; font-size:1.25rem; text-align:center;">
|
|
1118
|
+
📡 AGE SCAN: WHO GETS MISLABELED?
|
|
1119
|
+
</h3>
|
|
1120
|
+
|
|
1121
|
+
<p style="font-size:1.0rem; max-width:780px; margin:0 auto 16px auto; text-align:center;">
|
|
1122
|
+
Click the scan to reveal how the model performs across different age groups — especially for people
|
|
1123
|
+
the AI saw less often during training.
|
|
1124
|
+
</p>
|
|
1125
|
+
|
|
1126
|
+
<details style="border:1px solid var(--border-color-primary); border-radius:12px; overflow:hidden; margin-top:10px;">
|
|
1127
|
+
<summary style="list-style:none; cursor:pointer; padding:14px 18px; font-weight:800;
|
|
1128
|
+
text-align:center; background:var(--background-fill-secondary);">
|
|
1129
|
+
📡 SCAN: Prediction Accuracy by Age — Click to reveal
|
|
1130
|
+
</summary>
|
|
1131
|
+
|
|
1132
|
+
<!-- Revealed content -->
|
|
1133
|
+
<div style="text-align:center; padding:24px;">
|
|
1134
|
+
|
|
1135
|
+
<h4 style="margin-top:0; font-size:1.2rem; margin-bottom:14px;">
|
|
1136
|
+
📊 Example Scan: Incorrect “High Risk” Predictions Across Age Groups
|
|
1137
|
+
</h4>
|
|
1138
|
+
|
|
1139
|
+
<p style="font-size:0.95rem; max-width:780px; margin:0 auto 18px auto;">
|
|
1140
|
+
When we look at people who <strong>did not reoffend</strong>, how often did the AI incorrectly label them as “High Risk”?
|
|
1141
|
+
</p>
|
|
1142
|
+
|
|
1143
|
+
<!-- Simple bar chart -->
|
|
1144
|
+
<div style="display:flex; justify-content:center; gap:40px; margin-top:10px;">
|
|
1145
|
+
|
|
1146
|
+
<!-- Under 25 -->
|
|
1147
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
1148
|
+
<div style="font-size:1.6rem; font-weight:800; color:#ef4444; margin-bottom:6px;">
|
|
1149
|
+
33%
|
|
1150
|
+
</div>
|
|
1151
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
1152
|
+
width:80px; height:180px; background:#fee2e2; border-radius:8px;
|
|
1153
|
+
overflow:hidden; border:1px solid #fca5a5;">
|
|
1154
|
+
<div style="background:#ef4444; height:33%; width:100%;"></div>
|
|
1155
|
+
</div>
|
|
1156
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">Under 25</div>
|
|
1157
|
+
</div>
|
|
1158
|
+
|
|
1159
|
+
<!-- Age 25–45 -->
|
|
1160
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
1161
|
+
<div style="font-size:1.6rem; font-weight:800; color:#3b82f6; margin-bottom:6px;">
|
|
1162
|
+
18%
|
|
1163
|
+
</div>
|
|
1164
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
1165
|
+
width:80px; height:180px; background:#dbeafe; border-radius:8px;
|
|
1166
|
+
overflow:hidden; border:1px solid #93c5fd;">
|
|
1167
|
+
<div style="background:#3b82f6; height:18%; width:100%;"></div>
|
|
1168
|
+
</div>
|
|
1169
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">25–45</div>
|
|
1170
|
+
</div>
|
|
1171
|
+
|
|
1172
|
+
<!-- Over 45 -->
|
|
1173
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
1174
|
+
<div style="font-size:1.6rem; font-weight:800; color:#f97316; margin-bottom:6px;">
|
|
1175
|
+
27%
|
|
1176
|
+
</div>
|
|
1177
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
1178
|
+
width:80px; height:180px; background:#ffedd5; border-radius:8px;
|
|
1179
|
+
overflow:hidden; border:1px solid #fdba74;">
|
|
1180
|
+
<div style="background:#f97316; height:27%; width:100%;"></div>
|
|
1181
|
+
</div>
|
|
1182
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">Over 45</div>
|
|
1183
|
+
</div>
|
|
1184
|
+
|
|
1185
|
+
</div>
|
|
1186
|
+
|
|
1187
|
+
<p style="font-size:0.93rem; max-width:760px; margin:20px auto 0 auto;
|
|
1188
|
+
color:var(--body-text-color-subdued);">
|
|
1189
|
+
The AI is most accurate for ages 25–45 (the group it saw most often),
|
|
1190
|
+
and makes more mistakes for younger and older adults — groups with fewer training examples.
|
|
1191
|
+
</p>
|
|
1192
|
+
|
|
1193
|
+
<!-- Detective analysis -->
|
|
1194
|
+
<div class="hint-box" style="background:rgba(239,68,68,0.08); margin-top:22px; border-left:4px solid #ef4444;">
|
|
1195
|
+
<h4 style="margin-top:0;">🔍 Detective's Analysis</h4>
|
|
1196
|
+
|
|
1197
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
1198
|
+
This is classic <strong>Representation Bias</strong> + <strong>Generalization Error</strong>.
|
|
1199
|
+
The model understands 25–45 year olds well, because that’s where most of its data came from—
|
|
1200
|
+
but it misreads younger and older adults.
|
|
1201
|
+
</p>
|
|
1202
|
+
|
|
1203
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
1204
|
+
Even if the model boasts a “92% accuracy” score, that average hides the fact that its errors
|
|
1205
|
+
are <strong>unevenly distributed</strong> — with younger and older adults getting the worst predictions.
|
|
1206
|
+
</p>
|
|
1207
|
+
|
|
1208
|
+
<ul style="font-size:0.95rem; margin:0 0 8px 18px; padding:0;">
|
|
1209
|
+
<li><strong>Younger defendants</strong> → more “High Risk” false alarms.</li>
|
|
1210
|
+
<li><strong>Older defendants</strong> → misclassified because the model didn’t see enough examples.</li>
|
|
1211
|
+
</ul>
|
|
1212
|
+
|
|
1213
|
+
<p style="font-size:0.96rem; margin-top:8px;">
|
|
1214
|
+
The system doesn’t just make mistakes — it makes <strong>predictable mistakes</strong> for certain age groups.
|
|
1215
|
+
That’s a major Justice & Equity concern.
|
|
1216
|
+
</p>
|
|
1217
|
+
</div>
|
|
1218
|
+
|
|
1219
|
+
</div> <!-- end revealed content -->
|
|
1220
|
+
|
|
1221
|
+
</details>
|
|
1222
|
+
</div>
|
|
1223
|
+
|
|
1224
|
+
<!-- Short transition -->
|
|
1225
|
+
<div style="text-align:center; margin-top:24px; padding:14px; background:rgba(59,130,246,0.1); border-radius:10px;">
|
|
1226
|
+
<p style="font-size:1.04rem; margin:0; font-weight:600;">
|
|
1227
|
+
Next, you'll explore how <strong>Geography</strong> affects predictions before building your full audit report.
|
|
1228
|
+
</p>
|
|
1229
|
+
</div>
|
|
1230
|
+
|
|
1231
|
+
</div>
|
|
1232
|
+
</div>
|
|
1233
|
+
|
|
1234
|
+
"""
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
"id": 7, "title": "Geography Scan",
|
|
1238
|
+
"html": """
|
|
1239
|
+
<div class="scenario-box">
|
|
1240
|
+
<h2 class="slide-title">⚠️ THE “DOUBLE PROXY”: GEOGRAPHY AS RACE & CLASS</h2>
|
|
1241
|
+
<div class="slide-body">
|
|
1242
|
+
|
|
1243
|
+
<!-- Step badge -->
|
|
1244
|
+
<div style="display:flex; justify-content:center; margin-bottom:18px;">
|
|
1245
|
+
<div style="display:inline-flex; align-items:center; gap:10px; padding:10px 18px;
|
|
1246
|
+
border-radius:999px; background:var(--background-fill-secondary);
|
|
1247
|
+
border:1px solid var(--border-color-primary); font-size:0.95rem; font-weight:800;">
|
|
1248
|
+
<span style="font-size:1.1rem;">📋</span>
|
|
1249
|
+
<span>STEP 3: PROVE THE PREDICTION ERROR — Geography Scan</span>
|
|
1250
|
+
</div>
|
|
1251
|
+
</div>
|
|
1252
|
+
|
|
1253
|
+
<!-- Intro framing -->
|
|
1254
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 12px auto; text-align:center;">
|
|
1255
|
+
You’ve analyzed <strong>Race</strong>, <strong>Gender</strong>, and <strong>Age</strong>.
|
|
1256
|
+
Now we look at one of the most powerful — and misunderstood — risk factors:
|
|
1257
|
+
<strong>Where someone lives</strong>.
|
|
1258
|
+
</p>
|
|
1259
|
+
|
|
1260
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 16px auto; text-align:center;">
|
|
1261
|
+
Many people think: <em>“Just delete the Race or Income columns and the model becomes fair.”</em>
|
|
1262
|
+
But geography often acts as a <strong>Double Proxy</strong>:
|
|
1263
|
+
</p>
|
|
1264
|
+
|
|
1265
|
+
<ul style="max-width:760px; margin:0 auto 18px auto; font-size:0.98rem;">
|
|
1266
|
+
<li><strong>Proxy for Race:</strong> Neighborhood segregation means ZIP codes encode racial patterns.</li>
|
|
1267
|
+
<li><strong>Proxy for Class:</strong> Housing density and economic inequality are baked into location data.</li>
|
|
1268
|
+
</ul>
|
|
1269
|
+
|
|
1270
|
+
<p style="font-size:1.03rem; max-width:780px; margin:0 auto 22px auto; text-align:center;">
|
|
1271
|
+
So even if an AI model never sees Race or Income, it can still learn their patterns through geography.
|
|
1272
|
+
</p>
|
|
1273
|
+
|
|
1274
|
+
<!-- Geography Accuracy Trap -->
|
|
1275
|
+
<div class="hint-box" style="margin-top:10px;">
|
|
1276
|
+
<div style="font-weight:800;">🎭 The Geography Trap: Why “92% Accuracy” Means Nothing Here</div>
|
|
1277
|
+
|
|
1278
|
+
<p style="font-size:0.98rem; margin-top:6px;">
|
|
1279
|
+
Imagine the COMPAS team again reports:
|
|
1280
|
+
<br><strong>“Our model is 92% accurate across all locations.”</strong>
|
|
1281
|
+
</p>
|
|
1282
|
+
|
|
1283
|
+
<p style="font-size:0.98rem; margin-top:8px;">
|
|
1284
|
+
But “92% accuracy” could simply mean the model works well for <strong>low-density suburban areas</strong>,
|
|
1285
|
+
where most of the training data came from.
|
|
1286
|
+
</p>
|
|
1287
|
+
|
|
1288
|
+
<p style="font-size:0.98rem; margin-top:8px;">
|
|
1289
|
+
For people in <strong>high-density neighborhoods</strong> — often poorer areas with different policing patterns —
|
|
1290
|
+
the model might make <em>far more mistakes</em>.
|
|
1291
|
+
</p>
|
|
1292
|
+
|
|
1293
|
+
<p style="font-size:0.98rem; margin-top:10px; color:var(--body-text-color-subdued);">
|
|
1294
|
+
Your job is to uncover whether predictions change dramatically just based on a person’s address.
|
|
1295
|
+
</p>
|
|
1296
|
+
</div>
|
|
1297
|
+
|
|
1298
|
+
<!-- INTERACTIVE SCAN: FALSE POSITIVES BY LOCATION -->
|
|
1299
|
+
<div class="ai-risk-container" style="margin-top:22px; padding:22px; border-width:2px;">
|
|
1300
|
+
<h3 style="margin-top:0; font-size:1.25rem; text-align:center;">
|
|
1301
|
+
📡 GEOGRAPHY SCAN: WHO GETS FALSELY FLAGGED?
|
|
1302
|
+
</h3>
|
|
1303
|
+
|
|
1304
|
+
<p style="font-size:1.0rem; max-width:780px; margin:0 auto 16px auto; text-align:center;">
|
|
1305
|
+
Click below to reveal how a person’s neighborhood affects the AI’s mistakes — especially
|
|
1306
|
+
<strong>False Positives</strong> (“High Risk” labels for people who did <em>not</em> reoffend).
|
|
1307
|
+
</p>
|
|
1308
|
+
|
|
1309
|
+
<details style="border:1px solid var(--border-color-primary); border-radius:12px; overflow:hidden; margin-top:10px;">
|
|
1310
|
+
<summary style="list-style:none; cursor:pointer; padding:14px 18px; font-weight:800;
|
|
1311
|
+
text-align:center; background:var(--background-fill-secondary);">
|
|
1312
|
+
📡 SCAN: False Positive Rate by Neighborhood — Click to reveal
|
|
1313
|
+
</summary>
|
|
1314
|
+
|
|
1315
|
+
<!-- Revealed content -->
|
|
1316
|
+
<div style="text-align:center; padding:24px;">
|
|
1317
|
+
|
|
1318
|
+
<h4 style="margin-top:0; font-size:1.2rem; margin-bottom:14px;">
|
|
1319
|
+
📊 Incorrect “High Risk” Flags by Location
|
|
1320
|
+
</h4>
|
|
1321
|
+
|
|
1322
|
+
<p style="font-size:0.95rem; max-width:780px; margin:0 auto 18px auto;">
|
|
1323
|
+
How often does the model falsely label someone as “High Risk” based only on the type of neighborhood they come from?
|
|
1324
|
+
</p>
|
|
1325
|
+
|
|
1326
|
+
<!-- Bar chart -->
|
|
1327
|
+
<div style="display:flex; justify-content:center; gap:40px; margin-top:10px;">
|
|
1328
|
+
|
|
1329
|
+
<!-- Rural/Suburban -->
|
|
1330
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
1331
|
+
<div style="font-size:1.8rem; font-weight:800; color:#3b82f6; margin-bottom:6px;">
|
|
1332
|
+
22%
|
|
1333
|
+
</div>
|
|
1334
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
1335
|
+
width:80px; height:180px; background:#dbeafe; border-radius:8px;
|
|
1336
|
+
overflow:hidden; border:1px solid #93c5fd;">
|
|
1337
|
+
<div style="background:#3b82f6; height:22%; width:100%;"></div>
|
|
1338
|
+
</div>
|
|
1339
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">
|
|
1340
|
+
Rural / Suburban
|
|
1341
|
+
</div>
|
|
1342
|
+
</div>
|
|
1343
|
+
|
|
1344
|
+
<!-- High Density Urban -->
|
|
1345
|
+
<div style="display:flex; flex-direction:column; align-items:center; width:150px;">
|
|
1346
|
+
<div style="font-size:1.8rem; font-weight:800; color:#ef4444; margin-bottom:6px;">
|
|
1347
|
+
58%
|
|
1348
|
+
</div>
|
|
1349
|
+
<div style="display:flex; flex-direction:column; justify-content:flex-end;
|
|
1350
|
+
width:80px; height:180px; background:#fee2e2; border-radius:8px;
|
|
1351
|
+
overflow:hidden; border:1px solid #fca5a5;">
|
|
1352
|
+
<div style="background:#ef4444; height:58%; width:100%;"></div>
|
|
1353
|
+
</div>
|
|
1354
|
+
<div style="margin-top:10px; font-weight:700; font-size:0.95rem;">
|
|
1355
|
+
High-Density Urban
|
|
1356
|
+
</div>
|
|
1357
|
+
</div>
|
|
1358
|
+
</div>
|
|
1359
|
+
|
|
1360
|
+
<p style="font-size:0.93rem; max-width:760px; margin:20px auto 0 auto;
|
|
1361
|
+
color:var(--body-text-color-subdued);">
|
|
1362
|
+
The model is <strong>more than twice as likely</strong> to falsely flag someone from a
|
|
1363
|
+
high-density neighborhood as “High Risk.”
|
|
1364
|
+
</p>
|
|
1365
|
+
|
|
1366
|
+
<!-- Detective analysis -->
|
|
1367
|
+
<div class="hint-box" style="background:rgba(239,68,68,0.08); margin-top:22px; border-left:4px solid #ef4444;">
|
|
1368
|
+
<h4 style="margin-top:0;">🔍 Detective's Analysis</h4>
|
|
1369
|
+
|
|
1370
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
1371
|
+
The model is not “just predicting risk.”
|
|
1372
|
+
It is picking up on policing patterns baked into different neighborhoods.
|
|
1373
|
+
</p>
|
|
1374
|
+
|
|
1375
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
1376
|
+
High-density areas — which often contain more low-income and minority residents —
|
|
1377
|
+
have more recorded arrests. The model learns that pattern and <strong>treats location
|
|
1378
|
+
as a risk factor</strong>, even when individuals present no greater danger.
|
|
1379
|
+
</p>
|
|
1380
|
+
|
|
1381
|
+
<p style="font-size:0.98rem; margin-bottom:10px;">
|
|
1382
|
+
This is the essence of the <strong>Double Proxy</strong> problem:
|
|
1383
|
+
</p>
|
|
1384
|
+
|
|
1385
|
+
<ul style="font-size:0.95rem; margin:0 0 8px 18px; padding:0;">
|
|
1386
|
+
<li>Neighborhood → reflects Race patterns</li>
|
|
1387
|
+
<li>Neighborhood → reflects Class patterns</li>
|
|
1388
|
+
</ul>
|
|
1389
|
+
|
|
1390
|
+
<p style="font-size:0.96rem; margin-top:8px;">
|
|
1391
|
+
When the model is twice as harsh based on location alone, that’s not public safety —
|
|
1392
|
+
it’s <strong>proxy discrimination</strong> hidden inside an algorithm.
|
|
1393
|
+
</p>
|
|
1394
|
+
</div>
|
|
1395
|
+
|
|
1396
|
+
</div> <!-- end revealed content -->
|
|
1397
|
+
|
|
1398
|
+
</details>
|
|
1399
|
+
</div>
|
|
1400
|
+
|
|
1401
|
+
<!-- Short transition -->
|
|
1402
|
+
<div style="text-align:center; margin-top:24px; padding:14px; background:rgba(59,130,246,0.1); border-radius:10px;">
|
|
1403
|
+
<p style="font-size:1.04rem; margin:0; font-weight:600;">
|
|
1404
|
+
Now you have scans for <strong>Race → Gender → Age → Geography</strong>.
|
|
1405
|
+
You're ready to assemble your final audit report.
|
|
1406
|
+
</p>
|
|
1407
|
+
</div>
|
|
1408
|
+
|
|
1409
|
+
</div>
|
|
1410
|
+
</div>
|
|
1411
|
+
|
|
1412
|
+
"""
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
"id": 8, "title": "Final Audit Report",
|
|
1416
|
+
"html": """
|
|
1417
|
+
<div class="scenario-box">
|
|
1418
|
+
<h2 class="slide-title">🧾 FINAL AUDIT REPORT: PULLING IT ALL TOGETHER</h2>
|
|
1419
|
+
<div class="slide-body">
|
|
1420
|
+
|
|
1421
|
+
<!-- Status badge -->
|
|
1422
|
+
<div style="display:flex; justify-content:center; margin-bottom:14px;">
|
|
1423
|
+
<div style="display:inline-flex; align-items:center; gap:10px; padding:10px 18px;
|
|
1424
|
+
border-radius:999px; background:rgba(34,197,94,0.1);
|
|
1425
|
+
border:1px solid #22c55e; font-size:0.95rem; font-weight:700;">
|
|
1426
|
+
<span style="font-size:1.1rem;">🏁</span>
|
|
1427
|
+
<span>STATUS: STEP 4 — DIAGNOSE HARM (FINAL STEP)</span>
|
|
1428
|
+
</div>
|
|
1429
|
+
</div>
|
|
1430
|
+
|
|
1431
|
+
<!-- Mini roadmap strip -->
|
|
1432
|
+
<div class="ai-risk-container" style="margin-bottom:16px; padding:14px 16px;">
|
|
1433
|
+
<h4 style="margin-top:0; font-size:1.05rem; text-align:center;">🗺️ Your Investigation Journey</h4>
|
|
1434
|
+
<div style="display:grid; grid-template-columns:repeat(4, minmax(0,1fr)); gap:8px; margin-top:10px; font-size:0.9rem;">
|
|
1435
|
+
<div class="hint-box" style="margin-top:0; opacity:0.7;">
|
|
1436
|
+
<div style="font-weight:700;">Step 1</div>
|
|
1437
|
+
<div>Learn the Rules</div>
|
|
1438
|
+
<div style="font-size:0.8rem; margin-top:4px; color:var(--body-text-color-subdued);">Justice & Equity</div>
|
|
1439
|
+
</div>
|
|
1440
|
+
<div class="hint-box" style="margin-top:0; opacity:0.7;">
|
|
1441
|
+
<div style="font-weight:700;">Step 2</div>
|
|
1442
|
+
<div>Collect Evidence</div>
|
|
1443
|
+
<div style="font-size:0.8rem; margin-top:4px; color:var(--body-text-color-subdued);">Data forensics</div>
|
|
1444
|
+
</div>
|
|
1445
|
+
<div class="hint-box" style="margin-top:0; opacity:0.7;">
|
|
1446
|
+
<div style="font-weight:700;">Step 3</div>
|
|
1447
|
+
<div>Prove the Error</div>
|
|
1448
|
+
<div style="font-size:0.8rem; margin-top:4px; color:var(--body-text-color-subdued);">Error gaps by group</div>
|
|
1449
|
+
</div>
|
|
1450
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #22c55e; background:rgba(34,197,94,0.12);">
|
|
1451
|
+
<div style="font-weight:700; color:#166534;">Step 4</div>
|
|
1452
|
+
<div style="font-weight:700; color:#166534;">Diagnose Harm</div>
|
|
1453
|
+
<div style="font-size:0.8rem; margin-top:4px; color:#166534;">Turn findings into a report</div>
|
|
1454
|
+
</div>
|
|
1455
|
+
</div>
|
|
1456
|
+
<p style="font-size:0.9rem; margin:10px 0 0 0; text-align:center; color:var(--body-text-color-subdued);">
|
|
1457
|
+
You’re now at the final stage: explaining <strong>who is harmed, who is protected</strong>,
|
|
1458
|
+
and why this model can’t be treated as neutral.
|
|
1459
|
+
</p>
|
|
1460
|
+
</div>
|
|
1461
|
+
|
|
1462
|
+
<!-- Framing text -->
|
|
1463
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 16px auto; text-align:center;">
|
|
1464
|
+
You’ve completed your investigation. You scanned the COMPAS model’s behavior across
|
|
1465
|
+
<strong>Race, Gender, Age, and Geography</strong>, and uncovered patterns that affect
|
|
1466
|
+
who is punished and who is protected.
|
|
1467
|
+
</p>
|
|
1468
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 18px auto; text-align:center;">
|
|
1469
|
+
Now it’s time to turn your findings into a <strong>clear audit report</strong> that a judge,
|
|
1470
|
+
journalist, or policy-maker could understand.
|
|
1471
|
+
</p>
|
|
1472
|
+
|
|
1473
|
+
<!-- Evidence board / summary visualization -->
|
|
1474
|
+
<div class="ai-risk-container" style="margin-top:10px;">
|
|
1475
|
+
<h4 style="margin-top:0; font-size:1.15rem; text-align:center;">📌 Evidence Board: Bias Patterns You Found</h4>
|
|
1476
|
+
<p style="font-size:0.98rem; max-width:780px; margin:0 auto 10px auto; text-align:center; color:var(--body-text-color-subdued);">
|
|
1477
|
+
This is your “case file” overview. Each card is a key piece of evidence you can choose to include
|
|
1478
|
+
in your final report.
|
|
1479
|
+
</p>
|
|
1480
|
+
|
|
1481
|
+
<div style="display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:12px; margin-top:12px;">
|
|
1482
|
+
|
|
1483
|
+
<!-- Punitive Bias -->
|
|
1484
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #ef4444;">
|
|
1485
|
+
<div style="font-weight:800;">⚖️ Punitive Bias (False Alarms by Race)</div>
|
|
1486
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
1487
|
+
African-American defendants were <strong>far more likely</strong> to be falsely labeled
|
|
1488
|
+
“High Risk” compared to White defendants.
|
|
1489
|
+
</div>
|
|
1490
|
+
<div style="font-size:0.9rem; margin-top:6px; color:var(--body-text-color-subdued);">
|
|
1491
|
+
Result: More people from this group face unnecessary jail, stricter bail, or longer sentences.
|
|
1492
|
+
</div>
|
|
1493
|
+
</div>
|
|
1494
|
+
|
|
1495
|
+
<!-- Missed Risk / Leniency -->
|
|
1496
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #3b82f6;">
|
|
1497
|
+
<div style="font-weight:800;">🛑 Missed Risk (Leniency Pattern by Race)</div>
|
|
1498
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
1499
|
+
White defendants who went on to re-offend were <strong>more likely</strong> to be labeled
|
|
1500
|
+
“Low Risk” than African-American defendants.
|
|
1501
|
+
</div>
|
|
1502
|
+
<div style="font-size:0.9rem; margin-top:6px; color:var(--body-text-color-subdued);">
|
|
1503
|
+
Result: One group receives more “second chances” from the model than another.
|
|
1504
|
+
</div>
|
|
1505
|
+
</div>
|
|
1506
|
+
|
|
1507
|
+
<!-- Generalization Bias (Gender) -->
|
|
1508
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #ec4899;">
|
|
1509
|
+
<div style="font-weight:800;">👥 Generalization Bias (Gender)</div>
|
|
1510
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
1511
|
+
The dataset is <strong>81% male / 19% female</strong>, but the model reports a high-looking
|
|
1512
|
+
overall “accuracy” (for example, 92%) without showing group differences.
|
|
1513
|
+
</div>
|
|
1514
|
+
<div style="font-size:0.9rem; margin-top:6px; color:var(--body-text-color-subdued);">
|
|
1515
|
+
Result: The model appears strong overall but may be much less reliable for women than for men.
|
|
1516
|
+
</div>
|
|
1517
|
+
</div>
|
|
1518
|
+
|
|
1519
|
+
<!-- Age / Geography -->
|
|
1520
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #22c55e;">
|
|
1521
|
+
<div style="font-weight:800;">📍 Age Skew & Geography as Proxy</div>
|
|
1522
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
1523
|
+
Most data focuses on <strong>ages 25–45</strong> and on certain high-policing neighborhoods,
|
|
1524
|
+
meaning the model is “trained” more on some ages and places than others.
|
|
1525
|
+
</div>
|
|
1526
|
+
<div style="font-size:0.9rem; margin-top:6px; color:var(--body-text-color-subdued);">
|
|
1527
|
+
Result: Where someone lives and how old they are can act as <em>stand-ins</em> (proxies) for race
|
|
1528
|
+
and class in the risk score.
|
|
1529
|
+
</div>
|
|
1530
|
+
</div>
|
|
1531
|
+
|
|
1532
|
+
</div>
|
|
1533
|
+
</div>
|
|
1534
|
+
|
|
1535
|
+
<!-- Bridge to interactive report builder (Gradio components go below this HTML) -->
|
|
1536
|
+
<div class="ai-risk-container" style="margin-top:18px;">
|
|
1537
|
+
<h4 style="margin-top:0; font-size:1.15rem; text-align:center;">🧱 Build Your Audit Report</h4>
|
|
1538
|
+
<p style="font-size:0.98rem; max-width:780px; margin:0 auto 4px auto; text-align:center;">
|
|
1539
|
+
Now you’ll assemble a short, clear report using the evidence you’ve collected.
|
|
1540
|
+
</p>
|
|
1541
|
+
<p style="font-size:0.98rem; max-width:780px; margin:0 auto; text-align:center; color:var(--body-text-color-subdued);">
|
|
1542
|
+
Use the checklist below to select which bias patterns to include. We’ll help you auto-draft
|
|
1543
|
+
a paragraph you could share with a judge, journalist, or policy-maker.
|
|
1544
|
+
</p>
|
|
1545
|
+
</div>
|
|
1546
|
+
|
|
1547
|
+
</div>
|
|
1548
|
+
</div>
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
|
|
1552
|
+
"""
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
"id": 9, "title": "Mission Accomplished",
|
|
1556
|
+
"html": """
|
|
1557
|
+
<div class="scenario-box">
|
|
1558
|
+
<h2 class="slide-title">⚖️ THE FINAL VERDICT</h2>
|
|
1559
|
+
<div class="slide-body">
|
|
1560
|
+
|
|
1561
|
+
<!-- Status badge -->
|
|
1562
|
+
<div style="display:flex; justify-content:center; margin-bottom:18px;">
|
|
1563
|
+
<div style="
|
|
1564
|
+
display:inline-flex;
|
|
1565
|
+
align-items:center;
|
|
1566
|
+
gap:10px;
|
|
1567
|
+
padding:10px 18px;
|
|
1568
|
+
border-radius:999px;
|
|
1569
|
+
background:var(--background-fill-secondary);
|
|
1570
|
+
border:1px solid var(--border-color-primary);
|
|
1571
|
+
font-size:0.95rem;
|
|
1572
|
+
text-transform:uppercase;
|
|
1573
|
+
letter-spacing:0.08em;
|
|
1574
|
+
font-weight:700;">
|
|
1575
|
+
<span style="font-size:1.1rem;">🕵️♀️</span>
|
|
1576
|
+
<span>STATUS: FULL AUDIT COMPLETED</span>
|
|
1577
|
+
</div>
|
|
1578
|
+
</div>
|
|
1579
|
+
|
|
1580
|
+
<!-- Framing text -->
|
|
1581
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 16px auto; text-align:center;">
|
|
1582
|
+
You’ve seen behind the curtain. The vendor is still proudly advertising the model as
|
|
1583
|
+
<strong>“92% Accurate”</strong> and “ready to deploy” to clear the court backlog.
|
|
1584
|
+
</p>
|
|
1585
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 24px auto; text-align:center;">
|
|
1586
|
+
But your investigation uncovered something else: the model’s mistakes are not random —
|
|
1587
|
+
they <strong>hit some groups harder</strong> and <strong>protect others more</strong>.
|
|
1588
|
+
</p>
|
|
1589
|
+
|
|
1590
|
+
<!-- Evidence recap -->
|
|
1591
|
+
<div class="ai-risk-container" style="margin-top:6px;">
|
|
1592
|
+
<h4 style="margin-top:0; font-size:1.15rem; text-align:center;">🧾 Your Case File: What You Found</h4>
|
|
1593
|
+
<div style="display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:12px; margin-top:10px;">
|
|
1594
|
+
|
|
1595
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #ef4444;">
|
|
1596
|
+
<div style="font-weight:800;">Punitive Bias (False Alarms)</div>
|
|
1597
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
1598
|
+
African-American defendants were <strong>more often mislabeled “High Risk”</strong> when
|
|
1599
|
+
they did <em>not</em> re-offend — unfair extra punishment.
|
|
1600
|
+
</div>
|
|
1601
|
+
</div>
|
|
1602
|
+
|
|
1603
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #3b82f6;">
|
|
1604
|
+
<div style="font-weight:800;">Leniency Pattern (Missed Risk)</div>
|
|
1605
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
1606
|
+
White defendants who <strong>did re-offend</strong> were more likely to be labeled “Low Risk” —
|
|
1607
|
+
extra protection and second chances.
|
|
1608
|
+
</div>
|
|
1609
|
+
</div>
|
|
1610
|
+
|
|
1611
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #22c55e;">
|
|
1612
|
+
<div style="font-weight:800;">Generalization Gaps (Age & Gender)</div>
|
|
1613
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
1614
|
+
The model learned patterns mostly from younger men, making it <strong>less reliable</strong>
|
|
1615
|
+
for women and older adults.
|
|
1616
|
+
</div>
|
|
1617
|
+
</div>
|
|
1618
|
+
|
|
1619
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #f97316;">
|
|
1620
|
+
<div style="font-weight:800;">Location as a Proxy</div>
|
|
1621
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
1622
|
+
Even without an explicit “Race” column, features like <strong>ZIP code</strong> and neighborhood
|
|
1623
|
+
acted as <strong>proxies</strong> for race and social class.
|
|
1624
|
+
</div>
|
|
1625
|
+
</div>
|
|
1626
|
+
|
|
1627
|
+
</div>
|
|
1628
|
+
|
|
1629
|
+
<p style="font-size:0.96rem; max-width:780px; margin:16px auto 0 auto; text-align:center; color:var(--body-text-color-subdued);">
|
|
1630
|
+
In short: the model’s errors are <strong>systematic</strong>, not random — and they
|
|
1631
|
+
<strong>do not treat people equally</strong>.
|
|
1632
|
+
</p>
|
|
1633
|
+
</div>
|
|
1634
|
+
|
|
1635
|
+
<!-- Decision card -->
|
|
1636
|
+
<div class="ai-risk-container" style="margin-top:22px; border-width:2px;">
|
|
1637
|
+
<h4 style="margin-top:0; font-size:1.18rem; text-align:center;">🎯 Your Decision as Bias Detective</h4>
|
|
1638
|
+
<p style="font-size:1.02rem; max-width:780px; margin:0 auto 16px auto; text-align:center;">
|
|
1639
|
+
The court is waiting for your recommendation. Should this model be used on real people <em>as it is now</em>?
|
|
1640
|
+
</p>
|
|
1641
|
+
|
|
1642
|
+
<div style="display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:14px; margin-top:10px;">
|
|
1643
|
+
|
|
1644
|
+
<div class="hint-box" style="margin-top:0;">
|
|
1645
|
+
<div style="font-weight:800;">Option A: Approve & Deploy</div>
|
|
1646
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
1647
|
+
Treat <strong>92% overall accuracy</strong> as “good enough” and allow the model to
|
|
1648
|
+
keep making unequal errors across groups.
|
|
1649
|
+
</div>
|
|
1650
|
+
</div>
|
|
1651
|
+
|
|
1652
|
+
<div class="hint-box" style="margin-top:0; border-left:4px solid #22c55e; background:rgba(34,197,94,0.08);">
|
|
1653
|
+
<div style="font-weight:800;">Option B: Pause & Fix First</div>
|
|
1654
|
+
<div style="font-size:0.95rem; margin-top:4px;">
|
|
1655
|
+
Declare that the model is <strong>too unfair to deploy</strong> without changes and
|
|
1656
|
+
recommend a fairness upgrade before anyone relies on it.
|
|
1657
|
+
</div>
|
|
1658
|
+
</div>
|
|
1659
|
+
|
|
1660
|
+
</div>
|
|
1661
|
+
|
|
1662
|
+
<p style="font-size:0.98rem; max-width:780px; margin:18px auto 0 auto; text-align:center;">
|
|
1663
|
+
On the <strong>next screen</strong>, your choice will unlock your new mission as a
|
|
1664
|
+
<strong>Fairness Engineer</strong> — the person who doesn’t just find bias, but actually fixes it.
|
|
1665
|
+
</p>
|
|
1666
|
+
</div>
|
|
1667
|
+
|
|
1668
|
+
</div>
|
|
1669
|
+
</div>
|
|
1670
|
+
|
|
1671
|
+
|
|
1672
|
+
|
|
1673
|
+
"""
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
"id": 10, "title": "Mission Accomplished",
|
|
1677
|
+
"html": """
|
|
1678
|
+
<div class="scenario-box">
|
|
1679
|
+
<h2 class="slide-title">🎖️ PROMOTION UNLOCKED: FAIRNESS ENGINEER</h2>
|
|
1680
|
+
<div class="slide-body">
|
|
1681
|
+
|
|
1682
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 20px auto; text-align:center;">
|
|
1683
|
+
You made the call: <strong>the model cannot be deployed safely</strong> in its current form.
|
|
1684
|
+
That decision required evidence, judgment, and courage.
|
|
1685
|
+
</p>
|
|
1686
|
+
|
|
1687
|
+
<p style="font-size:1.05rem; max-width:780px; margin:0 auto 28px auto; text-align:center;">
|
|
1688
|
+
Exposing bias is only the first half of the mission.
|
|
1689
|
+
Now, the real work begins.
|
|
1690
|
+
</p>
|
|
1691
|
+
|
|
1692
|
+
<div style="
|
|
1693
|
+
margin-top:20px;
|
|
1694
|
+
padding:28px;
|
|
1695
|
+
background:linear-gradient(135deg, rgba(59,130,246,0.1), rgba(16,185,129,0.1));
|
|
1696
|
+
border-radius:14px;
|
|
1697
|
+
border:2px solid var(--color-accent);
|
|
1698
|
+
text-align:center;">
|
|
1699
|
+
|
|
1700
|
+
<h3 style="margin-top:0; color:var(--color-accent); font-size:1.4rem;">
|
|
1701
|
+
🎓 Your New Role: <strong>Fairness Engineer</strong>
|
|
1702
|
+
</h3>
|
|
1703
|
+
|
|
1704
|
+
<p style="font-size:1.05rem; margin-top:10px;">
|
|
1705
|
+
You’re no longer just investigating harm — you’re responsible for <strong>fixing the system</strong>.
|
|
1706
|
+
</p>
|
|
1707
|
+
|
|
1708
|
+
<ul style="list-style:none; padding:0; margin-top:20px; font-size:1.0rem; line-height:1.6;">
|
|
1709
|
+
<li>🔧 <strong>Remove biased features</strong> like direct demographic attributes (race, sex, age)</li>
|
|
1710
|
+
<li>🕵️♀️ <strong>Hunt down proxy variables</strong> (ZIP code, prior arrests, income) that quietly re-create bias</li>
|
|
1711
|
+
<li>📊 <strong>Design representative data strategies</strong> so the model reflects the people it affects</li>
|
|
1712
|
+
<li>🗺️ <strong>Build an ethical roadmap</strong> with ongoing audits, documentation, and stakeholder input</li>
|
|
1713
|
+
</ul>
|
|
1714
|
+
|
|
1715
|
+
<p style="font-size:1.05rem; margin-top:24px;">
|
|
1716
|
+
You’ve proven you can diagnose systemic failures.<br>
|
|
1717
|
+
Now you’ll learn how to rebuild a risk model that is <strong>more fair, more transparent, and safer</strong> to use.
|
|
1718
|
+
</p>
|
|
1719
|
+
</div>
|
|
1720
|
+
|
|
1721
|
+
<div style="text-align:center; margin-top:30px;">
|
|
1722
|
+
<p style="font-size:1.1rem; font-weight:600;">
|
|
1723
|
+
👉 Your next mission is <strong>Activity 8: Fairness Fixer</strong>.<br>
|
|
1724
|
+
There, you’ll remove biased and proxy features, redesign the data strategy,<br>
|
|
1725
|
+
and draft a continuous improvement plan for Justice & Equity.
|
|
1726
|
+
</p>
|
|
1727
|
+
</div>
|
|
1728
|
+
|
|
1729
|
+
</div>
|
|
1730
|
+
</div>
|
|
1731
|
+
|
|
1732
|
+
|
|
1733
|
+
"""
|
|
1734
|
+
}
|
|
1735
|
+
]
|
|
1736
|
+
|
|
1737
|
+
# --- 6. INTERACTIVE CONFIG ---
|
|
1738
|
+
QUIZ_CONFIG = {
|
|
1739
|
+
1: {
|
|
1740
|
+
"t": "t11",
|
|
1741
|
+
"q": "Which outcome shows why testing AI outputs is essential?",
|
|
1742
|
+
"o": [
|
|
1743
|
+
"A) Wrong predictions can harm people or communities when one group gets more mistakes",
|
|
1744
|
+
"B) Only the input data determines fairness",
|
|
1745
|
+
"C) Outputs don’t affect real decisions"
|
|
1746
|
+
],
|
|
1747
|
+
"a": "A) Wrong predictions can harm people or communities when one group gets more mistakes",
|
|
1748
|
+
"success": "Impact Identified. You understand that unequal mistakes shape real lives—and real safety."
|
|
1749
|
+
},
|
|
1750
|
+
2: {
|
|
1751
|
+
"t": "t12",
|
|
1752
|
+
"q": "How did ProPublica figure out when the AI was right or wrong?",
|
|
1753
|
+
"o": [
|
|
1754
|
+
"A) Interviewed judges",
|
|
1755
|
+
"B) Compared the AI’s predictions to what actually happened over 2 years",
|
|
1756
|
+
"C) Ran computer simulations"
|
|
1757
|
+
],
|
|
1758
|
+
"a": "B) Compared the AI’s predictions to what actually happened over 2 years",
|
|
1759
|
+
"success": "Hindsight Unlocked. You used the real outcomes to check the AI’s accuracy."
|
|
1760
|
+
},
|
|
1761
|
+
3: {
|
|
1762
|
+
"t": "t13",
|
|
1763
|
+
"q": "If Black defendants get 45% False Alarms and White defendants get 23%, what does this mean for fairness?",
|
|
1764
|
+
"o": [
|
|
1765
|
+
"A) The model is fair to both groups",
|
|
1766
|
+
"B) The model is lenient toward everyone",
|
|
1767
|
+
"C) The AI wrongly flags Black defendants as 'High Risk' almost twice as often"
|
|
1768
|
+
],
|
|
1769
|
+
"a": "C) The AI wrongly flags Black defendants as 'High Risk' almost twice as often",
|
|
1770
|
+
"success": "Harm Confirmed: Punitive Bias against Black defendants."
|
|
1771
|
+
},
|
|
1772
|
+
4: {
|
|
1773
|
+
"t": "t14",
|
|
1774
|
+
"q": "False Negatives: 48% (White) vs 28% (African-American). What does this pattern show?",
|
|
1775
|
+
"o": [
|
|
1776
|
+
"A) The model is giving White defendants more 'free passes' (leniency pattern / Missed Risk)",
|
|
1777
|
+
"B) The model is equally fair to both groups",
|
|
1778
|
+
"C) The model is harsher on White defendants"
|
|
1779
|
+
],
|
|
1780
|
+
"a": "A) The model is giving White defendants more 'free passes' (leniency pattern / Missed Risk)",
|
|
1781
|
+
"success": "Harm Verified: Missed Risk / Leniency Pattern — the model underestimates risk for White defendants, shifting protection unevenly."
|
|
1782
|
+
},
|
|
1783
|
+
5: {
|
|
1784
|
+
"t": "t15",
|
|
1785
|
+
"q": "If the model reports '92% accuracy' overall but frequently mislabels women as High Risk, what does this reveal?",
|
|
1786
|
+
"o": [
|
|
1787
|
+
"A) The high accuracy score is hiding errors for a smaller group (generalization bias)",
|
|
1788
|
+
"B) The model is equally accurate for everyone",
|
|
1789
|
+
"C) Women must actually be higher risk"
|
|
1790
|
+
],
|
|
1791
|
+
"a": "A) The high accuracy score is hiding errors for a smaller group (generalization bias)",
|
|
1792
|
+
"success": "Bias Confirmed: The '92% accuracy' average hides uneven mistakes. The model generalizes men’s patterns onto women."
|
|
1793
|
+
},
|
|
1794
|
+
6: {
|
|
1795
|
+
"t": "t16",
|
|
1796
|
+
"q": "Why does the model make fewer mistakes for ages 25–45?",
|
|
1797
|
+
"o": [
|
|
1798
|
+
"A) Because this age group appears most often in the model's data",
|
|
1799
|
+
"B) Because people 25–45 commit fewer crimes",
|
|
1800
|
+
"C) Because the model is designed to ignore younger and older adults"
|
|
1801
|
+
],
|
|
1802
|
+
"a": "A) Because this age group appears most often in the model's data",
|
|
1803
|
+
"success": "Pattern Detected: The model performs best on the group it saw the most — and struggles with younger and older adults."
|
|
1804
|
+
},
|
|
1805
|
+
7: {
|
|
1806
|
+
"t": "t17",
|
|
1807
|
+
"q": "If the model is twice as likely to falsely flag people from high-density neighborhoods as “High Risk,” what does this reveal?",
|
|
1808
|
+
"o": [
|
|
1809
|
+
"A) Geography is acting as a proxy for race and class patterns the model has learned",
|
|
1810
|
+
"B) High-density neighborhoods are naturally more dangerous",
|
|
1811
|
+
"C) The model needs more CPU power to run correctly"
|
|
1812
|
+
],
|
|
1813
|
+
"a": "A) Geography is acting as a proxy for race and class patterns the model has learned",
|
|
1814
|
+
"success": "Bias Confirmed: Location becomes a stand-in for race and class, causing unfair punishment toward people from certain neighborhoods."
|
|
1815
|
+
},
|
|
1816
|
+
8: {
|
|
1817
|
+
"t": "t18",
|
|
1818
|
+
"q": "After uncovering consistent unfair errors across Race, Gender, Age, and Geography, how should you classify the model’s behavior?",
|
|
1819
|
+
"o": [
|
|
1820
|
+
"A) A minor glitch — small coding bugs happen",
|
|
1821
|
+
"B) User error — judges are interpreting the scores wrong",
|
|
1822
|
+
"C) A systemic failure — the model’s patterns create unequal harm"
|
|
1823
|
+
],
|
|
1824
|
+
"a": "C) A systemic failure — the model’s patterns create unequal harm",
|
|
1825
|
+
"success": "Audit Complete: You correctly identified this as a systemic failure, not a small mistake."
|
|
1826
|
+
},
|
|
1827
|
+
9: {
|
|
1828
|
+
"t": "t19",
|
|
1829
|
+
"q": "Given everything you uncovered in your investigation, what should happen to this AI model?",
|
|
1830
|
+
"o": [
|
|
1831
|
+
"A) Deploy it immediately — 92% accuracy is good enough.",
|
|
1832
|
+
"B) Pause deployment — the model needs fairness repairs first.",
|
|
1833
|
+
"C) Delete the model entirely — AI can never be fair."
|
|
1834
|
+
],
|
|
1835
|
+
"a": "B) Pause deployment — the model needs fairness repairs first.",
|
|
1836
|
+
"success": "Correct. A responsible auditor doesn't rush a system with documented harm. Now step into your next mission: becoming the Fairness Engineer who fixes it."
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
# --- 7. RENDERERS ---
|
|
1841
|
+
def generate_success_message(prev, curr, specific_text):
|
|
1842
|
+
old_score = float(prev.get("score", 0) or 0) if prev else 0.0
|
|
1843
|
+
new_score = float(curr.get("score", 0) or 0)
|
|
1844
|
+
diff_score = new_score - old_score
|
|
1845
|
+
|
|
1846
|
+
old_rank = prev.get("rank", "–") if prev else "–"
|
|
1847
|
+
new_rank = curr.get("rank", "–")
|
|
1848
|
+
|
|
1849
|
+
# Are ranks integers? If yes, we can reason about direction.
|
|
1850
|
+
ranks_are_int = isinstance(old_rank, int) and isinstance(new_rank, int)
|
|
1851
|
+
rank_diff = old_rank - new_rank if ranks_are_int else 0 # positive => rank improved
|
|
1852
|
+
|
|
1853
|
+
# --- STYLE SELECTION -------------------------------------------------
|
|
1854
|
+
# First-time score: special "on the board" moment
|
|
1855
|
+
if old_score == 0 and new_score > 0:
|
|
1856
|
+
style_key = "first"
|
|
1857
|
+
else:
|
|
1858
|
+
if ranks_are_int:
|
|
1859
|
+
if rank_diff >= 3:
|
|
1860
|
+
style_key = "major" # big rank jump
|
|
1861
|
+
elif rank_diff > 0:
|
|
1862
|
+
style_key = "climb" # small climb
|
|
1863
|
+
elif diff_score > 0 and new_rank == old_rank:
|
|
1864
|
+
style_key = "solid" # better score, same rank
|
|
1865
|
+
else:
|
|
1866
|
+
style_key = "tight" # leaderboard shifted / no visible rank gain
|
|
1867
|
+
else:
|
|
1868
|
+
# When we can't trust rank as an int, lean on score change
|
|
1869
|
+
style_key = "solid" if diff_score > 0 else "tight"
|
|
1870
|
+
|
|
1871
|
+
# --- TEXT + CTA BY STYLE --------------------------------------------
|
|
1872
|
+
card_class = "profile-card success-card"
|
|
1873
|
+
|
|
1874
|
+
if style_key == "first":
|
|
1875
|
+
card_class += " first-score"
|
|
1876
|
+
header_emoji = "🎉"
|
|
1877
|
+
header_title = "You're Officially on the Board!"
|
|
1878
|
+
summary_line = (
|
|
1879
|
+
"You just earned your first Moral Compass Score — you're now part of the global rankings."
|
|
1880
|
+
)
|
|
1881
|
+
cta_line = "Scroll down to take your next step and start climbing."
|
|
1882
|
+
elif style_key == "major":
|
|
1883
|
+
header_emoji = "🔥"
|
|
1884
|
+
header_title = "Major Moral Compass Boost!"
|
|
1885
|
+
summary_line = (
|
|
1886
|
+
"Your decision made a big impact — you just moved ahead of other participants."
|
|
1887
|
+
)
|
|
1888
|
+
cta_line = "Scroll down to take on your next challenge and keep the boost going."
|
|
1889
|
+
elif style_key == "climb":
|
|
1890
|
+
header_emoji = "🚀"
|
|
1891
|
+
header_title = "You're Climbing the Leaderboard"
|
|
1892
|
+
summary_line = "Nice work — you edged out a few other participants."
|
|
1893
|
+
cta_line = "Scroll down to continue your investigation and push even higher."
|
|
1894
|
+
elif style_key == "tight":
|
|
1895
|
+
header_emoji = "📊"
|
|
1896
|
+
header_title = "The Leaderboard Is Shifting"
|
|
1897
|
+
summary_line = (
|
|
1898
|
+
"Other teams are moving too. You'll need a few more strong decisions to stand out."
|
|
1899
|
+
)
|
|
1900
|
+
cta_line = "Take on the next question to strengthen your position."
|
|
1901
|
+
else: # "solid"
|
|
1902
|
+
header_emoji = "✅"
|
|
1903
|
+
header_title = "Progress Logged"
|
|
1904
|
+
summary_line = "Your ethical insight increased your Moral Compass Score."
|
|
1905
|
+
cta_line = "Try the next scenario to break into the next tier."
|
|
1906
|
+
|
|
1907
|
+
# --- SCORE / RANK LINES ---------------------------------------------
|
|
1908
|
+
|
|
1909
|
+
# First-time: different wording (no previous score)
|
|
1910
|
+
if style_key == "first":
|
|
1911
|
+
score_line = f"🧭 Score: <strong>{new_score:.3f}</strong>"
|
|
1912
|
+
if ranks_are_int:
|
|
1913
|
+
rank_line = f"🏅 Initial Rank: <strong>#{new_rank}</strong>"
|
|
1914
|
+
else:
|
|
1915
|
+
rank_line = f"🏅 Initial Rank: <strong>#{new_rank}</strong>"
|
|
1916
|
+
else:
|
|
1917
|
+
score_line = (
|
|
1918
|
+
f"🧭 Score: {old_score:.3f} → <strong>{new_score:.3f}</strong> "
|
|
1919
|
+
f"(+{diff_score:.3f})"
|
|
1920
|
+
)
|
|
1921
|
+
|
|
1922
|
+
if ranks_are_int:
|
|
1923
|
+
if old_rank == new_rank:
|
|
1924
|
+
rank_line = f"📊 Rank: <strong>#{new_rank}</strong> (holding steady)"
|
|
1925
|
+
elif rank_diff > 0:
|
|
1926
|
+
rank_line = (
|
|
1927
|
+
f"📈 Rank: #{old_rank} → <strong>#{new_rank}</strong> "
|
|
1928
|
+
f"(+{rank_diff} places)"
|
|
1929
|
+
)
|
|
1930
|
+
else:
|
|
1931
|
+
rank_line = (
|
|
1932
|
+
f"🔻 Rank: #{old_rank} → <strong>#{new_rank}</strong> "
|
|
1933
|
+
f"({rank_diff} places)"
|
|
1934
|
+
)
|
|
1935
|
+
else:
|
|
1936
|
+
rank_line = f"📊 Rank: <strong>#{new_rank}</strong>"
|
|
1937
|
+
|
|
1938
|
+
# --- HTML COMPOSITION -----------------------------------------------
|
|
1939
|
+
return f"""
|
|
1940
|
+
<div class="{card_class}">
|
|
1941
|
+
<div class="success-header">
|
|
1942
|
+
<div>
|
|
1943
|
+
<div class="success-title">{header_emoji} {header_title}</div>
|
|
1944
|
+
<div class="success-summary">{summary_line}</div>
|
|
1945
|
+
</div>
|
|
1946
|
+
<div class="success-delta">
|
|
1947
|
+
+{diff_score:.3f}
|
|
1948
|
+
</div>
|
|
1949
|
+
</div>
|
|
1950
|
+
|
|
1951
|
+
<div class="success-metrics">
|
|
1952
|
+
<div class="success-metric-line">{score_line}</div>
|
|
1953
|
+
<div class="success-metric-line">{rank_line}</div>
|
|
1954
|
+
</div>
|
|
1955
|
+
|
|
1956
|
+
<div class="success-body">
|
|
1957
|
+
<p class="success-body-text">{specific_text}</p>
|
|
1958
|
+
<p class="success-cta">{cta_line}</p>
|
|
1959
|
+
</div>
|
|
1960
|
+
</div>
|
|
1961
|
+
"""
|
|
1962
|
+
|
|
1963
|
+
def render_top_dashboard(data, module_id):
|
|
1964
|
+
display_score = 0.0; count_completed = 0; rank_display = "–"; team_rank_display = "–"
|
|
1965
|
+
if data:
|
|
1966
|
+
display_score = float(data.get('score', 0.0))
|
|
1967
|
+
rank_display = f"#{data.get('rank', '–')}"
|
|
1968
|
+
team_rank_display = f"#{data.get('team_rank', '–')}"
|
|
1969
|
+
count_completed = len(data.get('completed_task_ids', []) or [])
|
|
1970
|
+
progress_pct = min(100, int((count_completed / TOTAL_COURSE_TASKS) * 100))
|
|
1971
|
+
return f"""<div class="summary-box"><div class="summary-box-inner"><div class="summary-metrics"><div style="text-align:center;"><div class="label-text">Moral Compass Score</div><div class="score-text-primary">🧭 {display_score:.3f}</div></div><div class="divider-vertical"></div><div style="text-align:center;"><div class="label-text">Team Rank</div><div class="score-text-team">{team_rank_display}</div></div><div class="divider-vertical"></div><div style="text-align:center;"><div class="label-text">Global Rank</div><div class="score-text-global">{rank_display}</div></div></div><div class="summary-progress"><div class="progress-label">Mission Progress: {progress_pct}%</div><div class="progress-bar-bg"><div class="progress-bar-fill" style="width:{progress_pct}%;"></div></div></div></div></div>"""
|
|
1972
|
+
|
|
1973
|
+
def render_leaderboard_card(data, username, team_name):
|
|
1974
|
+
team_rows = ""; user_rows = ""
|
|
1975
|
+
if data and data.get("all_teams"):
|
|
1976
|
+
for i, t in enumerate(data["all_teams"]):
|
|
1977
|
+
cls = "row-highlight-team" if t["team"] == team_name else "row-normal"
|
|
1978
|
+
team_rows += f"<tr class='{cls}'><td style='padding:8px;text-align:center;'>{i+1}</td><td style='padding:8px;'>{t['team']}</td><td style='padding:8px;text-align:right;'>{t['avg']:.3f}</td></tr>"
|
|
1979
|
+
if data and data.get("all_users"):
|
|
1980
|
+
for i, u in enumerate(data["all_users"]):
|
|
1981
|
+
cls = "row-highlight-me" if u.get("username") == username else "row-normal"
|
|
1982
|
+
sc = float(u.get('moralCompassScore',0))
|
|
1983
|
+
if u.get("username") == username and data.get('score') != sc: sc = data.get('score')
|
|
1984
|
+
user_rows += f"<tr class='{cls}'><td style='padding:8px;text-align:center;'>{i+1}</td><td style='padding:8px;'>{u.get('username','')}</td><td style='padding:8px;text-align:right;'>{sc:.3f}</td></tr>"
|
|
1985
|
+
return f"""<div class="scenario-box leaderboard-card"><h3 class="slide-title" style="margin-bottom:10px;">📊 Live Standings</h3><div class="lb-tabs"><input type="radio" id="lb-tab-team" name="lb-tabs" checked><label for="lb-tab-team" class="lb-tab-label">🏆 Team</label><input type="radio" id="lb-tab-user" name="lb-tabs"><label for="lb-tab-user" class="lb-tab-label">👤 Individual</label><div class="lb-tab-panels"><div class="lb-panel panel-team"><div class='table-container'><table class='leaderboard-table'><thead><tr><th>Rank</th><th>Team</th><th style='text-align:right;'>Avg 🧭</th></tr></thead><tbody>{team_rows}</tbody></table></div></div><div class="lb-panel panel-user"><div class='table-container'><table class='leaderboard-table'><thead><tr><th>Rank</th><th>Agent</th><th style='text-align:right;'>Score 🧭</th></tr></thead><tbody>{user_rows}</tbody></table></div></div></div></div></div>"""
|
|
1986
|
+
|
|
1987
|
+
# --- 8. CSS ---
|
|
1988
|
+
css = """
|
|
1989
|
+
/* Layout + containers */
|
|
1990
|
+
.summary-box {
|
|
1991
|
+
background: var(--block-background-fill);
|
|
1992
|
+
padding: 20px;
|
|
1993
|
+
border-radius: 12px;
|
|
1994
|
+
border: 1px solid var(--border-color-primary);
|
|
1995
|
+
margin-bottom: 20px;
|
|
1996
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
1997
|
+
}
|
|
1998
|
+
.summary-box-inner { display: flex; align-items: center; justify-content: space-between; gap: 30px; }
|
|
1999
|
+
.summary-metrics { display: flex; gap: 30px; align-items: center; }
|
|
2000
|
+
.summary-progress { width: 560px; max-width: 100%; }
|
|
2001
|
+
|
|
2002
|
+
/* Scenario cards */
|
|
2003
|
+
.scenario-box {
|
|
2004
|
+
padding: 24px;
|
|
2005
|
+
border-radius: 14px;
|
|
2006
|
+
background: var(--block-background-fill);
|
|
2007
|
+
border: 1px solid var(--border-color-primary);
|
|
2008
|
+
margin-bottom: 22px;
|
|
2009
|
+
box-shadow: 0 6px 18px rgba(0,0,0,0.08);
|
|
2010
|
+
}
|
|
2011
|
+
.slide-title { margin-top: 0; font-size: 1.9rem; font-weight: 800; }
|
|
2012
|
+
.slide-body { font-size: 1.12rem; line-height: 1.65; }
|
|
2013
|
+
|
|
2014
|
+
/* Hint boxes */
|
|
2015
|
+
.hint-box {
|
|
2016
|
+
padding: 12px;
|
|
2017
|
+
border-radius: 10px;
|
|
2018
|
+
background: var(--background-fill-secondary);
|
|
2019
|
+
border: 1px solid var(--border-color-primary);
|
|
2020
|
+
margin-top: 10px;
|
|
2021
|
+
font-size: 0.98rem;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
/* Success / profile card */
|
|
2025
|
+
.profile-card.success-card {
|
|
2026
|
+
padding: 20px;
|
|
2027
|
+
border-radius: 14px;
|
|
2028
|
+
border-left: 6px solid #22c55e;
|
|
2029
|
+
background: linear-gradient(135deg, rgba(34,197,94,0.08), var(--block-background-fill));
|
|
2030
|
+
margin-top: 16px;
|
|
2031
|
+
box-shadow: 0 4px 18px rgba(0,0,0,0.08);
|
|
2032
|
+
font-size: 1.04rem;
|
|
2033
|
+
line-height: 1.55;
|
|
2034
|
+
}
|
|
2035
|
+
.profile-card.first-score {
|
|
2036
|
+
border-left-color: #facc15;
|
|
2037
|
+
background: linear-gradient(135deg, rgba(250,204,21,0.18), var(--block-background-fill));
|
|
2038
|
+
}
|
|
2039
|
+
.success-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 8px; }
|
|
2040
|
+
.success-title { font-size: 1.26rem; font-weight: 900; color: #16a34a; }
|
|
2041
|
+
.success-summary { font-size: 1.06rem; color: var(--body-text-color-subdued); margin-top: 4px; }
|
|
2042
|
+
.success-delta { font-size: 1.5rem; font-weight: 800; color: #16a34a; }
|
|
2043
|
+
.success-metrics { margin-top: 10px; padding: 10px 12px; border-radius: 10px; background: var(--background-fill-secondary); font-size: 1.06rem; }
|
|
2044
|
+
.success-metric-line { margin-bottom: 4px; }
|
|
2045
|
+
.success-body { margin-top: 10px; font-size: 1.06rem; }
|
|
2046
|
+
.success-body-text { margin: 0 0 6px 0; }
|
|
2047
|
+
.success-cta { margin: 4px 0 0 0; font-weight: 700; font-size: 1.06rem; }
|
|
2048
|
+
|
|
2049
|
+
/* Numbers + labels */
|
|
2050
|
+
.score-text-primary { font-size: 2.05rem; font-weight: 900; color: var(--color-accent); }
|
|
2051
|
+
.score-text-team { font-size: 2.05rem; font-weight: 900; color: #60a5fa; }
|
|
2052
|
+
.score-text-global { font-size: 2.05rem; font-weight: 900; }
|
|
2053
|
+
.label-text { font-size: 0.82rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #6b7280; }
|
|
2054
|
+
|
|
2055
|
+
/* Progress bar */
|
|
2056
|
+
.progress-bar-bg { width: 100%; height: 10px; background: #e5e7eb; border-radius: 6px; overflow: hidden; margin-top: 8px; }
|
|
2057
|
+
.progress-bar-fill { height: 100%; background: var(--color-accent); transition: width 280ms ease; }
|
|
2058
|
+
|
|
2059
|
+
/* Leaderboard tabs + tables */
|
|
2060
|
+
.leaderboard-card input[type="radio"] { display: none; }
|
|
2061
|
+
.lb-tab-label {
|
|
2062
|
+
display: inline-block; padding: 8px 16px; margin-right: 8px; border-radius: 20px;
|
|
2063
|
+
cursor: pointer; border: 1px solid var(--border-color-primary); font-weight: 700; font-size: 0.94rem;
|
|
2064
|
+
}
|
|
2065
|
+
#lb-tab-team:checked + label, #lb-tab-user:checked + label {
|
|
2066
|
+
background: var(--color-accent); color: white; border-color: var(--color-accent);
|
|
2067
|
+
box-shadow: 0 3px 8px rgba(99,102,241,0.25);
|
|
2068
|
+
}
|
|
2069
|
+
.lb-panel { display: none; margin-top: 10px; }
|
|
2070
|
+
#lb-tab-team:checked ~ .lb-tab-panels .panel-team { display: block; }
|
|
2071
|
+
#lb-tab-user:checked ~ .lb-tab-panels .panel-user { display: block; }
|
|
2072
|
+
.table-container { height: 320px; overflow-y: auto; border: 1px solid var(--border-color-primary); border-radius: 10px; }
|
|
2073
|
+
.leaderboard-table { width: 100%; border-collapse: collapse; }
|
|
2074
|
+
.leaderboard-table th {
|
|
2075
|
+
position: sticky; top: 0; background: var(--background-fill-secondary);
|
|
2076
|
+
padding: 10px; text-align: left; border-bottom: 2px solid var(--border-color-primary);
|
|
2077
|
+
font-weight: 800;
|
|
2078
|
+
}
|
|
2079
|
+
.leaderboard-table td { padding: 10px; border-bottom: 1px solid var(--border-color-primary); }
|
|
2080
|
+
.row-highlight-me, .row-highlight-team { background: rgba(96,165,250,0.18); font-weight: 700; }
|
|
2081
|
+
|
|
2082
|
+
/* Containers */
|
|
2083
|
+
.ai-risk-container { margin-top: 16px; padding: 16px; background: var(--body-background-fill); border-radius: 10px; border: 1px solid var(--border-color-primary); }
|
|
2084
|
+
|
|
2085
|
+
/* Interactive blocks (text size tuned for 17–20 age group) */
|
|
2086
|
+
.interactive-block { font-size: 1.06rem; }
|
|
2087
|
+
.interactive-block .hint-box { font-size: 1.02rem; }
|
|
2088
|
+
.interactive-text { font-size: 1.06rem; }
|
|
2089
|
+
|
|
2090
|
+
/* Radio sizes */
|
|
2091
|
+
.scenario-radio-large label { font-size: 1.06rem; }
|
|
2092
|
+
.quiz-radio-large label { font-size: 1.06rem; }
|
|
2093
|
+
|
|
2094
|
+
/* Small utility */
|
|
2095
|
+
.divider-vertical { width: 1px; height: 48px; background: var(--border-color-primary); opacity: 0.6; }
|
|
2096
|
+
|
|
2097
|
+
/* Navigation loading overlay */
|
|
2098
|
+
#nav-loading-overlay {
|
|
2099
|
+
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
|
2100
|
+
background: color-mix(in srgb, var(--body-background-fill) 95%, transparent);
|
|
2101
|
+
z-index: 9999; display: none; flex-direction: column; align-items: center;
|
|
2102
|
+
justify-content: center; opacity: 0; transition: opacity 0.3s ease;
|
|
2103
|
+
}
|
|
2104
|
+
.nav-spinner {
|
|
2105
|
+
width: 50px; height: 50px; border: 5px solid var(--border-color-primary);
|
|
2106
|
+
border-top: 5px solid var(--color-accent); border-radius: 50%;
|
|
2107
|
+
animation: nav-spin 1s linear infinite; margin-bottom: 20px;
|
|
2108
|
+
}
|
|
2109
|
+
@keyframes nav-spin {
|
|
2110
|
+
0% { transform: rotate(0deg); }
|
|
2111
|
+
100% { transform: rotate(360deg); }
|
|
2112
|
+
}
|
|
2113
|
+
#nav-loading-text {
|
|
2114
|
+
font-size: 1.3rem; font-weight: 600; color: var(--color-accent);
|
|
2115
|
+
}
|
|
2116
|
+
@media (prefers-color-scheme: dark) {
|
|
2117
|
+
#nav-loading-overlay { background: rgba(15, 23, 42, 0.9); }
|
|
2118
|
+
.nav-spinner { border-color: rgba(148, 163, 184, 0.4); border-top-color: var(--color-accent); }
|
|
2119
|
+
}
|
|
2120
|
+
"""
|
|
2121
|
+
|
|
2122
|
+
def build_audit_report(selected_biases):
|
|
2123
|
+
"""
|
|
2124
|
+
Build a short markdown audit report based on which bias patterns
|
|
2125
|
+
the student selects in the Final Audit slide.
|
|
2126
|
+
"""
|
|
2127
|
+
if not selected_biases:
|
|
2128
|
+
return (
|
|
2129
|
+
"Select at least one bias pattern above to start drafting your audit report. "
|
|
2130
|
+
"Your report will appear here."
|
|
2131
|
+
)
|
|
2132
|
+
|
|
2133
|
+
lines = []
|
|
2134
|
+
lines.append("### 🧾 Draft Audit Report")
|
|
2135
|
+
lines.append("")
|
|
2136
|
+
lines.append(
|
|
2137
|
+
"Below is a draft summary of the main bias patterns you identified in the COMPAS model. "
|
|
2138
|
+
"You can refine this text in your own words."
|
|
2139
|
+
)
|
|
2140
|
+
lines.append("")
|
|
2141
|
+
|
|
2142
|
+
if "Punitive Bias (False Alarms by Race)" in selected_biases:
|
|
2143
|
+
lines.append("**1. Punitive Bias (False Alarms by Race)**")
|
|
2144
|
+
lines.append(
|
|
2145
|
+
"- African-American defendants were more likely to be falsely labeled 'High Risk' "
|
|
2146
|
+
"compared to White defendants, even when they did not re-offend."
|
|
2147
|
+
)
|
|
2148
|
+
lines.append(
|
|
2149
|
+
"- This leads to unfairly harsher treatment for one group (e.g., stricter bail, longer sentences)."
|
|
2150
|
+
)
|
|
2151
|
+
lines.append("")
|
|
2152
|
+
|
|
2153
|
+
if "Missed Risk (Leniency Pattern by Race)" in selected_biases:
|
|
2154
|
+
lines.append("**2. Missed Risk (Leniency Pattern by Race)**")
|
|
2155
|
+
lines.append(
|
|
2156
|
+
"- Among people who actually re-offended, White defendants were more likely to be labeled "
|
|
2157
|
+
"'Low Risk' than African-American defendants."
|
|
2158
|
+
)
|
|
2159
|
+
lines.append(
|
|
2160
|
+
"- This means one group is given more 'second chances' by the model, even when their risk is high."
|
|
2161
|
+
)
|
|
2162
|
+
lines.append("")
|
|
2163
|
+
|
|
2164
|
+
if "Generalization Bias (Gender)" in selected_biases:
|
|
2165
|
+
lines.append("**3. Generalization Bias (Gender)**")
|
|
2166
|
+
lines.append(
|
|
2167
|
+
"- The dataset is heavily skewed toward male cases (around 81% men, 19% women), "
|
|
2168
|
+
"yet the model is described with a single overall accuracy value (for example, 92%)."
|
|
2169
|
+
)
|
|
2170
|
+
lines.append(
|
|
2171
|
+
"- That single number can hide larger prediction errors for women, because the model learned "
|
|
2172
|
+
"mostly from male data."
|
|
2173
|
+
)
|
|
2174
|
+
lines.append("")
|
|
2175
|
+
|
|
2176
|
+
if "Age Skew & Geography as Proxy" in selected_biases:
|
|
2177
|
+
lines.append("**4. Age Skew & Geography as Proxy**")
|
|
2178
|
+
lines.append(
|
|
2179
|
+
"- Most training data comes from ages 25–45 and from certain high-policing neighborhoods."
|
|
2180
|
+
)
|
|
2181
|
+
lines.append(
|
|
2182
|
+
"- Age and location can act as stand-ins (proxies) for race and class, "
|
|
2183
|
+
"which can quietly reintroduce inequality even if race and income columns are removed."
|
|
2184
|
+
)
|
|
2185
|
+
lines.append("")
|
|
2186
|
+
|
|
2187
|
+
lines.append(
|
|
2188
|
+
"**Overall Conclusion:** The COMPAS model is not just 'a bit inaccurate.' Its mistakes are "
|
|
2189
|
+
"distributed in ways that **increase punishment for some groups and increase protection for others**, "
|
|
2190
|
+
"raising serious Justice & Equity concerns."
|
|
2191
|
+
)
|
|
2192
|
+
|
|
2193
|
+
return "\n".join(lines)
|
|
2194
|
+
|
|
2195
|
+
# --- 9. APP FACTORY ---
|
|
2196
|
+
def create_bias_detective_part2_app(theme_primary_hue: str = "indigo"):
|
|
2197
|
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue=theme_primary_hue), css=css) as demo:
|
|
2198
|
+
# States
|
|
2199
|
+
username_state = gr.State(None)
|
|
2200
|
+
token_state = gr.State(None)
|
|
2201
|
+
team_state = gr.State(None)
|
|
2202
|
+
module0_done = gr.State(False)
|
|
2203
|
+
accuracy_state = gr.State(0.0)
|
|
2204
|
+
task_list_state = gr.State([])
|
|
2205
|
+
|
|
2206
|
+
# --- TOP ANCHOR & LOADING OVERLAY FOR NAVIGATION ---
|
|
2207
|
+
gr.HTML("<div id='app_top_anchor' style='height:0;'></div>")
|
|
2208
|
+
gr.HTML("<div id='nav-loading-overlay'><div class='nav-spinner'></div><span id='nav-loading-text'>Loading...</span></div>")
|
|
2209
|
+
|
|
2210
|
+
with gr.Column(visible=True, elem_id="app-loader") as loader_col:
|
|
2211
|
+
gr.HTML("<div style='text-align:center; padding:100px;'><h2>🕵️♀️ Authenticating...</h2><p>Syncing Moral Compass Data...</p></div>")
|
|
2212
|
+
|
|
2213
|
+
with gr.Column(visible=False) as main_app_col:
|
|
2214
|
+
gr.Markdown("# 🕵️♀️ Bias Detective: Part 2 - Algorithmic Audit")
|
|
2215
|
+
out_top = gr.HTML()
|
|
2216
|
+
|
|
2217
|
+
# --- DYNAMIC MODULE GENERATION ---
|
|
2218
|
+
module_ui_elements = {}
|
|
2219
|
+
quiz_wiring_queue = []
|
|
2220
|
+
final_reset_btn = None
|
|
2221
|
+
|
|
2222
|
+
for i, mod in enumerate(MODULES):
|
|
2223
|
+
with gr.Column(elem_id=f"module-{i}", elem_classes=["module-container"], visible=(i==0)) as mod_col:
|
|
2224
|
+
gr.HTML(mod['html'])
|
|
2225
|
+
|
|
2226
|
+
# --- Final Audit interactive builder on module index 8 ---
|
|
2227
|
+
if i == 8:
|
|
2228
|
+
report_checklist = gr.CheckboxGroup(
|
|
2229
|
+
choices=[
|
|
2230
|
+
"Punitive Bias (False Alarms by Race)",
|
|
2231
|
+
"Missed Risk (Leniency Pattern by Race)",
|
|
2232
|
+
"Generalization Bias (Gender)",
|
|
2233
|
+
"Age Skew & Geography as Proxy",
|
|
2234
|
+
],
|
|
2235
|
+
label="Step 1: Select the bias patterns you want to include in your report",
|
|
2236
|
+
)
|
|
2237
|
+
|
|
2238
|
+
report_preview = gr.Markdown(
|
|
2239
|
+
"Select at least one bias pattern above to start drafting your audit report."
|
|
2240
|
+
)
|
|
2241
|
+
|
|
2242
|
+
report_checklist.change(
|
|
2243
|
+
fn=build_audit_report,
|
|
2244
|
+
inputs=report_checklist,
|
|
2245
|
+
outputs=report_preview,
|
|
2246
|
+
)
|
|
2247
|
+
|
|
2248
|
+
# Existing quiz wiring
|
|
2249
|
+
if i in QUIZ_CONFIG:
|
|
2250
|
+
q_data = QUIZ_CONFIG[i]
|
|
2251
|
+
gr.Markdown(f"### 🧠 {q_data['q']}")
|
|
2252
|
+
radio = gr.Radio(choices=q_data['o'], label="Select Answer:")
|
|
2253
|
+
feedback = gr.HTML("")
|
|
2254
|
+
pass
|
|
2255
|
+
|
|
2256
|
+
with gr.Row():
|
|
2257
|
+
btn_prev = gr.Button("⬅️ Previous", visible=(i > 0))
|
|
2258
|
+
next_label = "Next ▶️" if i < len(MODULES)-1 else "🎉 Finish Course"
|
|
2259
|
+
btn_next = gr.Button(next_label, variant="primary")
|
|
2260
|
+
|
|
2261
|
+
# Reset Button (Only created in last module loop)
|
|
2262
|
+
if i == len(MODULES) - 1:
|
|
2263
|
+
btn_reset = gr.Button("🔄 Reset Mission (Start Over)", variant="secondary", visible=True)
|
|
2264
|
+
final_reset_btn = btn_reset
|
|
2265
|
+
|
|
2266
|
+
module_ui_elements[i] = (mod_col, btn_prev, btn_next)
|
|
2267
|
+
|
|
2268
|
+
if i in QUIZ_CONFIG:
|
|
2269
|
+
reset_ref = btn_reset if i == len(MODULES) - 1 else None
|
|
2270
|
+
quiz_wiring_queue.append((i, radio, feedback, btn_next, reset_ref))
|
|
2271
|
+
|
|
2272
|
+
|
|
2273
|
+
|
|
2274
|
+
leaderboard_html = gr.HTML()
|
|
2275
|
+
|
|
2276
|
+
# --- WIRING: CONNECT QUIZZES ---
|
|
2277
|
+
for mod_id, radio_comp, feedback_comp, next_btn_comp, reset_btn_ref in quiz_wiring_queue:
|
|
2278
|
+
def quiz_logic_wrapper(user, tok, team, acc_val, task_list, ans, mid=mod_id):
|
|
2279
|
+
cfg = QUIZ_CONFIG[mid]
|
|
2280
|
+
if ans == cfg['a']:
|
|
2281
|
+
prev, curr, _, new_tasks = trigger_api_update(user, tok, team, mid, acc_val, task_list, cfg['t'])
|
|
2282
|
+
msg = generate_success_message(prev, curr, cfg['success'])
|
|
2283
|
+
return (render_top_dashboard(curr, mid), render_leaderboard_card(curr, user, team), msg, new_tasks)
|
|
2284
|
+
else:
|
|
2285
|
+
return (gr.update(), gr.update(), "<div class='hint-box' style='border-color:red;'>❌ Incorrect. Review the evidence above.</div>", task_list)
|
|
2286
|
+
|
|
2287
|
+
radio_comp.change(
|
|
2288
|
+
fn=quiz_logic_wrapper,
|
|
2289
|
+
inputs=[username_state, token_state, team_state, accuracy_state, task_list_state, radio_comp],
|
|
2290
|
+
outputs=[out_top, leaderboard_html, feedback_comp, task_list_state]
|
|
2291
|
+
)
|
|
2292
|
+
|
|
2293
|
+
# --- WIRING: RESET BUTTON ---
|
|
2294
|
+
if final_reset_btn:
|
|
2295
|
+
def handle_reset(user, tok, team, acc):
|
|
2296
|
+
new_list = reset_user_progress(user, tok, team, acc)
|
|
2297
|
+
data, _ = ensure_table_and_get_data(user, tok, team, new_list)
|
|
2298
|
+
return (
|
|
2299
|
+
render_top_dashboard(data, 0),
|
|
2300
|
+
render_leaderboard_card(data, user, team),
|
|
2301
|
+
new_list,
|
|
2302
|
+
gr.update(visible=True), # Show Module 0
|
|
2303
|
+
gr.update(visible=False) # Hide Module 10
|
|
2304
|
+
)
|
|
2305
|
+
|
|
2306
|
+
final_reset_btn.click(
|
|
2307
|
+
fn=handle_reset,
|
|
2308
|
+
inputs=[username_state, token_state, team_state, accuracy_state],
|
|
2309
|
+
outputs=[out_top, leaderboard_html, task_list_state, module_ui_elements[0][0], module_ui_elements[len(MODULES)-1][0]]
|
|
2310
|
+
)
|
|
2311
|
+
|
|
2312
|
+
# --- LOGIC WIRING (Global) ---
|
|
2313
|
+
def handle_load(req: gr.Request):
|
|
2314
|
+
success, user, token = _try_session_based_auth(req)
|
|
2315
|
+
team, acc = "Team-Unassigned", 0.0
|
|
2316
|
+
fetched_tasks = []
|
|
2317
|
+
|
|
2318
|
+
if success and user and token:
|
|
2319
|
+
acc, fetched_team = fetch_user_history(user, token)
|
|
2320
|
+
os.environ["MORAL_COMPASS_API_BASE_URL"] = DEFAULT_API_URL
|
|
2321
|
+
client = MoralcompassApiClient(api_base_url=DEFAULT_API_URL, auth_token=token)
|
|
2322
|
+
|
|
2323
|
+
exist_team = get_or_assign_team(client, user)
|
|
2324
|
+
if fetched_team != "Team-Unassigned": team = fetched_team
|
|
2325
|
+
elif exist_team != "team-a": team = exist_team
|
|
2326
|
+
else: team = "team-a"
|
|
2327
|
+
|
|
2328
|
+
try: user_stats = client.get_user(table_id=TABLE_ID, username=user)
|
|
2329
|
+
except: user_stats = None
|
|
2330
|
+
|
|
2331
|
+
if user_stats:
|
|
2332
|
+
if isinstance(user_stats, dict): fetched_tasks = user_stats.get("completedTaskIds") or []
|
|
2333
|
+
else: fetched_tasks = getattr(user_stats, "completed_task_ids", []) or []
|
|
2334
|
+
|
|
2335
|
+
if not user_stats or (team != "Team-Unassigned"):
|
|
2336
|
+
client.update_moral_compass(table_id=TABLE_ID, username=user, team_name=team, metrics={"accuracy": acc}, tasks_completed=len(fetched_tasks), total_tasks=TOTAL_COURSE_TASKS, primary_metric="accuracy", completed_task_ids=fetched_tasks)
|
|
2337
|
+
time.sleep(1.0)
|
|
2338
|
+
|
|
2339
|
+
data, _ = ensure_table_and_get_data(user, token, team, fetched_tasks)
|
|
2340
|
+
return (user, token, team, False, render_top_dashboard(data, 0), render_leaderboard_card(data, user, team), acc, fetched_tasks, gr.update(visible=False), gr.update(visible=True))
|
|
2341
|
+
|
|
2342
|
+
return (None, None, None, False, "<div class='hint-box'>⚠️ Auth Failed</div>", "", 0.0, [], gr.update(visible=False), gr.update(visible=True))
|
|
2343
|
+
|
|
2344
|
+
demo.load(handle_load, None, [username_state, token_state, team_state, module0_done, out_top, leaderboard_html, accuracy_state, task_list_state, loader_col, main_app_col])
|
|
2345
|
+
|
|
2346
|
+
# --- JAVASCRIPT HELPER FOR NAVIGATION ---
|
|
2347
|
+
def nav_js(target_id: str, message: str) -> str:
|
|
2348
|
+
"""Generate JavaScript for smooth navigation with loading overlay."""
|
|
2349
|
+
return f"""
|
|
2350
|
+
()=>{{
|
|
2351
|
+
try {{
|
|
2352
|
+
const overlay = document.getElementById('nav-loading-overlay');
|
|
2353
|
+
const messageEl = document.getElementById('nav-loading-text');
|
|
2354
|
+
if(overlay && messageEl) {{
|
|
2355
|
+
messageEl.textContent = '{message}';
|
|
2356
|
+
overlay.style.display = 'flex';
|
|
2357
|
+
setTimeout(() => {{ overlay.style.opacity = '1'; }}, 10);
|
|
2358
|
+
}}
|
|
2359
|
+
const startTime = Date.now();
|
|
2360
|
+
setTimeout(() => {{
|
|
2361
|
+
const anchor = document.getElementById('app_top_anchor');
|
|
2362
|
+
if(anchor) anchor.scrollIntoView({{behavior:'smooth', block:'start'}});
|
|
2363
|
+
}}, 40);
|
|
2364
|
+
const targetId = '{target_id}';
|
|
2365
|
+
const pollInterval = setInterval(() => {{
|
|
2366
|
+
const elapsed = Date.now() - startTime;
|
|
2367
|
+
const target = document.getElementById(targetId);
|
|
2368
|
+
const isVisible = target && target.offsetParent !== null &&
|
|
2369
|
+
window.getComputedStyle(target).display !== 'none';
|
|
2370
|
+
if((isVisible && elapsed >= 1200) || elapsed > 7000) {{
|
|
2371
|
+
clearInterval(pollInterval);
|
|
2372
|
+
if(overlay) {{
|
|
2373
|
+
overlay.style.opacity = '0';
|
|
2374
|
+
setTimeout(() => {{ overlay.style.display = 'none'; }}, 300);
|
|
2375
|
+
}}
|
|
2376
|
+
}}
|
|
2377
|
+
}}, 90);
|
|
2378
|
+
}} catch(e) {{ console.warn('nav-js error', e); }}
|
|
2379
|
+
}}
|
|
2380
|
+
"""
|
|
2381
|
+
|
|
2382
|
+
# 2. NAVIGATION WIRING
|
|
2383
|
+
for i in range(len(MODULES)):
|
|
2384
|
+
curr_col, prev_btn, next_btn = module_ui_elements[i]
|
|
2385
|
+
if i > 0:
|
|
2386
|
+
prev_col = module_ui_elements[i-1][0]
|
|
2387
|
+
prev_target_id = f"module-{i-1}"
|
|
2388
|
+
|
|
2389
|
+
def make_prev_handler(p_col, c_col, target_id):
|
|
2390
|
+
def navigate_prev():
|
|
2391
|
+
# First yield: hide current, show nothing (transition state)
|
|
2392
|
+
yield gr.update(visible=False), gr.update(visible=False)
|
|
2393
|
+
# Second yield: show previous, hide current
|
|
2394
|
+
yield gr.update(visible=True), gr.update(visible=False)
|
|
2395
|
+
return navigate_prev
|
|
2396
|
+
|
|
2397
|
+
prev_btn.click(
|
|
2398
|
+
fn=make_prev_handler(prev_col, curr_col, prev_target_id),
|
|
2399
|
+
outputs=[prev_col, curr_col],
|
|
2400
|
+
js=nav_js(prev_target_id, "Loading..."),
|
|
2401
|
+
)
|
|
2402
|
+
|
|
2403
|
+
if i < len(MODULES) - 1:
|
|
2404
|
+
next_col = module_ui_elements[i+1][0]
|
|
2405
|
+
next_target_id = f"module-{i+1}"
|
|
2406
|
+
|
|
2407
|
+
def make_next_handler(c_col, n_col, next_idx):
|
|
2408
|
+
def wrapper_next(user, tok, team, tasks):
|
|
2409
|
+
data, _ = ensure_table_and_get_data(user, tok, team, tasks)
|
|
2410
|
+
dash_html = render_top_dashboard(data, next_idx)
|
|
2411
|
+
return dash_html
|
|
2412
|
+
return wrapper_next
|
|
2413
|
+
|
|
2414
|
+
def make_nav_generator(c_col, n_col):
|
|
2415
|
+
def navigate_next():
|
|
2416
|
+
# First yield: hide current, show nothing (transition state)
|
|
2417
|
+
yield gr.update(visible=False), gr.update(visible=False)
|
|
2418
|
+
# Second yield: hide current, show next
|
|
2419
|
+
yield gr.update(visible=False), gr.update(visible=True)
|
|
2420
|
+
return navigate_next
|
|
2421
|
+
|
|
2422
|
+
next_btn.click(
|
|
2423
|
+
fn=make_next_handler(curr_col, next_col, i + 1),
|
|
2424
|
+
inputs=[username_state, token_state, team_state, task_list_state],
|
|
2425
|
+
outputs=[out_top],
|
|
2426
|
+
js=nav_js(next_target_id, "Loading..."),
|
|
2427
|
+
).then(
|
|
2428
|
+
fn=make_nav_generator(curr_col, next_col),
|
|
2429
|
+
outputs=[curr_col, next_col],
|
|
2430
|
+
)
|
|
2431
|
+
|
|
2432
|
+
return demo
|
|
2433
|
+
|
|
2434
|
+
def launch_bias_detective_part2_app(
|
|
2435
|
+
share: bool = False,
|
|
2436
|
+
server_name: str = "0.0.0.0",
|
|
2437
|
+
server_port: int = 8080,
|
|
2438
|
+
theme_primary_hue: str = "indigo",
|
|
2439
|
+
**kwargs
|
|
2440
|
+
) -> None:
|
|
2441
|
+
"""
|
|
2442
|
+
Launch the Bias Detective Part 2 app.
|
|
2443
|
+
|
|
2444
|
+
Args:
|
|
2445
|
+
share: Whether to create a public link
|
|
2446
|
+
server_name: Server hostname
|
|
2447
|
+
server_port: Server port
|
|
2448
|
+
theme_primary_hue: Primary color hue
|
|
2449
|
+
**kwargs: Additional Gradio launch arguments
|
|
2450
|
+
"""
|
|
2451
|
+
app = create_bias_detective_part2_app(theme_primary_hue=theme_primary_hue)
|
|
2452
|
+
app.launch(
|
|
2453
|
+
share=share,
|
|
2454
|
+
server_name=server_name,
|
|
2455
|
+
server_port=server_port,
|
|
2456
|
+
**kwargs
|
|
2457
|
+
)
|
|
2458
|
+
|
|
2459
|
+
|
|
2460
|
+
# ============================================================================
|
|
2461
|
+
# Main Entry Point
|
|
2462
|
+
# ============================================================================
|
|
2463
|
+
|
|
2464
|
+
if __name__ == "__main__":
|
|
2465
|
+
launch_bias_detective_part2_app(share=False, debug=True, height=1000)
|