burtenshaw's picture
burtenshaw HF Staff
Fix mega bug in grade submission
84e6742 verified
raw
history blame
10.4 kB
import os
from datetime import datetime
import random
import pandas as pd
from huggingface_hub import HfApi, hf_hub_download, Repository
from huggingface_hub.repocard import metadata_load
import gradio as gr
from datasets import load_dataset, Dataset
from huggingface_hub import whoami
EXAM_DATASET_ID = os.getenv("EXAM_DATASET_ID") or "agents-course/unit_1_quiz"
EXAM_MAX_QUESTIONS = os.getenv("EXAM_MAX_QUESTIONS") or 10
EXAM_PASSING_SCORE = os.getenv("EXAM_PASSING_SCORE") or 0.8
ds = load_dataset(EXAM_DATASET_ID, split="train")
DATASET_REPO_URL = "https://huggingface.co/datasets/agents-course/certificates"
CERTIFIED_USERS_FILENAME = "certified_students.csv"
CERTIFIED_USERS_DIR = "certificates"
repo = Repository(
local_dir=CERTIFIED_USERS_DIR,
clone_from=DATASET_REPO_URL,
use_auth_token=os.getenv("HF_TOKEN"),
)
# Convert dataset to a list of dicts and randomly sort
quiz_data = ds.to_pandas().to_dict("records")
random.shuffle(quiz_data)
# Limit to max questions if specified
if EXAM_MAX_QUESTIONS:
quiz_data = quiz_data[: int(EXAM_MAX_QUESTIONS)]
def on_user_logged_in(token: gr.OAuthToken | None):
"""
If the user has a valid token, show Start button.
Otherwise, keep the login button visible.
"""
if token is not None:
return [
gr.update(visible=False), # login button visibility
gr.update(visible=True), # start button visibility
gr.update(visible=False), # next button visibility
gr.update(visible=False), # submit button visibility
"", # question text
[], # radio choices (empty list = no choices)
"Click 'Start' to begin the quiz", # status message
0, # question_idx
[], # user_answers
"", # final_markdown content
token, # user token
]
else:
return [
gr.update(visible=True), # login button visibility
gr.update(visible=False), # start button visibility
gr.update(visible=False), # next button visibility
gr.update(visible=False), # submit button visibility
"", # question text
[], # radio choices
"", # status message
0, # question_idx
[], # user_answers
"", # final_markdown content
None, # no token
]
def add_certified_user(hf_username, pass_percentage, submission_time):
"""
Add the certified user to the database
"""
print("ADD CERTIFIED USER")
repo.git_pull()
history = pd.read_csv(os.path.join(CERTIFIED_USERS_DIR, CERTIFIED_USERS_FILENAME))
# Check if this hf_username is already in our dataset:
check = history.loc[history["hf_username"] == hf_username]
if not check.empty:
history = history.drop(labels=check.index[0], axis=0)
new_row = pd.DataFrame(
{
"hf_username": hf_username,
"pass_percentage": pass_percentage,
"datetime": submission_time,
},
index=[0],
)
history = pd.concat([new_row, history[:]]).reset_index(drop=True)
history.to_csv(
os.path.join(CERTIFIED_USERS_DIR, CERTIFIED_USERS_FILENAME), index=False
)
repo.push_to_hub(commit_message="Update certified users list")
def push_results_to_hub(user_answers, token: gr.OAuthToken | None):
"""
Create a new dataset from user_answers and push it to the Hub.
Calculates grade and checks against passing threshold.
"""
if token is None:
gr.Warning("Please log in to Hugging Face before pushing!")
return
# Calculate grade
correct_count = sum(1 for answer in user_answers if answer["is_correct"])
total_questions = len(user_answers)
grade = correct_count / total_questions if total_questions > 0 else 0
if grade < float(EXAM_PASSING_SCORE):
gr.Warning(
f"Score {grade:.1%} below passing threshold of {float(EXAM_PASSING_SCORE):.1%}"
)
return f"You scored {grade:.1%}. Please try again to achieve at least {float(EXAM_PASSING_SCORE):.1%}"
gr.Info("Submitting answers to the Hub. Please wait...", duration=2)
user_info = whoami(token=token.token)
repo_id = f"{EXAM_DATASET_ID}_student_responses"
submission_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
new_ds = Dataset.from_list([{"is_correct":row["is_correct"]} for row in user_answers])
new_ds = new_ds.map(
lambda x: {
"username": user_info["name"],
"datetime": submission_time,
"grade": grade,
}
)
sanitized_name = user_info["name"].replace("-", "000")
new_ds.push_to_hub(repo_id=repo_id, split=sanitized_name)
# I'm adding a csv version
# The idea, if the user passed, we create a simple row in a csv
print("ADD CERTIFIED USER")
# Add this user to our database
add_certified_user(sanitized_name, grade, submission_time)
return f"Your responses have been submitted to the Hub! Final grade: {grade:.1%}"
def handle_quiz(
question_idx,
user_answers,
selected_answer,
is_start,
token: gr.OAuthToken | None,
profile: gr.OAuthProfile | None,
):
"""
Handle quiz state transitions and store answers
"""
if token is None or profile is None:
gr.Warning("Please log in to Hugging Face before starting the quiz!")
return
if not is_start and question_idx < len(quiz_data):
current_q = quiz_data[question_idx]
correct_reference = current_q["correct_answer"]
correct_reference = f"answer_{correct_reference}".lower()
is_correct = selected_answer == current_q[correct_reference]
user_answers.append(
{
"question": current_q["question"],
"selected_answer": selected_answer,
"correct_answer": current_q[correct_reference],
"is_correct": is_correct,
"correct_reference": correct_reference,
}
)
question_idx += 1
if question_idx >= len(quiz_data):
correct_count = sum(1 for answer in user_answers if answer["is_correct"])
grade = correct_count / len(user_answers)
results_text = (
f"**Quiz Complete!**\n\n"
f"Your score: {grade:.1%}\n"
f"Passing score: {float(EXAM_PASSING_SCORE):.1%}\n\n"
)
return [
"", # question_text
gr.update(choices=[], visible=False), # hide radio choices
f"{'🎉 Passed! Click now on ✅ Submit to save your exam score!' if grade >= float(EXAM_PASSING_SCORE) else '❌ Did not pass'}",
question_idx,
user_answers,
gr.update(visible=False), # start button visibility
gr.update(visible=False), # next button visibility
gr.update(visible=True), # submit button visibility
results_text, # final results text
]
# Show next question
q = quiz_data[question_idx]
return [
f"## Question {question_idx + 1} \n### {q['question']}", # question text
gr.update( # properly update radio choices
choices=[q["answer_a"], q["answer_b"], q["answer_c"], q["answer_d"]],
value=None,
visible=True,
),
"Select an answer and click 'Next' to continue.",
question_idx,
user_answers,
gr.update(visible=False), # start button visibility
gr.update(visible=True), # next button visibility
gr.update(visible=False), # submit button visibility
"", # clear final markdown
]
def success_message(response):
# response is whatever push_results_to_hub returned
return f"{response}\n\n**Success!**"
with gr.Blocks() as demo:
demo.title = f"Dataset Quiz for {EXAM_DATASET_ID}"
# State variables
question_idx = gr.State(value=0)
user_answers = gr.State(value=[])
user_token = gr.State(value=None)
with gr.Row(variant="compact"):
gr.Markdown(f"## Welcome to the {EXAM_DATASET_ID} Quiz")
with gr.Row(variant="compact"):
gr.Markdown(
"- Log in first, then click 'Start' to begin. \n- Answer each question, click 'Next' \n- click 'Submit' to publish your results to the Hugging Face Hub."
)
with gr.Row(variant="panel"):
question_text = gr.Markdown("")
radio_choices = gr.Radio(
choices=[], label="Your Answer", scale=1.5, visible=False
)
with gr.Row(variant="compact"):
status_text = gr.Markdown("")
final_markdown = gr.Markdown("")
with gr.Row(variant="compact"):
login_btn = gr.LoginButton(visible=True)
start_btn = gr.Button("Start ⏭️", visible=True)
next_btn = gr.Button("Next ⏭️", visible=False)
submit_btn = gr.Button("Submit ✅", visible=False)
# Wire up the event handlers
login_btn.click(
fn=on_user_logged_in,
inputs=None,
outputs=[
login_btn,
start_btn,
next_btn,
submit_btn,
question_text,
radio_choices,
status_text,
question_idx,
user_answers,
final_markdown,
user_token,
],
)
start_btn.click(
fn=handle_quiz,
inputs=[question_idx, user_answers, gr.State(""), gr.State(True)],
outputs=[
question_text,
radio_choices,
status_text,
question_idx,
user_answers,
start_btn,
next_btn,
submit_btn,
final_markdown,
],
)
next_btn.click(
fn=handle_quiz,
inputs=[question_idx, user_answers, radio_choices, gr.State(False)],
outputs=[
question_text,
radio_choices,
status_text,
question_idx,
user_answers,
start_btn,
next_btn,
submit_btn,
final_markdown,
],
)
submit_btn.click(fn=push_results_to_hub, inputs=[user_answers])
if __name__ == "__main__":
# Note: If testing locally, you'll need to run `huggingface-cli login` or set HF_TOKEN
# environment variable for the login to work locally.
demo.launch()