Download app.py from teohteiktoe/ntu-programmingFoundation-June2026: direct link, hf CLI and curl.
- Browser
- Download file 20.8 kB
-
https://huggingface.co/spaces/teohteiktoe/ntu-programmingFoundation-June2026/resolve/main/app.py
- Command line
-
hf download hf://spaces/teohteiktoe/ntu-programmingFoundation-June2026/app.py
-
curl -L -o app.py https://huggingface.co/spaces/teohteiktoe/ntu-programmingFoundation-June2026/resolve/main/app.py
20.8 kB
| import gradio as gr | |
| import io | |
| import json | |
| import random | |
| import contextlib | |
| import traceback | |
| from datetime import datetime | |
| APP_TITLE = "PCEP Python Learning Academy" | |
| PASS_MARK = 70 | |
| LESSONS = { | |
| "1. Python Fundamentals": { | |
| "block": "Computer Programming and Python Fundamentals", | |
| "weight": "18%", | |
| "content": """ | |
| ### Learning goals | |
| - Understand what programming is. | |
| - Understand interpreter, syntax, comments, indentation, variables, and literals. | |
| - Write your first Python statements. | |
| ### Key explanation | |
| Python is an interpreted programming language. You write instructions, and the Python interpreter runs them line by line. | |
| Python uses indentation to show code structure. This is important for `if`, `for`, `while`, and functions. | |
| ### Example | |
| ```python | |
| # This is a comment | |
| print("Hello, Python!") | |
| age = 20 | |
| print(age) | |
| ``` | |
| ### Common exam points | |
| - Python is case-sensitive. | |
| - Comments start with `#`. | |
| - Indentation is part of Python syntax. | |
| - Variable names should be meaningful and follow Python naming rules. | |
| """, | |
| "starter_code": "print('Hello, PCEP learner!')\nname = 'Student'\nprint('Welcome', name)", | |
| }, | |
| "2. Variables and Data Types": { | |
| "block": "Computer Programming and Python Fundamentals", | |
| "weight": "18%", | |
| "content": """ | |
| ### Learning goals | |
| - Use variables to store values. | |
| - Recognize integers, floats, strings, and Booleans. | |
| - Convert values using `int()`, `float()`, and `str()`. | |
| ### Example | |
| ```python | |
| age = 25 # int | |
| price = 9.99 # float | |
| name = "Ali" # str | |
| passed = True # bool | |
| print(type(age)) | |
| print(int("10") + 5) | |
| ``` | |
| ### Common exam points | |
| - `10` is an integer. | |
| - `10.0` is a float. | |
| - `"10"` is a string. | |
| - `True` and `False` are Boolean values. | |
| """, | |
| "starter_code": "x = '10'\ny = 5\nprint(int(x) + y)", | |
| }, | |
| "3. Operators": { | |
| "block": "Computer Programming and Python Fundamentals", | |
| "weight": "18%", | |
| "content": """ | |
| ### Learning goals | |
| - Use arithmetic, comparison, logical, assignment, and string operators. | |
| - Understand operator priority. | |
| ### Example | |
| ```python | |
| print(2 + 3) | |
| print(2 ** 3) | |
| print(7 // 2) | |
| print(7 % 2) | |
| print(5 > 3 and 2 < 4) | |
| print("Py" + "thon") | |
| ``` | |
| ### Common exam points | |
| - `**` means exponentiation. | |
| - `//` means floor division. | |
| - `%` gives the remainder. | |
| - `and`, `or`, and `not` are Boolean operators. | |
| """, | |
| "starter_code": "a = 7\nb = 2\nprint(a // b)\nprint(a % b)", | |
| }, | |
| "4. Input and Output": { | |
| "block": "Computer Programming and Python Fundamentals", | |
| "weight": "18%", | |
| "content": """ | |
| ### Learning goals | |
| - Use `print()` for output. | |
| - Use `input()` for user input. | |
| - Convert input values when needed. | |
| ### Example | |
| ```python | |
| name = input("Enter your name: ") | |
| age = int(input("Enter your age: ")) | |
| print("Hello", name) | |
| print("Next year you will be", age + 1) | |
| ``` | |
| ### Common exam points | |
| - `input()` always returns a string. | |
| - Use `int()` or `float()` before doing numeric calculation. | |
| - `print()` supports `sep=` and `end=`. | |
| """, | |
| "starter_code": "print('Python', 'PCEP', sep=' - ')\nprint('Hello', end=' ')\nprint('World')", | |
| }, | |
| "5. Conditional Statements": { | |
| "block": "Control Flow – Conditional Blocks and Loops", | |
| "weight": "29%", | |
| "content": """ | |
| ### Learning goals | |
| - Use `if`, `elif`, and `else`. | |
| - Build Boolean expressions. | |
| - Understand nested conditions. | |
| ### Example | |
| ```python | |
| score = 75 | |
| if score >= 70: | |
| print("Pass") | |
| elif score >= 50: | |
| print("Borderline") | |
| else: | |
| print("Fail") | |
| ``` | |
| ### Common exam points | |
| - Conditions must evaluate to `True` or `False`. | |
| - `elif` means else-if. | |
| - Indentation is required inside each block. | |
| """, | |
| "starter_code": "score = 75\nif score >= 70:\n print('Pass')\nelse:\n print('Try again')", | |
| }, | |
| "6. Loops": { | |
| "block": "Control Flow – Conditional Blocks and Loops", | |
| "weight": "29%", | |
| "content": """ | |
| ### Learning goals | |
| - Use `while` loops and `for` loops. | |
| - Use `range()`. | |
| - Understand `break` and `continue`. | |
| ### Example | |
| ```python | |
| for i in range(5): | |
| print(i) | |
| count = 0 | |
| while count < 3: | |
| print(count) | |
| count += 1 | |
| ``` | |
| ### Common exam points | |
| - `range(5)` produces 0, 1, 2, 3, 4. | |
| - `break` exits a loop. | |
| - `continue` skips to the next iteration. | |
| """, | |
| "starter_code": "for i in range(1, 6):\n print(i)", | |
| }, | |
| "7. Lists, Tuples, Dictionaries, and Strings": { | |
| "block": "Data Collections – Tuples, Dictionaries, Lists, and Strings", | |
| "weight": "25%", | |
| "content": """ | |
| ### Learning goals | |
| - Use lists, tuples, dictionaries, and strings. | |
| - Use indexing and slicing. | |
| - Understand mutability. | |
| ### Example | |
| ```python | |
| items = ["pen", "book", "bag"] | |
| print(items[0]) | |
| items.append("pencil") | |
| person = {"name": "Ali", "age": 20} | |
| print(person["name"]) | |
| word = "Python" | |
| print(word[0]) | |
| print(word[1:4]) | |
| ``` | |
| ### Common exam points | |
| - Lists are mutable. | |
| - Tuples are immutable. | |
| - Dictionary values are accessed using keys. | |
| - Strings are immutable. | |
| """, | |
| "starter_code": "numbers = [3, 1, 2]\nnumbers.append(4)\nprint(sorted(numbers))\nprint(numbers[0:2])", | |
| }, | |
| "8. Functions": { | |
| "block": "Functions and Exceptions", | |
| "weight": "28%", | |
| "content": """ | |
| ### Learning goals | |
| - Define and call functions. | |
| - Use parameters and return values. | |
| - Understand `None`. | |
| ### Example | |
| ```python | |
| def add(a, b): | |
| return a + b | |
| result = add(3, 4) | |
| print(result) | |
| ``` | |
| ### Common exam points | |
| - A function is defined with `def`. | |
| - `return` sends a value back to the caller. | |
| - A function without `return` returns `None`. | |
| """, | |
| "starter_code": "def greet(name):\n return 'Hello ' + name\n\nprint(greet('PCEP'))", | |
| }, | |
| "9. Exceptions": { | |
| "block": "Functions and Exceptions", | |
| "weight": "28%", | |
| "content": """ | |
| ### Learning goals | |
| - Understand runtime errors. | |
| - Use `try` and `except`. | |
| - Handle common exceptions safely. | |
| ### Example | |
| ```python | |
| try: | |
| number = int("abc") | |
| except ValueError: | |
| print("Invalid number") | |
| ``` | |
| ### Common exam points | |
| - `ValueError` happens when a value has the wrong format. | |
| - `ZeroDivisionError` happens when dividing by zero. | |
| - Exceptions prevent the whole program from crashing if handled properly. | |
| """, | |
| "starter_code": "try:\n print(10 / 0)\nexcept ZeroDivisionError:\n print('Cannot divide by zero')", | |
| }, | |
| "10. Mock Exam": { | |
| "block": "All PCEP blocks", | |
| "weight": "100%", | |
| "content": """ | |
| ### Learning goals | |
| - Review all major PCEP topics. | |
| - Practise exam-style thinking. | |
| - Identify weak areas before the actual exam. | |
| Use the **Mock Exam** tab to attempt a 30-question quiz. | |
| """, | |
| "starter_code": "print('Ready for the mock exam!')", | |
| }, | |
| } | |
| QUESTION_BANK = [ | |
| {"topic":"Fundamentals","q":"Which symbol starts a single-line comment in Python?","choices":["//","#","--","/*"],"answer":"#","explain":"Python single-line comments start with #."}, | |
| {"topic":"Fundamentals","q":"Python uses indentation mainly to show what?","choices":["Code block structure","File size","Program speed","Package version"],"answer":"Code block structure","explain":"Indentation defines blocks such as if, loops, and functions."}, | |
| {"topic":"Variables","q":"What is the type of 3.14?","choices":["int","float","str","bool"],"answer":"float","explain":"Numbers with decimal points are usually floats."}, | |
| {"topic":"Variables","q":"What does input() return?","choices":["int","float","str","bool"],"answer":"str","explain":"input() always returns a string unless you convert it."}, | |
| {"topic":"Operators","q":"What is the result of 7 // 2?","choices":["3","3.5","1","4"],"answer":"3","explain":"// performs floor division."}, | |
| {"topic":"Operators","q":"What is the result of 2 ** 3?","choices":["5","6","8","9"],"answer":"8","explain":"** means exponentiation."}, | |
| {"topic":"Operators","q":"Which operator gives the remainder?","choices":["/","//","%","**"],"answer":"%","explain":"% is the modulo operator."}, | |
| {"topic":"Input/Output","q":"Which function displays output?","choices":["show()","print()","display()","output()"],"answer":"print()","explain":"print() displays output to the console."}, | |
| {"topic":"Conditionals","q":"Which keyword checks another condition after if fails?","choices":["elseif","elif","else if","then"],"answer":"elif","explain":"Python uses elif for else-if logic."}, | |
| {"topic":"Conditionals","q":"What is the result of 5 > 3?","choices":["True","False","5","3"],"answer":"True","explain":"5 is greater than 3."}, | |
| {"topic":"Loops","q":"What values are produced by range(3)?","choices":["1,2,3","0,1,2","0,1,2,3","3 only"],"answer":"0,1,2","explain":"range(3) starts at 0 and stops before 3."}, | |
| {"topic":"Loops","q":"Which keyword exits a loop immediately?","choices":["stop","exit","break","continue"],"answer":"break","explain":"break exits the nearest loop."}, | |
| {"topic":"Loops","q":"Which keyword skips the current loop iteration?","choices":["skip","continue","break","pass"],"answer":"continue","explain":"continue jumps to the next iteration."}, | |
| {"topic":"Lists","q":"Which method adds an item to the end of a list?","choices":["add()","append()","push()","insert_end()"],"answer":"append()","explain":"append() adds an item to the end of a list."}, | |
| {"topic":"Lists","q":"What is numbers[0] for numbers = [10, 20, 30]?","choices":["10","20","30","Error"],"answer":"10","explain":"Python indexing starts at 0."}, | |
| {"topic":"Tuples","q":"Which collection is immutable?","choices":["list","tuple","dictionary","set item"],"answer":"tuple","explain":"Tuples cannot be changed after creation."}, | |
| {"topic":"Dictionaries","q":"How do you access the value for key 'name' in d?","choices":["d.name","d['name']","d(name)","d->name"],"answer":"d['name']","explain":"Dictionary values are accessed using keys in square brackets."}, | |
| {"topic":"Strings","q":"What is 'Python'[0]?","choices":["P","y","Python","Error"],"answer":"P","explain":"The first character is at index 0."}, | |
| {"topic":"Strings","q":"Are Python strings mutable?","choices":["Yes","No","Only short strings","Only numeric strings"],"answer":"No","explain":"Strings are immutable."}, | |
| {"topic":"Functions","q":"Which keyword defines a function?","choices":["function","def","fun","define"],"answer":"def","explain":"Python uses def to define functions."}, | |
| {"topic":"Functions","q":"What does a function return if it has no return statement?","choices":["0","False","None","Empty string"],"answer":"None","explain":"A function without return returns None."}, | |
| {"topic":"Functions","q":"Which keyword sends a result back from a function?","choices":["send","return","yield only","break"],"answer":"return","explain":"return gives a result back to the caller."}, | |
| {"topic":"Exceptions","q":"Which exception often occurs when int('abc') is executed?","choices":["TypeError","ValueError","ZeroDivisionError","NameError"],"answer":"ValueError","explain":"'abc' cannot be converted to an integer."}, | |
| {"topic":"Exceptions","q":"Which exception occurs for 10 / 0?","choices":["ValueError","ZeroDivisionError","IndexError","KeyError"],"answer":"ZeroDivisionError","explain":"Division by zero raises ZeroDivisionError."}, | |
| {"topic":"Operators","q":"What is True and False?","choices":["True","False","None","Error"],"answer":"False","explain":"and returns True only when both sides are True."}, | |
| {"topic":"Operators","q":"What is not True?","choices":["True","False","None","Error"],"answer":"False","explain":"not reverses the Boolean value."}, | |
| {"topic":"Lists","q":"What does len([1, 2, 3]) return?","choices":["2","3","4","Error"],"answer":"3","explain":"len() returns the number of items."}, | |
| {"topic":"Dictionaries","q":"Which method returns dictionary keys?","choices":["keys()","items()","values()","index()"],"answer":"keys()","explain":"keys() returns the dictionary keys."}, | |
| {"topic":"Strings","q":"What is 'abc' + 'def'?","choices":["abcdef","abc def","Error","6"],"answer":"abcdef","explain":"+ concatenates strings."}, | |
| {"topic":"Variables","q":"Which is a valid Python variable name?","choices":["2name","first_name","first-name","class"],"answer":"first_name","explain":"Variable names cannot start with a number, contain hyphen, or use keywords."}, | |
| {"topic":"Fundamentals","q":"Which statement is case-sensitive in Python?","choices":["Name and name are different","Name and name are same","Only functions are case-sensitive","Only strings are case-sensitive"],"answer":"Name and name are different","explain":"Python identifiers are case-sensitive."}, | |
| {"topic":"Input/Output","q":"What does print('A', 'B', sep='-') display?","choices":["A B","A-B","AB","A, B"],"answer":"A-B","explain":"sep='-' places a hyphen between output items."}, | |
| ] | |
| progress = {"completed_lessons": [], "quiz_scores": {}, "exam_history": []} | |
| current_exam = [] | |
| def get_lesson(topic): | |
| data = LESSONS[topic] | |
| return f""" | |
| # {topic} | |
| **PCEP block:** {data['block']} | |
| **Approximate exam weight:** {data['weight']} | |
| {data['content']} | |
| """, data["starter_code"] | |
| def mark_complete(topic): | |
| if topic not in progress["completed_lessons"]: | |
| progress["completed_lessons"].append(topic) | |
| return dashboard() | |
| def run_code(code): | |
| safe_builtins = { | |
| "print": print, "range": range, "len": len, "int": int, "float": float, "str": str, | |
| "bool": bool, "list": list, "tuple": tuple, "dict": dict, "set": set, "sum": sum, | |
| "min": min, "max": max, "sorted": sorted, "type": type, "enumerate": enumerate, | |
| "abs": abs, "round": round, "True": True, "False": False, "None": None | |
| } | |
| env = {"__builtins__": safe_builtins} | |
| output = io.StringIO() | |
| try: | |
| with contextlib.redirect_stdout(output): | |
| exec(code, env, env) | |
| result = output.getvalue() | |
| return result if result else "Code ran successfully with no printed output." | |
| except Exception: | |
| return "Error:\n" + traceback.format_exc(limit=2) | |
| def get_practice_question(topic_filter): | |
| pool = QUESTION_BANK if topic_filter == "All" else [q for q in QUESTION_BANK if q["topic"] == topic_filter] | |
| if not pool: | |
| pool = QUESTION_BANK | |
| q = random.choice(pool) | |
| return q["q"], gr.update(choices=q["choices"], value=None), json.dumps(q) | |
| def check_practice_answer(choice, q_json): | |
| if not q_json: | |
| return "Please generate a question first." | |
| q = json.loads(q_json) | |
| if choice == q["answer"]: | |
| progress["quiz_scores"].setdefault(q["topic"], []).append(1) | |
| return f"✅ Correct. {q['explain']}" | |
| progress["quiz_scores"].setdefault(q["topic"], []).append(0) | |
| return f"❌ Incorrect. Correct answer: **{q['answer']}**. {q['explain']}" | |
| def start_exam(): | |
| global current_exam | |
| current_exam = random.sample(QUESTION_BANK, min(30, len(QUESTION_BANK))) | |
| outputs = [] | |
| for i, q in enumerate(current_exam): | |
| outputs.append(gr.update(label=f"Q{i+1}. {q['q']}", choices=q["choices"], value=None, visible=True)) | |
| return outputs + ["Exam started. Answer all questions, then click Submit Exam."] | |
| def submit_exam(*answers): | |
| if not current_exam: | |
| return "Please start an exam first.", dashboard() | |
| correct = 0 | |
| topic_total = {} | |
| topic_correct = {} | |
| review = [] | |
| for i, q in enumerate(current_exam): | |
| ans = answers[i] | |
| topic_total[q["topic"]] = topic_total.get(q["topic"], 0) + 1 | |
| if ans == q["answer"]: | |
| correct += 1 | |
| topic_correct[q["topic"]] = topic_correct.get(q["topic"], 0) + 1 | |
| else: | |
| review.append(f"Q{i+1}: {q['q']}\nYour answer: {ans}\nCorrect answer: {q['answer']}\nExplanation: {q['explain']}") | |
| score = round(correct / len(current_exam) * 100, 1) | |
| status = "PASS" if score >= PASS_MARK else "REVIEW NEEDED" | |
| progress["exam_history"].append({"date": datetime.now().strftime("%Y-%m-%d %H:%M"), "score": score}) | |
| weak = [] | |
| for topic, total in topic_total.items(): | |
| pct = round(topic_correct.get(topic, 0) / total * 100) | |
| if pct < 70: | |
| weak.append(f"{topic}: {pct}%") | |
| feedback = f"# Exam Result\nScore: **{score}%** ({correct}/{len(current_exam)})\n\nStatus: **{status}**\n\n" | |
| if weak: | |
| feedback += "## Weak areas to review\n" + "\n".join([f"- {w}" for w in weak]) + "\n\n" | |
| if review: | |
| feedback += "## Wrong-answer review\n" + "\n\n".join(review[:10]) | |
| else: | |
| feedback += "Excellent work. No wrong answers in this attempt." | |
| return feedback, dashboard() | |
| def dashboard(): | |
| total_lessons = len(LESSONS) | |
| completed = len(progress["completed_lessons"]) | |
| completion = round(completed / total_lessons * 100) | |
| quiz_attempts = sum(len(v) for v in progress["quiz_scores"].values()) | |
| quiz_correct = sum(sum(v) for v in progress["quiz_scores"].values()) | |
| quiz_pct = round(quiz_correct / quiz_attempts * 100, 1) if quiz_attempts else 0 | |
| best_exam = max([x["score"] for x in progress["exam_history"]], default=0) | |
| lines = [ | |
| "# Progress Dashboard", | |
| f"Lessons completed: **{completed}/{total_lessons}** ({completion}%)", | |
| f"Practice quiz accuracy: **{quiz_pct}%** ({quiz_correct}/{quiz_attempts})", | |
| f"Best mock exam score: **{best_exam}%**", | |
| "", | |
| "## Completed lessons", | |
| ] | |
| lines += [f"- {x}" for x in progress["completed_lessons"]] or ["- None yet"] | |
| lines.append("\n## Recommended next step") | |
| if completion < 100: | |
| lines.append("Complete all lessons, then attempt the mock exam.") | |
| elif best_exam < PASS_MARK: | |
| lines.append("Review weak areas and retake the mock exam.") | |
| else: | |
| lines.append("Good progress. Continue practising timed questions before the real exam.") | |
| return "\n".join(lines) | |
| def export_progress(): | |
| return json.dumps(progress, indent=2) | |
| with gr.Blocks(title=APP_TITLE, theme=gr.themes.Soft()) as demo: | |
| gr.Markdown(f"# {APP_TITLE}\nBeginner-friendly Python learning and PCEP exam preparation platform.") | |
| with gr.Tab("Learn"): | |
| topic = gr.Dropdown(list(LESSONS.keys()), value="1. Python Fundamentals", label="Choose lesson") | |
| lesson_md = gr.Markdown() | |
| complete_btn = gr.Button("Mark lesson as completed") | |
| dash_from_lesson = gr.Markdown() | |
| topic.change(get_lesson, topic, [lesson_md, gr.State()]) | |
| demo.load(get_lesson, topic, [lesson_md, gr.State()]) | |
| complete_btn.click(mark_complete, topic, dash_from_lesson) | |
| with gr.Tab("Code Playground"): | |
| code_topic = gr.Dropdown(list(LESSONS.keys()), value="1. Python Fundamentals", label="Load starter code") | |
| code_box = gr.Code(label="Python Code", language="python", lines=12) | |
| run_btn = gr.Button("Run Code") | |
| code_output = gr.Textbox(label="Output", lines=10) | |
| code_topic.change(lambda t: LESSONS[t]["starter_code"], code_topic, code_box) | |
| demo.load(lambda: LESSONS["1. Python Fundamentals"]["starter_code"], None, code_box) | |
| run_btn.click(run_code, code_box, code_output) | |
| with gr.Tab("Practice Quiz"): | |
| practice_topic = gr.Dropdown(["All"] + sorted(set(q["topic"] for q in QUESTION_BANK)), value="All", label="Question topic") | |
| q_text = gr.Markdown("Click Generate Question to begin.") | |
| q_choice = gr.Radio(label="Choose your answer", choices=[]) | |
| q_state = gr.State("") | |
| gen_btn = gr.Button("Generate Question") | |
| check_btn = gr.Button("Check Answer") | |
| feedback = gr.Markdown() | |
| gen_btn.click(get_practice_question, practice_topic, [q_text, q_choice, q_state]) | |
| check_btn.click(check_practice_answer, [q_choice, q_state], feedback) | |
| with gr.Tab("Mock Exam"): | |
| gr.Markdown("Start a randomized 30-question PCEP-style mock exam. Suggested time limit: 45 minutes.") | |
| start_btn = gr.Button("Start Mock Exam") | |
| exam_status = gr.Markdown() | |
| exam_radios = [] | |
| for i in range(30): | |
| exam_radios.append(gr.Radio(label=f"Q{i+1}", choices=[], visible=True)) | |
| submit_btn = gr.Button("Submit Exam") | |
| exam_result = gr.Markdown() | |
| exam_dash = gr.Markdown() | |
| start_btn.click(start_exam, None, exam_radios + [exam_status]) | |
| submit_btn.click(submit_exam, exam_radios, [exam_result, exam_dash]) | |
| with gr.Tab("Dashboard"): | |
| dash = gr.Markdown() | |
| refresh_btn = gr.Button("Refresh Dashboard") | |
| export_btn = gr.Button("Export Progress JSON") | |
| export_box = gr.Textbox(label="Progress JSON", lines=12) | |
| demo.load(dashboard, None, dash) | |
| refresh_btn.click(dashboard, None, dash) | |
| export_btn.click(export_progress, None, export_box) | |
| if __name__ == "__main__": | |
| demo.launch() | |