burtenshaw commited on
Commit
75075b9
Β·
1 Parent(s): e2c7eb7

generalize application for any course

Browse files
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .ruff_cache
2
+ .venv
3
+ __pycache__
4
+ uv.lock
5
+ .python-version
app.py CHANGED
@@ -1,435 +1,181 @@
1
- import gradio as gr
2
- from huggingface_hub import HfApi, hf_hub_download, Repository
3
- from huggingface_hub.repocard import metadata_load
4
-
5
- from PIL import Image, ImageDraw, ImageFont
6
-
7
- from datetime import date
8
- import time
9
-
10
  import os
11
- import pandas as pd
12
-
13
- from utils import *
14
-
15
- api = HfApi()
16
 
17
- DATASET_REPO_URL = "https://huggingface.co/datasets/huggingface-projects/Deep-RL-Course-Certification"
18
- CERTIFIED_USERS_FILENAME = "certified_users.csv"
19
- CERTIFIED_USERS_DIR = "certified_users"
20
 
21
- HF_TOKEN = os.environ.get("HF_TOKEN")
 
22
 
23
- repo = Repository(
24
- local_dir=CERTIFIED_USERS_DIR, clone_from=DATASET_REPO_URL, use_auth_token=HF_TOKEN
25
- )
26
 
27
- def get_user_models(hf_username, env_tag, lib_tag):
28
- """
29
- List the Reinforcement Learning models
30
- from user given environment and lib
31
- :param hf_username: User HF username
32
- :param env_tag: Environment tag
33
- :param lib_tag: Library tag
34
- """
35
- api = HfApi()
36
- models = api.list_models(author=hf_username, filter=["reinforcement-learning", env_tag, lib_tag])
37
-
38
- user_model_ids = [x.modelId for x in models]
39
- return user_model_ids
40
-
41
-
42
- def get_user_sf_models(hf_username, env_tag, lib_tag):
43
- models_sf = []
44
- models = api.list_models(author=hf_username, filter=["reinforcement-learning", lib_tag])
45
-
46
- user_model_ids = [x.modelId for x in models]
47
-
48
- for model in user_model_ids:
49
- meta = get_metadata(model)
50
- if meta is None:
51
- continue
52
- result = meta["model-index"][0]["results"][0]["dataset"]["name"]
53
- if result == env_tag:
54
- models_sf.append(model)
55
-
56
- return models_sf
57
-
58
-
59
- def get_metadata(model_id):
60
- """
61
- Get model metadata (contains evaluation data)
62
- :param model_id
63
- """
64
- try:
65
- readme_path = hf_hub_download(model_id, filename="README.md")
66
- return metadata_load(readme_path)
67
- except requests.exceptions.HTTPError:
68
- # 404 README.md not found
69
- return None
70
-
71
-
72
- def parse_metrics_accuracy(meta):
73
- """
74
- Get model results and parse it
75
- :param meta: model metadata
76
- """
77
- if "model-index" not in meta:
78
- return None
79
- result = meta["model-index"][0]["results"]
80
- metrics = result[0]["metrics"]
81
- accuracy = metrics[0]["value"]
82
-
83
- return accuracy
84
-
85
-
86
- def parse_rewards(accuracy):
87
- """
88
- Parse mean_reward and std_reward
89
- :param accuracy: model results
90
- """
91
- default_std = -1000
92
- default_reward= -1000
93
- if accuracy != None:
94
- accuracy = str(accuracy)
95
- parsed = accuracy.split(' +/- ')
96
- if len(parsed)>1:
97
- mean_reward = float(parsed[0])
98
- std_reward = float(parsed[1])
99
- elif len(parsed)==1: #only mean reward
100
- mean_reward = float(parsed[0])
101
- std_reward = float(0)
102
- else:
103
- mean_reward = float(default_std)
104
- std_reward = float(default_reward)
105
- else:
106
- mean_reward = float(default_std)
107
- std_reward = float(default_reward)
108
-
109
- return mean_reward, std_reward
110
-
111
- def calculate_best_result(user_model_ids):
112
- """
113
- Calculate the best results of a unit
114
- best_result = mean_reward - std_reward
115
- :param user_model_ids: RL models of a user
116
- """
117
- best_result = -1000
118
- best_model_id = ""
119
- for model in user_model_ids:
120
- meta = get_metadata(model)
121
- if meta is None:
122
- continue
123
- accuracy = parse_metrics_accuracy(meta)
124
- mean_reward, std_reward = parse_rewards(accuracy)
125
- result = mean_reward - std_reward
126
- if result > best_result:
127
- best_result = result
128
- best_model_id = model
129
-
130
- return best_result, best_model_id
131
-
132
- def check_if_passed(model):
133
- """
134
- Check if result >= baseline
135
- to know if you pass
136
- :param model: user model
137
- """
138
- if model["best_result"] >= model["min_result"]:
139
- model["passed_"] = True
140
-
141
-
142
- def certification(hf_username, first_name, last_name):
143
- results_certification = [
144
- {
145
- "unit": "Unit 1",
146
- "env": "LunarLander-v2",
147
- "library": "stable-baselines3",
148
- "min_result": 200,
149
- "best_result": 0,
150
- "best_model_id": "",
151
- "passed_": False
152
- },
153
- {
154
- "unit": "Unit 2",
155
- "env": "Taxi-v3",
156
- "library": "q-learning",
157
- "min_result": 4,
158
- "best_result": 0,
159
- "best_model_id": "",
160
- "passed_": False
161
- },
162
- {
163
- "unit": "Unit 3",
164
- "env": "SpaceInvadersNoFrameskip-v4",
165
- "library": "stable-baselines3",
166
- "min_result": 200,
167
- "best_result": 0,
168
- "best_model_id": "",
169
- "passed_": False
170
- },
171
- {
172
- "unit": "Unit 4",
173
- "env": "CartPole-v1",
174
- "library": "reinforce",
175
- "min_result": 350,
176
- "best_result": 0,
177
- "best_model_id": "",
178
- "passed_": False
179
- },
180
- {
181
- "unit": "Unit 4",
182
- "env": "Pixelcopter-PLE-v0",
183
- "library": "reinforce",
184
- "min_result": 5,
185
- "best_result": 0,
186
- "best_model_id": "",
187
- "passed_": False
188
- },
189
- {
190
- "unit": "Unit 5",
191
- "env": "ML-Agents-SnowballTarget",
192
- "library": "ml-agents",
193
- "min_result": -100,
194
- "best_result": 0,
195
- "best_model_id": "",
196
- "passed_": False
197
- },
198
- {
199
- "unit": "Unit 5",
200
- "env": "ML-Agents-Pyramids",
201
- "library": "ml-agents",
202
- "min_result": -100,
203
- "best_result": 0,
204
- "best_model_id": "",
205
- "passed_": False
206
- },
207
- {
208
- "unit": "Unit 6",
209
- "env": "PandaReachDense",
210
- "library": "stable-baselines3",
211
- "min_result": -3.5,
212
- "best_result": 0,
213
- "best_model_id": "",
214
- "passed_": False
215
- },
216
- {
217
- "unit": "Unit 7",
218
- "env": "ML-Agents-SoccerTwos",
219
- "library": "ml-agents",
220
- "min_result": -100,
221
- "best_result": 0,
222
- "best_model_id": "",
223
- "passed_": False
224
- },
225
- {
226
- "unit": "Unit 8 PI",
227
- "env": "LunarLander-v2",
228
- "library": "deep-rl-course",
229
- "min_result": -500,
230
- "best_result": 0,
231
- "best_model_id": "",
232
- "passed_": False
233
- },
234
- {
235
- "unit": "Unit 8 PII",
236
- "env": "doom_health_gathering_supreme",
237
- "library": "sample-factory",
238
- "min_result": 5,
239
- "best_result": 0,
240
- "best_model_id": "",
241
- "passed_": False
242
- },
243
- ]
244
- for unit in results_certification:
245
- if unit["unit"] == "Unit 6":
246
- # Since Unit 6 can use PandaReachDense-v2 or v3
247
- user_models = get_user_models(hf_username, "PandaReachDense-v3", unit["library"])
248
- if len(user_models) == 0:
249
- print("Empty")
250
- user_models = get_user_models(hf_username, "PandaReachDense-v2", unit["library"])
251
- elif unit["unit"] != "Unit 8 PII":
252
- # Get user model
253
- user_models = get_user_models(hf_username, unit['env'], unit['library'])
254
- # For sample factory vizdoom we don't have env tag for now
255
- else:
256
- user_models = get_user_sf_models(hf_username, unit['env'], unit['library'])
257
-
258
- # Calculate the best result and get the best_model_id
259
- best_result, best_model_id = calculate_best_result(user_models)
260
-
261
- # Save best_result and best_model_id
262
- unit["best_result"] = best_result
263
- unit["best_model_id"] = make_clickable_model(best_model_id)
264
-
265
- # Based on best_result do we pass the unit?
266
- check_if_passed(unit)
267
- unit["passed"] = pass_emoji(unit["passed_"])
268
-
269
- print(results_certification)
270
-
271
- df1 = pd.DataFrame(results_certification)
272
-
273
- df = df1[['passed', 'unit', 'env', 'min_result', 'best_result', 'best_model_id']]
274
-
275
- certificate, message, pdf, pass_ = verify_certification(results_certification, hf_username, first_name, last_name)
276
- print("MESSAGE", message)
277
-
278
- if pass_:
279
- visible = True
280
- else:
281
- visible = False
282
-
283
-
284
- return message, pdf, certificate, df, output_row.update(visible=visible)
285
-
286
- """
287
- Verify that the user pass.
288
- If yes:
289
- - Generate the certification
290
- - Send an email
291
- - Print the certification
292
-
293
- If no:
294
- - Explain why the user didn't pass yet
295
- """
296
- def verify_certification(df, hf_username, first_name, last_name):
297
- # Check that we pass
298
- model_pass_nb = 0
299
- pass_percentage = 0
300
- pass_ = False
301
-
302
- for unit in df:
303
- if unit["passed_"] is True:
304
- model_pass_nb += 1
305
-
306
- pass_percentage = (model_pass_nb/11) * 100
307
- print("pass_percentage", pass_percentage)
308
-
309
- if pass_percentage == 100:
310
- pass_ = True
311
- # Generate a certificate of excellence
312
- certificate, pdf = generate_certificate("./certificate_models/certificate-excellence.png", first_name, last_name)
313
-
314
- # Add this user to our database
315
- add_certified_user(hf_username, first_name, last_name, pass_percentage)
316
-
317
- # Add a message
318
- message = """
319
- Congratulations, you successfully completed the Hugging Face Deep Reinforcement Learning Course πŸŽ‰! \n
320
- Since you pass 100% of the hands-on you get a Certificate of Excellence πŸŽ“. \n
321
- You can download your certificate below ⬇️ \n
322
- Don't hesitate to share your certificate image below on Twitter and Linkedin (you can tag me @ThomasSimonini and @huggingface) πŸ€—
323
- """
324
 
325
- elif pass_percentage < 100 and pass_percentage >= 80:
326
- pass_ = True
327
- # Certificate of completion
328
- certificate, pdf = generate_certificate("./certificate_models/certificate-completion.png", first_name, last_name)
329
-
330
- # Add this user to our database
331
- add_certified_user(hf_username, first_name, last_name, pass_percentage)
332
-
333
- # Add a message
334
- message = """
335
- Congratulations, you successfully completed the Hugging Face Deep Reinforcement Learning Course πŸŽ‰! \n
336
- Since you pass 80% of the hands-on you get a Certificate of Completion πŸŽ“. \n
337
- You can download your certificate below ⬇️ \n
338
- Don't hesitate to share your certificate image below on Twitter and Linkedin (you can tag me @ThomasSimonini and @huggingface) πŸ€— \n
339
- You can try to get a Certificate of Excellence if you pass 100% of the hands-on, don't hesitate to check which unit you didn't pass and update these models.
340
- """
341
-
342
- else:
343
- # Not pass yet
344
- certificate = Image.new("RGB", (100, 100), (255, 255, 255))
345
- pdf = "./fail.pdf"
346
-
347
- # Add a message
348
- message = """
349
- You didn't pass the minimum of 80% of the hands-on to get a certificate of completion. But don't be discouraged! \n
350
- Check below which units you need to do again to get your certificate πŸ’ͺ
351
- """
352
- print("return certificate")
353
- return certificate, message, pdf, pass_
354
-
355
 
356
- def generate_certificate(certificate_model, first_name, last_name):
357
- im = Image.open(certificate_model)
 
 
 
358
  d = ImageDraw.Draw(im)
359
 
360
  name_font = ImageFont.truetype("Quattrocento-Regular.ttf", 100)
361
  date_font = ImageFont.truetype("Quattrocento-Regular.ttf", 48)
362
-
363
- name = str(first_name) + " " + str(last_name)
364
- print("NAME", name)
365
-
366
- # Debug line name
367
- #d.line(((200, 740), (1800, 740)), "gray")
368
- #d.line(((1000, 0), (1000, 1400)), "gray")
369
-
370
- # Name
371
  d.text((1000, 740), name, fill="black", anchor="mm", font=name_font)
372
 
373
- # Debug line date
374
- #d.line(((1500, 0), (1500, 1400)), "gray")
 
 
375
 
376
- # Date of certification
377
  d.text((1480, 1170), str(date.today()), fill="black", anchor="mm", font=date_font)
378
 
379
-
380
- pdf = im.convert('RGB')
381
- pdf.save('certificate.pdf')
382
 
383
  return im, "./certificate.pdf"
384
 
385
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
387
- def add_certified_user(hf_username, first_name, last_name, pass_percentage):
388
- """
389
- Add the certified user to the database
390
- """
391
- print("ADD CERTIFIED USER")
392
- repo.git_pull()
393
- history = pd.read_csv(os.path.join(CERTIFIED_USERS_DIR, CERTIFIED_USERS_FILENAME))
394
-
395
- # Check if this hf_username is already in our dataset:
396
- check = history.loc[history['hf_username'] == hf_username]
397
- if not check.empty:
398
- history = history.drop(labels=check.index[0], axis=0)
399
-
400
- new_row = pd.DataFrame({'hf_username': hf_username, 'first_name': first_name, 'last_name': last_name, 'pass_percentage': pass_percentage, 'datetime': time.time()}, index=[0])
401
- history = pd.concat([new_row, history[:]]).reset_index(drop=True)
402
-
403
- history.to_csv(os.path.join(CERTIFIED_USERS_DIR, CERTIFIED_USERS_FILENAME), index=False)
404
- repo.push_to_hub(commit_message="Update certified users list")
405
-
406
-
407
- with gr.Blocks() as demo:
408
- gr.Markdown(f"""
409
- # Get your Deep Reinforcement Learning Certificate πŸŽ“
410
- The certification process is completely free:
411
-
412
- - To get a *certificate of completion*: you need to **pass 80% of the assignments**.
413
- - To get a *certificate of honors*: you need to **pass 100% of the assignments**.
414
-
415
- There's **no deadlines, the course is self-paced**.
416
-
417
- For more information about the certification process [check this](https://huggingface.co/deep-rl-course/communication/certification)
418
-
419
- Don’t hesitate to share your certificate on Twitter (tag me @ThomasSimonini and @huggingface) and on Linkedin.
420
- """)
421
-
422
- hf_username = gr.Textbox(placeholder="ThomasSimonini", label="Your Hugging Face Username (case sensitive)")
423
- first_name = gr.Textbox(placeholder="Jane", label="Your First Name")
424
- last_name = gr.Textbox(placeholder="Doe", label="Your Last Name")
425
- #email = gr.Textbox(placeholder="jane.[email protected]", label="Your Email (to receive your certificate)")
426
- check_progress_button = gr.Button(value="Check if I pass")
427
- output_text = gr.components.Textbox()
428
- with gr.Row(visible=True) as output_row:
429
- output_pdf = gr.File()
430
- output_img = gr.components.Image(type="pil")
431
- output_dataframe = gr.components.Dataframe(headers=["Pass?", "Unit", "Environment", "Baseline", "Your best result", "Your best model id"], datatype=["markdown", "markdown", "markdown", "number", "number", "markdown", "bool"]) #value= certification(hf_username, first_name, last_name),
432
- check_progress_button.click(fn=certification, inputs=[hf_username, first_name, last_name], outputs=[output_text, output_pdf, output_img, output_dataframe, output_row])#[output1, output2])
433
-
434
-
435
- demo.launch(debug=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import requests
3
+ from io import BytesIO
4
+ from datetime import date
 
 
5
 
6
+ import gradio as gr
7
+ from PIL import Image, ImageDraw, ImageFont
8
+ from huggingface_hub import whoami
9
 
10
+ from criteria import check_certification as check_certification_criteria
11
+ from org import join_finishers_org
12
 
 
 
 
13
 
14
+ def download_profile_picture(profile_url: str):
15
+ """Download profile picture from URL."""
16
+ response = requests.get(profile_url)
17
+ return Image.open(BytesIO(response.content))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ def generate_certificate(
21
+ certificate_path: str, first_name: str, last_name: str, profile_url: str
22
+ ):
23
+ """Generate certificate image and PDF."""
24
+ im = Image.open(certificate_path)
25
  d = ImageDraw.Draw(im)
26
 
27
  name_font = ImageFont.truetype("Quattrocento-Regular.ttf", 100)
28
  date_font = ImageFont.truetype("Quattrocento-Regular.ttf", 48)
29
+
30
+ name = f"{first_name} {last_name}"
31
+
32
+ # Capitalize first letter of each name
33
+ name = name.title()
34
+
35
+ # Add name
 
 
36
  d.text((1000, 740), name, fill="black", anchor="mm", font=name_font)
37
 
38
+ # Add profile picture just below the name
39
+ profile_img = download_profile_picture(profile_url)
40
+ profile_img = profile_img.resize((100, 100))
41
+ im.paste(im=profile_img, box=(350, 700))
42
 
43
+ # Add date
44
  d.text((1480, 1170), str(date.today()), fill="black", anchor="mm", font=date_font)
45
 
46
+ # Save PDF
47
+ pdf = im.convert("RGB")
48
+ pdf.save("certificate.pdf")
49
 
50
  return im, "./certificate.pdf"
51
 
52
 
53
+ def get_user_info(oauth_token):
54
+ """Get user info from HF token."""
55
+ if oauth_token is None:
56
+ return None, None, None, None
57
+ try:
58
+ user_info = whoami(oauth_token.token)
59
+ username = user_info["name"]
60
+ name_parts = user_info["fullname"].split(" ", 1)
61
+ first_name = name_parts[0]
62
+ last_name = name_parts[1] if len(name_parts) > 1 else ""
63
+ profile_url = user_info["avatarUrl"]
64
+ return username, first_name, last_name, profile_url
65
+ except:
66
+ return None, None, None, None
67
+
68
+
69
+ def create_linkedin_button(username: str) -> str:
70
+ """Create LinkedIn 'Add to Profile' button HTML."""
71
+ current_year = date.today().year
72
+ current_month = date.today().month
73
+
74
+ # URL encode the certificate URL
75
+ cert_url = "https://huggingface.co/agents-course-finishers"
76
+
77
+ linkedin_params = {
78
+ "startTask": "CERTIFICATION_NAME",
79
+ "name": "Hugging Face Course Certificate",
80
+ # "organizationId": "40479", # Hugging Face's LinkedIn Organization ID
81
+ "organizationName": "Hugging Face",
82
+ "issueYear": str(current_year),
83
+ "issueMonth": str(current_month),
84
+ "certUrl": cert_url,
85
+ "certId": username, # Using username as cert ID
86
+ }
87
+
88
+ # Build the LinkedIn button URL
89
+ base_url = "https://www.linkedin.com/profile/add?"
90
+ params = "&".join(
91
+ f"{k}={requests.utils.quote(v)}" for k, v in linkedin_params.items()
92
+ )
93
+ button_url = base_url + params
94
+
95
+ return f"""
96
+ <a href="{button_url}" target="_blank" style="display: block; margin-top: 20px; text-align: center;">
97
+ <img src="https://download.linkedin.com/desktop/add2profile/buttons/en_US.png"
98
+ alt="LinkedIn Add to Profile button">
99
+ </a>
100
+ """
101
+
102
 
103
+ def check_certification(token: gr.OAuthToken | None):
104
+ """Check certification status for logged-in user."""
105
+ if token is None:
106
+ gr.Warning("Please log in to Hugging Face before checking certification!")
107
+ return None, None, None, gr.Row.update(visible=False)
108
+
109
+ username, first_name, last_name, profile_url = get_user_info(token)
110
+ if not username:
111
+ return (
112
+ "Please login with your Hugging Face account to check certification status",
113
+ None,
114
+ None,
115
+ gr.Row.update(visible=False),
116
+ )
117
+
118
+ # Check certification criteria
119
+ result = check_certification_criteria(username)
120
+
121
+ # Generate certificate if passed
122
+ if result.passed and result.certificate_path:
123
+ certificate_img, pdf_path = generate_certificate(
124
+ certificate_path=result.certificate_path,
125
+ first_name=first_name,
126
+ last_name=last_name,
127
+ profile_url=profile_url,
128
+ )
129
+
130
+ # Add LinkedIn button for passed certificates
131
+ linkedin_button = create_linkedin_button(username)
132
+ result_message = f"{result.message}\n\n{linkedin_button}"
133
+ else:
134
+ certificate_img = None
135
+ pdf_path = None
136
+ result_message = result.message
137
+
138
+ return (
139
+ gr.update(visible=True, value=result_message, label="Grade"),
140
+ gr.update(visible=result.passed, value=pdf_path, label="Download Certificate"),
141
+ gr.update(visible=result.passed, value=certificate_img, label="Certificate"),
142
+ )
143
+
144
+
145
+ def create_gradio_interface():
146
+ """Create Gradio web interface with OAuth login."""
147
+ with gr.Blocks() as demo:
148
+ gr.Markdown("""
149
+ # Get your Hugging Face Course Certificate πŸŽ“
150
+ The certification process is completely free.
151
+
152
+ To receive your certificate, you need to **pass 80% of the quiz**.
153
+
154
+ There's **no deadlines, the course is self-paced**.
155
+
156
+ Don't hesitate to share your certificate on Twitter
157
+ (tag @huggingface) and on Linkedin.
158
+ """)
159
+
160
+ # Add login button
161
+ gr.LoginButton()
162
+
163
+ check_progress_button = gr.Button(value="Check My Progress")
164
+
165
+ output_text = gr.Markdown(visible=False, sanitize_html=False)
166
+ output_img = gr.Image(type="pil", visible=False)
167
+ output_pdf = gr.File(visible=False)
168
+
169
+ check_progress_button.click(
170
+ fn=check_certification,
171
+ outputs=[output_text, output_pdf, output_img],
172
+ ).then(
173
+ fn=join_finishers_org,
174
+ )
175
+
176
+ return demo
177
+
178
+
179
+ if __name__ == "__main__":
180
+ demo = create_gradio_interface()
181
+ demo.launch(debug=True)
certificate.pdf ADDED
Binary file (193 kB). View file
 
certificate_models/certificate-excellence.png DELETED
Binary file (155 kB)
 
certificate_models/{certificate-completion.png β†’ certificate.png} RENAMED
File without changes
criteria.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Dict, List, Optional, NamedTuple, Tuple
3
+ from datetime import datetime
4
+
5
+ from datasets import load_dataset
6
+ from huggingface_hub import HfApi
7
+
8
+ # Environment variables and constants
9
+ EXAM_DATASET_ID = os.getenv(
10
+ "EXAM_DATASET_ID", "agents-course/unit_1_quiz_student_responses"
11
+ )
12
+ CERTIFICATE_MODELS_DIR = os.getenv("CERTIFICATE_MODELS_DIR", "./certificate_models")
13
+ CERTIFICATE_PATH = os.path.join(CERTIFICATE_MODELS_DIR, "certificate.png")
14
+
15
+ PASSING_THRESHOLD = float(os.getenv("PASSING_THRESHOLD", "0.8")) # 80%
16
+ MIN_QUESTIONS = int(os.getenv("MIN_QUESTIONS", "1"))
17
+
18
+
19
+ class CertificateResult(NamedTuple):
20
+ """Stores the result of a certificate check"""
21
+
22
+ message: str
23
+ certificate_path: Optional[str]
24
+ pass_percentage: float
25
+ passed: bool
26
+ results_df: Optional[object] = None
27
+
28
+
29
+ def get_user_results(username: str) -> List[Dict]:
30
+ """
31
+ Get user's quiz results from the dataset.
32
+
33
+ Args:
34
+ username: The Hugging Face username to check
35
+
36
+ Returns:
37
+ List of user's quiz results
38
+ """
39
+ try:
40
+ ds = load_dataset(EXAM_DATASET_ID, split="train")
41
+
42
+ # Filter for this user's results
43
+ user_results = ds.filter(lambda x: x["username"] == username)
44
+
45
+ results = user_results.to_list()
46
+ print(f"Found {len(results)} results for user {username}")
47
+ return results
48
+
49
+ except Exception as e:
50
+ print(f"Error in get_user_results: {str(e)}")
51
+ raise
52
+
53
+
54
+ def calculate_pass_percentage(results: List[Dict]) -> Tuple[float, int]:
55
+ """
56
+ Calculate the user's pass percentage and number of questions from their results.
57
+
58
+ The dataset structure has:
59
+ - is_correct: bool indicating if answer was correct
60
+ - grade: float64 indicating overall grade
61
+ - datetime: string of attempt timestamp
62
+
63
+ Args:
64
+ results: List of quiz results
65
+
66
+ Returns:
67
+ Tuple of (highest grade achieved, number of questions answered)
68
+ """
69
+ try:
70
+ if not results:
71
+ return 0.0, 0
72
+
73
+ # Group results by datetime to get distinct attempts
74
+ attempts = {}
75
+ for result in results:
76
+ timestamp = result["datetime"]
77
+ if timestamp not in attempts:
78
+ attempts[timestamp] = {
79
+ "correct": 0,
80
+ "total": 0,
81
+ "grade": result.get("grade", 0.0),
82
+ }
83
+
84
+ attempts[timestamp]["total"] += 1
85
+ if result["is_correct"]:
86
+ attempts[timestamp]["correct"] += 1
87
+
88
+ # Find the best attempt
89
+ best_attempt = max(
90
+ attempts.values(),
91
+ key=lambda x: x["grade"]
92
+ if x["grade"] is not None
93
+ else (x["correct"] / x["total"] if x["total"] > 0 else 0),
94
+ )
95
+
96
+ # If grade is available, use it; otherwise calculate from correct/total
97
+ if best_attempt["grade"] is not None and best_attempt["grade"] > 0:
98
+ pass_percentage = float(best_attempt["grade"])
99
+ else:
100
+ pass_percentage = (
101
+ best_attempt["correct"] / best_attempt["total"]
102
+ if best_attempt["total"] > 0
103
+ else 0.0
104
+ )
105
+
106
+ return pass_percentage, best_attempt["total"]
107
+
108
+ except Exception as e:
109
+ print(f"Error in calculate_pass_percentage: {str(e)}")
110
+ raise
111
+
112
+
113
+ def has_passed(pass_percentage: float, num_questions: int) -> bool:
114
+ """
115
+ Check if user has passed based on percentage and minimum questions.
116
+
117
+ Args:
118
+ pass_percentage: User's highest quiz score
119
+ num_questions: Number of questions answered
120
+
121
+ Returns:
122
+ Boolean indicating if user passed
123
+ """
124
+ return pass_percentage >= PASSING_THRESHOLD and num_questions >= MIN_QUESTIONS
125
+
126
+
127
+ def get_certificate_result(
128
+ pass_percentage: float, num_questions: int
129
+ ) -> CertificateResult:
130
+ """
131
+ Determine if user passed and create appropriate message.
132
+
133
+ Args:
134
+ pass_percentage: User's highest quiz score
135
+ num_questions: Number of questions answered
136
+
137
+ Returns:
138
+ CertificateResult with pass status and details
139
+ """
140
+ passed = has_passed(pass_percentage, num_questions)
141
+
142
+ if passed:
143
+ return CertificateResult(
144
+ message="""
145
+ Congratulations, you successfully completed the course! πŸŽ‰ \n
146
+ You can download your certificate below ⬇️ \n
147
+ You are now an <a href="https://huggingface.co/agents-course-finishers">Agent Course Finisher</a>!
148
+ """,
149
+ certificate_path=CERTIFICATE_PATH,
150
+ pass_percentage=pass_percentage,
151
+ passed=True,
152
+ )
153
+ else:
154
+ return CertificateResult(
155
+ message="""
156
+ You haven't completed all the requirements yet. \n
157
+ Keep trying! πŸ’ͺ
158
+ """,
159
+ certificate_path=None,
160
+ pass_percentage=pass_percentage,
161
+ passed=False,
162
+ )
163
+
164
+
165
+ def check_certification(username: str) -> CertificateResult:
166
+ """
167
+ Check if a user has completed the certification requirements.
168
+
169
+ Args:
170
+ username: The Hugging Face username to check
171
+
172
+ Returns:
173
+ CertificateResult containing pass status and details
174
+ """
175
+ try:
176
+ # Get user's quiz results
177
+ results = get_user_results(username)
178
+ if not results:
179
+ return CertificateResult(
180
+ message="No quiz results found. Please complete the quiz first.",
181
+ certificate_path=None,
182
+ pass_percentage=0.0,
183
+ passed=False,
184
+ )
185
+
186
+ # Calculate pass percentage and get appropriate certificate result
187
+ pass_percentage, num_questions = calculate_pass_percentage(results)
188
+ return get_certificate_result(pass_percentage, num_questions)
189
+
190
+ except Exception as e:
191
+ error_msg = """
192
+ There was an error checking your certification status.
193
+ Please try again later or contact support if the issue persists.
194
+ """
195
+ return CertificateResult(
196
+ message=error_msg,
197
+ certificate_path=None,
198
+ pass_percentage=0.0,
199
+ passed=False,
200
+ )
org.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+
4
+ # Remove unused import
5
+ # Break long URL into multiple lines using parentheses
6
+ JOIN_ORG_URL = (
7
+ os.getenv("JOIN_ORG_URL")
8
+ or "https://huggingface.co/organizations/agents-course-finishers/share/"
9
+ "XmxAybhNLOogLeBzZnBUVbazpTlDETqFId"
10
+ )
11
+
12
+
13
+ def join_finishers_org():
14
+ """Join the finishers organization using the provided auth cookie.
15
+
16
+ Args:
17
+ user_auth_cookie (str): User's authentication cookie
18
+
19
+ Returns:
20
+ bool: True if join was successful, False otherwise
21
+ """
22
+
23
+ # If you need to include a cookie for authentication, you can do so here
24
+ # Otherwise, you can leave the headers empty
25
+ headers = {
26
+ # "Cookie": "session=abc123def456ghi789jkl012mno345pqr678" # Uncomment and add your cookie if needed
27
+ }
28
+
29
+ # Send the POST request
30
+ response = requests.post(url=JOIN_ORG_URL, headers=headers)
31
+
32
+ # Check the response status code
33
+ if response.status_code == 200:
34
+ print("Successfully joined the organization!")
35
+ else:
36
+ print(f"Failed to join the organization. Status code: {response.status_code}")
37
+
38
+
pyproject.toml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "certification-app"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "datasets>=3.2.0",
9
+ "gradio[oauth]==5.15.0",
10
+ "huggingface-hub>=0.28.1",
11
+ "pandas>=2.2.3",
12
+ "pillow>=11.1.0",
13
+ ]
utils.py DELETED
@@ -1,16 +0,0 @@
1
- # Based on Omar Sanseviero work
2
- # Make model clickable link
3
- def make_clickable_model(model_name):
4
- # remove user from model name
5
- model_name_show = ' '.join(model_name.split('/')[1:])
6
-
7
- link = "https://huggingface.co/" + model_name
8
- return f'<a target="_blank" href="{link}">{model_name_show}</a>'
9
-
10
- def pass_emoji(passed):
11
- print("PASSED", passed)
12
- if passed is True:
13
- passed = "βœ…"
14
- else:
15
- passed = "❌"
16
- return passed