{
 "version": 1,
 "written": "2026-10-10",
 "units": [
  {"id": "python_start", "group": "Unit 1: The command line on Windows and macOS", "label": "Start Python: Terminal, vim, Jupyter", "at": "python_start.html"},
  {"id": "it__it__it_windows", "group": "Unit 1: Windows", "label": "Windows basics: the parts of the screen", "at": "it/it/it_windows.html"},
  {"id": "it__it__it_files", "group": "Unit 1: Windows", "label": "File management in Windows", "at": "it/it/it_files.html"},
  {"id": "mac_terminal", "group": "Unit 1: macOS", "label": "sed, awk, curl, > and | on a Mac", "at": "mac_terminal.html"},
  {"id": "it__net__net_types", "group": "Unit 2: Networking basics", "label": "Networks: types and topologies", "at": "it/net/net_types.html"},
  {"id": "it__net__net_ip", "group": "Unit 2: Networking basics", "label": "IP addresses and subnetting", "at": "it/net/net_ip.html"},
  {"id": "it__net__net_wifi", "group": "Unit 2: Networking basics", "label": "Wi-Fi and wireless words", "at": "it/net/net_wifi.html"},
  {"id": "it__net__net_dns", "group": "Unit 2: Networking basics", "label": "DNS and domain names", "at": "it/net/net_dns.html"},
  {"id": "it__net__net_troubleshoot", "group": "Unit 2: Networking basics", "label": "Network troubleshooting tools", "at": "it/net/net_troubleshoot.html"},
  {"id": "tailscale", "group": "Unit 2: Networking basics", "label": "Tailscale: your own private network", "at": "tailscale.html"},
  {"id": "easy_variables", "group": "Unit 3: Python basics (easy)", "label": "Variables, strings and numbers", "at": "python_piggy.html#step1"},
  {"id": "easy_lists", "group": "Unit 3: Python basics (easy)", "label": "Lists: [0], [-1], append and len", "at": "python_piggy.html#step2"},
  {"id": "easy_dicts", "group": "Unit 3: Python basics (easy)", "label": "Dictionaries: keys and values", "at": "python_piggy.html#step4"},
  {"id": "easy_loops", "group": "Unit 3: Python basics (easy)", "label": "for loops and the spaces in front", "at": "python_piggy.html#step5"},
  {"id": "easy_if", "group": "Unit 3: Python basics (easy)", "label": "True, False, if, elif and ==", "at": "python_piggy.html#step6"},
  {"id": "easy_functions", "group": "Unit 3: Python basics (easy)", "label": "Functions: def, return and main()", "at": "python_piggy.html#step8"},
  {"id": "easy_modules", "group": "Unit 3: Python basics (easy)", "label": "import and the modules that come with Python", "at": "python_piggy.html#step2"},
  {"id": "easy_comprehensions", "group": "Unit 3: Python basics (easy)", "label": "Comprehensions, sets and generators", "at": "python_browser.html#step3"},
  {"id": "python_toolkit", "group": "Unit 3: Python basics (difficult)", "label": "Python Toolkit: seven tools", "at": "python_toolkit.html"},
  {"id": "python_classes", "group": "Unit 3: Python basics (difficult)", "label": "Python classes, taught with a window", "at": "python_classes.html"},
  {"id": "python_data_classes", "group": "Unit 3: Python basics (difficult)", "label": "Data classes from the dataclasses module", "at": "python_data_classes.html"},
  {"id": "catch_your_own_bugs", "group": "Unit 3: Python libraries you install", "label": "Catch your own bugs with pytest", "at": "catch_your_own_bugs.html"},
  {"id": "python_libraries", "group": "Unit 3: Python libraries you install", "label": "requests, attrs and Pydantic", "at": "python_data_classes.html"},
  {"id": "send_email", "group": "Unit 4: Small real programs", "label": "Email, files and AI in Python", "at": "send_email.html"},
  {"id": "python_six_libraries", "group": "Unit 4: Small real programs", "label": "Six free Python data libraries", "at": "python_six_libraries.html"},
  {"id": "mailbot", "group": "Unit 4: Small real programs", "label": "A bot that reads your mail once an hour", "at": "mailbot.html"},
  {"id": "apis", "group": "Unit 4: Small real programs", "label": "What is an API?", "at": "apis.html"},
  {"id": "html_intro", "group": "Unit 5: A website on GitHub Pages", "label": "What is HTML: your first web page", "at": "html_intro.html"},
  {"id": "javascript_lab", "group": "Unit 5: A website on GitHub Pages", "label": "JavaScript Lab: nine basic ideas", "at": "javascript_lab.html"},
  {"id": "it__ai__ai_code", "group": "Unit 5: A website on GitHub Pages", "label": "AI code generation", "at": "it/ai/ai_code.html"},
  {"id": "prompt_lab", "group": "Unit 5: A website on GitHub Pages", "label": "Prompt Lab: the parts of a prompt", "at": "prompt_lab.html"},
  {"id": "it__git_basics", "group": "Unit 5: A website on GitHub Pages", "label": "Git basics: commit, branch, push, pull", "at": "it/git_basics.html"},
  {"id": "it__github_pages_lab", "group": "Unit 5: A website on GitHub Pages", "label": "Git and GitHub Pages: ship a website", "at": "it/github_pages_lab.html"},
  {"id": "javascript_space_game", "group": "Unit 5: A website on GitHub Pages", "label": "A space game in JavaScript", "at": "javascript_space_game.html"},
  {"id": "github", "group": "Unit 6: A private repository", "label": "Git and GitHub basics", "at": "github.html"},
  {"id": "free_local_ai", "group": "Unit 7: Ollama: a model on your computer", "label": "Free local AI with Ollama", "at": "free_local_ai.html"},
  {"id": "it__ai__ai_ollama", "group": "Unit 7: Ollama: a model on your computer", "label": "Ollama: AI models on your own PC", "at": "it/ai/ai_ollama.html"},
  {"id": "make_an_ai_tutor", "group": "Unit 7: Ollama: a model on your computer", "label": "An AI tutor with Python and Ollama", "at": "make_an_ai_tutor.html"},
  {"id": "tkinter_chat", "group": "Unit 7: Ollama: a model on your computer", "label": "A chat window with tkinter and Ollama", "at": "tkinter_chat.html"},
  {"id": "it__ai__ai_windows_stack", "group": "Unit 7: Ollama on Windows", "label": "Local AI stack on Windows", "at": "it/ai/ai_windows_stack.html"},
  {"id": "it__ai__ai_mac_stack", "group": "Unit 7: Ollama on macOS", "label": "Local AI stack on a Mac", "at": "it/ai/ai_mac_stack.html"},
  {"id": "decider", "group": "Unit 8: Decider: a model that picks", "label": "Decider: a model that picks an option", "at": "decider.html"},
  {"id": "jev", "group": "Unit 8: Decider: a model that picks", "label": "Jev and Kev: decision models", "at": "jev.html"},
  {"id": "agno_agents", "group": "Unit 9: Agno: a team of agents", "label": "Agno: a company of AI agents", "at": "agno_agents.html"},
  {"id": "smolagents", "group": "Unit 9: Agno: a team of agents", "label": "smolagents: agents that use your tools", "at": "smolagents.html"},
  {"id": "docker_lab", "group": "Unit 10: Docker", "label": "Docker Lab: boxes for your programs", "at": "docker_lab.html"},
  {"id": "it__ai__dockerfile_basics", "group": "Unit 10: Docker", "label": "Write your own Dockerfile", "at": "it/ai/dockerfile_basics.html"},
  {"id": "linux_lab", "group": "Unit 10: Docker", "label": "Linux Lab: Ubuntu and Rocky in Docker", "at": "linux_lab.html"},
  {"id": "bentoml", "group": "Unit 11: BentoML: a model as a web service", "label": "BentoML: a function as a web service", "at": "bentoml.html"},
  {"id": "reverse_engineer", "group": "Unit 12: Reverse engineering", "label": "Reverse engineering with REA", "at": "reverse_engineer.html"},
  {"id": "flask", "group": "Unit 11: BentoML: a model as a web service", "label": "Flask: a web page from Python", "at": "flask.html"},
  {"id": "gradio", "group": "Unit 11: BentoML: a model as a web service", "label": "Gradio: a web page from a function", "at": "gradio.html"},
  {"id": "rag_llm", "group": "Unit 13: Your own project", "label": "RAG with Python on a laptop", "at": "rag_llm.html"},
  {"id": "keyestudio_smart_home", "group": "Unit 13: Your own project", "label": "Keyestudio Smart Home with MicroPython", "at": "keyestudio_smart_home.html"},
  {"id": "thai_speech_to_text", "group": "Unit 13: Your own project", "label": "Free Thai speech-to-text on your PC", "at": "thai_speech_to_text.html"},
  {"id": "fine_tune_llm", "group": "Unit 13: Your own project", "label": "Fine-tune an LLM: full and LoRA", "at": "fine_tune_llm.html"},
  {"id": "python_to_exe", "group": "Unit 13: Python to an app on Windows", "label": "Python to EXE with PyInstaller", "at": "python_to_exe.html"},
  {"id": "python_to_mac_app", "group": "Unit 13: Python to an app on macOS", "label": "From a Python script to a Mac app", "at": "python_to_mac_app.html"},
  {"id": "space_invaders", "group": "More from Start Python", "label": "Space Invaders game with classes", "at": "space_invaders.html"},
  {"id": "chiang_mai_map", "group": "More from Start Python", "label": "Map Chiang Mai with Python and folium", "at": "chiang_mai_map.html"},
  {"id": "grokking_algorithms", "group": "More from Start Python", "label": "Grokking Algorithms: 16 algorithms in Python", "at": "grokking_algorithms.html"},
  {"id": "make_a_flashcard_app", "group": "More from Start Python", "label": "Make a flashcard app with tkinter", "at": "make_a_flashcard_app.html"},
  {"id": "make_a_homework_app", "group": "More from Start Python", "label": "An app that remembers: sqlite3, argparse", "at": "make_a_homework_app.html"},
  {"id": "make_a_school_board", "group": "More from Start Python", "label": "Flask: the homework app on school wifi", "at": "make_a_school_board.html"},
  {"id": "is_the_air_safe", "group": "More from Start Python", "label": "Is the air safe: PM2.5 from an API", "at": "is_the_air_safe.html"},
  {"id": "teach_the_computer_to_speak", "group": "More from Start Python", "label": "Text to speech in Python with edge-tts", "at": "teach_the_computer_to_speak.html"},
  {"id": "robot_python", "group": "More from Start Python", "label": "Drive a robot car with Python", "at": "robot_python.html"},
  {"id": "control_your_phone", "group": "More from Start Python", "label": "Control an Android phone from a laptop", "at": "control_your_phone.html"},
  {"id": "crewai_autogen", "group": "More from Start Python", "label": "CrewAI and AutoGen agent teams", "at": "crewai_autogen.html"},
  {"id": "langchain", "group": "More from Start Python", "label": "LangChain: prompt, model, parser", "at": "langchain.html"},
  {"id": "face_recognition", "group": "More from Start Python", "label": "Face recognition with OpenCV", "at": "face_recognition.html"},
  {"id": "physics_with_python", "group": "More from Start Python", "label": "Physics with Python: test the formulas", "at": "physics_with_python.html"},
  {"id": "telescope_data", "group": "More from Start Python", "label": "Ask a space telescope archive in Python", "at": "telescope_data.html"},
  {"id": "cloakbrowser", "group": "More from Start Python", "label": "Web scraping with CloakBrowser", "at": "cloakbrowser.html"},
  {"id": "rust_start", "group": "More from Start Python", "label": "Start Rust: variables, structs, errors", "at": "rust_start.html"}
 ],
 "questions": [
  {"id": "python_start_01", "unit": "python_start", "d": -0.8, "type": "mcq", "q": "In the Terminal, what does `mkdir Code` do?", "o": ["Opens the folder called Code", "Deletes the folder called Code", "Creates a folder called Code", "Lists the files inside Code"], "a": 2, "why": "`mkdir` means Make Directory. `mkdir Code` creates a folder called Code.", "at": "python_start.html#s5"},
  {"id": "python_start_02", "unit": "python_start", "d": -0.5, "type": "mcq", "q": "Why must you check twice before you use `rm` in the Terminal?", "o": ["It has no Trash and no undo", "It also deletes the folder above", "It needs the internet to work", "It stops the program that runs"], "a": 0, "why": "`rm` removes a file with no Trash and no undo. A delete with the mouse can be recovered, but `rm` cannot.", "at": "python_start.html#s5"},
  {"id": "python_start_03", "unit": "python_start", "d": -0.4, "type": "mcq", "q": "You open a file in vim and press `i`. What changes?", "o": ["The file is saved and vim closes", "The whole line is deleted at once", "Your last change is undone", "The keyboard now types letters"], "a": 3, "why": "`i` enters INSERT mode. In INSERT mode the keyboard types normally, and Esc leaves it.", "at": "python_start.html#s7"},
  {"id": "python_start_04", "unit": "python_start", "d": 0.0, "type": "mcq", "q": "You are lost in vim and want to quit and throw away your changes. Which command do you type after Esc?", "o": ["`:wq`", "`:q!`", "`:w`", "`:x`"], "a": 1, "why": "`:q!` quits vim and throws away your changes. It always works, so it is the way out when you are lost.", "at": "python_start.html#s7"},
  {"id": "python_start_05", "unit": "python_start", "d": -0.1, "type": "mcq", "q": "The prompt shows `>>>`. How do you go back to the Terminal prompt?", "o": ["Type `:wq` and press Return", "Type `exit()` and press Return", "Type `cd ..` and press Return", "Type `pwd` and press Return"], "a": 1, "why": "`>>>` means you are inside Python. To leave, type `exit()` and press Return.", "at": "python_start.html#s8"},
  {"id": "python_start_06", "unit": "python_start", "d": 0.3, "type": "mcq", "q": "Why should you run the cells of a Jupyter notebook in order?", "o": ["Cells share memory, so order changes the results", "Jupyter deletes any cell that is run out of order", "Each cell starts with empty memory when it runs", "The browser can show only one cell's answer at once"], "a": 0, "why": "Cells share memory: a variable made in one cell still exists in later cells. Running cells out of order causes almost every strange result.", "at": "python_start.html#s11"},
  {"id": "python_start_07", "unit": "python_start", "d": 0.4, "type": "mcq", "q": "`git push` asks for a password. What must you paste there?", "o": ["Your GitHub account password", "Your computer's login password", "A personal access token", "The name of your repository"], "a": 2, "why": "GitHub does not accept account passwords over HTTPS. You make a personal access token and paste it when git asks for the password.", "at": "python_start.html#s11"},
  {"id": "python_start_08", "unit": "python_start", "d": 0.5, "type": "mcq", "q": "What does the last line print?", "code": "class Creep:\n    def __init__(self, speed, hit_points):\n        self.speed = speed\n        self.hit_points = hit_points\n\na = Creep(2.0, 3)\nb = Creep(0.5, 1)\na.hit_points -= 1\nprint(b.hit_points)", "o": ["2", "1", "3", "0"], "a": 1, "why": "The dot reaches inside one object. Only `a` changed, so `b.hit_points` is still 1.", "at": "python_start.html#dots"},
  {"id": "it__it__it_windows_01", "unit": "it__it__it_windows", "d": -0.9, "type": "mcq", "q": "What is Windows?", "o": ["A program for writing letters and documents", "A hardware part inside the computer", "A website for downloading new apps", "An operating system made by Microsoft"], "a": 3, "why": "Windows is an operating system made by Microsoft. An operating system is the main software that manages everything on a computer.", "at": "it/it/it_windows.html"},
  {"id": "it__it__it_windows_02", "unit": "it__it__it_windows", "d": -0.6, "type": "mcq", "q": "You want to change the Wi-Fi, the display or the language. Which app do you open?", "o": ["Settings", "Recycle Bin", "Taskbar", "Wallpaper"], "a": 0, "why": "Settings is the app where you change computer options like Wi-Fi, display and language.", "at": "it/it/it_windows.html"},
  {"id": "it__it__it_windows_03", "unit": "it__it__it_windows", "d": -0.3, "type": "mcq", "q": "What is a shortcut on the desktop?", "o": ["A message that tells you about a new email", "The background image behind all the icons", "A small icon that opens a program quickly", "A folder that keeps the files you deleted"], "a": 2, "why": "A shortcut is a small icon on the desktop that opens a file or program quickly.", "at": "it/it/it_windows.html"},
  {"id": "it__it__it_windows_04", "unit": "it__it__it_windows", "d": -0.1, "type": "fill", "q": "What is the word for the background image on the desktop? Type one word.", "answers": ["wallpaper"], "why": "The wallpaper is the background image on the desktop.", "at": "it/it/it_windows.html"},
  {"id": "it__it__it_windows_05", "unit": "it__it__it_windows", "d": 0.3, "type": "mcq", "q": "You deleted a file by mistake. Where can you still find it?", "o": ["In the Start Menu", "In the Recycle Bin", "In the Settings app", "On the Taskbar"], "a": 1, "why": "The Recycle Bin stores deleted files until you permanently remove them, so you can restore the file from there.", "at": "it/it/it_windows.html"},
  {"id": "it__it__it_windows_06", "unit": "it__it__it_windows", "d": 0.5, "type": "mcq", "q": "Which two ways open the Start Menu?", "o": ["Click the gear icon, or press the Ctrl key", "Click the wallpaper, or press the Alt key", "Click the Recycle Bin, or press the Shift key", "Click the Windows icon, or press the Windows Key"], "a": 3, "why": "Click the Windows icon at the bottom left of the Taskbar, or press the Windows Key.", "at": "it/it/it_windows.html"},
  {"id": "it__it__it_files_01", "unit": "it__it__it_files", "d": -0.8, "type": "mcq", "q": "What happens first when you delete a file in Windows?", "o": ["It is copied to a backup", "It goes to the Recycle Bin", "It moves to the Documents folder", "It gets a new extension"], "a": 1, "why": "Delete removes a file. In Windows, a deleted file goes to the Recycle Bin first.", "at": "it/it/it_files.html"},
  {"id": "it__it__it_files_02", "unit": "it__it__it_files", "d": -0.4, "type": "mcq", "q": "What is the difference between Copy and Move?", "o": ["Copy changes the name; Move changes the type", "Copy is for folders; Move is for files", "Copy removes the original; Move keeps it", "Copy keeps the original; Move takes it away"], "a": 3, "why": "Copy makes a duplicate without moving the original. Move transfers the file from one location to another.", "at": "it/it/it_files.html"},
  {"id": "it__it__it_files_03", "unit": "it__it__it_files", "d": 0.0, "type": "mcq", "q": "A file is called `budget.xlsx`. What does the extension tell you?", "o": ["It is a video for Media Player", "It is a compressed file", "It is a spreadsheet for Excel", "It is a program installer"], "a": 2, "why": "The extension is the letters after the dot. `.xlsx` is a spreadsheet that opens in Microsoft Excel.", "at": "it/it/it_files.html"},
  {"id": "it__it__it_files_04", "unit": "it__it__it_files", "d": 0.3, "type": "mcq", "q": "How do you make a new folder in File Explorer?", "o": ["Right-click, then New, then Folder", "Press Windows + E two times", "Rename a file to end with `.zip`", "Search for the word \"Folder\""], "a": 0, "why": "In File Explorer: Right-click, New, Folder. Then you can move files into the new folder.", "at": "it/it/it_files.html"},
  {"id": "it__it__it_files_05", "unit": "it__it__it_files", "d": 0.5, "type": "mcq", "q": "You want a duplicate of `report.docx` in a second folder. Then you want to remove an old photo. Which two actions do you use?", "o": ["Move, then Rename", "Rename, then Search", "Copy, then Delete", "Move, then Backup"], "a": 2, "why": "Copy makes a duplicate and leaves the original. Delete removes a file.", "at": "it/it/it_files.html"},
  {"id": "mac_terminal_01", "unit": "mac_terminal", "d": -0.8, "type": "mcq", "q": "The file `hello.txt` already has two lines. You run `echo \"This wipes it\" > hello.txt`. What happens to the two old lines?", "o": ["They stay, and the new line is added", "They are gone, with no warning", "The shell asks you to confirm first", "They are moved to the Trash"], "a": 1, "why": "One `>` makes the file again, so the old content is gone. There is no warning and no Trash. `>>` adds to the end.", "at": "mac_terminal.html#step1"},
  {"id": "mac_terminal_02", "unit": "mac_terminal", "d": -0.3, "type": "mcq", "q": "What does `2> problems.txt` catch?", "o": ["Only the error messages", "Only the good output", "Both kinds of output", "Only the second line of output"], "a": 0, "why": "Every command has two exits. `>` catches the good output, and `2>` catches the errors.", "at": "mac_terminal.html#step2"},
  {"id": "mac_terminal_03", "unit": "mac_terminal", "d": -0.5, "type": "mcq", "q": "What does the `|` sign do with the output of a command?", "o": ["It saves it in a new file", "It adds it to the end of a file", "It sends it to another program", "It throws the output away"], "a": 2, "why": "`>` sends output to a file. `|` sends it to another program, so small tools are joined in a line.", "at": "mac_terminal.html#step3"},
  {"id": "mac_terminal_04", "unit": "mac_terminal", "d": 0.0, "type": "mcq", "q": "Without one flag, `curl` reports success even when the web page is missing. Which flag makes `curl` fail loudly on a 404?", "o": ["`-s`", "`-L`", "`-o`", "`-f`"], "a": 3, "why": "`-f` makes `curl` admit that the web page is missing: it prints the 404 error and the exit number is 22, not 0.", "at": "mac_terminal.html#step5"},
  {"id": "mac_terminal_05", "unit": "mac_terminal", "d": 0.2, "type": "mcq", "q": "What does `echo \"one one one\" | sed 's/one/two/'` print?", "o": ["two two two", "one one two", "one two one", "two one one"], "a": 3, "why": "Without `g`, `sed` changes the first match on the line and stops. With `g` it changes every match.", "at": "mac_terminal.html#step6"},
  {"id": "mac_terminal_06", "unit": "mac_terminal", "d": 0.5, "type": "mcq", "q": "A shell script changes a file with `sed 's/first/FIRST/' lines.txt > lines.new && mv lines.new lines.txt`, and not with `sed -i`. What does write-then-rename gain?", "o": ["It runs the same on a Mac and on Linux", "It is the only way to keep a backup copy", "It is much faster on a very big file", "It lets `sed` change two files at once"], "a": 0, "why": "On a Mac `sed -i` needs an empty pair of quotes (`sed -i ''`), and on Linux it does not. Writing a new file and renaming it works on both.", "at": "mac_terminal.html#step8"},
  {"id": "mac_terminal_07", "unit": "mac_terminal", "d": 0.4, "type": "mcq", "q": "What does `echo \"Nari teaches English in Chiang Mai\" | awk '{print $1, $NF}'` print?", "o": ["Nari Chiang Mai", "Nari Mai", "Nari 6", "Nari teaches"], "a": 1, "why": "`$NF` is the last piece. `awk` splits at spaces, so the last piece is Mai, not Chiang Mai.", "at": "mac_terminal.html#step9"},
  {"id": "mac_terminal_08", "unit": "mac_terminal", "d": 0.6, "type": "mcq", "q": "This shell script is run once a day. Its last line is `>> price_log.csv`. What would go wrong with one arrow, `> price_log.csv`?", "code": "curl -sS -f \"https://api.bitkub.com/api/v3/market/ticker?sym=BTC_THB\" \\\n  | sed -E 's/.*\"last\":\"([0-9.]+)\".*/\\1/' \\\n  | awk -v day=\"$(date +%F)\" '{printf \"%s,%.2f\\n\", day, $1}' \\\n  >> price_log.csv", "o": ["Each run would print the price twice", "Each run would send errors to the log", "Each run would stop `awk` from running", "Each run would erase the earlier days"], "a": 3, "why": "Two arrows add a line to the end of the file. One arrow makes the file again, so the lines of the earlier days would be gone.", "at": "mac_terminal.html#step11"},
  {"id": "it__net__net_types_01", "unit": "it__net__net_types", "d": -0.8, "type": "mcq", "q": "In computer networking, what does the word \"topology\" mean?", "o": ["The maximum amount of data sent", "A device that requests services", "A network that covers a large area", "The layout of how devices connect"], "a": 3, "why": "Topology is the layout or arrangement of how devices are connected in a network.", "at": "it/net/net_types.html"},
  {"id": "it__net__net_types_02", "unit": "it__net__net_types", "d": -0.5, "type": "mcq", "q": "A network spans one city or a large campus. Which type is it?", "o": ["LAN", "WAN", "MAN", "Mesh"], "a": 2, "why": "A MAN (Metropolitan Area Network) spans a city or large campus. A LAN is a small area and a WAN is a large geographic area.", "at": "it/net/net_types.html"},
  {"id": "it__net__net_types_03", "unit": "it__net__net_types", "d": 0.0, "type": "mcq", "q": "In which topology does every device connect to every other device?", "o": ["Mesh", "Ring", "Star", "Bus"], "a": 0, "why": "In a mesh, every device connects to every other device. With so many paths, a mesh is reliable.", "at": "it/net/net_types.html"},
  {"id": "it__net__net_types_04", "unit": "it__net__net_types", "d": 0.2, "type": "fill", "q": "In networking, one word names any device connected to a network, such as a computer or a printer. The word has four letters and starts with \"n\". Type that word.", "answers": ["node", "a node", "nodes"], "why": "A node is any device connected to a network, such as a computer or printer.", "at": "it/net/net_types.html"},
  {"id": "it__net__net_types_05", "unit": "it__net__net_types", "d": 0.4, "type": "mcq", "q": "How are devices connected in a bus topology?", "o": ["Each one connects to a central switch", "They all share one communication line", "They are joined in a circular chain", "Each one connects to all the others"], "a": 1, "why": "In a bus, all devices share a single communication line. A central switch is a star, and a circular chain is a ring.", "at": "it/net/net_types.html"},
  {"id": "it__net__net_ip_01", "unit": "it__net__net_ip", "d": -0.7, "type": "mcq", "q": "The address 127.0.0.1 is called the loopback address, or localhost. What does it point to?", "o": ["The router of your network", "Your own computer", "The DHCP server", "Your internet provider"], "a": 1, "why": "127.0.0.1 is a special address that refers to the local device itself. It always points to your own computer.", "at": "it/net/net_ip.html"},
  {"id": "it__net__net_ip_02", "unit": "it__net__net_ip", "d": -0.3, "type": "mcq", "q": "What is a static IP?", "o": ["An address set by hand that does not change", "An address given out by a DHCP server", "An address that points to the local device", "An address that your ISP shows the internet"], "a": 0, "why": "A static IP is manually assigned and does not change. A dynamic IP is automatically assigned by a DHCP server.", "at": "it/net/net_ip.html"},
  {"id": "it__net__net_ip_03", "unit": "it__net__net_ip", "d": 0.0, "type": "mcq", "q": "What does a subnet mask do?", "o": ["It connects two different networks", "It gives addresses to new devices", "It hides a device from the internet", "It marks the network and host parts"], "a": 3, "why": "A subnet mask defines which part of an IP address is the network and which part is the host.", "at": "it/net/net_ip.html"},
  {"id": "it__net__net_ip_04", "unit": "it__net__net_ip", "d": 0.3, "type": "fill", "q": "On Windows, which command do you type in Command Prompt to see your IP address, subnet mask and default gateway? Type it.", "answers": ["ipconfig"], "why": "On Windows, `ipconfig` shows the network settings of the computer: its IP address, subnet mask and default gateway.", "at": "it/net/net_ip.html"},
  {"id": "it__net__net_ip_05", "unit": "it__net__net_ip", "d": 0.6, "type": "mcq", "q": "A laptop at home gets its address automatically when it joins the network. Which line describes this?", "o": ["A static IP, set by the gateway", "A public IP, set by the subnet mask", "A dynamic IP, from a DHCP server", "A loopback IP, from the ISP"], "a": 2, "why": "A dynamic IP is assigned automatically by a DHCP server. Home devices usually get their addresses this way.", "at": "it/net/net_ip.html"},
  {"id": "it__net__net_wifi_01", "unit": "it__net__net_wifi", "d": -0.7, "type": "mcq", "q": "What is an access point?", "o": ["A device that boosts a weak signal", "A device that makes a wireless network", "The name that a wireless network has", "An area where the Wi-Fi signal is weak"], "a": 1, "why": "An access point is a device that creates a wireless network for devices to connect to.", "at": "it/net/net_wifi.html"},
  {"id": "it__net__net_wifi_02", "unit": "it__net__net_wifi", "d": -0.5, "type": "fill", "q": "Which word means the name of a wireless network, the name devices see when they search? Type the four letters.", "answers": ["ssid"], "why": "SSID means Service Set Identifier. It is the name of a wireless network.", "at": "it/net/net_wifi.html"},
  {"id": "it__net__net_wifi_03", "unit": "it__net__net_wifi", "d": 0.0, "type": "mcq", "q": "How is 5 GHz Wi-Fi different from 2.4 GHz Wi-Fi?", "o": ["Longer range, but slower", "Longer range and faster", "Shorter range and slower", "Shorter range, but faster"], "a": 3, "why": "2.4 GHz has a longer range and a slower speed. 5 GHz has a shorter range and is faster.", "at": "it/net/net_wifi.html"},
  {"id": "it__net__net_wifi_04", "unit": "it__net__net_wifi", "d": 0.3, "type": "mcq", "q": "What is a channel?", "o": ["A specific frequency range within a Wi-Fi band", "The name that devices see when they search", "The device that connects you to the internet", "The radio waves that carry data between devices"], "a": 0, "why": "A channel is a specific frequency range within a Wi-Fi band. The band itself is the frequency, 2.4 GHz or 5 GHz.", "at": "it/net/net_wifi.html"},
  {"id": "it__net__net_wifi_05", "unit": "it__net__net_wifi", "d": 0.5, "type": "mcq", "q": "A repeater boosts the signal to extend coverage. What is special about mesh Wi-Fi?", "o": ["It uses a cable and no radio waves at all", "It works only with old 2.4 GHz devices", "It uses many units that work together", "It is a security standard, not a device"], "a": 2, "why": "Mesh Wi-Fi is a system of multiple Wi-Fi units that work together for full coverage.", "at": "it/net/net_wifi.html"},
  {"id": "it__net__net_dns_01", "unit": "it__net__net_dns", "d": -0.7, "type": "mcq", "q": "What is the job of DNS?", "o": ["It translates domain names to IP addresses.", "It stores the files of a website on a server.", "It registers new domain names for companies.", "It makes a website load faster for its users."], "a": 0, "why": "DNS is the Domain Name System. It translates domain names to IP addresses.", "at": "it/net/net_dns.html"},
  {"id": "it__net__net_dns_02", "unit": "it__net__net_dns", "d": -0.3, "type": "mcq", "q": "What does an A record do?", "o": ["It points one domain name to another domain.", "It maps a domain name to an IPv4 address.", "It says how long a record stays in the cache.", "It stores the files of a website on a server."], "a": 1, "why": "An A record maps a domain name to an IPv4 address, like example.com → 93.184.216.34. An alias to another domain is a CNAME.", "at": "it/net/net_dns.html"},
  {"id": "it__net__net_dns_03", "unit": "it__net__net_dns", "d": -0.2, "type": "fill", "q": "Which Command Prompt tool asks a DNS server for the records of a name, such as the IP address of `google.com`? Type only the command name.", "answers": ["nslookup", "nslookup google.com"], "why": "`nslookup` asks DNS for the records of a name. In Command Prompt, `nslookup google.com` shows the IP address of `google.com`.", "at": "it/net/net_dns.html"},
  {"id": "it__net__net_dns_04", "unit": "it__net__net_dns", "d": 0.2, "type": "mcq", "q": "A DNS record has a TTL of 3600. What does that tell you?", "o": ["The record points to 3600 different IP addresses.", "The domain name was registered 3600 days ago.", "The record is cached for 3600 seconds (1 hour).", "The change takes 3600 hours to spread worldwide."], "a": 2, "why": "TTL is Time to Live: how long a DNS record is cached before refreshing. It is counted in seconds, and 3600 is 1 hour.", "at": "it/net/net_dns.html"},
  {"id": "it__net__net_dns_05", "unit": "it__net__net_dns", "d": 0.5, "type": "mcq", "q": "What is the difference between a resolver and a nameserver?", "o": ["They are two names for the same kind of DNS server.", "A resolver registers names; a nameserver hosts websites.", "A resolver stores the records; a nameserver finds the IP.", "A resolver finds the IP; a nameserver stores the records."], "a": 3, "why": "A resolver receives DNS queries and finds the correct IP address. A nameserver stores DNS records and answers queries.", "at": "it/net/net_dns.html"},
  {"id": "it__net__net_troubleshoot_01", "unit": "it__net__net_troubleshoot", "d": -0.7, "type": "fill", "q": "You want to test if a device on the network is reachable. Which command does this? Type its name.", "answers": ["ping"], "why": "`ping` is a command that tests if a device on a network is reachable.", "at": "it/net/net_troubleshoot.html"},
  {"id": "it__net__net_troubleshoot_02", "unit": "it__net__net_troubleshoot", "d": -0.3, "type": "mcq", "q": "What does `traceroute` show?", "o": ["The path data takes to a destination", "The IP addresses that DNS has for a domain", "The ports that are listening now", "The network settings on Windows"], "a": 0, "why": "`traceroute` shows the path data takes from your computer to a destination.", "at": "it/net/net_troubleshoot.html"},
  {"id": "it__net__net_troubleshoot_03", "unit": "it__net__net_troubleshoot", "d": 0.0, "type": "mcq", "q": "Which command asks DNS for the IP address of a domain name?", "o": ["`netstat`", "`ipconfig`", "`nslookup`", "`traceroute`"], "a": 2, "why": "`nslookup` is a command to query DNS and find IP addresses for domain names.", "at": "it/net/net_troubleshoot.html"},
  {"id": "it__net__net_troubleshoot_04", "unit": "it__net__net_troubleshoot", "d": 0.4, "type": "mcq", "q": "What does TTL (Time to Live) limit?", "o": ["How long a request waits for a reply", "How many hops a packet can make", "How many ports a device can open", "How much traffic a monitor can watch"], "a": 1, "why": "TTL limits how many hops a packet can make before it is discarded.", "at": "it/net/net_troubleshoot.html"},
  {"id": "it__net__net_troubleshoot_05", "unit": "it__net__net_troubleshoot", "d": 0.6, "type": "mcq", "q": "A network request takes too long and is cancelled. What is this called?", "o": ["Latency", "Packet loss", "TTL", "Timeout"], "a": 3, "why": "A timeout is when a request takes too long and is cancelled. Latency is only the delay, and packet loss is data that does not arrive.", "at": "it/net/net_troubleshoot.html"},
  {"id": "tailscale_01", "unit": "tailscale", "d": -0.8, "type": "mcq", "q": "In Tailscale, what puts your devices in the same tailnet?", "o": ["Connecting them all to the same Wi-Fi", "Using the same login on every device", "Giving them all the same device name", "Opening the same port on your router"], "a": 1, "why": "A tailnet is all the devices on one account. So the same login on every device puts them in the same tailnet.", "at": "tailscale.html#install"},
  {"id": "tailscale_02", "unit": "tailscale", "d": -0.5, "type": "fill", "q": "Complete the command that lists every device in your tailnet: `tailscale ____`. Type the missing word.", "answers": ["status", "tailscale status", "sudo tailscale status"], "why": "`tailscale status` lists every device. Each line shows its address, name and connection.", "at": "tailscale.html#look"},
  {"id": "tailscale_03", "unit": "tailscale", "d": -0.3, "type": "mcq", "q": "In Tailscale, what is the difference between Serve and Funnel?", "o": ["Serve is tailnet only; Funnel is the whole internet", "Serve is for phones; Funnel is for Linux servers", "Serve uses plain HTTP; Funnel is the only one with HTTPS", "Serve is free; Funnel needs the Standard plan"], "a": 0, "why": "Serve shares an app with devices in your tailnet only. Funnel shares it with everyone on the internet.", "at": "tailscale.html#share"},
  {"id": "tailscale_04", "unit": "tailscale", "d": 0.0, "type": "mcq", "q": "`tailscale status` shows `active; direct` for a device. What does that mean?", "o": ["The device is off, or Tailscale is stopped on it", "Traffic goes through a DERP relay", "Traffic goes straight between the devices", "The device's key will expire soon"], "a": 2, "why": "`active; direct` is the best result: the traffic goes straight between the devices, not through a relay.", "at": "tailscale.html#look"},
  {"id": "tailscale_05", "unit": "tailscale", "d": 0.3, "type": "mcq", "q": "`tailscale ping` works, but the app on the other device does not open. What is one thing to check?", "o": ["That both devices use the very same Google account", "That the other device's key has not expired yet", "That UDP works, with `tailscale netcheck`", "That the app does not listen only on `127.0.0.1`"], "a": 3, "why": "If the ping works, the network is fine. The app must listen on the network, not only on localhost.", "at": "tailscale.html#trouble"},
  {"id": "tailscale_06", "unit": "tailscale", "d": 0.5, "type": "mcq", "q": "In Tailscale, why is it better to change one setting with `tailscale set` than with `tailscale up`?", "o": ["`set` works without `sudo` on Linux, but `up` does not", "`set` changes only the settings you name", "`set` also logs you in again with a new key", "`set` sends the change to every device at once"], "a": 1, "why": "`tailscale set` changes only the settings you name. With `tailscale up` you must repeat every setting you want to keep.", "at": "tailscale.html#configure"},
  {"id": "tailscale_07", "unit": "tailscale", "d": 0.6, "type": "mcq", "q": "This Docker Compose file runs Tailscale in a container beside an app. What happens if you take out the `ts-state` volume?", "code": "services:\n  app:\n    image: nginx:alpine\n  app-tailscale:\n    image: tailscale/tailscale:latest\n    environment:\n      TS_AUTHKEY: ${TS_AUTHKEY}\n      TS_HOSTNAME: myapp\n      TS_STATE_DIR: /var/lib/tailscale\n    volumes:\n      - ts-state:/var/lib/tailscale\nvolumes:\n  ts-state:", "o": ["The app loses its HTTPS certificate", "Docker cannot find `app` by name", "Every restart adds a new machine", "The auth key is written in the compose file"], "a": 2, "why": "`TS_STATE_DIR` and the `ts-state` volume keep the Tailscale login between restarts. Without the volume, every restart joins the tailnet as a new machine.", "at": "tailscale.html#docker"},
  {"id": "ev_01", "unit": "easy_variables", "d": -1.0, "type": "mcq", "q": "What does the `=` do in `pig = \"Princess Taiping\"`?", "o": ["Stores the value in the name", "Asks if both sides are equal", "Prints the value on the screen", "Joins the two words together"], "a": 0, "why": "One `=` gives a name a value: read it as \"is now\". Two of them, `==`, ask whether two values are the same.", "at": "python_piggy.html#step1"},
  {"id": "ev_02", "unit": "easy_variables", "d": -0.9, "type": "fill", "q": "Text inside quotes, such as `\"mango\"`, is one kind of value. What is that kind called? Type one word.", "answers": ["string", "str", "strings"], "why": "Text in quotes is a string, `str` for short. Python keeps it exactly as you typed it.", "at": "python_piggy.html#step1"},
  {"id": "ev_03", "unit": "easy_variables", "d": -0.8, "type": "mcq", "q": "`age` starts at 3 and has a birthday. What does this print?", "code": "age = 3\nage = age + 1\nprint(age)", "o": ["3", "4", "age + 1", "1"], "a": 1, "why": "Python works out the right side first, 3 + 1, and then puts 4 into `age`. A variable's value can change.", "at": "python_piggy.html#step1"},
  {"id": "ev_04", "unit": "easy_variables", "d": -0.7, "type": "fill", "q": "A whole number such as `3` is one kind of value. What is its short name in Python? Type it.", "answers": ["int", "integer"], "why": "A whole number is an `int`, short for integer. Numbers have no quotes, so Python can do sums with them.", "at": "python_piggy.html#step1"},
  {"id": "ev_05", "unit": "easy_variables", "d": -0.6, "type": "mcq", "q": "What does this `print` line show?", "code": "print(\"Princess Taiping\", \"is\", 3, \"years old\")", "o": ["Princess Taipingis3years old", "\"Princess Taiping\" \"is\" 3 \"years old\"", "Princess Taiping is 3 years old", "Princess Taiping, is, 3, years old"], "a": 2, "why": "`print` shows its values with one space between them, and without the quotes.", "at": "python_piggy.html#step1"},
  {"id": "ev_06", "unit": "easy_variables", "d": -0.5, "type": "mcq", "q": "What does `#` do in the line `age = 3  # a whole number`?", "o": ["Makes the number a whole number", "Turns the rest of the line into text", "Prints the rest of the line on screen", "Starts a note that Python skips"], "a": 3, "why": "Everything after `#` is a comment: a note for people. Python skips it.", "at": "python_piggy.html#step1"},
  {"id": "ev_07", "unit": "easy_variables", "d": -0.4, "type": "mcq", "q": "What does this f-string print?", "code": "name = \"Mimi\"\nprint(f\"Hello {name}\")", "o": ["Hello Mimi", "Hello {name}", "f\"Hello Mimi\"", "Hello name"], "a": 0, "why": "An f-string has `f` before the quotes. Python puts the value of each name in `{curly brackets}` into the text.", "at": "python_song.html#step2"},
  {"id": "ev_08", "unit": "easy_variables", "d": -0.3, "type": "mcq", "q": "This string has no `f` before it. What does it print?", "code": "name = \"Mimi\"\nprint(\"Hello {name}\")", "o": ["Hello Mimi", "Hello {name}", "Hello name", "Hello"], "a": 1, "why": "There is no `f` before the quotes, so it is a plain string. Python prints the curly brackets as they are.", "at": "python_song.html#step2"},
  {"id": "ev_09", "unit": "easy_variables", "d": -0.6, "type": "mcq", "q": "`animal` is given two values, one after the other. What does this print?", "code": "animal = \"pig\"\nanimal = \"dog\"\nprint(animal)", "o": ["pig", "pig dog", "dog", "pig, then dog"], "a": 2, "why": "A name holds one value at a time. The second line gives `animal` a new value, so the old one is gone.", "at": "python_song.html#step1"},
  {"id": "ev_10", "unit": "easy_variables", "d": -0.3, "type": "mcq", "q": "What does `int(\"15\")` give?", "o": ["The text \"15\"", "The numbers 1 and 5", "An error: text is never a number", "The number 15"], "a": 3, "why": "`int()` turns text that looks like a whole number into the number itself.", "at": "python_piggy.html#step7"},
  {"id": "ev_11", "unit": "easy_variables", "d": -0.2, "type": "mcq", "q": "What does `str(2147483647)` give?", "o": ["The text \"2147483647\"", "The number 2147483647", "The number 10, its digits", "A list of the ten digits"], "a": 0, "why": "`str()` turns a value into text. Then `len()` can count its characters, which are the number's digits.", "at": "python_piggy.html#step8"},
  {"id": "ev_12", "unit": "easy_variables", "d": -0.1, "type": "mcq", "q": "What does this print about the year 2026?", "code": "print(len(str(2026)))", "o": ["2026", "4", "1", "\"2026\""], "a": 1, "why": "`str(2026)` is the text \"2026\", and `len()` counts its four characters.", "at": "python_piggy.html#step8"},
  {"id": "ev_13", "unit": "easy_variables", "d": -0.4, "type": "mcq", "q": "What does `print(7 // 2)` print?", "o": ["3.5", "4", "3", "1"], "a": 2, "why": "`//` divides and keeps the whole number: 7 divided by 2 is 3, with 1 left over.", "at": "python_song.html#step6"},
  {"id": "ev_14", "unit": "easy_variables", "d": -0.5, "type": "mcq", "q": "What does `print(\"ha\" * 3)` print?", "o": ["ha3", "ha ha ha", "An error: text cannot be multiplied", "hahaha"], "a": 3, "why": "`*` with a string and a whole number repeats the string, with nothing in between.", "at": "python_piggy.html#puzzle3"},
  {"id": "ev_15", "unit": "easy_variables", "d": -0.4, "type": "mcq", "q": "What does `print(\"mango\" + \"roti\")` print?", "o": ["mangoroti", "mango roti", "mango + roti", "An error: text cannot be added"], "a": 0, "why": "`+` joins two strings into one. It adds no space: put one in the text if you want one.", "at": "python_piggy.html#puzzle1"},
  {"id": "ev_16", "unit": "easy_variables", "d": -0.2, "type": "mcq", "q": "What does `print(\"Mango\".upper())` print?", "o": ["Mango", "MANGO", "mango", "An error: upper() needs a number"], "a": 1, "why": "`upper()` gives the same text with every letter in capitals. `lower()` makes every letter small.", "at": "python_piggy.html#step5"},
  {"id": "ev_17", "unit": "easy_variables", "d": 0.1, "type": "mcq", "q": "What does `print(\"3.14.2\".split(\".\"))` print?", "o": ["[3, 14, 2]", "3 14 2", "['3', '14', '2']", "['3.14.2']"], "a": 2, "why": "`split(\".\")` cuts the text at each dot into a list of pieces. The pieces are still text, so they have quotes.", "at": "python_piggy.html#step7"},
  {"id": "ev_18", "unit": "easy_variables", "d": 0.0, "type": "mcq", "q": "Python stops with a `SyntaxError` on `print(\"Hello)`. Why?", "o": ["print needs two values", "Hello must start with a small h", "Strings cannot be printed", "The closing quote is missing"], "a": 3, "why": "A string needs a quote at each end. Without the second one, Python cannot tell where the text stops.", "at": "python_song.html#step1"},
  {"id": "ev_19", "unit": "easy_variables", "d": -0.3, "type": "mcq", "q": "Which line makes a variable called `my_age` that holds the number 10?", "o": ["`my_age = 10`", "`10 = my_age`", "`my_age == 10`", "`my age = 10`"], "a": 0, "why": "The name goes on the left and the value on the right. `==` compares, and a name cannot have a space in it.", "at": "python_piggy.html#step1"},
  {"id": "ev_20", "unit": "easy_variables", "d": 0.2, "type": "mcq", "q": "A string can be written between three quotes, `\"\"\"`. What can it do that a string in one pair of quotes cannot?", "o": ["Hold numbers as well as words", "Go on for many lines", "Be changed after it is made", "Hold Chinese and Thai letters"], "a": 1, "why": "Three quotes let a piece of text go on over many lines. Any string can hold numbers or any language.", "at": "python_piggy.html#step10"},
  {"id": "el_01", "unit": "easy_lists", "d": -1.0, "type": "mcq", "q": "Which brackets make a list?", "o": ["Round brackets: ( )", "Curly brackets: { }", "Square brackets: [ ]", "Angle brackets: < >"], "a": 2, "why": "A list is written in square brackets, with a comma between the items.", "at": "python_piggy.html#step2"},
  {"id": "el_02", "unit": "easy_lists", "d": -0.8, "type": "mcq", "q": "What does `snacks[0]` print here?", "code": "snacks = [\"mango\", \"sticky rice\", \"khao soi\"]\nprint(snacks[0])", "o": ["sticky rice", "khao soi", "An error: there is no item 0", "mango"], "a": 3, "why": "Python counts from 0, so `[0]` is the first item.", "at": "python_piggy.html#step2"},
  {"id": "el_03", "unit": "easy_lists", "d": -0.6, "type": "mcq", "q": "What does `snacks[-1]` print here?", "code": "snacks = [\"mango\", \"sticky rice\", \"khao soi\"]\nprint(snacks[-1])", "o": ["khao soi", "mango", "sticky rice", "An error: no index is below 0"], "a": 0, "why": "`[-1]` is the last item of a list, `[-2]` the one before it.", "at": "python_piggy.html#step2"},
  {"id": "el_04", "unit": "easy_lists", "d": -0.7, "type": "fill", "q": "Which function counts the items in a list, as in `___(snacks)`? Type its name.", "answers": ["len"], "why": "`len()` counts the items of a list. Given a string, it counts the characters.", "at": "python_piggy.html#step2"},
  {"id": "el_05", "unit": "easy_lists", "d": -0.6, "type": "mcq", "q": "What does `snacks.append(\"roti\")` do?", "o": ["Puts \"roti\" at the very start", "Adds \"roti\" at the end", "Swaps \"roti\" for the last item", "Makes a list that holds only \"roti\""], "a": 1, "why": "`append()` adds one more item at the end of the list. The items already there stay where they are.", "at": "python_piggy.html#step2"},
  {"id": "el_06", "unit": "easy_lists", "d": -0.5, "type": "mcq", "q": "After `append()`, what does `len(snacks)` print?", "code": "snacks = [\"mango\", \"sticky rice\", \"khao soi\"]\nsnacks.append(\"roti\")\nprint(len(snacks))", "o": ["3", "5", "4", "roti"], "a": 2, "why": "The list had three items, and `append()` added a fourth.", "at": "python_piggy.html#step2"},
  {"id": "el_07", "unit": "easy_lists", "d": -0.4, "type": "mcq", "q": "What does `snacks[1]` print here?", "code": "snacks = [\"mango\", \"sticky rice\", \"khao soi\"]\nprint(snacks[1])", "o": ["mango", "khao soi", "sticky", "sticky rice"], "a": 3, "why": "Counting from 0, `[1]` is the second item. The item is the whole string, `sticky rice`.", "at": "python_piggy.html#step2"},
  {"id": "el_08", "unit": "easy_lists", "d": -0.3, "type": "mcq", "q": "What is the number in the brackets of `snacks[2]` called?", "o": ["An index", "A key", "A value", "A parameter"], "a": 0, "why": "The place number of an item in a list is its index. A dictionary uses keys.", "at": "python_piggy.html#step2"},
  {"id": "el_09", "unit": "easy_lists", "d": -0.2, "type": "mcq", "q": "Two snacks, and the code asks for `snacks[2]`. What happens?", "code": "snacks = [\"mango\", \"roti\"]\nprint(snacks[2])", "o": ["It prints `roti`, the last one", "Python stops with an `IndexError`", "It prints `mango`, going round", "It prints an empty line"], "a": 1, "why": "The list has places 0 and 1 only. Asking for place 2 stops Python with an `IndexError`.", "at": "python_piggy.html#step2"},
  {"id": "el_10", "unit": "easy_lists", "d": -0.3, "type": "mcq", "q": "Two lists are joined with `+`. What does this print?", "code": "a = [\"do\", \"re\"]\nb = [\"mi\"]\nprint(len(a + b))", "o": ["2", "1", "3", "5"], "a": 2, "why": "`+` joins two lists into one longer list, like train cars: `['do', 're', 'mi']` has three items.", "at": "python_song.html#step5"},
  {"id": "el_11", "unit": "easy_lists", "d": -0.1, "type": "mcq", "q": "What does this `join()` print?", "code": "print(\"-\".join([\"do\", \"re\", \"mi\"]))", "o": ["do re mi", "['do', 're', 'mi']", "-do-re-mi-", "do-re-mi"], "a": 3, "why": "`join()` glues the items of a list into one string, with the text before the dot between each two.", "at": "python_song.html#step5"},
  {"id": "el_12", "unit": "easy_lists", "d": 0.0, "type": "mcq", "q": "`\"\\n\".join(lines)` glues a list of lines into one string. What goes between the lines?", "o": ["A new line", "A space", "A comma", "Nothing at all"], "a": 0, "why": "`\\n` is the new-line character, so each line of the list starts on a row of its own.", "at": "python_song.html#step5"},
  {"id": "el_13", "unit": "easy_lists", "d": -0.2, "type": "mcq", "q": "What does `sorted()` print here?", "code": "print(sorted([\"mango\", \"banana\", \"longan\"]))", "o": ["['mango', 'banana', 'longan']", "['banana', 'longan', 'mango']", "['mango', 'longan', 'banana']", "banana longan mango"], "a": 1, "why": "`sorted()` makes a new list in order from A to Z.", "at": "python_piggy.html#step2"},
  {"id": "el_14", "unit": "easy_lists", "d": 0.1, "type": "mcq", "q": "A list of names is sorted. Why does `_abc` come before `array`?", "o": ["Names with _ are always kept first", "sorted() puts short names first", "_ comes before every small letter", "_abc was put into the list first"], "a": 2, "why": "`sorted()` compares the characters, and `_` comes before all the small letters a to z.", "at": "python_piggy.html#step2"},
  {"id": "el_15", "unit": "easy_lists", "d": -0.7, "type": "mcq", "q": "In a list, what goes between the items?", "o": ["A space", "A colon", "A full stop", "A comma"], "a": 3, "why": "Items are separated by commas: `[\"mango\", \"roti\"]`. A missing comma is a common mistake.", "at": "python_song.html#step3"},
  {"id": "el_16", "unit": "easy_lists", "d": 0.2, "type": "mcq", "q": "Two items are appended to a list. What does it print?", "code": "fruits = [\"mango\", \"longan\"]\nfruits.append(\"banana\")\nfruits.append(\"mango\")\nprint(fruits)", "o": ["['mango', 'longan', 'banana', 'mango']", "['mango', 'longan', 'banana']", "['banana', 'mango', 'mango', 'longan']", "['mango', 'mango', 'longan', 'banana']"], "a": 0, "why": "Each `append()` adds at the end, in order. A list may hold the same item twice.", "at": "python_piggy.html#step2"},
  {"id": "el_17", "unit": "easy_lists", "d": 0.0, "type": "mcq", "q": "`platform.architecture()` answers with two things, like a list of two. How do you get the first one?", "o": ["`platform.architecture()[1]`", "`platform.architecture()[0]`", "`platform.architecture(0)`", "`platform.architecture[0]()`"], "a": 1, "why": "Call the function first, with its brackets, and then take place 0 of its answer with `[0]`.", "at": "python_piggy.html#step3"},
  {"id": "el_18", "unit": "easy_lists", "d": -0.1, "type": "mcq", "q": "What does `word[0]` print?", "code": "word = \"Pigs\"\nprint(word[0])", "o": ["Pigs", "s", "P", "i"], "a": 2, "why": "A string can be counted like a list: `[0]` is its first character.", "at": "python_piggy.html#puzzle1"},
  {"id": "el_19", "unit": "easy_lists", "d": 0.2, "type": "mcq", "q": "`sorted()`, and then the old list. What does this print, on two lines?", "code": "numbers = [3, 1, 2]\nprint(sorted(numbers))\nprint(numbers)", "o": ["[1, 2, 3] then [1, 2, 3]", "[3, 1, 2] then [1, 2, 3]", "[3, 2, 1] then [3, 1, 2]", "[1, 2, 3] then [3, 1, 2]"], "a": 3, "why": "`sorted()` makes a new list in order. The old list is not changed.", "at": "python_piggy.html#step2"},
  {"id": "el_20", "unit": "easy_lists", "d": 0.3, "type": "mcq", "q": "`len()` of a list and of its first item. What does this print?", "code": "verse = [\"la\", \"la\", \"la\"]\nprint(len(verse), len(verse[0]))", "o": ["3 2", "3 3", "2 3", "6 2"], "a": 0, "why": "`len(verse)` counts the three items. `verse[0]` is the string `la`, and `len()` counts its two characters.", "at": "python_song.html#step3"},
  {"id": "ed_01", "unit": "easy_dicts", "d": -0.9, "type": "mcq", "q": "Which brackets make a dictionary?", "o": ["Square brackets: [ ]", "Curly brackets: { }", "Round brackets: ( )", "Angle brackets: < >"], "a": 1, "why": "A dictionary is written in curly brackets, with pairs inside: a key, a colon, and its value.", "at": "python_piggy.html#step4"},
  {"id": "ed_02", "unit": "easy_dicts", "d": -0.8, "type": "mcq", "q": "What does `pig[\"food\"]` print?", "code": "pig = {\"name\": \"Princess Taiping\", \"food\": \"mango\"}\nprint(pig[\"food\"])", "o": ["food", "Princess Taiping", "mango", "{'food': 'mango'}"], "a": 2, "why": "`pig[\"food\"]` looks up the key `food` and gives its value.", "at": "python_piggy.html#step4"},
  {"id": "ed_03", "unit": "easy_dicts", "d": -0.6, "type": "mcq", "q": "In `{\"name\": \"Mali\", \"age\": 10}`, which are the keys?", "o": ["`\"Mali\"` and `10`", "`\"name\"` and `\"Mali\"`", "Only `\"name\"`", "`\"name\"` and `\"age\"`"], "a": 3, "why": "In each pair the key comes before the colon, and its value after it.", "at": "python_piggy.html#step4"},
  {"id": "ed_04", "unit": "easy_dicts", "d": -0.6, "type": "mcq", "q": "How do you add the key `\"colour\"`, with the value `\"pink\"`, to the dictionary `pig`?", "o": ["`pig[\"colour\"] = \"pink\"`", "`pig.append(\"colour\", \"pink\")`", "`pig[\"pink\"] = \"colour\"`", "`pig(\"colour\") = \"pink\"`"], "a": 0, "why": "Give a new key a value with square brackets and `=`. A dictionary has no `append()`.", "at": "python_piggy.html#step4"},
  {"id": "ed_05", "unit": "easy_dicts", "d": -0.5, "type": "mcq", "q": "A list finds an item by its place: 0, 1, 2. How does a dictionary find a value?", "o": ["By its place, from 0", "By its key", "By its length", "By the time it was added"], "a": 1, "why": "A dictionary finds a value by its key, the way a price list finds a price by the fruit's name.", "at": "python_browser.html#step5"},
  {"id": "ed_06", "unit": "easy_dicts", "d": -0.3, "type": "mcq", "q": "A key that is there already gets a new value. What does this print?", "code": "prices = {\"mango\": 60}\nprices[\"mango\"] = 55\nprint(prices)", "o": ["{'mango': 60, 'mango': 55}", "{'mango': 60}", "{'mango': 55}", "{'mango': [60, 55]}"], "a": 2, "why": "A key is never in a dictionary twice. A key that is there already gets the new value.", "at": "python_browser.html#step5"},
  {"id": "ed_07", "unit": "easy_dicts", "d": -0.2, "type": "mcq", "q": "The code looks up a key that is not there. What happens?", "code": "prices = {\"mango\": 60}\nprint(prices[\"durian\"])", "o": ["It prints `0`, for no price", "It prints `None` and goes on", "It adds `durian` with no value", "Python stops with a `KeyError`"], "a": 3, "why": "There is no key `durian`, so looking it up with square brackets stops the program with a `KeyError`.", "at": "python_browser.html#step5"},
  {"id": "ed_08", "unit": "easy_dicts", "d": -0.1, "type": "mcq", "q": "What does `get()` print here?", "code": "prices = {\"mango\": 60}\nprint(prices.get(\"durian\", 0))", "o": ["0", "60", "durian", "KeyError"], "a": 0, "why": "`get()` gives the second value, here 0, when the key is not there, and does not stop the program.", "at": "python_browser.html#step5"},
  {"id": "ed_09", "unit": "easy_dicts", "d": -0.3, "type": "mcq", "q": "What does `in` print here?", "code": "prices = {\"mango\": 60, \"longan\": 50}\nprint(\"durian\" in prices)", "o": ["True", "False", "0", "durian"], "a": 1, "why": "`in` asks whether a key is in the dictionary. `durian` is not one of the keys.", "at": "python_browser.html#step5"},
  {"id": "ed_10", "unit": "easy_dicts", "d": -0.4, "type": "mcq", "q": "A loop goes over a dictionary. What does it print, line by line?", "code": "pig = {\"name\": \"Taiping\", \"food\": \"mango\"}\nfor key in pig:\n    print(key)", "o": ["Taiping, then mango", "name: Taiping, then food: mango", "name, then food", "Only name"], "a": 2, "why": "A loop over a dictionary gives you its keys, one at a time.", "at": "python_piggy.html#step5"},
  {"id": "ed_11", "unit": "easy_dicts", "d": -0.2, "type": "mcq", "q": "What does `len()` count when you give it a dictionary?", "o": ["The letters in all the keys", "The values, added up", "The longest value", "The number of keys"], "a": 3, "why": "`len()` counts the keys of a dictionary, the way it counts the items of a list.", "at": "python_piggy.html#step4"},
  {"id": "ed_12", "unit": "easy_dicts", "d": -0.7, "type": "mcq", "q": "What does `python = {}` make?", "o": ["An empty dictionary", "An empty list", "An empty set", "An empty string"], "a": 0, "why": "`{}` on its own is an empty dictionary, ready to be filled one key at a time. An empty set is `set()`.", "at": "python_piggy.html#step4"},
  {"id": "ed_13", "unit": "easy_dicts", "d": 0.1, "type": "mcq", "q": "A stock count is changed and a key is added. What does this print?", "code": "stock = {\"mango\": 3}\nstock[\"mango\"] = stock[\"mango\"] + 2\nstock[\"lime\"] = 1\nprint(stock)", "o": ["{'mango': 3, 'mango': 5, 'lime': 1}", "{'mango': 5, 'lime': 1}", "{'mango': 2, 'lime': 1}", "{'mango': 3, 'lime': 1}"], "a": 1, "why": "`mango` gets 3 + 2, which is 5, and `lime` is a new key with the value 1.", "at": "python_browser.html#step5"},
  {"id": "ed_14", "unit": "easy_dicts", "d": 0.1, "type": "mcq", "q": "Which loop gives each key of `prices` together with its value?", "o": ["`for fruit in prices.keys(1):`", "`for fruit, price in prices:`", "`for fruit, price in prices.items():`", "`for price in prices.values(fruit):`"], "a": 2, "why": "`items()` gives each key with its value, so the loop can take two names.", "at": "python_browser.html#step5"},
  {"id": "ed_15", "unit": "easy_dicts", "d": 0.2, "type": "mcq", "q": "A loop counts what was sold. What does this print?", "code": "sold = {}\nfor fruit in [\"mango\", \"lime\", \"mango\"]:\n    sold[fruit] = sold.get(fruit, 0) + 1\nprint(sold)", "o": ["{'mango': 1, 'lime': 1}", "{'mango': 1, 'lime': 1, 'mango': 1}", "{'mango': 3, 'lime': 3}", "{'mango': 2, 'lime': 1}"], "a": 3, "why": "`sold.get(fruit, 0) + 1` counts one more each time, starting from 0. Mango comes twice, lime once.", "at": "python_browser.html#step9"},
  {"id": "ed_16", "unit": "easy_dicts", "d": 0.0, "type": "mcq", "q": "`sold = {\"mango\": 2, \"lime\": 1}`. What does `sum(sold.values())` give?", "o": ["3", "2", "2 1", "mangolime"], "a": 0, "why": "`values()` gives just the values, 2 and 1, and `sum()` adds them up.", "at": "python_browser.html#step9"},
  {"id": "ed_17", "unit": "easy_dicts", "d": 0.3, "type": "mcq", "q": "A treasure hunt follows two clues. What does this print?", "code": "clues = {\"door\": \"sofa\", \"sofa\": \"lamp\"}\nplace = \"door\"\nplace = clues[place]\nplace = clues[place]\nprint(place)", "o": ["sofa", "lamp", "door", "KeyError"], "a": 1, "why": "The first lookup turns `door` into `sofa`, and the second turns `sofa` into `lamp`.", "at": "python_piggy.html#puzzle4"},
  {"id": "ed_18", "unit": "easy_dicts", "d": -0.1, "type": "mcq", "q": "Is `pig[\"best friend\"] = \"Mali\"` allowed, with a space inside the key?", "o": ["No: a key must be one word", "No: keys must be numbers", "Yes: any string can be a key", "Yes, but it then has no value"], "a": 2, "why": "A key can be any string, spaces and all. A variable's name cannot have a space; a key can.", "at": "python_piggy.html#step4"},
  {"id": "lo_01", "unit": "easy_loops", "d": -0.9, "type": "mcq", "q": "What does this loop print?", "code": "for snack in [\"mango\", \"roti\"]:\n    print(\"Taiping eats\", snack)", "o": ["Taiping eats ['mango', 'roti'] on one line", "Taiping eats snack, two times", "Taiping eats mango, only once", "Taiping eats mango, then Taiping eats roti"], "a": 3, "why": "The loop takes the items one at a time. Each time round, `snack` holds the next item and the line under it runs once.", "at": "python_piggy.html#step5"},
  {"id": "lo_02", "unit": "easy_loops", "d": -0.8, "type": "mcq", "q": "How does Python know which lines a `for` loop repeats?", "o": ["They are pushed in under it", "They are in curly brackets", "They end with a semicolon", "They come before the for line"], "a": 0, "why": "The lines pushed in by four spaces, under the line that ends with a colon, are what repeats. Python reads the spaces.", "at": "python_piggy.html#step5"},
  {"id": "lo_03", "unit": "easy_loops", "d": -0.7, "type": "fill", "q": "Which sign must end the line `for snack in snacks`? Type the sign.", "answers": [":", "colon"], "why": "A `for` line ends with a colon. So do `if`, `else`, `def` and every other line that has lines pushed in under it.", "at": "python_piggy.html#step5"},
  {"id": "lo_04", "unit": "easy_loops", "d": -0.5, "type": "mcq", "q": "A loop, then one more line. What does this print?", "code": "for word in [\"la\", \"la\"]:\n    print(word)\nprint(\"end\")", "o": ["la, end, la, end: on four lines", "la, la, end: on three lines", "la, la, end, end: on four lines", "end, la, la: on three lines"], "a": 1, "why": "Only the pushed-in line repeats. `print(\"end\")` is not pushed in, so it runs once, after the loop.", "at": "python_song.html#step3"},
  {"id": "lo_05", "unit": "easy_loops", "d": -0.4, "type": "mcq", "q": "A loop counts the names. What does it print?", "code": "count = 0\nfor name in [\"sys\", \"math\", \"time\"]:\n    count = count + 1\nprint(count)", "o": ["0", "1", "3", "time"], "a": 2, "why": "The loop runs once for each of the three names, and each time adds 1 to `count`.", "at": "python_piggy.html#step5"},
  {"id": "lo_06", "unit": "easy_loops", "d": -0.3, "type": "mcq", "q": "A loop calls `upper()`. What does it print?", "code": "for snack in [\"mango\", \"roti\"]:\n    print(snack.upper())", "o": ["mango, then roti", "MANGO ROTI on one line", "SNACK, two times", "MANGO, then ROTI"], "a": 3, "why": "Each time round, `snack` holds the next item, and `upper()` puts it in capitals.", "at": "python_piggy.html#step5"},
  {"id": "lo_07", "unit": "easy_loops", "d": -0.2, "type": "mcq", "q": "A loop builds a message from first letters. What does it print?", "code": "message = \"\"\nfor word in [\"Pigs\", \"Yell\"]:\n    message = message + word[0]\nprint(message)", "o": ["PY", "Pigs Yell", "PigsYell", "P"], "a": 0, "why": "`word[0]` is the first letter of each word, and `+` joins it onto the end of `message`.", "at": "python_piggy.html#puzzle1"},
  {"id": "lo_08", "unit": "easy_loops", "d": -0.1, "type": "mcq", "q": "What does this loop over `range(3)` print?", "code": "for number in range(3):\n    print(number)", "o": ["1, 2, 3", "0, 1, 2", "0, 1, 2, 3", "3"], "a": 1, "why": "`range(3)` counts from 0 and stops before 3: 0, 1 and 2.", "at": "python_browser.html#step7"},
  {"id": "lo_09", "unit": "easy_loops", "d": 0.0, "type": "mcq", "q": "A loop adds up prices. What does it print?", "code": "total = 0\nfor price in [60, 50, 25]:\n    total = total + price\nprint(total)", "o": ["25", "60 50 25", "135", "0"], "a": 2, "why": "Each time round, the next price is added to `total`: 60, then 110, then 135.", "at": "python_piggy.html#step5"},
  {"id": "lo_10", "unit": "easy_loops", "d": 0.1, "type": "mcq", "q": "A loop looks for the longest name. What does it print?", "code": "longest = \"\"\nfor name in [\"sys\", \"math\", \"json\"]:\n    if len(name) > len(longest):\n        longest = name\nprint(longest)", "o": ["json", "sys", "mathjson", "math"], "a": 3, "why": "`json` is as long as `math`, not longer, so `>` is False for it and `math` stays.", "at": "python_piggy.html#puzzle2"},
  {"id": "lo_11", "unit": "easy_loops", "d": -0.6, "type": "mcq", "q": "What does a `for` loop over a list do?", "o": ["Takes the items one at a time", "Sorts the items from A to Z", "Counts the items and stops", "Takes only the first item"], "a": 0, "why": "A `for` loop takes the items one at a time, in order, and runs the lines under it once for each.", "at": "python_piggy.html#step5"},
  {"id": "lo_12", "unit": "easy_loops", "d": 0.2, "type": "mcq", "q": "Two lines are pushed in under the loop. What does it print?", "code": "for fruit in [\"mango\", \"lime\"]:\n    print(fruit)\n    print(\"~\")", "o": ["mango, lime, ~", "mango, ~, lime, ~", "mango, lime, ~, ~", "~, mango, ~, lime"], "a": 1, "why": "Both lines are pushed in, so both repeat: one fruit, then `~`, each time round.", "at": "python_song.html#step3"},
  {"id": "lo_13", "unit": "easy_loops", "d": 0.2, "type": "mcq", "q": "Only one line is pushed in under the loop. What does it print?", "code": "for fruit in [\"mango\", \"lime\"]:\n    print(fruit)\nprint(\"~\")", "o": ["mango, ~, lime, ~", "~, mango, lime", "mango, lime, ~", "mango, lime, ~, ~"], "a": 2, "why": "`print(\"~\")` is not pushed in, so it is not part of the loop. It runs once, after the loop ends.", "at": "python_song.html#step3"},
  {"id": "lo_14", "unit": "easy_loops", "d": 0.1, "type": "mcq", "q": "What does `break` do inside a loop?", "o": ["Skips one line of the loop", "Starts the loop again from the top", "Stops Python with an error", "Ends the loop at once"], "a": 3, "why": "`break` ends the loop straight away. The program goes on with the first line after the loop.", "at": "python_browser.html#step9"},
  {"id": "lo_15", "unit": "easy_loops", "d": 0.3, "type": "mcq", "q": "A loop has a `break` in it. What does it print?", "code": "for number in range(10):\n    if number == 3:\n        break\n    print(number)", "o": ["0, 1, 2", "0, 1, 2, 3", "3", "0 to 9"], "a": 0, "why": "When `number` is 3, `break` ends the loop before the print, so 3 is never printed.", "at": "python_browser.html#step9"},
  {"id": "lo_16", "unit": "easy_loops", "d": -0.3, "type": "mcq", "q": "A loop goes over a string: `for letter in \"pig\":`. What does `letter` hold each time round?", "o": ["The whole word pig, once", "One letter: p, then i, then g", "The number of letters, 3", "Nothing: a word is not a list"], "a": 1, "why": "A loop over a string takes its characters one at a time.", "at": "python_piggy.html#puzzle5"},
  {"id": "lo_17", "unit": "easy_loops", "d": 0.0, "type": "mcq", "q": "In Jupyter you type a line that ends with a colon and press Enter. What happens?", "o": ["The line runs straight away", "The colon is taken away again", "The next line is pushed in for you", "A new cell starts under it"], "a": 2, "why": "Jupyter adds the spaces for you after a colon, because the lines under it must be pushed in.", "at": "python_piggy.html#step5"},
  {"id": "lo_18", "unit": "easy_loops", "d": 0.2, "type": "mcq", "q": "A loop counts the True answers. What does it print?", "code": "points = 0\nfor answer in [True, False, True]:\n    if answer:\n        points = points + 1\nprint(\"*\" * points)", "o": ["***", "*", "2", "**"], "a": 3, "why": "Two of the three answers are True, so `points` is 2, and `\"*\" * 2` repeats the star twice.", "at": "python_piggy.html#puzzle3"},
  {"id": "lo_19", "unit": "easy_loops", "d": -0.1, "type": "mcq", "q": "A loop over a list of three names runs `print(\"name:\", name)` for each. How many lines does it print?", "o": ["3", "1", "6", "4"], "a": 0, "why": "The print runs once each time round, and the loop goes round once for each of the three names.", "at": "python_piggy.html#step5"},
  {"id": "lo_20", "unit": "easy_loops", "d": 0.3, "type": "mcq", "q": "This loop is meant to print each snack, but Python stops with an `IndentationError`. What is wrong?", "code": "for snack in [\"mango\", \"roti\"]:\nprint(snack)", "o": ["The list needs round brackets", "print is not pushed in under the for", "snack must be called item", "A loop cannot print text"], "a": 1, "why": "The lines a loop repeats must be pushed in under the line with the colon. Here nothing is.", "at": "python_piggy.html#step5"},
  {"id": "ei_01", "unit": "easy_if", "d": -1.0, "type": "mcq", "q": "A boolean has only two values. Which?", "o": ["Yes and No", "1 and 2", "True and False", "On and Off"], "a": 2, "why": "A boolean is either `True` or `False`, with capital letters.", "at": "python_piggy.html#step6"},
  {"id": "ei_02", "unit": "easy_if", "d": -0.9, "type": "mcq", "q": "What does `print(5 > 3)` print?", "o": ["False", "5", "2", "True"], "a": 3, "why": "A question such as `5 > 3` gives a boolean. 5 is bigger than 3, so it is True.", "at": "python_piggy.html#step6"},
  {"id": "ei_03", "unit": "easy_if", "d": -0.8, "type": "mcq", "q": "What does `print(\"roti\" in [\"mango\", \"roti\"])` print?", "o": ["True", "False", "roti", "1"], "a": 0, "why": "`in` asks whether something is in a list, and answers True or False.", "at": "python_piggy.html#step6"},
  {"id": "ei_04", "unit": "easy_if", "d": -0.6, "type": "mcq", "q": "What does `print(\"Taiping\" == \"taiping\")` print?", "o": ["True", "False", "Taiping", "An error"], "a": 1, "why": "A capital T is a different letter from a small t, so the two strings are not the same.", "at": "python_piggy.html#step7"},
  {"id": "ei_05", "unit": "easy_if", "d": -0.5, "type": "mcq", "q": "What does `print(\"15\" == 15)` print?", "o": ["True", "15", "False", "An error: text and numbers can't be compared"], "a": 2, "why": "Text is never a number, even when it looks like one, so they are not equal.", "at": "python_piggy.html#step7"},
  {"id": "ei_06", "unit": "easy_if", "d": -0.4, "type": "mcq", "q": "What does `print(int(\"15\") == 15)` print?", "o": ["False", "15", "\"15\"", "True"], "a": 3, "why": "`int(\"15\")` turns the text into the number 15, and 15 is equal to 15.", "at": "python_piggy.html#step7"},
  {"id": "ei_07", "unit": "easy_if", "d": -0.6, "type": "mcq", "q": "What is the difference between `=` and `==`?", "o": ["`=` gives a value; `==` compares two", "`=` compares two; `==` gives a value", "They do the same; `==` is for numbers", "`==` is a typing mistake for `=`"], "a": 0, "why": "One `=` gives a name a value. Two, `==`, ask whether two values are the same and give True or False.", "at": "python_piggy.html#step7"},
  {"id": "ei_08", "unit": "easy_if", "d": -0.3, "type": "mcq", "q": "What happens when you write `if sys.platform = \"darwin\":`?", "o": ["sys.platform becomes darwin", "Python stops with a SyntaxError", "The if part always runs", "The if part never runs"], "a": 1, "why": "An `if` needs a question, `==`. One `=` there is a mistake, and Python stops before it runs anything.", "at": "python_piggy.html#step7"},
  {"id": "ei_09", "unit": "easy_if", "d": -0.5, "type": "mcq", "q": "What does `!=` ask?", "o": ["Are they the same?", "Is the first bigger?", "Are they different?", "Is it not a number?"], "a": 2, "why": "`!=` asks the opposite of `==`: are these two values different?", "at": "python_piggy.html#step7"},
  {"id": "ei_10", "unit": "easy_if", "d": -0.3, "type": "mcq", "q": "`language` is `\"th\"`. What does this print?", "code": "language = \"th\"\nif language == \"en\":\n    print(\"English\")\nelif language == \"zh\":\n    print(\"Chinese\")\nelse:\n    print(\"something else\")", "o": ["English", "Chinese", "Nothing at all", "something else"], "a": 3, "why": "Neither test is True for `th`, so the `else` part runs.", "at": "python_song.html#step6"},
  {"id": "ei_11", "unit": "easy_if", "d": -0.2, "type": "mcq", "q": "What does `elif` mean?", "o": ["Else, if", "Else, finish", "Every line, if", "End the if"], "a": 0, "why": "`elif` means \"else, if\": it is tried only when the tests above it were False.", "at": "python_piggy.html#step7"},
  {"id": "ei_12", "unit": "easy_if", "d": 0.0, "type": "mcq", "q": "`x` is 10, and two of the tests could be True. What does this print?", "code": "x = 10\nif x > 5:\n    print(\"big\")\nelif x > 2:\n    print(\"middle\")\nelse:\n    print(\"small\")", "o": ["big, then middle", "big", "middle", "big, middle, small"], "a": 1, "why": "Python tries each test from the top and runs only the first one that is True. 10 > 2 is True too, but it is never looked at.", "at": "python_piggy.html#step7"},
  {"id": "ei_13", "unit": "easy_if", "d": -0.4, "type": "mcq", "q": "What does this `if` and `else` print?", "code": "if 2 + 2 == 5:\n    print(\"yes\")\nelse:\n    print(\"no\")", "o": ["yes", "yes, then no", "no", "Nothing"], "a": 2, "why": "2 + 2 is 4, so the test is False and the `else` part runs.", "at": "python_piggy.html#step7"},
  {"id": "ei_14", "unit": "easy_if", "d": -0.1, "type": "mcq", "q": "What does `print(\"Mango\".lower() == \"mango\")` print?", "o": ["False", "mango", "Mango", "True"], "a": 3, "why": "`lower()` makes every letter small, so the two strings are now exactly the same.", "at": "python_piggy.html#step7"},
  {"id": "ei_15", "unit": "easy_if", "d": -0.1, "type": "mcq", "q": "What does `print(\"_abc\".startswith(\"_\"))` print?", "o": ["True", "False", "_", "abc"], "a": 0, "why": "`startswith()` asks whether the text begins with the piece you give it, and answers True or False.", "at": "python_piggy.html#step6"},
  {"id": "ei_16", "unit": "easy_if", "d": 0.0, "type": "mcq", "q": "What does `print(\"wasm\" in \"wasm32\")` print?", "o": ["False", "True", "32", "wasm"], "a": 1, "why": "`in` also works on text: it asks whether one piece of text is inside another.", "at": "python_piggy.html#step6"},
  {"id": "ei_17", "unit": "easy_if", "d": 0.1, "type": "mcq", "q": "What does `print(len(\"mango\") == 5)` print?", "o": ["False", "5", "True", "mango"], "a": 2, "why": "`len(\"mango\")` is 5, and `5 == 5` is True.", "at": "python_piggy.html#step7"},
  {"id": "ei_18", "unit": "easy_if", "d": 0.2, "type": "mcq", "q": "`mood` is `\"sleepy\"`. What does this print?", "code": "mood = \"sleepy\"\nif mood == \"happy\":\n    bpm = 140\nelif mood == \"sleepy\":\n    bpm = 60\nelse:\n    bpm = 300\nprint(bpm)", "o": ["140", "300", "sleepy", "60"], "a": 3, "why": "The first test is False and the second is True, so `bpm` becomes 60.", "at": "python_song.html#step6"},
  {"id": "ei_19", "unit": "easy_if", "d": 0.2, "type": "fill", "q": "Which word joins two tests so that one True is enough? Type it.", "answers": ["or"], "why": "`a or b` is True when at least one of them is True. `and` needs both.", "at": "python_piggy.html#puzzle5"},
  {"id": "ei_20", "unit": "easy_if", "d": 0.3, "type": "mcq", "q": "Three `if` tests add points. What does this print?", "code": "points = 0\nif 3 > 1:\n    points = points + 1\nif \"a\" in \"cat\":\n    points = points + 1\nif 2 == \"2\":\n    points = points + 1\nprint(points)", "o": ["2", "3", "1", "0"], "a": 0, "why": "The first two tests are True. The last is False: the number 2 is not the text \"2\".", "at": "python_piggy.html#puzzle3"},
  {"id": "ei_21", "unit": "easy_if", "d": -0.7, "type": "mcq", "q": "An `if` has an `else`. How many of the two parts run?", "o": ["Both, one after the other", "Exactly one of them", "Neither, unless you press Enter", "Only the else part"], "a": 1, "why": "The `if` part runs when the test is True, the `else` part when it is False. Only one of the two ever runs.", "at": "python_piggy.html#step6"},
  {"id": "ei_22", "unit": "easy_if", "d": 0.1, "type": "mcq", "q": "In `version.split(\".\")[1] == \"14\"`, why is 14 in quotes?", "o": ["Quotes make a number bigger", "== only works on text", "split() gives text, not numbers", "14 is a key in a dictionary"], "a": 2, "why": "The pieces that `split()` cuts are text, so they are compared with text. The number 14 would never be equal.", "at": "python_piggy.html#step7"},
  {"id": "ef_01", "unit": "easy_functions", "d": -0.9, "type": "fill", "q": "Which word starts a new function, as in `___ greet(name):`? Type it.", "answers": ["def"], "why": "`def` makes a function and gives it a name.", "at": "python_piggy.html#step8"},
  {"id": "ef_02", "unit": "easy_functions", "d": -0.8, "type": "mcq", "q": "When do the lines under `def greet(name):` run?", "o": ["Once, when Python reads the def", "Never, until the file is saved", "Only the first time", "Each time greet is called"], "a": 3, "why": "The lines in a function's body do not run yet when Python reads them. They run each time you call it, as in `greet(\"Mali\")`.", "at": "python_piggy.html#step8"},
  {"id": "ef_03", "unit": "easy_functions", "d": -0.6, "type": "mcq", "q": "In `def greet(name):`, what is `name`?", "o": ["A parameter", "A key", "A module", "A string"], "a": 0, "why": "A parameter is a variable that holds whatever you put in the brackets when you call the function.", "at": "python_piggy.html#step8"},
  {"id": "ef_04", "unit": "easy_functions", "d": -0.6, "type": "mcq", "q": "What does `return` do?", "o": ["Prints the value on the screen", "Hands a value back to the caller", "Starts the function again", "Ends the whole program"], "a": 1, "why": "`return` hands a value back to the line that called the function, and ends the function there.", "at": "python_piggy.html#step8"},
  {"id": "ef_05", "unit": "easy_functions", "d": -0.5, "type": "mcq", "q": "What does `double(21)` print?", "code": "def double(number):\n    return number * 2\n\nprint(double(21))", "o": ["21", "2", "42", "double(21)"], "a": 2, "why": "`double(21)` puts 21 into `number`, and the function returns 21 * 2.", "at": "python_piggy.html#step8"},
  {"id": "ef_06", "unit": "easy_functions", "d": -0.4, "type": "mcq", "q": "What does `double(\"ha\")` print?", "code": "def double(x):\n    return x + x\n\nprint(double(\"ha\"))", "o": ["ha", "x + x", "An error: text cannot be added", "haha"], "a": 3, "why": "`x` holds the text `ha`, and `+` joins two strings: `haha`.", "at": "python_song.html#step4"},
  {"id": "ef_07", "unit": "easy_functions", "d": -0.2, "type": "mcq", "q": "What does `sale_price(60)` print?", "code": "def sale_price(price, off=10):\n    return price - off\n\nprint(sale_price(60))", "o": ["50", "60", "10", "70"], "a": 0, "why": "`off` has a default value: it is 10 unless you give another number. 60 - 10 is 50.", "at": "python_browser.html#step1"},
  {"id": "ef_08", "unit": "easy_functions", "d": -0.1, "type": "mcq", "q": "What does `sale_price(50, 20)` print?", "code": "def sale_price(price, off=10):\n    return price - off\n\nprint(sale_price(50, 20))", "o": ["40", "30", "20", "70"], "a": 1, "why": "The second number replaces the default, so `off` is 20 and 50 - 20 is 30.", "at": "python_browser.html#step1"},
  {"id": "ef_09", "unit": "easy_functions", "d": 0.0, "type": "mcq", "q": "A function prints, but has no `return`. What does this print?", "code": "def greet(name):\n    print(\"Hello\", name)\n\nresult = greet(\"Mali\")\nprint(result)", "o": ["Hello Mali, two times", "None, then Hello Mali", "Hello Mali, then None", "Only Hello Mali"], "a": 2, "why": "`greet` prints, but it has no `return`, so what it hands back is `None`.", "at": "python_piggy.html#step8"},
  {"id": "ef_10", "unit": "easy_functions", "d": -0.3, "type": "mcq", "q": "A function has no `return` line. What does it give back?", "o": ["0", "An empty string", "The last value it printed", "None"], "a": 3, "why": "A function with no `return` gives back `None`, Python's word for \"nothing\".", "at": "python_piggy.html#step8"},
  {"id": "ef_11", "unit": "easy_functions", "d": -0.2, "type": "mcq", "q": "How many lines does this print?", "code": "def twice(phrase):\n    print(phrase)\n    print(phrase)\n\ntwice(\"la\")\ntwice(\"do\")", "o": ["4", "2", "1", "3"], "a": 0, "why": "The function prints two lines each time, and it is called twice.", "at": "python_song.html#step4"},
  {"id": "ef_12", "unit": "easy_functions", "d": 0.0, "type": "mcq", "q": "What is a function for?", "o": ["To make Python run faster", "To use the same lines again, by name", "To let print work on numbers", "To keep variables safe from typos"], "a": 1, "why": "A function gives a few lines a name, so you can use them again and again without writing them again.", "at": "python_piggy.html#step8"},
  {"id": "ef_13", "unit": "easy_functions", "d": 0.1, "type": "mcq", "q": "What kind of value does `is_open(\"json\")` give back?", "code": "import sys\n\ndef is_open(module):\n    return module in sys.modules", "o": ["A module", "A list of modules", "A boolean: True or False", "A string such as json"], "a": 2, "why": "`module in sys.modules` is a question, so its answer, and what the function returns, is True or False.", "at": "python_piggy.html#step8"},
  {"id": "ef_14", "unit": "easy_functions", "d": 0.2, "type": "mcq", "q": "What does `if __name__ == \"__main__\":` check?", "o": ["Whether the file has a main() function", "Whether Python is the newest version", "Whether another file imported main()", "Whether this file is the one you ran"], "a": 3, "why": "When you run a file as the program, Python names it `\"__main__\"`. When another file imports it, its name is its file name.", "at": "python_browser.html#step1"},
  {"id": "ef_15", "unit": "easy_functions", "d": 0.2, "type": "mcq", "q": "A file `stall.py` ends with `if __name__ == \"__main__\":` and `main()`. Another file runs `import stall`. Does `main()` run?", "o": ["No: there `__name__` is `\"stall\"`", "Yes: import runs every line", "Yes, but its prints are hidden", "No: import never runs any line"], "a": 0, "why": "Importing runs the file, but its `__name__` is now `\"stall\"`, so the `if` is False and `main()` does not run.", "at": "python_browser.html#step2"},
  {"id": "ef_16", "unit": "easy_functions", "d": -0.1, "type": "mcq", "q": "Where do the functions usually go in a Python file that has a `main()`?", "o": ["At the very end, after main()", "First, then main(), then the if line", "Inside the if line at the end", "Each in a file of its own"], "a": 1, "why": "Almost every program is laid out the same way: functions first, then `main()`, then the `if __name__` line that runs it.", "at": "python_browser.html#step1"},
  {"id": "ef_17", "unit": "easy_functions", "d": 0.1, "type": "mcq", "q": "What does `len(chorus(\"Peppa\"))` print?", "code": "def chorus(name):\n    return [\"[chorus]\", name + \" is a pig\"]\n\nprint(len(chorus(\"Peppa\")))", "o": ["1", "5", "2", "[chorus]"], "a": 2, "why": "The function returns a list of two strings, and `len()` counts its two items.", "at": "python_song.html#step4"},
  {"id": "ef_18", "unit": "easy_functions", "d": 0.3, "type": "mcq", "q": "A `print` comes after `return`. What does this print?", "code": "def add(a, b):\n    return a + b\n    print(\"done\")\n\nprint(add(2, 3))", "o": ["5, then done", "done, then 5", "An error: print after return", "5 only: return ends it"], "a": 3, "why": "`return` hands back the answer and ends the function there, so the line after it never runs.", "at": "python_piggy.html#step8"},
  {"id": "ef_19", "unit": "easy_functions", "d": 0.3, "type": "mcq", "q": "What does `digits(2147483647)` print?", "code": "def digits(number):\n    return len(str(number))\n\nprint(digits(2147483647))", "o": ["10", "2147483647", "1", "4"], "a": 0, "why": "`str()` turns the number into text, and `len()` counts its ten characters.", "at": "python_piggy.html#step8"},
  {"id": "ef_20", "unit": "easy_functions", "d": 0.2, "type": "mcq", "q": "In `play(\"G A B G\", bpm=bpm)`, what is `bpm=bpm`?", "o": ["Two variables made equal", "play's name for it, given your value", "A test: are the two the same?", "A typing mistake Python skips"], "a": 1, "why": "The first `bpm` is the name that `play` uses, and the second is your variable with the number in it.", "at": "python_song.html#step6"},
  {"id": "em_01", "unit": "easy_modules", "d": -0.9, "type": "mcq", "q": "What does `import platform` do?", "o": ["Installs a program from the internet", "Makes a new file called platform", "Opens a toolbox of code, a module", "Prints facts about the computer"], "a": 2, "why": "`import` opens a module: a toolbox of code. `platform` tells you about the computer under Python.", "at": "python_piggy.html#step1"},
  {"id": "em_02", "unit": "easy_modules", "d": -0.7, "type": "mcq", "q": "What are the modules that come with Python called, all together?", "o": ["The Python store", "The package index", "The module shop", "The standard library"], "a": 3, "why": "Python comes with hundreds of modules, called its standard library. You install none of them.", "at": "python_piggy.html#step2"},
  {"id": "em_03", "unit": "easy_modules", "d": -0.5, "type": "fill", "q": "On a Mac, what is `sys.platform`? Type it.", "answers": ["darwin"], "why": "`sys.platform` is one word for where Python runs: `darwin` on a Mac, `win32` on Windows, `linux` on Linux.", "at": "python_piggy.html#step1"},
  {"id": "em_04", "unit": "easy_modules", "d": -0.5, "type": "fill", "q": "On Windows, what is `sys.platform`? Type it.", "answers": ["win32"], "why": "On Windows it is `win32`, even on a 64-bit computer. On a Mac it is `darwin`.", "at": "python_piggy.html#step1"},
  {"id": "em_05", "unit": "easy_modules", "d": -0.3, "type": "mcq", "q": "Why does `sys.platform` have no brackets after it, when `platform.system()` has?", "o": ["It is a value, not a function you call", "It is a function that needs no input", "Brackets are optional in Python", "sys is older than platform"], "a": 0, "why": "`sys.platform` is a value that `sys` keeps. `platform.system()` is a function: the brackets ask it the question.", "at": "python_piggy.html#step1"},
  {"id": "em_06", "unit": "easy_modules", "d": -0.4, "type": "mcq", "q": "What does `from tune import play` do?", "o": ["Plays the file tune.py out loud", "Borrows the function play from tune.py", "Copies tune.py into a new file", "Imports every function except play"], "a": 1, "why": "`from ... import ...` takes one name out of another file, so you can call `play()` straight away.", "at": "python_song.html#step1"},
  {"id": "em_07", "unit": "easy_modules", "d": -0.2, "type": "mcq", "q": "Inside a web browser, `sys.platform` says `emscripten`. What is Emscripten?", "o": ["A web browser made for Python", "A kind of computer chip", "The tool that made Python WebAssembly", "A Python module for music"], "a": 2, "why": "Emscripten turned Python into WebAssembly, a kind of program every web browser can run.", "at": "python_browser.html#inside"},
  {"id": "em_08", "unit": "easy_modules", "d": -0.1, "type": "mcq", "q": "`statistics` is looked for before and after its import. What does this print?", "code": "import sys\nprint(\"statistics\" in sys.modules)\nimport statistics\nprint(\"statistics\" in sys.modules)", "o": ["True, then True", "False, then False", "True, then False", "False, then True"], "a": 3, "why": "`sys.modules` holds every module Python has opened so far. `statistics` is opened only by the import.", "at": "python_piggy.html#step6"},
  {"id": "em_09", "unit": "easy_modules", "d": 0.0, "type": "mcq", "q": "What does `sys.modules` hold?", "o": ["Every module Python has opened so far", "Every module that comes with Python", "Only the modules you installed", "The modules found on the internet"], "a": 0, "why": "`sys.modules` is a dictionary of the modules that Python has opened: the key is the name, the value is the module.", "at": "python_piggy.html#step4"},
  {"id": "em_10", "unit": "easy_modules", "d": 0.1, "type": "mcq", "q": "`platform.python_version()` gives `\"3.14.2\"`. What kind of value is that?", "o": ["A number", "A string", "A list", "A boolean"], "a": 1, "why": "It is in quotes: a string. Three numbers with dots between them are not one number.", "at": "python_piggy.html#step1"},
  {"id": "em_11", "unit": "easy_modules", "d": -0.3, "type": "mcq", "q": "Which of these comes with Python, so you install nothing?", "o": ["requests", "pandas", "json", "pydantic"], "a": 2, "why": "`json` is in the standard library. `requests`, `pandas` and `pydantic` are installed with pip.", "at": "python_piggy.html#step2"},
  {"id": "em_12", "unit": "easy_modules", "d": 0.0, "type": "fill", "q": "Which module that comes with Python picks things by chance? Type its name.", "answers": ["random"], "why": "`random` comes with Python: `\"random\" in sys.stdlib_module_names` is True.", "at": "python_piggy.html#step6"},
  {"id": "em_13", "unit": "easy_modules", "d": 0.1, "type": "fill", "q": "A module is a file of Python code. Which ending does that file's name have? Type it.", "answers": [".py", "py"], "why": "A Python file ends in `.py`, such as `tune.py`. `import tune` finds that file.", "at": "python_browser.html#boxes"},
  {"id": "em_14", "unit": "easy_modules", "d": 0.2, "type": "mcq", "q": "In Python 3.14, what does `print(sys.version_info.minor > 12)` print?", "o": ["False", "14", "12", "True"], "a": 3, "why": "`sys.version_info.minor` is the middle number of the version, 14, and 14 > 12 is True.", "at": "python_piggy.html#puzzle3"},
  {"id": "em_15", "unit": "easy_modules", "d": 0.3, "type": "mcq", "q": "You type `python -m http.server 8000` in a folder. What does it do?", "o": ["Serves the folder's files as web pages", "Downloads a web server from the internet", "Opens Python's >>> prompt", "Uploads the folder to a website"], "a": 0, "why": "`http.server` comes with Python. It serves the files in the folder, so a browser can open them at a web address.", "at": "python_piggy.html#avatar"},
  {"id": "ec_01", "unit": "easy_comprehensions", "d": -0.6, "type": "mcq", "q": "What does this list comprehension print?", "code": "prices = [60, 50, 25]\nprint([price - 10 for price in prices])", "o": ["[60, 50, 25]", "[50, 40, 15]", "[50]", "125"], "a": 1, "why": "A list comprehension makes a new list: \"price minus 10, for each price in prices\".", "at": "python_browser.html#step3"},
  {"id": "ec_02", "unit": "easy_comprehensions", "d": -0.5, "type": "mcq", "q": "A list comprehension adds `!`. What does it print?", "code": "words = [\"hi\", \"bye\"]\nprint([word + \"!\" for word in words])", "o": ["hi! bye!", "['hi', 'bye', '!']", "['hi!', 'bye!']", "['hi bye!']"], "a": 2, "why": "Each word gets `!` joined to it, and the new list holds the results.", "at": "python_browser.html#step3"},
  {"id": "ec_03", "unit": "easy_comprehensions", "d": -0.3, "type": "mcq", "q": "A list comprehension has an `if`. What does it print?", "code": "prices = [5, 50, 15, 80]\nprint([price for price in prices if price > 10])", "o": ["[50, 80]", "[5]", "[5, 50, 15, 80]", "[50, 15, 80]"], "a": 3, "why": "With `if` at the end, only the items that pass go into the new list. 15 is bigger than 10 too.", "at": "python_browser.html#step4"},
  {"id": "ec_04", "unit": "easy_comprehensions", "d": -0.4, "type": "mcq", "q": "Which brackets make a list comprehension?", "o": ["Square brackets: [ ]", "Round brackets: ( )", "Curly brackets: { }", "No brackets at all"], "a": 0, "why": "A list comprehension is in square brackets. In round brackets it is a generator; in curly ones, a set or a dictionary.", "at": "python_browser.html#step3"},
  {"id": "ec_05", "unit": "easy_comprehensions", "d": -0.2, "type": "mcq", "q": "A list with a repeat becomes a set. What does this print?", "code": "basket = [\"mango\", \"longan\", \"mango\"]\nprint(len(set(basket)))", "o": ["3", "2", "1", "5"], "a": 1, "why": "A set holds each thing only once, so the second mango is dropped: two kinds.", "at": "python_browser.html#step6"},
  {"id": "ec_06", "unit": "easy_comprehensions", "d": -0.1, "type": "mcq", "q": "What does `{}` on its own make?", "o": ["An empty set", "An empty list", "An empty dictionary", "Nothing: it is an error"], "a": 2, "why": "`{}` is an empty dictionary. An empty set is written `set()`.", "at": "python_browser.html#step6"},
  {"id": "ec_07", "unit": "easy_comprehensions", "d": 0.0, "type": "mcq", "q": "What does `&` print here?", "code": "north = {\"mango\", \"longan\"}\nsouth = {\"mango\", \"banana\"}\nprint(north & south)", "o": ["{'mango', 'longan', 'banana'}", "{'longan', 'banana'}", "{'longan'}", "{'mango'}"], "a": 3, "why": "`&` keeps what is in both sets. `|` would join them, and `-` would take away.", "at": "python_browser.html#step6"},
  {"id": "ec_08", "unit": "easy_comprehensions", "d": 0.1, "type": "mcq", "q": "A set is filled in a loop. What does this print?", "code": "seen = set()\nfor fruit in [\"mango\", \"lime\", \"mango\"]:\n    seen.add(fruit)\nprint(len(seen))", "o": ["2", "3", "1", "0"], "a": 0, "why": "Adding `mango` a second time changes nothing: a set holds each thing once.", "at": "python_browser.html#step6"},
  {"id": "ec_09", "unit": "easy_comprehensions", "d": 0.1, "type": "mcq", "q": "What does `next()` print here?", "code": "sale = (price - 10 for price in [60, 50])\nprint(next(sale))", "o": ["40", "50", "60", "[50, 40]"], "a": 1, "why": "Round brackets make a generator. `next()` asks it for one item: the first, 60 - 10.", "at": "python_browser.html#step7"},
  {"id": "ec_10", "unit": "easy_comprehensions", "d": 0.2, "type": "mcq", "q": "A generator is given to `sum()` twice. What does this print?", "code": "squares = (number * number for number in [1, 2, 3])\nprint(sum(squares))\nprint(sum(squares))", "o": ["14, then 14", "6, then 6", "14, then 0", "0, then 14"], "a": 2, "why": "1 + 4 + 9 is 14. A generator is used once, so the second `sum()` finds nothing left: 0.", "at": "python_browser.html#step7"},
  {"id": "ec_11", "unit": "easy_comprehensions", "d": 0.0, "type": "mcq", "q": "What does this dictionary comprehension print?", "code": "prices = {\"mango\": 60, \"lime\": 20}\nprint({fruit: price - 10 for fruit, price in prices.items()})", "o": ["{'mango': 60, 'lime': 20}", "[50, 10]", "{50, 10}", "{'mango': 50, 'lime': 10}"], "a": 3, "why": "A dictionary comprehension keeps the same keys and makes new values: each price minus 10.", "at": "python_browser.html#step5"},
  {"id": "ec_12", "unit": "easy_comprehensions", "d": 0.2, "type": "mcq", "q": "Why might you use a generator and not a list?", "o": ["It keeps only where it is up to", "It can be used again and again", "It sorts its items as it makes them", "It can hold text, which a list cannot"], "a": 0, "why": "A list keeps every item in memory at once. A generator keeps only where it is up to, and makes each item when asked.", "at": "python_browser.html#step7"},
  {"id": "ec_13", "unit": "easy_comprehensions", "d": 0.3, "type": "mcq", "q": "What does `yield` do inside a function?", "o": ["Ends the function for good", "Hands out one item and pauses", "Prints one item on the screen", "Gives back every item at once"], "a": 1, "why": "`yield` hands out one item and pauses the function there. When the loop asks for the next item, it carries on.", "at": "python_browser.html#step8"},
  {"id": "ec_14", "unit": "easy_comprehensions", "d": 0.1, "type": "mcq", "q": "A list comprehension uses `len()`. What does it print?", "code": "print([len(fruit) for fruit in [\"fig\", \"mango\"]])", "o": ["[2]", "8", "[3, 5]", "['fig', 'mango']"], "a": 2, "why": "The new list holds the length of each fruit's name: 3 for fig, 5 for mango.", "at": "python_browser.html#step3"},
  {"id": "ec_15", "unit": "easy_comprehensions", "d": 0.3, "type": "fill", "q": "`next()` asks a generator for an item when it has none left. What is the error called? Type its name.", "answers": ["stopiteration"], "why": "`StopIteration` is how a generator says \"no more\".", "at": "python_browser.html#step7"},
  {"id": "python_toolkit_01", "unit": "python_toolkit", "d": -0.7, "type": "mcq", "q": "In this Python class, what does `self` mean?", "code": "class Dog:\n    def __init__(self, name, age):\n        self.name = name\n        self.age = age\n\n    def bark(self):\n        return f\"{self.name} says woof!\"", "o": ["The class `Dog` itself, the template.", "This particular dog, the one object.", "The first dog that the program made.", "The file that holds the class code."], "a": 1, "why": "`self` is the one object that the method was called on. Every method takes it as the first input, and Python passes it for you.", "at": "python_toolkit.html#s1"},
  {"id": "python_toolkit_02", "unit": "python_toolkit", "d": -0.5, "type": "mcq", "q": "What does `json.dumps(student)` do?", "o": ["It reads a JSON file into a dictionary.", "It deletes the dictionary from memory.", "It turns a Python object into JSON text.", "It sends the dictionary to a web server."], "a": 2, "why": "`json.dumps` goes from Python to text: dump to string. `json.loads` goes the other way.", "at": "python_toolkit.html#s6"},
  {"id": "python_toolkit_03", "unit": "python_toolkit", "d": -0.3, "type": "fill", "q": "A path `p` points to the file `notes.txt`. What does `p.stem` give? Type it.", "answers": ["notes", "\"notes\"", "'notes'"], "why": "`p.stem` is the name without the extension. `p.name` gives `notes.txt` and `p.suffix` gives `.txt`.", "at": "python_toolkit.html#s5"},
  {"id": "python_toolkit_04", "unit": "python_toolkit", "d": -0.1, "type": "mcq", "q": "The function `greet` has the hint `name: str`. What happens when you call `greet(42)`?", "o": ["Python stops with a `TypeError` at once.", "Python refuses to load the whole file.", "The function returns `None` every time.", "It still runs. Python does not enforce hints."], "a": 3, "why": "Type hints are notes for humans and tools, not rules for the interpreter. `greet(42)` still runs.", "at": "python_toolkit.html#s4"},
  {"id": "python_toolkit_05", "unit": "python_libraries", "d": 0.1, "type": "mcq", "q": "Why do you call `r.raise_for_status()` after `requests.get(...)`?", "o": ["It raises an error if the server said 404 or 500.", "It gives up if the server is silent for 10 seconds.", "It turns the reply from JSON into a dictionary.", "It adds your API key to the request headers."], "a": 0, "why": "Without `raise_for_status()`, a failed request looks like a successful one. It raises an error for a 404 or a 500.", "at": "python_toolkit.html#s7"},
  {"id": "python_toolkit_06", "unit": "python_toolkit", "d": 0.4, "type": "mcq", "q": "You loop over one generator object two times. What does the second loop get?", "o": ["The same items again, in the same order.", "Nothing. A generator is used only once.", "The items again, but in reverse order.", "A `TypeError`, because it has no `len()`."], "a": 1, "why": "A generator is used once. Loop over it twice and the second loop gets nothing.", "at": "python_toolkit.html#s2"},
  {"id": "python_toolkit_07", "unit": "python_toolkit", "d": 0.5, "type": "mcq", "q": "In this decorator, someone forgot the line `return result` inside `wrapper`. What does a function wrapped with `@timed` return now?", "code": "def timed(fn):\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        result = fn(*args, **kwargs)\n        print(f\"{fn.__name__} took {time.time() - start:.2f}s\")\n    return wrapper", "o": ["It returns the time, in seconds.", "It returns the function `fn`.", "It returns `None` every time.", "It returns the same value as before."], "a": 2, "why": "`wrapper` replaces the function, so the caller gets what `wrapper` returns. A Python function with no `return` line returns `None`.", "at": "python_toolkit.html#s3"},
  {"id": "python_classes_01", "unit": "python_classes", "d": -0.8, "type": "mcq", "q": "A tkinter window made line by line at the Python `>>>` prompt works without `root.mainloop()`. Why does a script file need that line?", "o": ["A file cannot import `tkinter` without it", "A file reaches its end and closes", "A file has no window title without it", "A file does not pack labels without it"], "a": 1, "why": "The prompt keeps the window alive while it waits for your next line. A file has nothing to wait for, so it reaches the end and closes. `mainloop` draws the window and waits for keys and clicks.", "at": "python_classes.html#step1"},
  {"id": "python_classes_02", "unit": "python_classes", "d": -0.6, "type": "mcq", "q": "You build a `tk.Label`, but it never appears. Python shows no error. What did you forget?", "o": ["To call `title` on the window", "To import `scrolledtext` first", "To call `pack` on the label", "To give the label a font"], "a": 2, "why": "Nothing appears until `pack` is called. A widget that is never packed is invisible, and Python says nothing.", "at": "python_classes.html#step1"},
  {"id": "python_classes_03", "unit": "python_classes", "d": -0.3, "type": "mcq", "q": "This tkinter program has no class. The widgets `box` and `transcript` are made at the top of the file, as globals, and not inside a function such as `main()`. Why?", "code": "import tkinter as tk\nfrom tkinter import scrolledtext\n\nroot = tk.Tk()\ntranscript = scrolledtext.ScrolledText(root, state=\"disabled\")\nrow = tk.Frame(root)\nrow.pack(side=\"bottom\", fill=\"x\", padx=10, pady=10)\nbox = tk.Entry(row, font=(\"Segoe UI\", 11))\nbox.pack(side=\"left\", fill=\"x\", expand=True, ipady=6)\n\ndef send(event=None):\n    said = box.get().strip()\n    transcript.configure(state=\"normal\")\n    transcript.insert(\"end\", \"You: \" + said + \"\\n\")\n    transcript.configure(state=\"disabled\")\n\ntk.Button(row, text=\"Send\", width=8, command=send).pack(side=\"left\", padx=(8, 0))\ntranscript.pack(side=\"top\", fill=\"both\", expand=True)\nroot.mainloop()", "o": ["`send()` cannot see names inside another function", "tkinter can only draw widgets kept in global names", "Global names make the window open much faster", "A file with no class cannot have a `main()`"], "a": 0, "why": "`send()` is a separate function, and a function cannot see names that live inside another one. With no class to hold the widgets, globals are the only thing both halves of the file can reach.", "at": "python_classes.html#step2"},
  {"id": "python_classes_04", "unit": "python_classes", "d": 0.0, "type": "mcq", "q": "What does the line `if __name__ == \"__main__\":` do in this Python file?", "code": "import tkinter as tk\n\ndef main():\n    root = tk.Tk()\n    root.title(\"Chat\")\n    root.geometry(\"520x420\")\n    root.mainloop()\n\nif __name__ == \"__main__\":\n    main()", "o": ["It gives `tkinter` the short name `tk`", "It keeps the window open until you close it", "It runs `main()` when another file imports it", "It runs `main()` only when you ran this file"], "a": 3, "why": "Python sets `__name__` to `\"__main__\"` only when this file is the one you ran. When another file imports it, the test is false, `main()` does not run, and no window opens.", "at": "python_classes.html#step3"},
  {"id": "python_classes_05", "unit": "python_classes", "d": 0.1, "type": "fill", "q": "Inside `__init__` in a Python class, the first parameter is the object being built. You never pass it, because Python puts it there for you. Which name do Python programmers always give it? Type that name.", "answers": ["self"], "why": "`self` is the object being built. The caller passes only the other arguments, and Python supplies `self`.", "at": "python_classes.html#step4"},
  {"id": "python_classes_06", "unit": "python_classes", "d": 0.2, "type": "mcq", "q": "In this tkinter class the typing box is kept as `self.box`, but the frame is a plain variable, `row`. Why?", "code": "class ChatWindow:\n    def __init__(self, root):\n        self.root = root\n        row = tk.Frame(root)\n        row.pack(side=\"bottom\", fill=\"x\", padx=10, pady=10)\n        self.box = tk.Entry(row, font=(\"Segoe UI\", 11))\n        self.box.pack(side=\"left\", fill=\"x\", expand=True, ipady=6)\n        tk.Button(row, text=\"Send\", width=8, command=self.send).pack(\n            side=\"left\", padx=(8, 0))\n\n    def send(self, event=None):\n        said = self.box.get().strip()\n        if not said:\n            return\n        self.box.delete(0, \"end\")", "o": ["A `Frame` cannot be stored on `self`", "Plain variables are packed before all the others", "`send()` needs the box later, not the frame", "`self.box` makes the typing box read-only"], "a": 2, "why": "Keep a thing on `self` only if a method needs it later. `send()` reads the box, but nothing looks for the frame again after `__init__` ends.", "at": "python_classes.html#step5"},
  {"id": "python_classes_07", "unit": "python_classes", "d": 0.4, "type": "mcq", "q": "In tkinter, a button is made with `tk.Button(row, text=\"Send\", command=self.send())`, with brackets after `send`. What happens?", "o": ["`send` runs once now; clicks do nothing", "Python stops with an error on that line", "`send` runs two times on every click", "The button works, but the Enter key does not"], "a": 0, "why": "With brackets Python calls `send` at once, while the window is being built. The button gets what `send` returned, which is `None`, so clicking does nothing.", "at": "python_classes.html#step6"},
  {"id": "python_classes_08", "unit": "python_classes", "d": 0.3, "type": "mcq", "q": "In tkinter, can the widgets inside one frame be placed with both `pack` and `grid`?", "o": ["Yes, when `grid` is called before `pack`", "Yes, when each widget uses only one of the two", "No. `grid` works in the main window only", "No. One frame takes one of the two, not both"], "a": 3, "why": "Each frame has one way of placing its widgets. Mix the two in one frame and tkinter stops with an error. Two separate frames may each use a different one.", "at": "python_classes.html#step7"},
  {"id": "python_classes_09", "unit": "python_classes", "d": 0.6, "type": "mcq", "q": "These are methods of a tkinter window class. `self.answers` is a `queue.Queue()`, and `ask` runs on a worker thread. Why does `ask` call `self.answers.put(said)` and not `self.write(said)`?", "code": "    def send(self, event=None):\n        said = self.box.get().strip()\n        self.write(\"You: \" + said + \"\\n\")\n        self.history.append({\"role\": \"user\", \"content\": said})\n        threading.Thread(target=self.ask, args=(list(self.history),),\n                         daemon=True).start()\n\n    def ask(self, history):\n        # ... a slow call to a model puts its reply in said ...\n        self.answers.put(said)\n\n    def drain(self):\n        while not self.answers.empty():\n            said = self.answers.get()\n            self.write(\"Bot: \" + said + \"\\n\\n\")\n        self.root.after(250, self.drain)", "o": ["A queue is faster than writing to a widget", "A worker thread must never touch a widget", "A worker thread cannot call methods on `self`", "The queue is where the chat history is kept"], "a": 1, "why": "In tkinter, only the main thread may touch a widget. The worker puts the text in the queue, and `drain`, booked with `after` on the main thread, takes it out and writes it.", "at": "python_classes.html#step8"},
  {"id": "python_classes_10", "unit": "python_classes", "d": 0.8, "type": "mcq", "q": "This Python function builds a class with `type()`. The last line prints `AskName can send?  False`. Why?", "code": "def window_class(name, title, kind):\n    # __init__, close, run, build_form, submit, build_chat, write,\n    # send, think and drain are plain functions written here\n    inside = {\"__init__\": __init__, \"close\": close, \"run\": run}\n    if kind == \"form\":\n        inside.update({\"build_form\": build_form, \"submit\": submit})\n    else:\n        inside.update({\"build_chat\": build_chat, \"write\": write,\n                       \"send\": send, \"think\": think, \"drain\": drain})\n    return type(name, (), inside)\n\nAskName = window_class(\"AskName\", \"Before we start\", \"form\")\nprint(\"AskName can send? \", hasattr(AskName, \"send\"))", "o": ["A flag on each object hides `send` in a form", "The dict given to `type()` holds no `send`", "`send` is deleted when the form window is closed", "`AskName` inherits from a parent with no `send`"], "a": 1, "why": "`type(name, bases, dict)` builds a class from a dict of what is inside it. For the kind `\"form\"` the dict holds no `send`, so the class has no such attribute at all. No flag is checked later.", "at": "python_classes.html#step8"},
  {"id": "catch_your_own_bugs_01", "unit": "catch_your_own_bugs", "d": -0.8, "type": "mcq", "q": "What happens when the condition after `assert` is false?", "o": ["Nothing happens at all", "The test is skipped quietly", "Python fixes the value for you", "The test fails and tells you"], "a": 3, "why": "`assert` means this must be true. If it is true, nothing happens. If it is false, the test fails and tells you.", "at": "catch_your_own_bugs.html#step1"},
  {"id": "catch_your_own_bugs_02", "unit": "catch_your_own_bugs", "d": -0.5, "type": "fill", "q": "pytest only finds files and functions whose names start with the same few characters. What must the names start with? Type it.", "answers": ["test_", "test"], "why": "pytest finds files that start with `test_` and functions that start with `test_`. With a wrong name it finds nothing.", "at": "catch_your_own_bugs.html#step0"},
  {"id": "catch_your_own_bugs_03", "unit": "catch_your_own_bugs", "d": -0.3, "type": "mcq", "q": "This pytest fixture uses `tmp_path`. Every test that works with the database takes `db`. Why?", "code": "@pytest.fixture\ndef db(tmp_path, monkeypatch):\n    monkeypatch.setattr(homework, \"DB_PATH\", tmp_path / \"test.db\")\n    conn = homework.open_db()\n    yield conn            # the test runs here\n    conn.close()", "o": ["To get a fresh database, not the real one", "To run the same test once for every name in a list", "To catch everything that the program has printed", "To stop the test run at the very first failure"], "a": 0, "why": "`tmp_path` is a new empty folder that pytest makes and throws away after. So each test starts with an empty database and never touches the real one.", "at": "catch_your_own_bugs.html#step2"},
  {"id": "catch_your_own_bugs_04", "unit": "catch_your_own_bugs", "d": 0.2, "type": "mcq", "q": "What does `@pytest.mark.parametrize` do in this pytest test?", "code": "@pytest.mark.parametrize(\"name\", [\n    \"Ploy\",\n    \"O'Brien\",\n    \"\",\n])\ndef test_any_name_survives(db, name):\n    homework.add(db, Args(student=name, title=\"Read page 40\", due=None))\n    saved = db.execute(\"SELECT student FROM jobs\").fetchone()[0]\n    assert saved == name", "o": ["Points the app at a new folder for one test only", "Runs the same test once for each name in the list", "Runs the set-up code again after the test finishes", "Picks only the tests with a given word in the name"], "a": 1, "why": "`parametrize` runs the same test once for every value in the list, and names each run after its input.", "at": "catch_your_own_bugs.html#step3"},
  {"id": "catch_your_own_bugs_05", "unit": "catch_your_own_bugs", "d": 0.5, "type": "mcq", "q": "Your pytest tests all pass. Why is it a good idea to break your program on purpose and run the tests again?", "o": ["A broken app makes the tests run faster", "pytest needs one failure to find the files", "A test you never saw fail may test nothing", "The fixture only cleans up after a failure"], "a": 2, "why": "A test you have never seen fail is not a test. It might be checking nothing at all, and you would not know.", "at": "catch_your_own_bugs.html#step4"},
  {"id": "catch_your_own_bugs_06", "unit": "catch_your_own_bugs", "d": 0.8, "type": "mcq", "q": "A function adds a row to an SQLite database, but somebody deleted its `conn.commit()`. Everything looks fine while the program runs. What must a test do to catch this?", "o": ["Catch what the program printed and look for the name", "Add a name with an apostrophe and read it back", "Count the rows on the same open connection", "Close the connection, reopen the file, then count"], "a": 3, "why": "In memory everything looks perfect. Only when the data is read back from disk through a new connection does the missing `commit()` show up.", "at": "catch_your_own_bugs.html#step6"},
  {"id": "python_data_classes_01", "unit": "python_libraries", "d": -0.7, "type": "mcq", "q": "A Python library such as Pydantic can coerce a value. What does it mean to coerce a value?", "o": ["To check it and stop if it is wrong", "To change it into the right kind", "To lock it so it cannot change later", "To read it from raw JSON text"], "a": 1, "why": "To validate is to check. To coerce is to change, for example text \"28\" becomes the number 28.", "at": "python_data_classes.html#s1"},
  {"id": "python_data_classes_02", "unit": "python_data_classes", "d": -0.5, "type": "mcq", "q": "Which of these comes with Python, so you install nothing?", "o": ["attrs", "Pydantic", "pydantic-settings", "Data classes"], "a": 3, "why": "Data classes come free with Python. attrs and Pydantic are libraries you install.", "at": "python_data_classes.html#s0"},
  {"id": "python_data_classes_03", "unit": "python_data_classes", "d": -0.3, "type": "mcq", "q": "A Python class `Booking` is made with `@dataclass(frozen=True, slots=True)`, and `b` is a `Booking` object. What happens when the code runs `b.people = 5`?", "o": ["A `FrozenInstanceError` is raised", "The field quietly changes to 5", "A new `Booking` object is returned", "The value is coerced into text"], "a": 0, "why": "`frozen=True` locks the object after you make it. Python raises `FrozenInstanceError: cannot assign to field 'people'`.", "at": "python_data_classes.html#s5"},
  {"id": "python_data_classes_04", "unit": "python_libraries", "d": 0.1, "type": "mcq", "q": "In attrs, how do you make the text `\"28\"` become the number 28?", "o": ["Add `v.instance_of(int)` to the field", "Write the type hint `age: int`", "Add `converter=int` to the field", "Add `v.gt(0)` to the field"], "a": 2, "why": "attrs never coerces by magic. Coercion happens only where you wrote `converter=`.", "at": "python_data_classes.html#s6"},
  {"id": "python_data_classes_05", "unit": "python_libraries", "d": 0.3, "type": "mcq", "q": "What changes when a Pydantic class has `model_config = {\"strict\": True}`?", "o": ["Text `\"28\"` is stored as text, unchanged", "Text `\"28\"` is refused for an `int` field", "Fields are locked after the object is made", "Checking is turned off to make it faster"], "a": 1, "why": "By default Pydantic repairs \"28\" into 28. Strict mode turns repair off, so \"28\" fails too.", "at": "python_data_classes.html#s4"},
  {"id": "python_data_classes_06", "unit": "python_libraries", "d": 0.5, "type": "mcq", "q": "A healthy Python program often uses Pydantic and plain data classes together. How?", "o": ["Data classes check at the edge; Pydantic holds data inside", "attrs checks every class; Pydantic is used only in tests", "Pydantic is put on every class; data classes on none", "Pydantic checks at the edge; data classes hold data inside"], "a": 3, "why": "Check once, at the edge, with Pydantic. Inside, everything is already clean, so plain data classes are enough and they are fast.", "at": "python_data_classes.html#s10"},
  {"id": "python_data_classes_07", "unit": "python_libraries", "d": 0.7, "type": "mcq", "q": "A Pydantic settings object `settings` has a field `app_password` of type `SecretStr`, so printing the settings shows only stars. How does the code get the real password when it logs in?", "o": ["It calls `settings.model_dump()`", "It prints `settings` and reads it", "It calls `.get_secret_value()` on it", "It calls `.model_dump_json()` on it"], "a": 2, "why": "`print`, `model_dump()` and `model_dump_json()` all keep a `SecretStr` masked. The real value comes out only with `.get_secret_value()`.", "at": "python_data_classes.html#s11"},
  {"id": "python_data_classes_08", "unit": "python_data_classes", "d": -0.4, "type": "mcq", "q": "What does printing this data class show?", "code": "from dataclasses import dataclass\n\n@dataclass\nclass Student:\n    name: str\n    age: int\n    city: str = \"Chiang Mai\"\n\nprint(Student(\"Ploy\", 28))", "o": ["<__main__.Student object at 0x10f3a2b50>", "Student(\"Ploy\", 28)", "An error: city was not given", "Student(name='Ploy', age=28, city='Chiang Mai')"], "a": 3, "why": "`@dataclass` writes `__init__`, `__repr__` and `__eq__` for you. The `__repr__` shows every field, and `city` takes its default.", "at": "python_data_classes.html#s2"},
  {"id": "python_data_classes_09", "unit": "python_data_classes", "d": 0.2, "type": "mcq", "q": "A data class has the field `guests: list = []`. What happens?", "o": ["Python refuses it with a `ValueError`", "Every object gets its own new list", "The list is locked and cannot change", "The field quietly becomes `None`"], "a": 0, "why": "One list would be shared by every object, so Python refuses it. Write `field(default_factory=list)` for a new list each time.", "at": "python_data_classes.html#s5"},
  {"id": "python_data_classes_10", "unit": "python_data_classes", "d": 0.0, "type": "mcq", "q": "A data class has the field `age: int`. What happens with `Student(name=\"Ploy\", age=\"28\")`?", "o": ["It turns \"28\" into the number 28", "It stores the text \"28\" as it is", "It stops with a `TypeError`", "It stores `None` for the age"], "a": 1, "why": "A data class checks nothing: its type hints are notes only. The text \"28\" is stored as text.", "at": "python_data_classes.html#s3"},
  {"id": "send_email_01", "unit": "send_email", "d": -0.7, "type": "mcq", "q": "Gmail and Yahoo let you make an app password for a program that sends your email. Why use it, and not your normal password?", "o": ["It is a separate password, and you can cancel it alone.", "It is shorter, so it is easier to type in the terminal.", "It lets the program send email without a mail server.", "It is the only password that works on port 465 at all."], "a": 0, "why": "An app password is a separate password, just for programs. You can cancel it on its own, and your normal password stays out of the program.", "at": "send_email.html#need"},
  {"id": "send_email_02", "unit": "send_email", "d": -0.5, "type": "mcq", "q": "A file `shopping.txt` already has four lines. What does opening it with `\"w\"` do?", "o": ["It adds new text after the four lines.", "It empties the file before writing.", "It opens the file for reading only.", "It makes a second copy of the file."], "a": 1, "why": "`\"w\"` empties the file first. `\"a\"` is the letter that adds to the end and keeps what is there.", "at": "send_email.html#open-mode"},
  {"id": "send_email_03", "unit": "send_email", "d": -0.3, "type": "fill", "q": "With Python's `imaplib`, `box.search(None, \"ALL\")` finds every email in the folder. Which IMAP search word, in place of `ALL`, gives only the unread emails? Type it.", "answers": ["unseen", "\"unseen\"", "'unseen'"], "why": "`UNSEEN` is the IMAP search word for unread emails. `SEEN` gives the opposite.", "at": "send_email.html#search"},
  {"id": "send_email_04", "unit": "send_email", "d": 0.0, "type": "mcq", "q": "This Python `imaplib` line fetches three headers of an email with `BODY.PEEK[...]`. Why does `PEEK` matter?", "code": "status, raw = box.fetch(message_id, \"(BODY.PEEK[HEADER.FIELDS (FROM DATE SUBJECT)])\")", "o": ["Without it, the server sends only the subject.", "Without it, the fetch downloads every attachment.", "Without it, reading marks the email as read.", "Without it, the newest emails come back last."], "a": 2, "why": "On an IMAP server, fetching an email marks it as read. `PEEK` fetches it and leaves it unread.", "at": "send_email.html#list-found"},
  {"id": "send_email_05", "unit": "send_email", "d": -0.2, "type": "mcq", "q": "This file is saved as `mailer.py`. What happens when you run `python mailer.py` by itself?", "code": "import smtplib\nfrom email.message import EmailMessage\n\nSMTP_HOST = \"smtp.gmail.com\"\n\ndef send_email(sender, password, to, subject, body, host=SMTP_HOST):\n    message = EmailMessage()\n    message[\"From\"] = sender\n    message[\"To\"] = to\n    message[\"Subject\"] = subject\n    message.set_content(body)\n    with smtplib.SMTP_SSL(host, 465) as server:\n        server.login(sender, password)\n        server.send_message(message)\n    return to\n\nif __name__ == \"__main__\":\n    print(\"mailer.py holds send_email().\")", "o": ["It asks for an address and sends one test email.", "It sends one email with an empty subject and body.", "It stops with an error, because nothing imports it.", "It prints one line of text and sends no email."], "a": 3, "why": "Running the file defines `send_email()` and then runs only the `print` under `if __name__ == \"__main__\":`. Nothing calls the function, so no email is sent.", "at": "send_email.html#mailer-tool"},
  {"id": "send_email_06", "unit": "send_email", "d": 0.2, "type": "mcq", "q": "A program gives a language model a numbered list of search results. It asks the model to answer with the numbers of the good results, not their web addresses. Why numbers?", "o": ["A model copies a long address wrongly.", "A model cannot read a web address at all.", "Numbers make the model answer much faster.", "A web address is not allowed inside a prompt."], "a": 0, "why": "A model often copies a long web address wrongly, but a short number it can manage. The program then looks up the address by its number.", "at": "send_email.html#pick-links"},
  {"id": "send_email_07", "unit": "send_email", "d": 0.4, "type": "mcq", "q": "A program asks NVIDIA's model API (`/chat/completions`) for a model by name and gets HTTP status 410. What does that mean?", "o": ["Your key is wrong, or the wrong kind of key.", "The model is retired for everyone.", "The model is listed, but not open to your key.", "The model answered, so you can use it."], "a": 1, "why": "410 means the model is retired for everyone. 404 is the different problem: listed, but not open to your key.", "at": "send_email.html#nvidia-models"},
  {"id": "send_email_08", "unit": "send_email", "d": 0.1, "type": "mcq", "q": "With tool calling (for example in Ollama's chat API), a language model decides to use a tool named `check_pantry`. What does the model send back?", "o": ["The text of a file, which the model opened itself.", "A new Python function that your code must save.", "The name of the function and its arguments.", "The final answer, as one finished sentence."], "a": 2, "why": "The model never runs your code. It only sends a name and arguments. Your code runs the function and sends the answer back.", "at": "send_email.html#tools"},
  {"id": "send_email_09", "unit": "send_email", "d": 0.6, "type": "mcq", "q": "A program drafts replies to emails that other people sent you. Why give this job to a local model (for example in Ollama), and not to a faster cloud model API?", "o": ["Cloud models cannot write a short, polite email.", "A local model never invents facts in its reply.", "A free cloud key does not work with IMAP email.", "The emails then never leave your own computer."], "a": 3, "why": "Everything in a prompt to a cloud API is seen by the company that runs it. An email belongs to the person who wrote it, and a local model keeps it on your computer.", "at": "send_email.html#reply-draft"},
  {"id": "send_email_10", "unit": "send_email", "d": 0.4, "type": "mcq", "q": "This code uses Piper, a text-to-speech library, to say every line in a list. Why is the line `PiperVoice.load(ENGLISH)` outside the loop?", "code": "voice = PiperVoice.load(ENGLISH)\nfor number, line in enumerate(LESSON, start=1):\n    with wave.open(\"lesson\" + str(number) + \".wav\", \"wb\") as wav:\n        voice.synthesize_wav(line, wav)", "o": ["Loading is slow, so do it only once.", "A voice can only be loaded one time.", "The loop cannot see names made inside it.", "Piper needs the internet for each load."], "a": 0, "why": "Loading a voice is slow and speaking is fast. Inside the loop, every line would pay for the loading again.", "at": "send_email.html#say-fast"},
  {"id": "python_six_libraries_01", "unit": "python_six_libraries", "d": -0.4, "type": "mcq", "q": "With Biopython, `Entrez.efetch` downloads a gene from the NCBI gene bank. What must you give NCBI before you ask?", "o": ["A paid key for each request", "An account name and a password", "An email address, and no password", "A letter from your own school"], "a": 2, "why": "NCBI lets anyone ask for its data. You set `Entrez.email` to your email address, and that is the only login there is.", "at": "python_six_libraries.html"},
  {"id": "python_six_libraries_02", "unit": "python_six_libraries", "d": -0.4, "type": "mcq", "q": "Which of these Python libraries needs no internet at all and makes a picture that a phone can read?", "o": ["`qrcode`", "`wbgapi`", "`skyfield`", "`osmnx`"], "a": 0, "why": "`qrcode` needs no internet. Two lines make a PNG picture, and a phone that scans it opens the link.", "at": "python_six_libraries.html"},
  {"id": "python_six_libraries_03", "unit": "python_six_libraries", "d": 0.0, "type": "mcq", "q": "This Skyfield code prints the height of the space station in the sky, in degrees, seen from one place on Earth. What does a height above 0 mean?", "code": "from skyfield.api import load, wgs84\n\nurl  = (\"https://celestrak.org/NORAD/elements/\"\n        \"gp.php?GROUP=stations&FORMAT=tle\")\nsats = load.tle_file(url)\niss  = {s.name: s for s in sats}[\"ISS (ZARYA)\"]\nhere = wgs84.latlon(18.79, 98.98)\nalt, az, dist = (iss - here).at(load.timescale().now()).altaz()\nprint(\"height\", alt.degrees)", "o": ["The station is below the horizon now", "The station is exactly over your head", "The satellite list must be loaded again", "The station is up, above the horizon"], "a": 3, "why": "`alt` is the angle above the horizon. Above 0 degrees the station is up right now, so you can go outside and look for it.", "at": "python_six_libraries.html"},
  {"id": "python_six_libraries_04", "unit": "python_six_libraries", "d": 0.2, "type": "mcq", "q": "OSMnx and folium are two Python libraries for maps. What is the difference between them?", "o": ["folium downloads the streets; OSMnx draws a map", "OSMnx downloads the streets; folium draws a map", "OSMnx needs an account; folium needs none", "OSMnx works with no internet; folium needs wifi"], "a": 1, "why": "OSMnx downloads the streets of OpenStreetMap into Python, so you can ask questions about them. folium draws a map.", "at": "python_six_libraries.html"},
  {"id": "python_six_libraries_05", "unit": "python_six_libraries", "d": 0.3, "type": "mcq", "q": "What does this code ask the World Bank for?", "code": "import wbgapi as wb\n\n# SE.XPD.TOTL.GD.ZS = money spent on school, as a share of GDP\ntable = wb.data.DataFrame(\"SE.XPD.TOTL.GD.ZS\",\n                          [\"THA\", \"VNM\", \"MYS\"], mrnev=5)\nprint(table)", "o": ["School spending as a share of GDP in three countries", "The number of schools in three provinces of Thailand", "The GDP of the three biggest economies in the world", "The price of school books over the last five years"], "a": 0, "why": "The first argument is the World Bank's code for money spent on school, as a share of GDP. The list names three countries: Thailand, Vietnam and Malaysia.", "at": "python_six_libraries.html"},
  {"id": "python_six_libraries_06", "unit": "python_six_libraries", "d": 0.6, "type": "mcq", "q": "This ObsPy code asks a data centre for what one seismometer wrote down, but no data comes back. What should you check first?", "code": "from obspy import UTCDateTime\nfrom obspy.clients.fdsn import Client\n\nstart = UTCDateTime(\"2025-03-28T06:20:00\")\nstream = Client(\"IRIS\").get_waveforms(\n    \"IU\", \"CHTO\", \"00\", \"BHZ\",\n    start, start + 900)\nprint(stream)", "o": ["Your account and your key", "The speed of your computer", "The station codes and time", "The size of the plot window"], "a": 2, "why": "Wrong station codes bring no data, and some stations go quiet for months. So check the station codes and the time first. No account or key is needed.", "at": "python_six_libraries.html"},
  {"id": "mailbot_01", "unit": "mailbot", "d": -0.8, "type": "mcq", "q": "A Python bot reads a mailbox once an hour and replies to some emails by itself. What is the main danger it must guard against?", "o": ["Reading the mail too slowly from the server", "Writing a reply that is too short to read", "Replying to its own replies, again and again", "Logging in with an app password every hour"], "a": 2, "why": "A reply to the bot's email can look like a new request. A bot that answers its own answer makes a mail loop and fills the inbox.", "at": "mailbot.html"},
  {"id": "mailbot_02", "unit": "mailbot", "d": -0.5, "type": "mcq", "q": "With Python's `imaplib`, a program opens the mailbox with `box.select(\"INBOX\", readonly=True)`. What is `readonly=True` for?", "o": ["So no email is marked as read by accident", "So the server sends the emails more quickly", "So the program needs no password to log in", "So only emails with a tag can be searched"], "a": 0, "why": "A mailbox opened read only cannot be changed. The program can look at the mail, and nothing is marked as read by accident.", "at": "mailbot.html#step1"},
  {"id": "mailbot_03", "unit": "mailbot", "d": -0.3, "type": "mcq", "q": "This Python function keeps one text for each day in a JSON file. `load_saved()` returns that file as a dictionary. Today's date is already a key in it. What does the function do?", "code": "def for_today():\n    day = time.strftime(\"%Y-%m-%d\")\n    saved = load_saved()\n    if day in saved:\n        return day, saved[day], \"from the file\"\n    written = ask_ollama(ASK.format(day=day))\n    saved[day] = written\n    SAVED.write_text(json.dumps(saved, ensure_ascii=False, indent=2),\n                     encoding=\"utf-8\")\n    return day, written, \"written just now\"", "o": ["It asks the model to write a new text.", "It deletes the old text and writes a new one.", "It waits until midnight for a new key.", "It returns the saved text, with no model call."], "a": 3, "why": "The file is a cache. When the key is found, the function returns the saved value at once and never reaches the line that calls the model.", "at": "mailbot.html#step2"},
  {"id": "mailbot_04", "unit": "mailbot", "d": 0.1, "type": "mcq", "q": "A mail bot puts a tag such as `[my-bot]` on every email it sends, so that it can recognise its own mail later. Why is the subject line a good place for the tag?", "o": ["Mail servers remove tags from the body.", "Every reply quotes the subject back.", "The subject is hidden from the reader.", "Spam filters do not read the subject."], "a": 1, "why": "Anything that answers an email quotes its subject back: a reply, a forward, even an out of office message. So the tag comes back by itself.", "at": "mailbot.html#step3"},
  {"id": "mailbot_05", "unit": "mailbot", "d": 0.3, "type": "mcq", "q": "A mail bot searches an IMAP mailbox for `UNSEEN` emails on every pass. It finds one of its own emails and skips it, but it still marks that email as read. Why?", "o": ["So the sender knows that the bot has read it.", "So the email moves to the spam folder.", "So the next pass does not find it again.", "So a tag is added to the subject line."], "a": 2, "why": "The search asks only for unread mail. An email that is skipped but left unread is found again on every pass.", "at": "mailbot.html#step4"},
  {"id": "mailbot_06", "unit": "mailbot", "d": 0.4, "type": "mcq", "q": "This Python loop runs a mail bot. `one_pass` logs in to the mail server, reads the mailbox and sends replies. What is the `try` and `except Exception` for?", "code": "EVERY = 3600\n\nwhile True:\n    clock = time.strftime(\"%H:%M:%S\")\n    try:\n        sent = one_pass(user, password)\n        print(clock, \"done.\", sent, \"sent. Sleeping\", EVERY, \"seconds.\")\n    except Exception as problem:\n        print(clock, \"that pass failed:\", problem)\n        print(clock, \"trying again in\", EVERY, \"seconds.\")\n    time.sleep(EVERY)", "o": ["One failed pass must not stop the loop.", "It makes the loop sleep for one hour.", "It stops the bot replying to itself.", "It sends the error to you by email."], "a": 0, "why": "A program that runs for weeks will meet a dropped connection. The `except` prints the problem, and the loop sleeps and tries again.", "at": "mailbot.html#step5"},
  {"id": "mailbot_07", "unit": "mailbot", "d": 0.6, "type": "mcq", "q": "You ask a small local model to answer with JSON: a yes or no, and a `confidence` number from 0 to 1. Every yes comes back with 0.80 and every no with 0.20. How should your program use the answer?", "o": ["Reply only when the number is above 0.5.", "Ask the model twice and take the mean.", "Raise the temperature to get real numbers.", "Use the yes or no, and ignore the number."], "a": 3, "why": "The model is not measuring anything with that number. Use the yes or no, and do not build a threshold on the number.", "at": "mailbot.html#step6"},
  {"id": "mailbot_08", "unit": "mailbot", "d": 0.8, "type": "mcq", "q": "A mail bot looks at all of today's unread mail in your inbox. It asks a model if each email is a request for the bot. For one email the model says no. Why should the bot leave that email unread?", "o": ["The model may be wrong, so the bot asks again later.", "It is not the bot's mail; marking it would hide it.", "The server does not allow flags on today's mail.", "Unread mail is faster to search on the next pass."], "a": 1, "why": "A bot that reads your whole inbox touches mail that is meant for you. If it marks such an email as read, you may never see it.", "at": "mailbot.html#step7"},
  {"id": "apis_01", "unit": "apis", "d": -0.8, "type": "mcq", "q": "What does an API do?", "o": ["It does the hard work inside the server", "It keeps your secret key safe", "It carries messages between two programs", "It stores your files on your own computer"], "a": 2, "why": "An API is a messenger. It carries your request to the server and brings the response back.", "at": "apis.html"},
  {"id": "apis_02", "unit": "apis", "d": -0.4, "type": "fill", "q": "Which status code means the request worked? Type the number.", "answers": ["200"], "why": "200 means OK: success. 401, 404 and 500 are all problems.", "at": "apis.html"},
  {"id": "apis_03", "unit": "apis", "d": -0.2, "type": "mcq", "q": "Which part of a request tells the API WHO you are?", "o": ["The key", "The body", "The method", "The address"], "a": 0, "why": "The key is your secret password, so the API knows it is really you. The address is where, the method is what, the body is the order.", "at": "apis.html"},
  {"id": "apis_04", "unit": "apis", "d": 0.1, "type": "mcq", "q": "The API answers with status code 401. What is wrong?", "o": ["The address is wrong", "Something broke on the server", "The request body is too long", "Your key is missing or wrong"], "a": 3, "why": "401 means \"Who are you?\": your key is missing or wrong. A wrong address is 404, and a broken server is 500.", "at": "apis.html"},
  {"id": "apis_05", "unit": "apis", "d": 0.4, "type": "mcq", "q": "Why should you never put your real key in JavaScript on a public web page?", "o": ["JavaScript cannot send a POST request at all", "Everyone who visits could see and steal it", "The key stops working inside a browser", "The API only accepts keys from Python"], "a": 1, "why": "In a web page, everyone who visits could peek and steal the key. Real websites hide the key on a server.", "at": "apis.html"},
  {"id": "apis_06", "unit": "apis", "d": 0.6, "type": "mcq", "q": "This Python code uses the `requests` library. `response` is the answer from an image API. Why does the `if not response.ok:` check come before the file is saved?", "code": "if not response.ok:\n    print(f\"API rejected the request ({response.status_code}): {response.text}\")\n    raise SystemExit(1)\n\nwith open(\"robot.jpg\", \"wb\") as file:\n    file.write(response.content)", "o": ["So an error is not written into `robot.jpg`", "So the API key is checked a second time", "So the picture is sent a second time", "So `robot.jpg` is deleted after each run"], "a": 0, "why": "If the API refused, the response holds an error message, not a picture. The check shows why and stops, so the error is not saved as the picture file.", "at": "apis.html"},
  {"id": "html_intro_01", "unit": "html_intro", "d": -0.8, "type": "mcq", "q": "A web page is built with HTML, CSS and JavaScript. What is the job of HTML?", "o": ["It decorates the web page: colours and fonts.", "It makes the web page react to your clicks.", "It describes the structure of the web page.", "It gives the web page an address on the internet."], "a": 2, "why": "HTML describes the structure of a web page with tags. CSS is the decoration, and JavaScript makes it functional.", "at": "html_intro.html#overview"},
  {"id": "html_intro_02", "unit": "html_intro", "d": -0.4, "type": "mcq", "q": "You write a web page in a text editor and save it as `index.html`. Why must the name end in `.html`?", "o": ["It makes the file smaller to save.", "It lets Notepad open the file again.", "It puts the file on the internet.", "It tells the computer it is a web page."], "a": 3, "why": "The `.html` extension tells your computer that the file is a web page, so it opens in a browser.", "at": "html_intro.html#create-file"},
  {"id": "html_intro_03", "unit": "html_intro", "d": 0.3, "type": "mcq", "q": "A web page puts a list of goals in `<ol>` and a list of interests in `<ul>`. What is the difference between the two tags?", "o": ["`<ol>` is an ordered list; `<ul>` is an unordered list.", "`<ol>` is a list of web links; `<ul>` is a list of plain text.", "`<ol>` is shown in the browser; `<ul>` stays hidden.", "`<ol>` is for one item only; `<ul>` is for many items."], "a": 0, "why": "`<ul>` is an unordered list (bullets) and `<ol>` is an ordered list.", "at": "html_intro.html#first-project"},
  {"id": "html_intro_04", "unit": "html_intro", "d": -0.3, "type": "mcq", "q": "You changed `index.html` in the text editor. How do you see the change in the browser?", "o": ["Close the browser and restart the computer.", "Save the file, then press F5 in the browser.", "Rename the file, then drag it to the editor.", "Type the new text into the browser address bar."], "a": 1, "why": "After editing, save the file and press F5 (or Ctrl+R) in the browser to see your changes.", "at": "html_intro.html#view-browser"},
  {"id": "html_intro_05", "unit": "html_intro", "d": 0.1, "type": "mcq", "q": "You open a local HTML file in the browser. The address does not start with `http://` or `https://`. What does it start with?", "o": ["`file://`", "`local://`", "`html://`", "`disk://`"], "a": 0, "why": "When you view a local file, the URL starts with `file://`, like `file:///C:/Users/YourName/Desktop/index.html`.", "at": "html_intro.html#view-browser"},
  {"id": "html_intro_06", "unit": "html_intro", "d": 0.5, "type": "mcq", "q": "An HTML file has `<title>My First Web Page</title>` in its `<head>` and `<h1>Hello World!</h1>` in its `<body>`. Where do you see the words `My First Web Page`?", "o": ["As the big heading in the browser", "As a paragraph under the heading", "In the tab of the browser window", "Nowhere, because it is a comment"], "a": 2, "why": "`<title>` is the title shown in the browser tab. The `<h1>` text is the heading you see in the body of the web page.", "at": "html_intro.html#create-file"},
  {"id": "javascript_lab_01", "unit": "javascript_lab", "d": -0.7, "type": "mcq", "q": "What does the word `let` do in JavaScript?", "o": ["Puts a new value in an old box", "Prints a value on the console", "Asks if two values are the same", "Makes a new box with a name"], "a": 3, "why": "A variable is a box with a name. `let` makes a new box, and you only write it the first time.", "at": "javascript_lab.html#L-var"},
  {"id": "javascript_lab_02", "unit": "javascript_lab", "d": -0.4, "type": "mcq", "q": "What does `\"5\" + \"5\"` give?", "o": ["`10`", "`\"55\"`", "`\"10\"`", "`25`"], "a": 1, "why": "Quotes make it words, so the plus sign joins the two strings. It does not add numbers.", "at": "javascript_lab.html#L-str"},
  {"id": "javascript_lab_03", "unit": "javascript_lab", "d": 0.0, "type": "mcq", "q": "What is the difference between `=` and `==`?", "o": ["`=` puts a value in a box; `==` asks if two are the same", "`=` asks if two are the same; `==` puts a value in a box", "`=` is used for numbers; `==` is used for strings only", "`=` makes a new box; `==` changes a box that is there"], "a": 0, "why": "One `=` puts a value in a box. Two `==` asks \"are these the same?\". Mixing them up is a very common beginner bug.", "at": "javascript_lab.html#L-if"},
  {"id": "javascript_lab_04", "unit": "javascript_lab", "d": 0.4, "type": "mcq", "q": "What is `places.length` after these lines run?", "code": "let places = [\"Tha Phae Gate\", \"Warorot Market\"];\nplaces.push(\"Wat Don Chan\");\nplaces.pop();", "o": ["0", "1", "2", "3"], "a": 2, "why": "`push` adds one item at the end, which makes three. `pop` takes the last one away, so two are left.", "at": "javascript_lab.html#L-arr"},
  {"id": "javascript_lab_05", "unit": "javascript_lab", "d": 0.2, "type": "fill", "q": "What does `points(4)` give? Type the number.", "code": "function points(hits) {\n  return hits * 50;\n}", "answers": ["200"], "why": "The function takes `hits` in and `return` gives back `hits * 50`. 4 times 50 is 200.", "at": "javascript_lab.html#L-fn"},
  {"id": "javascript_lab_06", "unit": "javascript_lab", "d": 0.7, "type": "mcq", "q": "This JavaScript code moves a picture across a canvas. You delete the `ctx.clearRect(...)` line. What do you see?", "code": "let x = 0;\nfunction frame() {\n  ctx.clearRect(0, 0, 520, 280);\n  x = x + 3;\n  ctx.drawImage(alien, x, 100);\n  requestAnimationFrame(frame);\n}\nframe();", "o": ["The picture stays still at the left side", "The picture leaves a long smear behind it", "The picture moves twice as fast as before", "The picture is drawn once and then stops"], "a": 1, "why": "Animation is wipe, move, draw, again. `clearRect` is the wipe. Without it the old pictures stay, so you get a long smear across the canvas.", "at": "javascript_lab.html#L-anim"},
  {"id": "it__ai__ai_code_01", "unit": "it__ai__ai_code", "d": -0.8, "type": "mcq", "q": "With AI code generation, what do you give the AI?", "o": ["What you want the program to do", "A finished program to compile", "A list of every error in your code", "A new programming language"], "a": 0, "why": "You tell the AI what you want your program to do, and the AI writes the code for you.", "at": "it/ai/ai_code.html"},
  {"id": "it__ai__ai_code_02", "unit": "it__ai__ai_code", "d": -0.4, "type": "mcq", "q": "What does it mean to refactor code?", "o": ["To find and fix the errors in it", "To turn it into a program that runs", "To repeat the same action a great many times", "To improve its structure, not what it does"], "a": 3, "why": "Refactor means to improve code structure without changing what it does. To find and fix errors is to debug.", "at": "it/ai/ai_code.html"},
  {"id": "it__ai__ai_code_03", "unit": "it__ai__ai_code", "d": -0.1, "type": "mcq", "q": "Which word means the rules for writing code in a programming language, like grammar for code?", "o": ["Runtime", "Syntax", "Snippet", "Algorithm"], "a": 1, "why": "Syntax is the rules for writing code in a programming language. If the syntax is wrong, the code will not compile.", "at": "it/ai/ai_code.html"},
  {"id": "it__ai__ai_code_04", "unit": "it__ai__ai_code", "d": 0.2, "type": "mcq", "q": "An AI has just written code for your prompt. What is the best thing to do next?", "o": ["Paste it into your project at once", "Ask the AI for a longer version", "Read it and understand each line", "Delete it and write your own code"], "a": 2, "why": "Do not just copy the code. Read it first and try to understand what each line does.", "at": "it/ai/ai_code.html"},
  {"id": "it__ai__ai_code_05", "unit": "it__ai__ai_code", "d": 0.4, "type": "mcq", "q": "You read the code that an AI wrote for you, but you do not understand it. What is the best thing to do?", "o": ["Ask the AI to explain each line", "Ask the AI for another program", "Run it many times until it is clear", "Change to another language"], "a": 0, "why": "If you do not understand the code, ask the AI: \"Please explain each line of this code in simple English.\"", "at": "it/ai/ai_code.html"},
  {"id": "it__ai__ai_code_06", "unit": "it__ai__ai_code", "d": 0.4, "type": "mcq", "q": "You ask an AI to write a calculator program. Your prompt has this line: \"If the user enters zero for division, the program will show an error.\" Why is this line good to have in the prompt?", "o": ["It makes the AI write the code faster", "It says what to do in a special case", "It stops the AI from using any loops", "It makes the AI explain every line"], "a": 1, "why": "A clear prompt tells the AI exactly what the program must do, also in a special case such as division by zero. Then the AI writes code for that case.", "at": "it/ai/ai_code.html"},
  {"id": "prompt_lab_01", "unit": "prompt_lab", "d": -0.7, "type": "mcq", "q": "You write a prompt for an AI chat model and give it no role. What usually happens?", "o": ["The AI refuses to answer the prompt.", "The AI answers in the wrong language.", "The AI gives generic answers.", "The AI gives a much longer answer."], "a": 2, "why": "Without a role, the AI gives generic answers. `Act as a [role]` reshapes the whole response.", "at": "prompt_lab.html#anatomy"},
  {"id": "prompt_lab_02", "unit": "prompt_lab", "d": -0.4, "type": "mcq", "q": "A well-built prompt for an AI model can have up to four parts. Which list names them?", "o": ["Role, context, task, constraints", "Question, answer, examples, score", "English, Thai, Chinese, grammar", "Title, intro, body, conclusion"], "a": 0, "why": "A prompt can be built from a role, context, a task and constraints such as length, style and format.", "at": "prompt_lab.html#builder"},
  {"id": "prompt_lab_03", "unit": "prompt_lab", "d": 0.0, "type": "mcq", "q": "A prompt for an AI model can have a role, context, a task and constraints. What should a good prompt have at least?", "o": ["A length and a format", "A context and a style", "A task in three languages", "A role and a task"], "a": 3, "why": "The role tells the AI who to be, and the task tells it what to do. A good prompt has at least these two.", "at": "prompt_lab.html#builder"},
  {"id": "prompt_lab_04", "unit": "prompt_lab", "d": 0.4, "type": "mcq", "q": "In a prompt you ask the AI for a length, a style and a format for its answer. Which part of the prompt are these?", "o": ["The role", "The constraints", "The context", "The task"], "a": 1, "why": "A prompt has a role, context, a task and constraints. Length, style and format limit how the answer looks, so they are the constraints.", "at": "prompt_lab.html#builder"},
  {"id": "prompt_lab_05", "unit": "prompt_lab", "d": 0.6, "type": "mcq", "q": "An AI gives the same answer in English and in Chinese. Why does the Chinese one look shorter on the screen?", "o": ["The AI leaves out the constraints in Chinese.", "The AI understands Chinese prompts less well.", "The AI answers with fewer ideas in Chinese.", "One character can carry a whole word."], "a": 3, "why": "Thai compresses with no spaces and short particles, and Chinese compresses further: one character can carry a whole English word.", "at": "prompt_lab.html#length"},
  {"id": "it__git_basics_01", "unit": "it__git_basics", "d": -0.8, "type": "mcq", "q": "What is a commit in Git?", "o": ["A saved record of your files, plus a message", "A separate line of development for a feature", "A personal copy of someone else's repository", "A request to merge your code into a project"], "a": 0, "why": "A commit is Git's save action. It records the state of your files at that moment, with a message that says what you changed.", "at": "it/git_basics.html"},
  {"id": "it__git_basics_02", "unit": "it__git_basics", "d": -0.5, "type": "mcq", "q": "What does `git status` do?", "o": ["Shows the full commit history", "Shows what has changed", "Sends commits to the remote", "Saves a snapshot of the files"], "a": 1, "why": "`git status` checks what has changed in your files. It is a good command to start with, before you stage or commit.", "at": "it/git_basics.html"},
  {"id": "it__git_basics_03", "unit": "it__git_basics", "d": -0.3, "type": "fill", "q": "`git ____` creates a brand new repo in the current folder. Type the missing word.", "answers": ["init", "git init"], "why": "`git init` creates a new repo in the current folder. `git clone` copies an existing one from a server.", "at": "it/git_basics.html"},
  {"id": "it__git_basics_04", "unit": "it__git_basics", "d": 0.1, "type": "mcq", "q": "The workflow is stage, commit, push. What does `git add` do?", "o": ["Uploads local commits to the remote repo", "Creates a new branch and switches to it", "Puts file changes in the staging area", "Downloads the latest changes from GitHub"], "a": 2, "why": "The staging area holds file changes before you commit them. Files are staged with `git add`.", "at": "it/git_basics.html"},
  {"id": "it__git_basics_05", "unit": "it__git_basics", "d": 0.4, "type": "mcq", "q": "What does `git checkout -b feature/my-new-feature` do?", "o": ["Merges the branch back into `main`", "Lists all local branches by name", "Only switches to an existing branch", "Creates the branch and switches to it"], "a": 3, "why": "`git branch` creates a branch and `git checkout` switches to one. `git checkout -b` does both in one step.", "at": "it/git_basics.html"},
  {"id": "it__git_basics_06", "unit": "it__git_basics", "d": 0.7, "type": "mcq", "q": "You forked a repo on GitHub and cloned your fork. How do you get later updates from the original repo?", "o": ["Add it as `upstream`, then `git pull upstream main`", "Run `git push -u origin main` from your clone", "Run `git init` again inside the cloned folder", "Open a pull request against your own fork"], "a": 0, "why": "`git remote add upstream <address>` names the original repo, and `git pull upstream main` downloads its updates.", "at": "it/git_basics.html"},
  {"id": "it__github_pages_lab_01", "unit": "it__github_pages_lab", "d": -0.8, "type": "mcq", "q": "What is the difference between Git and GitHub?", "o": ["Git is a website; GitHub is a tool on your computer", "Git is a tool on your computer; GitHub is a website", "Git and GitHub are two names for the same website", "Git is for teams; GitHub is for people working alone"], "a": 1, "why": "Git is the tool that tracks changes on your computer. GitHub is the website that stores Git repositories online.", "at": "it/github_pages_lab.html#github"},
  {"id": "it__github_pages_lab_02", "unit": "it__github_pages_lab", "d": -0.4, "type": "mcq", "q": "What does `git add index.html` do?", "o": ["It sends your commits up to GitHub", "It saves the staged changes into the history", "It brings other people's commits down to you", "It puts the change in the staging area"], "a": 3, "why": "`git add` puts a change in the staging area. Only what is staged goes into the next commit.", "at": "it/github_pages_lab.html#git"},
  {"id": "it__github_pages_lab_03", "unit": "it__github_pages_lab", "d": -0.2, "type": "mcq", "q": "On GitHub Pages, a repository named exactly `username.github.io` (with your own username) is special. Why does the name matter?", "o": ["GitHub turns that exact name into your main website", "Git cannot clone a repository that has any other name", "Only that name lets you make a public repository", "That name gives the repository a free README file"], "a": 0, "why": "GitHub turns a repository with this exact name into your main website, at `https://username.github.io`. If the name is spelled wrong, Pages cannot find it.", "at": "it/github_pages_lab.html#mission-a"},
  {"id": "it__github_pages_lab_04", "unit": "it__github_pages_lab", "d": 0.1, "type": "mcq", "q": "Why do you run `git config --global user.name \"Your Name\"` once on each computer?", "o": ["It signs you in to GitHub for the first push", "It chooses the address of your live website", "Every commit is signed with that name and email", "It makes your repository public for GitHub Pages"], "a": 2, "why": "Every commit is signed with this name and email. That is how the history says who changed what.", "at": "it/github_pages_lab.html#mission-b"},
  {"id": "it__github_pages_lab_05", "unit": "it__github_pages_lab", "d": 0.5, "type": "mcq", "q": "You edit `index.html`, run `git add` and `git commit`, but you forget `git push`. What does your live website show?", "o": ["The new page, after you wait one or two minutes", "The old page: GitHub does not have the commit", "An error page, until you push the commit", "The new page, but only in your own browser"], "a": 1, "why": "`git commit` saves the change in the repository on your computer. Only `git push` sends your commits up to GitHub, where the site lives.", "at": "it/github_pages_lab.html#mission-b"},
  {"id": "it__github_pages_lab_06", "unit": "it__github_pages_lab", "d": 0.3, "type": "mcq", "q": "In Git, which one command makes a new branch named `add-about` and moves onto it?", "o": ["`git branch add-about`", "`git checkout add-about`", "`git switch -c add-about`", "`git switch add-about`"], "a": 2, "why": "`git switch -c add-about` makes a new branch and moves onto it. Then `git branch` shows a star on `add-about`. The older `git checkout -b add-about` does the same.", "at": "it/github_pages_lab.html#mission-c"},
  {"id": "it__github_pages_lab_07", "unit": "it__github_pages_lab", "d": 0.7, "type": "mcq", "q": "You merged a pull request on GitHub, from your branch into `main`. Then you run `git switch main` and `git pull` on your computer. Why?", "o": ["To delete the merged branch on GitHub", "To open a second pull request for review", "To send your branch up to GitHub once more", "To bring the merge down to your own `main`"], "a": 3, "why": "The merge happened on GitHub. `git pull` brings those commits down, so `main` on your computer matches.", "at": "it/github_pages_lab.html#mission-c"},
  {"id": "javascript_space_game_01", "unit": "javascript_space_game", "d": -0.7, "type": "mcq", "q": "In an HTML file, why do people put the `script` at the bottom of the `body`?", "o": ["So the HTML elements exist before the code needs them", "So the browser can load every one of the pictures first", "So the console is already open when the code runs", "So the title is drawn above everything else"], "a": 0, "why": "The browser reads the file from top to bottom. With the script at the bottom, the elements already exist when the code looks for them, for example with `document.getElementById`.", "at": "javascript_space_game.html"},
  {"id": "javascript_space_game_02", "unit": "javascript_space_game", "d": -0.4, "type": "mcq", "q": "This JavaScript function draws on a canvas. `pics[name]` is an `Image` that has not finished loading. What does the function do?", "code": "function drawPic(name, x, y, size, colour) {\n  const img = pics[name];\n  if (img && img.complete && img.naturalWidth > 0) {\n    ctx.drawImage(img, x, y, size, size);\n  } else {\n    ctx.fillStyle = colour;\n    ctx.fillRect(x, y, size, size);\n  }\n}", "o": ["It waits until the picture arrives", "It draws nothing in that frame", "It draws a coloured box instead", "It prints an error in the console"], "a": 2, "why": "Pictures take time to arrive. Until `img.complete` is true, the `else` part runs and fills a box with the colour that was passed in.", "at": "javascript_space_game.html"},
  {"id": "javascript_space_game_03", "unit": "javascript_space_game", "d": 0.0, "type": "mcq", "q": "A JavaScript canvas game has two functions: `update` changes the numbers and `draw` shows them. What does keeping them apart let this loop do?", "code": "function loop() {\n  if (!over) update();\n  draw();\n  requestAnimationFrame(loop);\n}", "o": ["Draw each frame without wiping the canvas", "Make the browser run the whole game faster", "Use more than one canvas at the same time", "Pause the game by not calling `update`"], "a": 3, "why": "`update` changes numbers and `draw` shows them. When `over` is true the loop stops calling `update` but keeps drawing, so the picture stays and nothing moves.", "at": "javascript_space_game.html"},
  {"id": "javascript_space_game_04", "unit": "javascript_space_game", "d": 0.1, "type": "mcq", "q": "In this JavaScript game, bullets fly up the canvas. What goes wrong if you delete the last line?", "code": "let bullets = [];\n\nfunction shoot() {\n  bullets.push({ x: ship.x + 18, y: ship.y, w: 4, h: 12 });\n}\n\n// inside update(), which runs on every frame:\nbullets.forEach(b => { b.y -= 8; });\nbullets = bullets.filter(b => b.y > -20);", "o": ["Bullets stop moving at the top of the screen", "Old bullets stay and the game gets slower", "Only one bullet can be fired at a time", "The space bar scrolls the window down"], "a": 1, "why": "`filter` makes a new array with only the bullets still near the screen. Without it, old bullets fly up for ever, the array keeps growing, and the game slowly gets slower.", "at": "javascript_space_game.html"},
  {"id": "javascript_space_game_05", "unit": "javascript_space_game", "d": 0.3, "type": "mcq", "q": "This `draw` function runs on every frame of a canvas game, and the ship moves. You take out its first two lines. What do you see?", "code": "function draw() {\n  ctx.fillStyle = \"#05070f\";\n  ctx.fillRect(0, 0, canvas.width, canvas.height);\n  drawPic(\"player\", ship.x, ship.y, ship.w, \"#ffd83d\");\n}", "o": ["The ship stops moving at once", "The canvas stays empty for ever", "The ship leaves a smear behind", "The ship is drawn in black"], "a": 2, "why": "Those two lines wipe the canvas each frame. Without the wipe, every old drawing stays, so everything that moves smears.", "at": "javascript_space_game.html#sec-play"},
  {"id": "javascript_space_game_06", "unit": "javascript_space_game", "d": 0.5, "type": "mcq", "q": "In this JavaScript game code, rows of aliens walk sideways. The code sets one flag, `atEdge`, and does not turn each alien by itself when it meets the wall. Why?", "code": "let atEdge = false;\naliens.forEach(a => {\n  a.x += dir * 0.9;\n  if (a.x < 6 || a.x > canvas.width - a.w - 6) atEdge = true;\n});\nif (atEdge) {\n  dir = -dir;\n  aliens.forEach(a => { a.y += 18; });\n}", "o": ["So only the first row steps down", "So the whole army turns together", "So the aliens move faster at the wall", "So dead aliens leave the list"], "a": 1, "why": "If each alien turned by itself, the rows would break apart. The code checks them all, sets one flag, and then turns the whole army together.", "at": "javascript_space_game.html"},
  {"id": "javascript_space_game_07", "unit": "javascript_space_game", "d": 0.7, "type": "mcq", "q": "This JavaScript runs on every frame of a game. Why does the `if` test `!a.dead` before it adds to the score?", "code": "bullets.forEach(b => {\n  aliens.forEach(a => {\n    if (!a.dead && hit(b, a)) { a.dead = true; b.dead = true; score += 50; }\n  });\n});\nbullets = bullets.filter(b => !b.dead);\naliens  = aliens.filter(a => !a.dead);", "o": ["So a dead alien is drawn in grey", "So the loop does not skip the next bullet", "So the next wave starts at once", "So two bullets cannot score on one alien"], "a": 3, "why": "Two fast bullets can both be inside one alien on the same frame. The alien is only marked dead, and stays in the array until `filter` runs, so the test stops it from being hit again.", "at": "javascript_space_game.html"},
  {"id": "github_01", "unit": "github", "d": -0.8, "type": "mcq", "q": "What is Git?", "o": ["A website that stores your projects online", "A simple way to format text with `#` and `-`", "A version control system that tracks code changes", "A free service that hosts a website for you"], "a": 2, "why": "Git is a version control system: it tracks changes in your code and lets you go back to previous versions. GitHub is the website.", "at": "github.html#overview"},
  {"id": "github_02", "unit": "github", "d": -0.5, "type": "mcq", "q": "What does \"push\" mean?", "o": ["Sending your changes to GitHub", "Getting changes from GitHub", "Combining two branches into one", "Copying a repository to your PC"], "a": 0, "why": "Push sends your changes to GitHub. Pull gets changes from GitHub to your computer.", "at": "github.html#vocabulary"},
  {"id": "github_03", "unit": "github", "d": 0.2, "type": "mcq", "q": "Which word means copying someone else's repository to your own GitHub account?", "o": ["Clone", "Merge", "Pull", "Fork"], "a": 3, "why": "A fork copies someone else's repository to your GitHub account. A clone copies a repository from GitHub to your computer.", "at": "github.html#vocabulary"},
  {"id": "github_04", "unit": "github", "d": 0.1, "type": "mcq", "q": "Which of these can be the name of a GitHub repository, exactly as it is written?", "o": ["`my first repo`", "`hello_world2`", "`My Website`", "`my repo!`"], "a": 1, "why": "A repository name can hold letters, numbers, hyphens and underscores. It cannot hold a space or a sign such as `!`.", "at": "github.html#create-repo"},
  {"id": "github_05", "unit": "github", "d": 0.4, "type": "mcq", "q": "Why is `update` a bad commit message?", "o": ["It is too short for GitHub to accept it", "It does not say what you changed or why", "It does not start with a capital letter", "It has no version number such as v1.0"], "a": 1, "why": "A commit message should be clear and specific, and explain what you changed and why. \"Fix typo in introduction\" is a good one.", "at": "github.html#edit-files"},
  {"id": "github_06", "unit": "github", "d": -0.3, "type": "mcq", "q": "In Markdown, which character at the start of a line makes a heading?", "o": ["`*`", "`#`", "`>`", "`-`"], "a": 1, "why": "In Markdown, `#` makes a heading, `-` makes a list item and `**` makes bold text.", "at": "github.html#edit-files"},
  {"id": "github_07", "unit": "github", "d": 0.6, "type": "mcq", "q": "Your repository is named `my-first-repo`. It holds `index.html`, and you turned on GitHub Pages. Where is your website?", "o": ["`https://yourusername.github.io/my-first-repo/`", "`https://github.com/yourusername/index.html`", "`https://my-first-repo.github.com/yourusername/`", "`https://yourusername.github.com/pages/main/`"], "a": 0, "why": "A GitHub Pages website is at `https://yourusername.github.io/repository-name/`.", "at": "github.html#github-pages"},
  {"id": "free_local_ai_01", "unit": "free_local_ai", "d": -0.7, "type": "mcq", "q": "You run AI models on your own computer with Ollama and Open WebUI. People call this free. Which two things are NOT free?", "o": ["Electricity and your time", "Ollama and Open WebUI software", "Ollama and electricity", "Open WebUI and your time"], "a": 0, "why": "The software (Ollama, Open WebUI) is free. Electricity, and your time to set it up and look after it, are not.", "at": "free_local_ai.html"},
  {"id": "free_local_ai_02", "unit": "free_local_ai", "d": -0.4, "type": "fill", "q": "You chat with a model after `ollama run qwen2.5:7b`. What do you type to leave?", "answers": ["/bye"], "why": "In a chat started with `ollama run`, you type `/bye` to end it and go back to the terminal.", "at": "free_local_ai.html#fit"},
  {"id": "free_local_ai_03", "unit": "free_local_ai", "d": 0.0, "type": "mcq", "q": "What does Open WebUI give you?", "o": ["A faster model for a small laptop", "A way to run Ollama with no Docker", "A ChatGPT-style chat page to share", "A free Gemini account for schools"], "a": 2, "why": "Open WebUI gives you a ChatGPT-style chat page. Other people on the same Wi-Fi can use it too, and it needs Docker first.", "at": "free_local_ai.html#fit"},
  {"id": "free_local_ai_04", "unit": "free_local_ai", "d": 0.4, "type": "mcq", "q": "A model does not fit in the graphics card's own memory and spills into normal RAM. What happens?", "o": ["Ollama refuses to load the model.", "The model gets much slower.", "The answers become less correct.", "The model is downloaded again."], "a": 1, "why": "On a graphics card, the model must fit in the card's own memory. If it spills into normal RAM, it gets much slower.", "at": "free_local_ai.html#fit"},
  {"id": "free_local_ai_05", "unit": "free_local_ai", "d": 0.7, "type": "mcq", "q": "Many people use one machine at the same time. How do Ollama and vLLM differ?", "o": ["Ollama is free, but vLLM costs money for each user.", "Ollama shares one model; vLLM loads one per person.", "Ollama serves all together; vLLM serves one at a time.", "Ollama serves one at a time; vLLM serves all together."], "a": 3, "why": "Ollama answers one person at a time. vLLM reads the model once and answers everyone together, but it is not unlimited: it slows down when very many people ask at once.", "at": "free_local_ai.html#fit"},
  {"id": "it__ai__ai_ollama_01", "unit": "it__ai__ai_ollama", "d": -0.7, "type": "mcq", "q": "What does Ollama do?", "o": ["It trains new AI models in the cloud", "It runs AI models on your own computer", "It sells access to models for each token", "It turns web apps into Docker containers"], "a": 1, "why": "Ollama lets you run large language models entirely on your own computer. It does not train models; it makes them easy to run.", "at": "it/ai/ai_ollama.html"},
  {"id": "it__ai__ai_ollama_02", "unit": "it__ai__ai_ollama", "d": -0.3, "type": "fill", "q": "On which port does Ollama's REST API listen? Type the number.", "answers": ["11434"], "why": "Ollama has a REST API on port 11434 that any app can call, for example `http://localhost:11434/api/chat`.", "at": "it/ai/ai_ollama.html"},
  {"id": "it__ai__ai_ollama_03", "unit": "it__ai__ai_ollama", "d": 0.2, "type": "mcq", "q": "Ollama loads a model into RAM for the first request. What happens to the model after that?", "o": ["It stays in RAM, kept warm for later requests", "It is deleted from the disk to save space", "It is sent to the cloud so it can be checked", "It is downloaded again for each new request"], "a": 0, "why": "The model is loaded into RAM only the first time. It is kept warm afterwards.", "at": "it/ai/ai_ollama.html"},
  {"id": "it__ai__ai_ollama_04", "unit": "it__ai__ai_ollama", "d": 0.5, "type": "mcq", "q": "In Ollama, what is the rule of thumb for how much RAM a model needs?", "o": ["About half of the size of the model file", "Always 48 GB, for a model of any size", "About the model file's size, plus about 30%", "Only as much as the length of the prompt"], "a": 2, "why": "You need roughly as much RAM as the model file is large, plus about 30% headroom.", "at": "it/ai/ai_ollama.html"},
  {"id": "it__ai__ai_ollama_05", "unit": "it__ai__ai_ollama", "d": 0.6, "type": "mcq", "q": "Ollama streams its reply. What does that mean for the user?", "o": ["The whole answer arrives only when it is finished", "The prompt is sent to several models at one time", "The model file is loaded from the disk in pieces", "Each token is sent back as soon as it is made"], "a": 3, "why": "Ollama generates one token at a time and streams each one back as it is produced, so the user sees the first words very soon.", "at": "it/ai/ai_ollama.html"},
  {"id": "make_an_ai_tutor_01", "unit": "make_an_ai_tutor", "d": -0.7, "type": "mcq", "q": "What is the job of Ollama?", "o": ["It runs the AI on your own machine", "It sends your words to a big company", "It is the Python library that chats", "It fixes Thai letters on Windows"], "a": 0, "why": "Ollama runs an AI model on your own machine. A Python library such as LlamaIndex can then talk to Ollama.", "at": "make_an_ai_tutor.html#step0"},
  {"id": "make_an_ai_tutor_02", "unit": "make_an_ai_tutor", "d": -0.4, "type": "mcq", "q": "In a Modelfile, what does the `SYSTEM` block say?", "o": ["Which brain to start with", "How brave the word choice is", "What job the model must do", "Which computer runs the model"], "a": 2, "why": "`FROM` says which brain to start with. `SYSTEM` says what job to do.", "at": "make_an_ai_tutor.html#step4"},
  {"id": "make_an_ai_tutor_03", "unit": "make_an_ai_tutor", "d": -0.2, "type": "fill", "q": "In an Ollama Modelfile, which value of `PARAMETER temperature` makes the model always pick the most likely word, so the same question gives the same answer every time? Type the number.", "answers": ["0", "0.0", "zero"], "why": "At temperature 0 the model always picks the most likely word. The same question then gives the same answer every time.", "at": "make_an_ai_tutor.html#step6"},
  {"id": "make_an_ai_tutor_04", "unit": "make_an_ai_tutor", "d": 0.1, "type": "mcq", "q": "You change an Ollama Modelfile and run your program again, but the model acts the same as before. What did you forget?", "o": ["To download again with `ollama pull`", "To build again with `ollama create`", "To start again with `ollama serve`", "To check again with `ollama list`"], "a": 1, "why": "Ollama reads a Modelfile only when it builds a model. After every change you must build again with `ollama create <name> -f Modelfile`.", "at": "make_an_ai_tutor.html#step5"},
  {"id": "make_an_ai_tutor_05", "unit": "make_an_ai_tutor", "d": 0.3, "type": "mcq", "q": "A Python chat program talks to a model that runs in Ollama. How can the program make the model remember what you said earlier?", "o": ["It saves each answer inside the Modelfile", "It sets `num_ctx` so Ollama stores the talk", "It asks Ollama to switch its memory on", "It sends the whole talk again every time"], "a": 3, "why": "The model has no memory. The program keeps a list of every message and sends the whole conversation again every time.", "at": "make_an_ai_tutor.html#step8"},
  {"id": "make_an_ai_tutor_06", "unit": "make_an_ai_tutor", "d": 0.6, "type": "mcq", "q": "A model keeps writing markdown stars, `**like this**`, after its `SYSTEM` block told it three times not to. Which fix works every time?", "o": ["Take the stars out in your own code", "Write the rule a fourth time in `SYSTEM`", "Change to a bigger model that obeys rules", "Set temperature to 0 so the stars go away"], "a": 0, "why": "One line such as `.replace(\"*\", \"\")` removes the stars every single time. A rule you can enforce in code beats a rule you have to hope for.", "at": "make_an_ai_tutor.html#step5"},
  {"id": "make_an_ai_tutor_07", "unit": "make_an_ai_tutor", "d": 0.7, "type": "mcq", "q": "You ask four local AI models one easy question, and you know the answer. Three are wrong, each in a different way. What is the best conclusion?", "o": ["Something is broken in the Modelfile", "Small models are safe for easy questions", "The models guess, so test yours first", "The question was too hard for any model"], "a": 2, "why": "When all models are wrong in the same way, something is broken. When they are wrong in different ways, they are guessing, so test a model before you trust it.", "at": "make_an_ai_tutor.html#step7"},
  {"id": "tkinter_chat_01", "unit": "tkinter_chat", "d": -0.8, "type": "mcq", "q": "In a tkinter program, what does `root.mainloop()` do?", "o": ["It saves the text in the window to a file.", "It draws the window and waits for clicks.", "It starts a second thread for slow work.", "It closes the window when the work is done."], "a": 1, "why": "`mainloop` draws the window and waits for keys and clicks, over and over, until the window closes.", "at": "tkinter_chat.html#step1"},
  {"id": "tkinter_chat_02", "unit": "tkinter_chat", "d": -0.3, "type": "mcq", "q": "In tkinter, you make a button with `tk.Button(row, text=\"Send\", command=self.send())`, with brackets after `send`. What happens?", "o": ["The button then sends every message two times.", "Python stops with an error at the click.", "The window opens but it shows no button.", "`send` runs once now; a click does nothing."], "a": 3, "why": "With brackets Python calls `send` now, once, and hands the button what it returned. `command` needs the function itself, with no brackets.", "at": "tkinter_chat.html#step1"},
  {"id": "tkinter_chat_03", "unit": "tkinter_chat", "d": -0.1, "type": "mcq", "q": "A small tkinter window has a `ScrolledText` on top and a one-line typing box under it. The `ScrolledText` is made with no `height` option. What can go wrong?", "o": ["The typing box falls off the bottom.", "The `ScrolledText` can no longer scroll.", "The window closes when you press Enter.", "Long lines are cut in the middle of words."], "a": 0, "why": "A `ScrolledText` asks for 24 lines by default. That can be taller than the window, so the typing box is pushed off the bottom. A smaller `height` fixes it.", "at": "tkinter_chat.html#step1"},
  {"id": "tkinter_chat_04", "unit": "tkinter_chat", "d": 0.2, "type": "mcq", "q": "A tkinter program asks a slow web API on a worker thread. The thread puts each piece of the answer into a `queue.Queue`, and the main thread takes the pieces out. Why is it built this way?", "o": ["A queue makes the API answer more quickly.", "A queue keeps the words for the next day.", "A worker thread may not touch the window.", "A queue puts the words in the right order."], "a": 2, "why": "In tkinter, a thread that did not make the window may not touch it. So the worker only fills a queue, and the main thread empties it into the window.", "at": "tkinter_chat.html#step2"},
  {"id": "tkinter_chat_05", "unit": "tkinter_chat", "d": 0.5, "type": "mcq", "q": "In this tkinter method, `self.inbox` is a queue that a worker thread fills with words. What happens if you leave out the last line?", "code": "def drain(self):\n    try:\n        while True:\n            kind, text = self.inbox.get_nowait()\n            if kind == \"word\":\n                self.write(text)\n    except queue.Empty:\n        pass\n    self.root.after(50, self.drain)", "o": ["The window freezes while the worker runs.", "The queue is emptied exactly one time.", "The program stops with a thread error.", "The answer arrives as one block of text."], "a": 1, "why": "`after` books one call, not a repeating timer. Without the line, `drain` never runs again, so the queue is emptied exactly once.", "at": "tkinter_chat.html#step2"},
  {"id": "tkinter_chat_06", "unit": "tkinter_chat", "d": -0.5, "type": "mcq", "q": "A chat program talks to a model through Ollama's `/api/chat`. The model seems to remember what you said before. Where is that memory kept?", "o": ["In a list in your program, sent every time", "Inside the model that Ollama has loaded in RAM", "In the text box of the chat window", "On the Ollama server, one file per chat"], "a": 0, "why": "Ollama keeps nothing between questions. The memory is a list of messages in your program, and you send it with every request.", "at": "tkinter_chat.html#step3"},
  {"id": "tkinter_chat_07", "unit": "tkinter_chat", "d": 0.6, "type": "fill", "q": "A tkinter program books a call with `self.next_drain = self.root.after(50, self.drain)`. Which Tk method stops that booked call before the window is destroyed? Type its name.", "answers": ["after_cancel", "after_cancel()", "root.after_cancel", "root.after_cancel()", "self.root.after_cancel", "self.root.after_cancel()"], "why": "A call still booked when the window goes leaves Tk calling a command that no longer exists. `after_cancel` takes the value that `after` returned and stops the call.", "at": "tkinter_chat.html#step5"},
  {"id": "tkinter_chat_08", "unit": "tkinter_chat", "d": 0.8, "type": "mcq", "q": "A chat program keeps the conversation in a list and sends the list to the model with every question. One request fails. Why should the program take that question back out of the list?", "o": ["So the history file stays small on the disk.", "So the user must type the question again.", "So the status line shows the right model.", "So the next answer does not reply to it."], "a": 3, "why": "A failed question must leave no trace. If it stays in the list, the model sees it next time and replies to something the user thinks was never asked.", "at": "tkinter_chat.html#step5"},
  {"id": "it__ai__ai_windows_stack_01", "unit": "it__ai__ai_windows_stack", "d": -0.7, "type": "mcq", "q": "What is Ollama?", "o": ["A free program that runs AI models on your PC", "A Python library that indexes your own notes", "A text file with the model's job description", "A model that is trained to pick and call tools"], "a": 0, "why": "Ollama is the engine: a free program that downloads AI models and runs them on your own PC.", "at": "it/ai/ai_windows_stack.html"},
  {"id": "it__ai__ai_windows_stack_02", "unit": "it__ai__ai_windows_stack", "d": -0.5, "type": "mcq", "q": "A file named `Modelfile` holds a `SYSTEM` prompt for Ollama. What does `ollama create my-tutor -f Modelfile` do?", "o": ["It downloads the base model from the cloud", "It builds a search index over your files", "It makes a custom model with that prompt", "It starts a chat loop with the base model"], "a": 2, "why": "The Modelfile holds the system prompt. `ollama create` bakes it into a custom model, here named `my-tutor`, so you never type the prompt again.", "at": "it/ai/ai_windows_stack.html#quickstart"},
  {"id": "it__ai__ai_windows_stack_03", "unit": "it__ai__ai_windows_stack", "d": -0.3, "type": "mcq", "q": "You want a local model to answer from your class notes, and the notes change often. Which technique fits best?", "o": ["A longer `SYSTEM` block in the Modelfile", "RAG with LlamaIndex over the notes", "An agent model that has more tools", "Fine-tuning with new model weights"], "a": 1, "why": "What the model knows is a job for RAG. LlamaIndex looks the notes up at question time, so you only edit a file and run again.", "at": "it/ai/ai_windows_stack.html"},
  {"id": "it__ai__ai_windows_stack_04", "unit": "it__ai__ai_windows_stack", "d": 0.1, "type": "mcq", "q": "What is the `.venv` folder for?", "o": ["It stores the models that Ollama downloads", "It saves the index so the next run is fast", "It lets PowerShell run the scripts that are normally blocked", "It keeps the AI packages apart from the rest of the PC"], "a": 3, "why": "The virtual environment keeps the AI packages separate. If something breaks, you can throw the folder away.", "at": "it/ai/ai_windows_stack.html"},
  {"id": "it__ai__ai_windows_stack_05", "unit": "it__ai__ai_windows_stack", "d": 0.4, "type": "mcq", "q": "A LlamaIndex RAG script uses plain `llama3.2` from Ollama. It saves its index of some blog posts in a folder. You delete that folder. What is true now?", "o": ["The model is plain `llama3.2` and knows no post", "The model still knows them; it was fine-tuned", "Ollama must download the chat model once more", "The embedding model is deleted from the disk too"], "a": 0, "why": "This is RAG, not fine-tuning. The index is the library and the model is only the librarian, so without the index it is plain `llama3.2`.", "at": "it/ai/ai_windows_stack.html"},
  {"id": "it__ai__ai_windows_stack_06", "unit": "it__ai__ai_windows_stack", "d": 0.5, "type": "mcq", "q": "A LlamaIndex `ReActAgent` gets plain Python functions as its tools, each wrapped with `FunctionTool.from_defaults()`. It often calls the wrong tool. Where should you look first?", "o": ["The `request_timeout` of the model", "The outer chat loop of the script", "The docstring of each tool function", "The `verbose=True` setting of the agent"], "a": 2, "why": "The model reads each tool's docstring to decide whether to call it. A bad docstring is the main reason an agent picks the wrong tool.", "at": "it/ai/ai_windows_stack.html#walkthroughs"},
  {"id": "it__ai__ai_windows_stack_07", "unit": "it__ai__ai_windows_stack", "d": 0.6, "type": "mcq", "q": "In this LlamaIndex code, what does `similarity_top_k=3` do?", "code": "index = VectorStoreIndex.from_documents(docs)\nengine = index.as_query_engine(similarity_top_k=3)\nresponse = engine.query(question)", "o": ["The engine reads only the first 3 files in the folder", "The engine finds the 3 closest chunks for a question", "The model answers in at most 3 short sentences", "The engine tries each question up to 3 times"], "a": 1, "why": "The query engine embeds the question and finds the closest 3 stored vectors. A higher number gives more context but is slower.", "at": "it/ai/ai_windows_stack.html#walkthroughs"},
  {"id": "it__ai__ai_windows_stack_08", "unit": "it__ai__ai_windows_stack", "d": 0.2, "type": "mcq", "q": "You have just installed Ollama on Windows with `winget`. In the same PowerShell window you type `ollama`, and PowerShell says `'ollama' is not recognized as a cmdlet`. What is the fix?", "o": ["Run `Set-ExecutionPolicy` with `Bypass` once", "Pull a smaller model such as `llama3.2:1b`", "Delete the `.venv` folder and make it again", "Open the Ollama app, then a new PowerShell"], "a": 3, "why": "Ollama is installed, but its folder is not on PATH yet. Open the Ollama app from the Start menu, then close PowerShell and open a fresh window.", "at": "it/ai/ai_windows_stack.html#downloads"},
  {"id": "it__ai__ai_mac_stack_01", "unit": "it__ai__ai_mac_stack", "d": -0.7, "type": "mcq", "q": "What is Ollama?", "o": ["A Python library that indexes your files", "A model trained to pick and call tools", "A program that runs AI models locally", "A text file with the model's job description"], "a": 2, "why": "Ollama is a free program that downloads AI models and runs them on your own computer, with no cloud.", "at": "it/ai/ai_mac_stack.html#agent-video"},
  {"id": "it__ai__ai_mac_stack_02", "unit": "it__ai__ai_mac_stack", "d": -0.4, "type": "mcq", "q": "What does the command `ollama create kru-ai -f Modelfile` do?", "o": ["It makes a custom model from the Modelfile", "It downloads the model `kru-ai` from the internet", "It builds a search index over the folder `kru-ai`", "It starts a chat with the model `kru-ai`"], "a": 0, "why": "`ollama create` builds a new custom model, here named `kru-ai`, from the Modelfile. The `SYSTEM` prompt in that file is baked into the model.", "at": "it/ai/ai_mac_stack.html#quickstart"},
  {"id": "it__ai__ai_mac_stack_03", "unit": "it__ai__ai_mac_stack", "d": -0.3, "type": "mcq", "q": "You want a local model to answer with facts from your own notes, and you edit the notes often. Which technique fits best?", "o": ["A Modelfile `SYSTEM` block", "RAG over the notes (LlamaIndex)", "A tool-calling model (Hermes 3)", "Fine-tuning the model"], "a": 1, "why": "RAG looks the notes up at question time, so it suits a few files that change often: edit a file, run again, done. A `SYSTEM` block and fine-tuning both need a rebuild to change.", "at": "it/ai/ai_mac_stack.html"},
  {"id": "it__ai__ai_mac_stack_04", "unit": "it__ai__ai_mac_stack", "d": 0.2, "type": "mcq", "q": "A RAG program built with LlamaIndex prints a line such as `sources: about_me.md` after each answer. Why does this line matter?", "o": ["It shows which model wrote the answer", "It shows the file was saved to the index", "It shows the tool that the agent called", "It shows the answer came from a real file"], "a": 3, "why": "The line names the file that the retrieved text came from. The model did not guess, and you can open the file to check the answer.", "at": "it/ai/ai_mac_stack.html"},
  {"id": "it__ai__ai_mac_stack_05", "unit": "it__ai__ai_mac_stack", "d": 0.4, "type": "mcq", "q": "In this LlamaIndex agent code, plain Python functions are the tools. What does the model read to decide which tool to call?", "code": "def multiply(a: float, b: float) -> float:\n    \"\"\"Multiply two numbers and return the product.\"\"\"\n    return a * b\n\ndef days_until(iso_date: str) -> str:\n    \"\"\"How many days between today and a YYYY-MM-DD date.\"\"\"\n    target = date.fromisoformat(iso_date)\n    delta = (target - date.today()).days\n    return f\"{delta} day(s) from now\"\n\nTOOLS = [\n    FunctionTool.from_defaults(multiply),\n    FunctionTool.from_defaults(days_until),\n]\nllm = Ollama(model=\"hermes3:8b\", request_timeout=300)\nagent = ReActAgent(tools=TOOLS, llm=llm, verbose=True)", "o": ["The order of the `TOOLS` list", "The docstring of each function", "The result of running every tool", "The `return` line of each function"], "a": 1, "why": "`FunctionTool.from_defaults()` turns each function's docstring into the tool description. The model reads that sentence to decide whether to call the function, so a bad docstring gives a wrong tool choice.", "at": "it/ai/ai_mac_stack.html#walkthroughs"},
  {"id": "it__ai__ai_mac_stack_06", "unit": "it__ai__ai_mac_stack", "d": 0.5, "type": "mcq", "q": "A RAG program answers questions about one writer's blog. It uses plain `llama3.2` and an index folder saved by LlamaIndex. You delete the index folder and the blog files. What does the model know about the writer now?", "o": ["Only what plain `llama3.2` already knew", "Everything, because RAG trained the model", "Only the last chunks that it looked up", "Only the titles of the blog posts"], "a": 0, "why": "RAG is not fine-tuning, so the model itself never changes. It only looks things up in the index, and without the index it is the plain base model again.", "at": "it/ai/ai_mac_stack.html"},
  {"id": "it__ai__ai_mac_stack_07", "unit": "it__ai__ai_mac_stack", "d": 0.7, "type": "mcq", "q": "For a tool-using agent on Ollama, why choose `hermes3:8b` and not plain `llama3.2`?", "o": ["`llama3.2` needs the internet to run", "`llama3.2` needs too much memory", "`llama3.2` cannot answer in English", "`llama3.2` is bad at tool use"], "a": 3, "why": "Plain `llama3.2` makes up arguments or calls the wrong tool. Hermes 3 was fine-tuned to choose tools reliably.", "at": "it/ai/ai_mac_stack.html#walkthroughs"},
  {"id": "decider_01", "unit": "decider", "d": -0.8, "type": "mcq", "q": "You give Decider a text, a question and some options. What does it give back?", "o": ["A sentence that explains its choice", "A probability for every option", "A list of new options to try", "A Python file that you can run"], "a": 1, "why": "Decider does not write text. It gives every option a probability, and the probabilities add up to 1.", "at": "decider.html"},
  {"id": "decider_02", "unit": "decider", "d": -0.5, "type": "mcq", "q": "Decider's `decide()` picks one of three options, with a confidence of 0.4887. What does that number tell you?", "o": ["The model is not sure about its choice", "The model is almost certain it is right", "The model could not read the message", "The model needs a Hugging Face token"], "a": 0, "why": "In `decide()` the confidence is the highest probability. 0.49 is under half, so the model is not sure. Read the number, not only the choice.", "at": "decider.html#step1"},
  {"id": "decider_03", "unit": "decider", "d": 0.1, "type": "mcq", "q": "The text that the Decider model reads lists the options as `(A)`, `(B)`, `(C)` and ends with `Answer: (`. Why does it stop there?", "o": ["So the model writes a full sentence next", "So the model stops reading the options", "So the next token would be a letter", "So the tokenizer can count the tokens"], "a": 2, "why": "The next token would be the letter of an option. So the logits of A, B and C are the answer, and no text is generated.", "at": "decider.html#step2"},
  {"id": "decider_04", "unit": "decider", "d": 0.0, "type": "mcq", "q": "A Decider choice question has bare names as its options. It picks the wrong option, and the model is not sure. What should you try first?", "o": ["Load the model a second time", "Give each option a meaning", "Make the text much shorter", "Set the temperature lower"], "a": 1, "why": "In `criteria`, each option can be a name and its meaning. The meaning tells the model what the name covers. The temperature never changes which option is highest.", "at": "decider.html#step3"},
  {"id": "decider_05", "unit": "decider", "d": -0.4, "type": "fill", "q": "In Decider's `system_one()`, a yes/no question has its own type. Its answer is one number: the probability of yes. Type the name of this type.", "answers": ["noul"], "why": "`noul` is the name of the yes/no type. Near 0 means no and near 1 means yes.", "at": "decider.html#step4"},
  {"id": "decider_06", "unit": "decider", "d": 0.3, "type": "mcq", "q": "A Decider model is made with `abstain_below=0.7` and asked with `decide_batch()`. For one text, the best option has a probability of 0.42. What is the choice for that text?", "o": ["The option that has 0.42, as usual", "The text is asked a second time", "The call stops with an error", "The choice is `None`: not sure"], "a": 3, "why": "When the best option has less than `abstain_below`, the choice is `None`. A person can then read that text.", "at": "decider.html#step9"},
  {"id": "decider_07", "unit": "decider", "d": 0.5, "type": "mcq", "q": "A Decider yes/no question asks if one number is higher than another. Each text holds only the two numbers. On 12 texts the model is right 6 times, and 6 of the true answers are no. What most likely happened?", "o": ["It compared half of the pairs correctly", "It read only the first six of the texts", "It said no every time: it cannot compare", "It failed when a number had two decimals"], "a": 2, "why": "Decider reads once and does not reason, so it cannot compare two numbers. It said no every time, and six true answers are no. Let your program compare, and give the model the result in words.", "at": "decider.html#step10"},
  {"id": "decider_08", "unit": "decider", "d": 0.7, "type": "mcq", "q": "Decider's `decide()` gets a long text: 150 rules, and then the message that the question is about. That is 2,969 tokens. It answers at 0.99, and the answer is wrong. Why?", "o": ["The message was cut off before it was read", "The rules told the model which option to pick", "The meanings of the options were all `None`", "The model gave each question its own pass"], "a": 0, "why": "`decide()` reads only the first 1,536 tokens of the text and gives no warning. The message came after that, so the model never saw it.", "at": "decider.html#step12"},
  {"id": "decider_09", "unit": "decider", "d": -0.6, "type": "mcq", "q": "Decider is called a System One model. What kind of model is that?", "o": ["A language model that writes its answer one word at a time", "A language model that does not generate text", "A model that works only on the first question you ask it", "A model that needs one GPU, and no more than one"], "a": 1, "why": "Its makers call it \"a language model that does not generate text\". System One is the name of this kind of model: it reads once and gives a probability for every option.", "at": "decider.html"},
  {"id": "decider_10", "unit": "decider", "d": 0.0, "type": "mcq", "q": "People say this about a System One model such as Decider: \"It is fast. It does not think.\" What does it mean?", "o": ["It answers before it has read the whole of the text", "It gives the same answer to every question, so it is quick", "It reads once and answers. It does not reason in steps", "It keeps its old answers, and it looks them up again"], "a": 2, "why": "Decider reads once and answers. It does not reason in steps, and that is why it cannot compare two numbers.", "at": "decider.html"},
  {"id": "decider_11", "unit": "decider", "d": 0.3, "type": "mcq", "q": "A program asks a chat model and Decider the same thing. What does the program have to do with each reply?", "o": ["With both, it has to read a sentence and find the decision in it", "With both, it reads a key in a dictionary and gets a probability", "With Decider it has to read a sentence; with the chat model it reads a key", "With the chat model it has to read a sentence; with Decider it reads a key"], "a": 3, "why": "A chat model sends back a sentence, and your program has to read it. Decider sends back a probability for every option, and your program reads a key in a dictionary.", "at": "decider.html"},
  {"id": "decider_12", "unit": "decider", "d": 0.6, "type": "mcq", "q": "Which of these four jobs is for a chat model, and not for a System One model like Decider?", "o": ["Write a short, polite reply to a guest's message", "Pick which of three teams should get a message", "Say yes or no: is this message urgent?", "Say how sure it is that a message asks for a refund"], "a": 0, "why": "Decider does not write text, and it can answer only with one of your options. A chat model can answer anything, in a sentence. Picking, yes or no, and how sure are what Decider is for.", "at": "decider.html"},
  {"id": "jev_01", "unit": "jev", "d": -0.7, "type": "mcq", "q": "A chat model writes a sentence. What does a decision model return?", "o": ["A long sentence you must parse", "A short summary of the document", "A number for every option", "A list of questions to ask next"], "a": 2, "why": "A decision model returns a number between 0 and 1 for every option of every question you asked.", "at": "jev.html"},
  {"id": "jev_02", "unit": "jev", "d": -0.4, "type": "mcq", "q": "This code asks Kev, the small open copy of the Jev decision model, a yes/no question about a customer message. `kev.noul()` sends the text and the question to the model. The code does not pass the bare message: it puts it inside `SITUATION` first. Why?", "code": "SITUATION = \"A support desk is reading a customer message:\\n{ticket}\"\nASK = \"Is this customer asking for a refund?\"\n\nsure = kev.noul(SITUATION.format(ticket=ticket), ASK)", "o": ["The model answers worse with no setting", "The model refuses text with no first line", "It hides the message from the model's log", "It makes the model answer in a sentence"], "a": 0, "why": "Telling a decision model the setting, not only the question, is the biggest thing you control. In one run, a message about an invoice went from 0.082 to about 0.5 without the setting.", "at": "jev.html#step1"},
  {"id": "jev_03", "unit": "jev", "d": -0.3, "type": "mcq", "q": "The Jev decision model, and its open copy Kev, take three types of question. Which type places the text on an ordered scale?", "o": ["`choice`", "`score`", "`noul`", "`state`"], "a": 1, "why": "`choice` picks one option, `score` places it on a scale, and `noul` is yes or no.", "at": "jev.html#step2"},
  {"id": "jev_04", "unit": "jev", "d": 0.3, "type": "mcq", "q": "A chat model is asked to report its own confidence with each answer. In one run it said 0.80 for eleven of fifteen messages, easy ones and hard ones alike. All fifteen answers were right. Why is that confidence still a problem?", "o": ["It shows the chat model guessed its answers", "You cannot set a threshold on that number", "It makes the chat model answer more slowly", "The number is lost when the JSON is parsed"], "a": 1, "why": "A confidence is useful only if it moves when the model is less sure. A number that barely moves gives you no threshold to act on.", "at": "jev.html#step3"},
  {"id": "jev_05", "unit": "jev", "d": 0.5, "type": "mcq", "q": "A program routes a message on its own only when a decision model's best option has a probability above a threshold. The rest go to a person. You raise the threshold from 0.60 to 0.90. What happens?", "o": ["More messages routed, and more mistakes", "More messages routed, and fewer mistakes", "Fewer messages routed, and fewer mistakes", "Fewer messages routed, and more mistakes"], "a": 2, "why": "A higher threshold means fewer messages handled with no person, and fewer mistakes let through. In one run, 0.60 routed 13 of 15 with 11 right, and 0.90 routed 5 with all 5 right.", "at": "jev.html#step4"},
  {"id": "jev_06", "unit": "jev", "d": 0.4, "type": "mcq", "q": "A small decision model is asked \"Is this customer asking for a refund?\". The message \"What is your refund policy, out of interest?\" gets 0.979. What does this show about the small model?", "o": ["It tracks the word, not the request", "It cannot read a question mark at the end", "It scores long messages too high", "It needs a key for hard messages"], "a": 0, "why": "A small model has learned that the word refund is a strong signal. It cannot tell discussing a thing from requesting it.", "at": "jev.html#step5"},
  {"id": "jev_07", "unit": "jev", "d": 0.1, "type": "fill", "q": "A request to the Jev decision model is the ordinary OpenAI chat shape. One field carries the questions. It has the same name as the field with which OpenAI's chat API is asked for structured output. Type its name.", "answers": ["response_format"], "why": "`response_format` carries the questions. Each one is named, typed, and given its options.", "at": "jev.html#step6"},
  {"id": "jev_08", "unit": "jev", "d": 0.8, "type": "mcq", "q": "A 0.6B decision model gets a yes/no question wrong on some messages. You add an exception to the question: \"Asking about the policy does not count.\" In one run this made the score much worse. What is the rule for such a small model?", "o": ["Always add every exception to the question", "Ask each question three times and average", "Use a chat model to rewrite the question", "A short, broad question works better"], "a": 3, "why": "A 0.6B model cannot hold an exception. The extra clause blurs the question, so short and broad beats long and precise.", "at": "jev.html#step7"},
  {"id": "jev_09", "unit": "jev", "d": 0.2, "type": "mcq", "q": "You have three questions about one ticket. How does each kind of model take them?", "o": ["Both need three calls, one call for each of the questions", "The decision model takes them in one pass. The chat model needs three calls, or one answer to unpick", "The chat model takes them in one pass. The decision model needs three calls, or one answer to unpick", "Neither can take three: one ticket can have only one question"], "a": 1, "why": "A decision model answers all the questions in one pass over the text. A chat model needs three calls, or one call and an answer to unpick.", "at": "jev.html"},
  {"id": "jev_10", "unit": "jev", "d": 0.6, "type": "mcq", "q": "In one test, a small open decision model got 12 of 15 messages right, and a chat model got all 15. So what does a decision model give you?", "o": ["A better answer than the chat model, on every kind of message", "The same answer as the chat model, with no key and no cost", "A number you can act on, not a better answer", "A sentence that is shorter, and so easier to read"], "a": 2, "why": "A decision model gives you a probability that your program can act on. It does not give a better answer than a chat model: the interface is the good part, and a small model is not smart.", "at": "jev.html"},
  {"id": "a1_01", "unit": "agno_agents", "d": -0.5, "type": "mcq", "q": "In Agno, `agent.run(...)` sends one message to an agent. In this code, what does the call come back with?", "code": "MODEL = \"qwen2.5:3b\"\n\nagent = Agent(model=Ollama(id=MODEL), instructions=\"Answer in one short sentence.\")\nrun = agent.run(\"What is the capital of Thailand?\")", "o": ["Only a piece of text, which is the model's answer and nothing more than that", "A run: the answer in `run.content`, and a record of what it took, such as the tokens", "A list of the models that Ollama has on this computer, with the size of each", "Nothing. Agno prints the answer in the terminal itself, and `run` is always `None` after it"], "a": 1, "why": "`run.metrics.input_tokens` and `run.metrics.output_tokens` are part of that record.", "at": "agno_agents.html#step2"},
  {"id": "a1_02", "unit": "agno_agents", "d": 0.0, "type": "mcq", "q": "`pip install agno` succeeds. Later, a line such as `from agno.db.sqlite import SqliteDb` stops with an error that names a library which is not installed. Why?", "o": ["Agno's install was damaged, and it has to be removed and installed again", "The library is a paid part of Agno, and it needs a key before it can load", "Agno installs only its core. Each other library is asked for when a part needs it", "Python is too old for Agno, and every import of the newer parts fails this way"], "a": 2, "why": "Agno keeps its own install small. The error names the missing library, so install that one with pip and run again.", "at": "agno_agents.html#step1"},
  {"id": "a1_03", "unit": "agno_agents", "d": 0.0, "type": "mcq", "q": "This Agno agent is given an order such as `2 khao soi, 1 water`. Who works out the total?", "code": "PRICES = {\"khao soi\": 60, \"pad thai\": 60, \"water\": 10}\n\ndef bill(order: str) -> str:\n    \"\"\"Add up an order: the dishes and how many of each, such as \"2 khao soi, 1 water\".\"\"\"\n    ...   # looks up each dish in PRICES and returns the bill as text\n\ncashier = Agent(\n    model=Ollama(id=MODEL, options={\"temperature\": 0}),\n    tools=[bill],\n    instructions=\"Use the bill tool for every order. Never work out or guess a price yourself.\",\n)", "o": ["The model, from the prices that it was shown in its instructions", "Ollama, which has a calculator of its own for every model it runs", "Agno, which adds up every number that it finds in the model's answer", "The Python function `bill`, which the model asks for as a tool"], "a": 3, "why": "The model reads the message and asks for the tool. Agno runs the function, so the sum is ordinary Python and it is exact.", "at": "agno_agents.html#step3"},
  {"id": "a1_04", "unit": "agno_agents", "d": -0.4, "type": "mcq", "q": "In Agno, what makes a Python function such as `bill` a tool of an agent?", "o": ["Handing it to the agent in `tools=[bill]`", "A name that begins with `tool_`, which Agno looks for", "The line `import bill` at the top of the file", "A file named `tools.json` that lists it beside the code"], "a": 0, "why": "The model may then ask for it to be run. Agno runs it and gives the model what it returned.", "at": "agno_agents.html#step3"},
  {"id": "a1_05", "unit": "agno_agents", "d": 0.3, "type": "mcq", "q": "An Agno agent has a Python function as a tool. The model never sees the code of the function. How does it know what the tool is for?", "o": ["It guesses from the user's message, and from the answers that it gave before", "Agno shows it the function's name, its docstring, and its argument with its type", "It opens the Python file on the disk and reads the comments above the function", "It was trained on this very function, so it already knows what the tool returns"], "a": 1, "why": "So the docstring is not only a note for the programmer. It is the instruction the model reads.", "at": "agno_agents.html#step3"},
  {"id": "a1_06", "unit": "agno_agents", "d": 0.6, "type": "mcq", "q": "An Agno tool is written as `def bill(order):`, with no type hint, where `def bill(order: str):` was meant. What can go wrong?", "o": ["Nothing can: Agno reads the docstring and adds the missing hint by itself", "Agno refuses the function, and the agent then starts with no tools at all", "The model is not told the argument is text, so it may send a dict instead", "The model can no longer see the tool's name, so it never asks for the tool"], "a": 2, "why": "The type hint is part of what Agno shows the model. In one run, a model sent `{'khao soi': 2, 'thai iced tea': 1}` where the function expected a string.", "at": "agno_agents.html#step3"},
  {"id": "a1_07", "unit": "agno_agents", "d": 0.1, "type": "mcq", "q": "In Agno, an Ollama model is made with `Ollama(id=\"qwen2.5:3b\", options={\"temperature\": 0})`. What does `temperature` 0 do?", "o": ["It makes the model answer faster, with shorter replies", "It stops the computer from getting hot while the model runs", "It switches the tools off, so the model can only talk", "The model gives its likeliest answer every time"], "a": 3, "why": "So two runs of the same question on one computer agree.", "at": "agno_agents.html#step3"},
  {"id": "a2_01", "unit": "agno_agents", "d": 0.2, "type": "mcq", "q": "This file, `shop.py`, holds Agno agents. Another file runs `from shop import books, cashier`. Which objects are made at the moment of the import?", "code": "DB_FILE = \"shop.db\"\n\ndef books():\n    return SqliteDb(db_file=DB_FILE)\n\ndef cashier(db):\n    return Agent(name=\"Cashier\", model=local_model(WORKER), tools=[bill], db=db)", "o": ["No agent and no database. Each function builds its part when it is called", "The agent and its database, so that both are ready for the first message", "Only the database, because the agent needs it before it can be made", "The agent's model, which Ollama then loads into memory straight away"], "a": 0, "why": "A `def` only defines a function. With Agno, a group of agents can be a Python module, and importing it starts no model.", "at": "agno_agents.html#step4"},
  {"id": "a2_02", "unit": "agno_agents", "d": 0.1, "type": "mcq", "q": "An Agno agent is made with `db=SqliteDb(db_file=\"shop.db\")`. What does that give the agent?", "o": ["A place to look up facts: the model can ask the file questions in SQL", "A record: Agno saves the agent's conversations and runs in that file", "A cache: Agno keeps the model's weights there so that it loads faster", "A list of tools: Agno reads the functions the agent may use from it"], "a": 1, "why": "Every agent that is given the same database writes its runs there. The file is an ordinary SQLite database.", "at": "agno_agents.html#step4"},
  {"id": "a2_03", "unit": "agno_agents", "d": 0.4, "type": "mcq", "q": "In Agno, a tool function is marked `@tool(stop_after_tool_call=True)`. What does that change?", "o": ["The tool may be called only one time in a conversation", "The agent stops and waits for a person to say yes", "What the function returns is the agent's answer", "The tool is stopped when it has run for too long"], "a": 2, "why": "The model still chooses the tool and its argument. It does not get to reword the result, and one more question to the model is saved.", "at": "agno_agents.html#step4"},
  {"id": "a2_04", "unit": "agno_agents", "d": 0.5, "type": "mcq", "q": "An Agno agent calls a tool, and the tool returns `I do not know the price of pizza.` The model then writes the reply to the user itself. What is the risk?", "o": ["None: a model always repeats a tool's text word for word in its reply", "The reply is empty, because a model cannot read the text a tool returns", "Agno runs the same tool again and again, until it returns a number", "The model may reword it wrongly: in one run it asked for the price"], "a": 3, "why": "The tool can be right and the sentence around it wrong. `stop_after_tool_call=True` sends the tool's own words as the answer.", "at": "agno_agents.html#step5"},
  {"id": "a2_05", "unit": "agno_agents", "d": 0.0, "type": "mcq", "q": "In this Agno code, what ties the three runs into one conversation?", "code": "TABLE = \"table-4\"\nMESSAGES = [\"2 khao soi, 1 thai iced tea\",\n            \"1 pad thai, 2 pizza\",\n            \"Sorry, no pizza. Make that 1 pad thai, 2 water.\"]\n\nfor message in MESSAGES:\n    run = agent.run(message, session_id=TABLE)", "o": ["Every run gets the same `session_id`", "They are sent from one `for` loop", "They are all in the one list, `MESSAGES`", "The one variable, `run`, holds each of them"], "a": 0, "why": "Agno calls a conversation a session. Runs with the same `session_id` belong to one session.", "at": "agno_agents.html#step5"},
  {"id": "a2_06", "unit": "agno_agents", "d": 0.3, "type": "mcq", "q": "What does `add_history_to_context=True` do for an Agno agent?", "o": ["It writes each conversation to a text file that a person can read later", "The agent is shown what was said before in the same conversation", "It adds the date and the time of the message to every answer", "It lets the agent read the conversations of every other session as well"], "a": 1, "why": "A message that only makes sense after an earlier one, such as a correction, can then be understood.", "at": "agno_agents.html#step4"},
  {"id": "a2_07", "unit": "agno_agents", "d": 0.5, "type": "mcq", "q": "An Agno agent has a tool `bill(order: str)` that expects text like `2 khao soi, 1 water`. In a saved conversation the user writes \"Sorry, no pizza. Make that 1 pad thai, 2 water.\" The tool is called with `'1 pad thai, 2 water'`. Who put the message into the tool's form?", "o": ["The function `bill`, with its own code", "The user, who typed it in that form", "The model, which read the conversation", "The database, which saved the first message"], "a": 2, "why": "Reading a messy sentence and choosing the tool's argument is the model's part. The sum that follows is Python's.", "at": "agno_agents.html#step5"},
  {"id": "a3_01", "unit": "agno_agents", "d": 0.0, "type": "mcq", "q": "An Agno `Team` is made with `mode=TeamMode.route`. What is the work of the team's leader?", "o": ["To do the main work alone, and to hand the small questions to one member", "To check each member's answer, and to write it again in its own words", "To ask every member, and to join their answers into one answer of its own", "To read the message and pick one member. That member's answer is the answer"], "a": 3, "why": "In route mode the leader's model only chooses. The chosen member's answer goes back as the team's answer, untouched.", "at": "agno_agents.html#step6"},
  {"id": "a3_02", "unit": "agno_agents", "d": 0.4, "type": "mcq", "q": "In an Agno team in route mode, the leader only reads a message and picks a member. In one run a 3b model did the members' work well, but as leader it sent 0 of 6 messages to a member. A 14b model sent 6 of 6. What does this show?", "o": ["Choosing a member can be the harder job, so the leader may need a bigger model", "A team only works when every member thinks with the same model as its leader", "A small model is too slow to lead a team, so each of its six runs timed out", "Route mode does not work with Ollama, so a team has to use another mode there"], "a": 0, "why": "A small model may write the instruction out as text and not carry it out. Test the leader's model with real messages.", "at": "agno_agents.html#step6"},
  {"id": "a3_03", "unit": "agno_agents", "d": 0.2, "type": "mcq", "q": "A program asks a model in Ollama two questions. The first answer takes 72.6 seconds, and the second takes 16.5. What is the usual reason for a slow first answer?", "o": ["The first question is always harder for a model than the second one is", "Ollama had to load the model into memory before it could answer", "Ollama downloads the model again each time that a program starts", "The database was empty, and all of its tables had to be made first"], "a": 1, "why": "Loading a model into memory is paid one time. The questions after it are faster.", "at": "agno_agents.html#step6"},
  {"id": "a3_04", "unit": "agno_agents", "d": 0.5, "type": "mcq", "q": "What does `determine_input_for_members=False` do in an Agno `Team`?", "o": ["The leader may not choose a member, so every member gets the message", "The members cannot see the user's message, only the leader's answer", "The member gets the user's own words, not the leader's version of them", "The members choose their own tools, and the leader cannot change them"], "a": 2, "why": "A message retold by the leader can reach the member changed, for example with parts of it dropped.", "at": "agno_agents.html#step4"},
  {"id": "a3_05", "unit": "agno_agents", "d": 0.1, "type": "mcq", "q": "In Agno, a tool is marked `@tool(requires_confirmation=True)`. An agent asks for it. What does Agno do?", "o": ["It runs the tool, and tells a person about it later", "It refuses, and ends the run with the status `ERROR`", "It asks the model a second time, to be sure of it", "It does not run it. The run stops as `PAUSED`"], "a": 3, "why": "The paused run carries a list of what it is waiting for. A person's yes or no decides whether the tool runs.", "at": "agno_agents.html#step7"},
  {"id": "a3_06", "unit": "agno_agents", "d": 0.4, "type": "mcq", "q": "An Agno run is `PAUSED` because a tool needs a person's confirmation. Which calls answer it and carry on?", "o": ["`confirm()` or `reject()` for each thing it waits for, and then `continue_run()`", "`agent.run()` a second time, with the word yes or the word no as the message", "`pause()` when the agent asks for the tool, and `resume()` when a person agrees", "None: a paused run is lost, and the whole conversation has to be started again"], "a": 0, "why": "Each item in `run.active_requirements` gets `confirm()` or `reject()`. After a `reject`, the run also ends as `COMPLETED`, and the agent says that the answer was no.", "at": "agno_agents.html#step7"},
  {"id": "a3_07", "unit": "agno_agents", "d": 0.6, "type": "mcq", "q": "An Agno run is paused for a person's yes, and the yes may come hours later. Why can `continue_run()` still find the paused run?", "o": ["The model remembers every run that it has done", "The paused run is saved in the agent's database", "The terminal keeps it in memory until that time", "Agno sends it to a web service of its own"], "a": 1, "why": "`continue_run()` finds the run by its `run_id` and `session_id`. An agent with no database answers that no run with that number exists.", "at": "agno_agents.html#step7"},
  {"id": "a4_01", "unit": "agno_agents", "d": -0.2, "type": "mcq", "q": "Why is a budget of tokens a budget of money?", "o": ["Every token costs one baht, whichever model the agent uses, paid or free", "Tokens are saved on the disk, and a bigger disk is what costs the money", "A token is a piece of a word, and it is the unit a paid model is billed in", "It is not: tokens only measure the speed, and have nothing to do with the cost"], "a": 2, "why": "A model on your own computer costs nothing for each token. An agent with a paid model inside can spend money fast.", "at": "agno_agents.html#step8"},
  {"id": "a4_02", "unit": "agno_agents", "d": 0.4, "type": "mcq", "q": "This Agno hook refuses a run when a conversation has used 800 tokens. `spent(agent)` returns the tokens used so far. In one run the total was 527, and the next message took it to 899 without being refused. Why?", "code": "BUDGET = 800\n\ndef check_budget(agent):\n    \"\"\"Agno calls this before every run, and hands it the agent.\"\"\"\n    if spent(agent) >= BUDGET:\n        raise InputCheckError(f\"The budget of {BUDGET} tokens is spent.\")\n\nagent.pre_hooks = [check_budget]", "o": ["The hook counts only the tokens of the newest run, and that was 372", "A pre-hook is called after each run, so it is always one message late", "Agno lets every budget go over by 100 tokens before it stops a run", "A pre-hook looks before a run, and cannot know what that run will cost"], "a": 3, "why": "Before that run the total was 527, which is under 800. A check made before a run can only refuse the run after the one that went over.", "at": "agno_agents.html#step8"},
  {"id": "a4_03", "unit": "agno_agents", "d": 0.6, "type": "mcq", "q": "In Agno, a pre-hook must stop a run, for example when a budget is spent. Which error does it have to raise?", "o": ["`InputCheckError`: of the errors a hook can raise, this kind stops the run", "Any error at all: Agno stops a run as soon as one of its hooks fails", "`StopAgentRun`, because its name says that it is the one made for this", "`KeyError`, the same error that Python raises when a dict lacks a key"], "a": 0, "why": "In one test with `StopAgentRun`, Agno printed `Pre-hook #1 execution failed` and completed the run all the same. Test a budget by spending it.", "at": "agno_agents.html#step8"},
  {"id": "a4_04", "unit": "agno_agents", "d": 0.3, "type": "mcq", "q": "An Agno agent has `add_history_to_context=True`. Three short messages in one session cost 235, then 292, then 372 tokens. Why does each cost more than the one before?", "o": ["The later messages were longer, and each word costs the same tokens", "The agent is shown the conversation so far, and pays to read it again", "A model answers with more and more words as a conversation gets longer", "Each hook uses tokens of its own, every time that Agno calls it"], "a": 1, "why": "A long conversation costs more for every new message, because the earlier messages are read again each time.", "at": "agno_agents.html#step8"},
  {"id": "a4_05", "unit": "agno_agents", "d": 0.1, "type": "mcq", "q": "Agno agents were given `db=SqliteDb(db_file=\"shop.db\")`. Later, a script has to list every conversation, with the status, the tokens and the tools of each run. What does it need?", "o": ["Ollama running, because the model has to tell what it did in each run", "The same agents, built again and asked to repeat every message in turn", "Only the file: `db.get_sessions(...)` reads it, and no model is started", "A paid Agno account, because the records are kept on Agno's own server"], "a": 2, "why": "The file is an ordinary SQLite database. A refused run is in it too, with status `ERROR` and 0 tokens.", "at": "agno_agents.html#step9"},
  {"id": "a4_06", "unit": "agno_agents", "d": 0.2, "type": "mcq", "q": "Agno's AgentOS serves agents as a FastAPI app. How can a program that imports nothing from Agno, or is written in another language, use those agents?", "o": ["It cannot. Only Python code that imports Agno can reach an agent", "It opens the SQLite file and writes its message into a table there", "It starts a model of its own in Ollama with the same instructions", "It sends ordinary web requests, such as a POST to `/agents/<id>/runs`"], "a": 3, "why": "AgentOS puts the agents and the teams behind web addresses, such as `/health`, `/agents` and `/sessions`.", "at": "agno_agents.html#step10"},
  {"id": "a4_07", "unit": "agno_agents", "d": 0.5, "type": "mcq", "q": "An Agno AgentOS server is started for the first time. At once it can list conversations that the same agents had earlier, in other scripts. How?", "o": ["It was given the same database file as they were", "It ran all of the old scripts again when it started", "The client sent the old conversations with its request", "Ollama remembers every conversation that it has had"], "a": 0, "why": "The agents are Python objects, the records are one file, and the server is one more way in to both.", "at": "agno_agents.html#step10"},
  {"id": "a4_08", "unit": "agno_agents", "d": 0.4, "type": "mcq", "q": "An Agno `AgentOS` is made with `telemetry=False`. What is that for?", "o": ["It switches off every tool, so that the server's agents can only talk", "Agno sends anonymous usage numbers to its makers unless it is `False`", "It hides the server from the other computers on the same network", "It stops the server from saving its conversations in the database"], "a": 1, "why": "Keeping the server to this computer is a different setting: `host=\"127.0.0.1\"` in `uvicorn.run(...)`.", "at": "agno_agents.html#step10"},
  {"id": "a5_01", "unit": "agno_agents", "d": -0.2, "type": "mcq", "q": "This pydantic class is given to an Agno agent as its `output_schema`. What does `Field(ge=1, le=100)` say about `score`?", "code": "class Review(BaseModel):\n    score: int = Field(ge=1, le=100, description=\"the quality of the paper, from 1 to 100\")\n    fixes: list[str] = Field(min_length=3, max_length=3,\n                             description=\"three sentences, each naming a thing to fix\")", "o": ["It is 1 at first, and may grow to 100", "It is worked out from 1 to 100 times", "It must be a number from 1 to 100", "It must have from 1 to 100 digits"], "a": 2, "why": "`ge` means greater than or equal, and `le` means less than or equal. `score: int` makes it a whole number.", "at": "agno_agents.html#step11"},
  {"id": "a5_02", "unit": "agno_agents", "d": 0.3, "type": "mcq", "q": "An Agno agent is made with `output_schema=Review`, where `Review` is a pydantic class with a field `score: int`. What does that do?", "o": ["It saves each answer as a file of its own, named after the class `Review`", "It gives the agent a tool with which it can search the web for a score", "It cuts each answer down to one hundred words, so that it is read faster", "Agno asks the model for exactly that form, and returns a Python object"], "a": 3, "why": "`run.content` is then a `Review`, so `review.score` is a number that code can compare, as in `if review.score >= TARGET`.", "at": "agno_agents.html#step11"},
  {"id": "a5_03", "unit": "agno_agents", "d": 0.0, "type": "mcq", "q": "In this loop an Agno agent gives a paper a score, and other agents then improve the paper. When does the loop stop?", "code": "TARGET = 85\nROUNDS = 3\n\nfor turn in range(1, ROUNDS + 1):\n    review = reviewer.run(paper, session_id=session).content\n    if review.score >= TARGET or turn == ROUNDS:\n        break\n    ...   # search for facts, then write the paper again", "o": ["When the score reaches 85, or after the third review", "Only when the score reaches 85, however long it takes", "After 85 seconds, or after three searches of the web", "Only when the third review gives a score of 85 or more"], "a": 0, "why": "The line is `if review.score >= TARGET or turn == ROUNDS: break`: good enough, or out of rounds.", "at": "agno_agents.html#step11"},
  {"id": "a5_04", "unit": "agno_agents", "d": 0.5, "type": "mcq", "q": "In this loop a reviewer agent scores a paper and a writer agent writes it again. `TARGET` is 85 and `ROUNDS` is 3. The three scores are 45, 25 and 30. Which paper is printed?", "code": "best_score = 0\nfor turn in range(1, ROUNDS + 1):\n    review = reviewer.run(paper, session_id=session).content\n    if review.score > best_score:\n        best_score = review.score\n        best_paper = paper\n    if review.score >= TARGET or turn == ROUNDS:\n        break\n    paper = writer.run(paper, session_id=session).content\nprint(best_paper)", "o": ["The third one, because it is the newest", "The first one, as it was before any rewrite", "The second one, because it is the first rewrite", "None, because the target was not reached"], "a": 1, "why": "`best_paper` changes only when a score beats `best_score`. A rewrite can be worse than what it replaced, and this rule keeps the best one.", "at": "agno_agents.html#step11"},
  {"id": "a5_05", "unit": "agno_agents", "d": 0.6, "type": "mcq", "q": "An Agno `output_schema` has `fixes: list[str] = Field(min_length=3, max_length=3)`, described as three sentences. In one run a small model returned three items: half a sentence, a blank, and the rest of the sentence. What does that show?", "o": ["Agno did not hand the schema to the model, so the model answered in its own way", "A small model cannot count to three, so the schema should ask for one item only", "A schema checks the shape, not the content: three items, but not three sentences", "The schema is too strict: one that asks for four items would have worked well"], "a": 2, "why": "A schema can make a score a whole number from 1 to 100. It cannot make the score the right one, so a person still has to read the result.", "at": "agno_agents.html#step11"},
  {"id": "a5_06", "unit": "agno_agents", "d": 0.3, "type": "mcq", "q": "Three Agno agents work on one paper in this code. What decides whose turn it is, and when the work stops?", "code": "for turn in range(1, ROUNDS + 1):\n    review = reviewer.run(paper, session_id=session).content\n    if review.score >= TARGET or turn == ROUNDS:\n        break\n    # both: the paper and the review, as one text\n    notes = researcher.run(both, session_id=session).content\n    paper = writer.run(both + notes, session_id=session).content", "o": ["The reviewer, because it is the agent that gives the score", "A fourth agent, which Agno adds to every group of agents", "Ollama, which runs the agents one after the other by itself", "The `for` loop: the manager here is ordinary Python code"], "a": 3, "why": "Each `run()` is an ordinary call. The loop sends the paper round the agents, and its `if` decides when to stop.", "at": "agno_agents.html#research"},
  {"id": "a5_07", "unit": "agno_agents", "d": 0.1, "type": "mcq", "q": "What does the Python library `ddgs` need before it can search the web?", "o": ["No account and no key", "A paid account and a key", "A free key from Ollama", "A browser that is open"], "a": 0, "why": "It asks public search engines a question and returns the first results. Each search does send the question out to the web.", "at": "agno_agents.html#step11"},
  {"id": "smolagents_01", "unit": "smolagents", "d": -0.8, "type": "mcq", "q": "An agent needs a tool loop: offer the tools, read the model's choice, run the function, hand back the answer. What does smolagents do with that loop?", "o": ["It removes the loop, so no tools are needed.", "It writes the loop; you bring the tools.", "It turns the loop into a web page with Flask.", "It runs the loop without using any model."], "a": 1, "why": "smolagents writes the tool loop for you. `agent.run()` holds the loop, the retries and the stopping.", "at": "smolagents.html"},
  {"id": "smolagents_02", "unit": "smolagents", "d": -0.5, "type": "mcq", "q": "In smolagents, what does the `@tool` decorator above a function do?", "o": ["It runs the function once to test it.", "It sends the function to the Ollama server.", "It lets an agent choose that function.", "It stops the agent after that function has run."], "a": 2, "why": "`@tool` turns the function below it into something an agent can choose. smolagents reads its name, docstring and type hints to build the menu.", "at": "smolagents.html#first-agent"},
  {"id": "smolagents_03", "unit": "smolagents", "d": -0.2, "type": "mcq", "q": "This function is a smolagents tool. You delete the `Args:` block from its docstring. What happens?", "code": "@tool\ndef check_pantry(item: str) -> str:\n    \"\"\"Check if one ingredient is in the kitchen right now.\n\n    Args:\n        item: the name of one ingredient, like \"coconut milk\"\n    \"\"\"\n    return \"no, there is no \" + item + \" in the kitchen\"", "o": ["smolagents refuses the tool with an error.", "The agent runs, but it never calls the tool.", "The agent guesses the argument from its name.", "Nothing: the docstring is only good manners."], "a": 0, "why": "smolagents checks the docstring when it builds the tool. No `Args:` block gives a `DocstringParsingException`.", "at": "smolagents.html#first-agent"},
  {"id": "smolagents_04", "unit": "smolagents", "d": 0.1, "type": "mcq", "q": "In smolagents you can write a tool as a function with `@tool`, or as a class that extends `Tool`. When is the class the better choice?", "o": ["When the tool must search the internet.", "When the tool is used by two agents.", "When the tool has more than one argument.", "When the tool needs data of its own."], "a": 3, "why": "A function is the quick kind. A class is for a tool that needs to remember something: it is built with its data and does not read a global.", "at": "smolagents.html#tools"},
  {"id": "smolagents_05", "unit": "smolagents", "d": 0.4, "type": "fill", "q": "In smolagents, one agent can manage other agents. Which argument of `ToolCallingAgent` takes the list of worker agents? Type the argument name.", "answers": ["managed_agents", "managed_agents="], "why": "You give each worker agent a `name` and a `description`, and pass the workers to the manager in `managed_agents`.", "at": "smolagents.html#crew"},
  {"id": "smolagents_06", "unit": "smolagents", "d": 0.6, "type": "mcq", "q": "A smolagents agent has this tool. `SHOP` is the path of a text file. In one run the agent called the tool three times with the same item, so the file held that item three times. What is the right fix?", "code": "@tool\ndef add_to_shopping_list(item: str) -> str:\n    \"\"\"Add one thing to the shopping list.\n\n    Args:\n        item: the name of one ingredient\n    \"\"\"\n    with SHOP.open(\"a\", encoding=\"utf-8\") as shopping:\n        shopping.write(item + \"\\n\")\n    return \"added \" + item", "o": ["Use a smaller model, so the agent makes fewer calls.", "Let the tool read the file first and skip what is there.", "Give the tool its own agent with a low `max_steps`.", "Raise `max_steps`, so the agent has time to notice it."], "a": 1, "why": "An agent retries, so a tool can be called twice with the same input. A tool that appends must check first, so that it is safe to call twice.", "at": "smolagents.html#tools"},
  {"id": "smolagents_07", "unit": "smolagents", "d": 0.8, "type": "mcq", "q": "A smolagents manager agent has a tool that writes items to the file `shopping.txt` (`SHOP` is its path). After the run, this code prints the agent's answer and then reads the file itself. Why read the file?", "code": "answer = manager.run(task)\nprint(\"\\nANSWER:\", answer)\nwritten = SHOP.read_text(encoding=\"utf-8\").split() if SHOP.exists() else []\nprint(\"shopping.txt holds:\", written)", "o": ["An agent can say it added an item when it did not.", "The manager cannot give an answer until the file is read.", "The worker agents need the file for their next call.", "Reading the file lowers the number of model calls."], "a": 0, "why": "An agent's words and its actions can differ: it can report success without calling a tool, or write a wrong item. Check the result itself, not the agent's summary.", "at": "smolagents.html#crew"},
  {"id": "docker_lab_01", "unit": "docker_lab", "d": -0.8, "type": "mcq", "q": "What is the difference between an image and a container?", "o": ["An image is free; a container costs money.", "An image sits on disk; a container runs.", "An image holds code; a container holds data.", "An image is on Docker Hub; a container is local."], "a": 1, "why": "An image is a cold box that only sits on the disk. A container is a hot box that runs, made from an image with `docker run`.", "at": "docker_lab.html#L-img"},
  {"id": "docker_lab_02", "unit": "docker_lab", "d": -0.5, "type": "mcq", "q": "In `docker run -d -p 8080:80 my-site`, what is the left number, `8080`?", "o": ["The version number of the image", "The port inside the container", "The memory the container may use", "The port on your own computer"], "a": 3, "why": "The flag `-p` opens one port. The left number is the port on your computer and the right number is the port inside the container.", "at": "docker_lab.html#L-port"},
  {"id": "docker_lab_03", "unit": "docker_lab", "d": -0.3, "type": "mcq", "q": "`docker run hello-world` says \"cannot connect to the Docker daemon\". What does this mean?", "o": ["Docker Desktop is not running yet.", "The internet connection is down.", "The `hello-world` image was deleted.", "Your Docker Hub password is wrong."], "a": 0, "why": "Docker Desktop is not running. Open the app, wait for green, then try again.", "at": "docker_lab.html#sec-install"},
  {"id": "docker_lab_04", "unit": "docker_lab", "d": 0.6, "type": "mcq", "q": "This Dockerfile copies `package*.json` and runs `npm install` before it copies the rest of the code. Why?", "code": "FROM node:20-alpine AS build\nWORKDIR /app\nCOPY package*.json ./\nRUN npm install\nCOPY . .\nRUN npm run build", "o": ["Docker reads files in alphabetical order.", "The code cannot be copied before Node starts.", "A code change then does not install again.", "It makes the finished image much smaller."], "a": 2, "why": "Docker keeps each line as a cached layer. A changed line rebuilds its layer and every layer after it. `npm install` is slow, so it goes before the code that changes often.", "at": "docker_lab.html#L-layer"},
  {"id": "docker_lab_05", "unit": "docker_lab", "d": 0.1, "type": "mcq", "q": "What changes when you use `tailscale funnel` in place of `tailscale serve`?", "o": ["The site moves to a different port.", "The whole internet can open the site.", "The site keeps running with the lid closed.", "Only your own devices can open the site."], "a": 1, "why": "`serve` shares the site with your own devices only. `funnel` shares it with everybody: anyone with the link can open it.", "at": "docker_lab.html#sec-tailscale"},
  {"id": "docker_lab_06", "unit": "docker_lab", "d": 0.4, "type": "fill", "q": "`docker ____ my-site:1.0 yourname/my-site:1.0` gives an image a second name, without copying it. Type the missing word.", "answers": ["tag", "docker tag"], "why": "`docker tag my-site:1.0 yourname/my-site:1.0` does not copy. It only adds a second name to the same image.", "at": "docker_lab.html#sec-hub"},
  {"id": "docker_lab_07", "unit": "docker_lab", "d": 0.3, "type": "mcq", "q": "A deployment file says `replicas: 4`. One pod dies. What does Kubernetes do?", "o": ["It starts a new pod to keep four.", "It changes the number to three.", "It stops the other three pods too.", "It waits for you to restart it."], "a": 0, "why": "You say the number you want and Kubernetes keeps it true. If one copy dies, it starts a new one.", "at": "docker_lab.html#L-k8s"},
  {"id": "it__ai__dockerfile_basics_01", "unit": "it__ai__dockerfile_basics", "d": -0.8, "type": "mcq", "q": "What is a Dockerfile?", "o": ["A finished image that you download and then run", "A text file of steps for building an image", "A container that is running on your computer", "A numbered port that lets you reach a container"], "a": 1, "why": "A Dockerfile is a plain text file of instructions. `docker build` follows it to make an image.", "at": "it/ai/dockerfile_basics.html#s2"},
  {"id": "it__ai__dockerfile_basics_02", "unit": "it__ai__dockerfile_basics", "d": -0.5, "type": "mcq", "q": "In a Dockerfile, what does the line `FROM python:3.12-slim` do?", "o": ["Installs the packages listed in `requirements.txt`", "Copies your Python code into the `/app` folder", "Starts from a ready image that already has Python", "Says which command runs when the container starts"], "a": 2, "why": "`FROM` means your image starts from someone else's finished image, here a small one with Python already installed. Your lines add layers on top.", "at": "it/ai/dockerfile_basics.html#s3"},
  {"id": "it__ai__dockerfile_basics_03", "unit": "it__ai__dockerfile_basics", "d": -0.3, "type": "mcq", "q": "In `docker build -t joke-machine .`, what does `-t joke-machine` do?", "o": ["It gives the image a name to run it by later", "It sets a time limit, so a slow build stops", "It runs the tests in the folder before it builds", "It tells Docker which folder holds the Dockerfile"], "a": 0, "why": "`-t` is the tag: the name of the image. Here the image is named `joke-machine`.", "at": "it/ai/dockerfile_basics.html#s6"},
  {"id": "it__ai__dockerfile_basics_04", "unit": "it__ai__dockerfile_basics", "d": 0.1, "type": "mcq", "q": "An image was built from this Dockerfile. You change only `app.py` and run `docker build` again. What happens?", "code": "FROM python:3.12-slim\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install -r requirements.txt\nCOPY app.py .\nEXPOSE 8000\nCMD [\"python\", \"app.py\"]", "o": ["Every line rebuilds, because the image is one piece", "Nothing rebuilds, because the Dockerfile is the same", "Only `pip install` runs again; the rest is CACHED", "Lines 1 to 4 are CACHED; line 5 and below rebuild"], "a": 3, "why": "Docker reuses every layer above the change. The first line that depends on the changed file, and every line below it, must rebuild. Here that is `COPY app.py .` on line 5.", "at": "it/ai/dockerfile_basics.html#s4"},
  {"id": "it__ai__dockerfile_basics_05", "unit": "it__ai__dockerfile_basics", "d": 0.5, "type": "mcq", "q": "Why does this Dockerfile copy `requirements.txt` and run `pip install` before it copies `app.py`?", "code": "FROM python:3.12-slim\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install -r requirements.txt\nCOPY app.py .", "o": ["So editing your code does not re-run the install", "So Docker can check the code against the package list", "So the image is smaller than with one `COPY` line", "So `pip install` can find `app.py` in the workspace"], "a": 0, "why": "The slow part is `pip install`. Your code changes often and the requirements almost never, so separate lines keep the install cached.", "at": "it/ai/dockerfile_basics.html#s4"},
  {"id": "it__ai__dockerfile_basics_06", "unit": "it__ai__dockerfile_basics", "d": 0.6, "type": "mcq", "q": "A Docker container serves a website on `localhost:8000`. You change the code and rebuild the image, but the browser still shows the old site. Why?", "o": ["The cache reused the old code layer of the image", "The running container still uses the old image", "The `.dockerignore` file left your code out", "The image name must change on every rebuild"], "a": 1, "why": "A running container keeps running the old code. Rebuilding the image does not touch it. You must remove it with `docker rm -f` and start a new one from the new image.", "at": "it/ai/dockerfile_basics.html#s8"},
  {"id": "it__ai__dockerfile_basics_07", "unit": "it__ai__dockerfile_basics", "d": 0.2, "type": "mcq", "q": "What is a `.dockerignore` file for?", "o": ["It lists packages that pip must not install", "It lists ports that the container keeps closed", "It lists files that stay out of the build", "It lists images to remove when you clean up"], "a": 2, "why": "Docker hands your whole folder to the builder. `.dockerignore` lists what to leave out, such as videos and secrets.", "at": "it/ai/dockerfile_basics.html#s9"},
  {"id": "linux_lab_01", "unit": "linux_lab", "d": -0.7, "type": "mcq", "q": "Ubuntu installs programs with `apt`. Which command does Rocky Linux use for the same job?", "o": ["`apt-get`", "`dnf`", "`dpkg`", "`getent`"], "a": 1, "why": "Ubuntu uses `apt` and deb packages. Rocky uses `dnf` and rpm packages.", "at": "linux_lab.html#L-pkg"},
  {"id": "linux_lab_02", "unit": "linux_lab", "d": -0.4, "type": "mcq", "q": "You delete a container with `docker rm`. Which file is lost?", "o": ["A file in a volume that Docker made", "A file in a folder shared from your computer", "A file in a volume mounted at `/data`", "A file in the container's own filesystem"], "a": 3, "why": "A container's own filesystem is scratch paper. Files in a volume or a shared folder are still there after the container is deleted.", "at": "linux_lab.html#L-store"},
  {"id": "linux_lab_03", "unit": "linux_lab", "d": 0.0, "type": "mcq", "q": "In a fresh Ubuntu container, `apt-get install -y vim` fails with `Unable to locate package`. What is the cause?", "o": ["You did not run `apt-get update` first", "The package does not exist on Ubuntu", "You must type `sudo` before the command", "The container has no internet connection"], "a": 0, "why": "`apt-get install` reads a shop list that a fresh image does not have yet. The package exists; you must run `apt-get update` first.", "at": "linux_lab.html#sec-two"},
  {"id": "linux_lab_04", "unit": "linux_lab", "d": -0.3, "type": "mcq", "q": "What does `echo \"again\" >> note.txt` do?", "o": ["Empties the file, then writes the text", "Prints the file and the text together", "Adds the text to the end of the file", "Sends the text into the next command"], "a": 2, "why": "One `>` empties the file first. Two `>>` add to the bottom, which is much safer.", "at": "linux_lab.html#sec-cli"},
  {"id": "linux_lab_05", "unit": "linux_lab", "d": 0.4, "type": "fill", "q": "Read is 4, write is 2, run is 1. Which number do you give `chmod` so that only the owner may read and write a file, and nobody else may even read it? Type the three digits.", "answers": ["600", "chmod 600"], "why": "6 is read plus write for the owner, and 0 gives nothing to the group and to everyone else. So the command is `chmod 600` and the file name.", "at": "linux_lab.html#sec-cli"},
  {"id": "linux_lab_06", "unit": "linux_lab", "d": 0.4, "type": "mcq", "q": "Two containers run on the network Docker gives you by default. `getent hosts web` prints nothing. Why?", "o": ["The `web` container was started without `-p`", "Names do not work on the default network", "Ubuntu has no `ping` installed by default", "You must use `localhost` in place of names"], "a": 1, "why": "Containers can find each other by name only on a network you made yourself with `docker network create`. On the default network names do not work.", "at": "linux_lab.html#sec-net"},
  {"id": "linux_lab_07", "unit": "linux_lab", "d": 0.6, "type": "mcq", "q": "A program in the container `app` looks for a database on `localhost`. The database runs in another container. What happens?", "o": ["It never finds it: `localhost` is `app` itself", "It finds it: `localhost` is your computer", "It finds it if both were run with `-p 8080:80`", "It fails until `iproute2` is installed in `app`"], "a": 0, "why": "Inside a container, `localhost` means the container itself. To reach the database, use the other container's name.", "at": "linux_lab.html#sec-net"},
  {"id": "linux_lab_08", "unit": "linux_lab", "d": 0.7, "type": "mcq", "q": "A Dockerfile starts an SSH server with `CMD [\"/usr/sbin/sshd\", \"-D\", \"-e\"]`. Why is `-D` needed?", "o": ["So `sshd` writes its log into a file in `/run/sshd`", "So `sshd` makes new host keys each time it starts", "So `sshd` stays in front and the container keeps living", "So `sshd` listens on port 2222 and not on port 22"], "a": 2, "why": "`-D` means do not go into the background. A container lives only as long as its main program, so a service that goes to the background kills the container.", "at": "linux_lab.html#sec-ssh"},
  {"id": "b1_01", "unit": "bentoml", "d": -0.8, "type": "mcq", "q": "BentoML turns a Python class into a web service. What is a web service?", "o": ["A Python file that prints its answer one time, and then finishes its work", "A program that stays on, waits, and answers what other programs send it", "A web page that shows the code of a function, so that other people can copy it", "A folder that holds a separate copy of Python, with the libraries of one project"], "a": 1, "why": "A function answers only its own program, while that program runs. A service stays on and answers any program that can reach it.", "at": "bentoml.html#function"},
  {"id": "b1_02", "unit": "bentoml", "d": -0.5, "type": "mcq", "q": "What does the command `python -m venv venv` make?", "o": ["A new web service named `venv`, which waits for questions at port 3000", "A file named `venv.py`, with the code of BentoML and of `httpx` in it", "A separate copy of Python with its own libraries, in a folder named `venv`", "A Bento named `venv`, in a folder named `bentoml` in your home folder"], "a": 2, "why": "It makes a virtual environment. That keeps this project's libraries apart from your other projects.", "at": "bentoml.html#step1"},
  {"id": "b1_03", "unit": "bentoml", "d": -0.4, "type": "fill", "q": "Read `price.py`. What number does `price(\"dumplings\", 6)` return?", "code": "MENU = {\"beef noodles\": 60, \"fried rice\": 50, \"dumplings\": 40}\n\ndef price(dish, bowls):\n    return MENU[dish] * bowls", "answers": ["240"], "why": "`MENU[\"dumplings\"]` is 40, and 40 times 6 is 240.", "at": "bentoml.html#step2"},
  {"id": "b1_04", "unit": "bentoml", "d": 0.0, "type": "mcq", "q": "In `bentoml serve service:Shop`, what are the two halves of `service:Shop`?", "o": ["The address of the service, then the port it waits at", "The name of the Bento, then the version that BentoML made up", "The class in the file, then the method inside that class", "The file `service.py` without the `.py`, then the class inside it"], "a": 3, "why": "The word before the colon is the file, and the word after it is the class that BentoML serves.", "at": "bentoml.html#step4"},
  {"id": "b1_05", "unit": "bentoml", "d": 0.1, "type": "mcq", "q": "You run `bentoml serve service:Shop`. It prints `Service Shop initialized`, and then the terminal gives you no new line to type on. What does that mean?", "o": ["The service is running and waiting for questions", "The service has stopped with an error, and must be started again", "BentoML is still installing the libraries it needs", "The service has finished its work, and has closed itself"], "a": 0, "why": "A service does not finish. It waits, and it writes one line for every request that comes in. Ctrl+C stops it.", "at": "bentoml.html#step4"},
  {"id": "b1_06", "unit": "bentoml", "d": 0.4, "type": "mcq", "q": "This BentoML service is running at port 3000. A client sends its request to `http://localhost:3000/price`. Where does `/price` come from?", "code": "import bentoml\n\nMENU = {\"beef noodles\": 60, \"fried rice\": 50, \"dumplings\": 40}\n\n@bentoml.service\nclass Shop:\n    @bentoml.api\n    def price(self, dish: str, bowls: int) -> int:\n        return MENU[dish] * bowls", "o": ["From the name of the Python file, without the `.py`", "From the name of the method marked `@bentoml.api`", "From a line that you write in the file `bentofile.yaml`", "From a key in the JSON that the client sends with it"], "a": 1, "why": "The name of the method becomes part of the address. A second method with `@bentoml.api` makes a second address.", "at": "bentoml.html#step5"},
  {"id": "b1_07", "unit": "bentoml", "d": 0.5, "type": "mcq", "q": "A BentoML service is running. It was started with `bentoml serve service:Shop`. You change the code in `service.py` and save the file. What must you do before the service uses the new code?", "o": ["Nothing: it reads the file again for every request", "Send it two requests, so that the service reads the file again", "Stop the service with Ctrl+C and start it again", "Install BentoML again in the virtual environment"], "a": 2, "why": "A running service does not see a change in its file until you start it again.", "at": "bentoml.html#step5"},
  {"id": "b1_08", "unit": "bentoml", "d": 0.3, "type": "mcq", "q": "This file is a client of a BentoML service. The file has no function `price` in it. Where does `price` run when you run the file?", "code": "import bentoml\n\nshop = bentoml.SyncHTTPClient(\"http://localhost:3000\")\nprint(shop.price(dish=\"beef noodles\", bowls=2))", "o": ["In the service at that address: the question travels there and the answer back", "Inside this file, because BentoML copies the function into the client when it starts", "In the web browser, which opens the address `http://localhost:3000` to run it", "Nowhere: the client remembers the answer from the last time that it was run"], "a": 0, "why": "`bentoml.SyncHTTPClient` makes a client. It knows the address of the service and sends each call to it as a request.", "at": "bentoml.html#step5"},
  {"id": "b2_01", "unit": "bentoml", "d": 0.4, "type": "mcq", "q": "A method of a BentoML service is marked `@bentoml.api` and has the type hint `bowls: int`. A client sends it the JSON `{\"dish\": \"dumplings\", \"bowls\": \"five\"}`. What comes back?", "o": ["Status 200, and the answer 0. BentoML counts a word as zero bowls, and the method ran", "Status 500. BentoML let the word in, and then the service itself failed on the sum", "Status 404. BentoML could not find a dish for that order, and the method did not run", "Status 400. BentoML saw that \"five\" is not an `int`, and the method did not run"], "a": 3, "why": "BentoML checks every request against the type hints before the method runs. The reply names the place of the mistake, `\"loc\":[\"bowls\"]`.", "at": "bentoml.html#step6"},
  {"id": "b2_02", "unit": "bentoml", "d": 0.4, "type": "mcq", "q": "This BentoML service gets a request with the JSON `{\"dish\": \"pizza\", \"bowls\": 2}`. Both types are right, but `MENU` has no key `\"pizza\"`. What happens?", "code": "import bentoml\n\nMENU = {\"beef noodles\": 60, \"fried rice\": 50, \"dumplings\": 40}\n\n@bentoml.service\nclass Shop:\n    @bentoml.api\n    def price(self, dish: str, bowls: int) -> int:\n        return MENU[dish] * bowls", "o": ["Status 400. BentoML saw that `MENU` has no such key, and the method did not run", "Status 500. The method ran, and `MENU[\"pizza\"]` raised a `KeyError` in the service", "Status 200, and the answer 0, because a key that is not in a dictionary has the value 0", "Status 500. The service stopped, and it must be started again before the next request"], "a": 1, "why": "A type hint checks the kind of a value. It cannot know which keys a dictionary has. After the error the service goes on running.", "at": "bentoml.html#step6"},
  {"id": "b2_03", "unit": "bentoml", "d": -0.3, "type": "mcq", "q": "A web service puts a status code at the start of every reply. What does the first digit of a status code tell you?", "o": ["4 means the service failed, and 5 means the request was wrong", "4 means that the answer is a number, and 5 means that it is text", "4 means the request was wrong, and 5 means the service itself failed", "4 and 5 both mean that the request went well, and 2 means that it failed"], "a": 2, "why": "A code that begins with 4, such as 400 or 404, means the request was wrong. A code that begins with 5, such as 500, means the service failed. 200 means the request went well.", "at": "bentoml.html#step6"},
  {"id": "b2_04", "unit": "bentoml", "d": 0.1, "type": "mcq", "q": "This BentoML service is started one time, and then it answers 100 requests. How many times does the `print` in `__init__` run?", "code": "@bentoml.service\nclass Shop:\n    def __init__(self):\n        with open(\"menu.json\", encoding=\"utf-8\") as file:\n            self.menu = json.load(file)\n        print(\"The menu has\", len(self.menu), \"dishes.\")\n\n    @bentoml.api\n    def dishes(self) -> list[str]:\n        return list(self.menu)", "o": ["One time, when the service starts", "100 times: one time for every request", "One time for every key in the JSON file", "Never: a service cannot print"], "a": 0, "why": "Python runs `__init__` when an object of the class is made, and BentoML makes that object one time, when the service starts.", "at": "bentoml.html#step7"},
  {"id": "b2_05", "unit": "bentoml", "d": 0.5, "type": "mcq", "q": "This method is part of a BentoML service. `self.menu` is a dictionary that has no key `\"pizza\"`. What does a request for the dish `\"pizza\"` get?", "code": "    @bentoml.api\n    def price(self, dish: str, bowls: int) -> int:\n        if dish not in self.menu:\n            raise bentoml.exceptions.NotFound(\"We do not sell \" + dish + \".\")\n        return self.menu[dish] * bowls", "o": ["Status 500 and `An unexpected error has occurred`", "Status 400 and the place of the mistake", "Status 200 and the price 0 for the dish", "Status 404 and the message from `raise`"], "a": 3, "why": "`raise` stops the method and reports the error. `bentoml.exceptions.NotFound` gives status 404, and the reply holds the message that the code wrote.", "at": "bentoml.html#step7"},
  {"id": "b2_06", "unit": "bentoml", "d": 0.3, "type": "mcq", "q": "In this BentoML service, what is `self.menu` for?", "code": "@bentoml.service\nclass Shop:\n    def __init__(self):\n        with open(\"menu.json\", encoding=\"utf-8\") as file:\n            self.menu = json.load(file)\n\n    @bentoml.api\n    def dishes(self) -> list[str]:\n        return list(self.menu)\n\n    @bentoml.api\n    def price(self, dish: str, bowls: int) -> int:\n        return self.menu[dish] * bowls", "o": ["It makes the menu a web address, `/menu`, that other programs can ask for the list of dishes", "It keeps the dictionary in the object, so that every method of the class can read it", "It sends the menu to each client, so that the client can work out a price by itself", "It saves the menu back to `menu.json` each time that a program asks for a price"], "a": 1, "why": "`self` is how the methods of one class share data. `__init__` loads the dictionary, and the other methods read it as `self.menu`.", "at": "bentoml.html#step7"},
  {"id": "b2_07", "unit": "bentoml", "d": 0.2, "type": "fill", "q": "`bentoml ____` packs a service into a Bento. Type the missing word.", "answers": ["build", "bentoml build"], "why": "`bentoml build` reads `bentofile.yaml`, which says which service to pack.", "at": "bentoml.html#step8"},
  {"id": "b2_08", "unit": "bentoml", "d": 0.6, "type": "mcq", "q": "With `bentoml build` you pack a service from the file `shop.py` into a Bento named `shop`. Later you start the service with `bentoml serve shop:latest`. Which `shop.py` does it read?", "o": ["The one in your folder, as always. It answers only when it is started from that folder", "The newest file named `shop.py` anywhere on the computer, which BentoML finds by a search", "The copy inside the Bento. It answers even when it is started from a different folder", "None: a Bento holds no Python files, only the list of libraries and the list of APIs"], "a": 2, "why": "A Bento holds a copy of your files, the list of libraries, and a description of the APIs. `latest` means the newest version.", "at": "bentoml.html#step8"},
  {"id": "b3_01", "unit": "bentoml", "d": 0.0, "type": "mcq", "q": "This BentoML service loads an AI model in `__init__` and not inside `yes_or_no`. Why?", "code": "@bentoml.service(traffic={\"timeout\": 300})\nclass Judge:\n    def __init__(self):\n        self.model = Decider(\"Mapika/decider-2b\")\n\n    @bentoml.api\n    def yes_or_no(self, text: str, question: str) -> float:\n        questions = {\"answer\": {\"type\": \"noul\", \"instructions\": question}}\n        result = self.model.system_one(text, questions)\n        return result[\"answers\"][\"answer\"][\"noul\"]", "o": ["A method with `@bentoml.api` may not use a model, so the model has to stay outside", "Loading is slow. Inside `yes_or_no`, every request would have to wait for it", "`__init__` is the only method of a class that may open and read files on the disk", "The model is too big for a method, and Python gives `__init__` more memory"], "a": 1, "why": "`__init__` runs one time, when the service starts. The model is loaded there and kept in `self`, and each request only uses it.", "at": "bentoml.html#judge"},
  {"id": "b3_02", "unit": "bentoml", "d": 0.4, "type": "mcq", "q": "What is `@bentoml.service(traffic={\"timeout\": 300})` for?", "o": ["It stops the service after it has answered 300 requests, so that the model can rest", "It tells the model to keep each of its answers under 300 words, so that they come fast", "It lets 300 programs ask the service at the same time, and makes the others wait", "It lets a request take 300 seconds. Without it, BentoML gives up after 60 seconds"], "a": 3, "why": "BentoML gives up on a request after 60 seconds. The first answer of an AI model can take longer than that, so this option gives a request more time.", "at": "bentoml.html#judge"},
  {"id": "b3_03", "unit": "bentoml", "d": 0.5, "type": "mcq", "q": "A BentoML service has `traffic={\"timeout\": 300}`, and it needs 50 seconds for one answer. A program makes its client with `bentoml.SyncHTTPClient(\"http://localhost:3000\", timeout=300)`. What happens if you leave out `timeout=300` in the client?", "o": ["The client waits 30 seconds and then stops with an error", "The client waits for ever, and never stops by itself", "The client sends the same request again every 30 seconds", "Nothing changes: the service's own `timeout` of 300 is enough"], "a": 0, "why": "There are two clocks. A BentoML service gives up on a request after 60 seconds, and a BentoML client stops waiting after 30. A slow model needs both raised.", "at": "bentoml.html#judge"},
  {"id": "b3_04", "unit": "bentoml", "d": -0.3, "type": "mcq", "q": "The model decider-2b answers a yes-or-no question about a text with one number. The text is \"Two bowls of beef noodles, please. No chilli.\" and the question is \"Does the customer want it spicy?\" The model returns `0.0085`. What does that mean?", "o": ["Yes: the number is not zero", "The model does not know", "No: the number is near 0", "The model found an error in the text"], "a": 2, "why": "decider-2b does not write text. It gives a number that says how sure it is. Near 1 means yes, and near 0 means no.", "at": "bentoml.html#judge"},
  {"id": "b3_05", "unit": "bentoml", "d": 0.2, "type": "mcq", "q": "This BentoML service gets its answers from a language model, but its class has no `__init__`. Why?", "code": "import bentoml\nimport httpx\n\nOLLAMA = \"http://localhost:11434/api/generate\"\nMODEL = \"llama3.2\"\n\n@bentoml.service\nclass Talker:\n    @bentoml.api\n    def say(self, prompt: str) -> str:\n        request = {\"model\": MODEL, \"prompt\": prompt, \"stream\": False}\n        reply = httpx.post(OLLAMA, json=request, timeout=120)\n        return reply.json()[\"response\"]", "o": ["An LLM is too big to be kept in `self`, so it is loaded again in the method `say` each time", "It has nothing to load: Ollama keeps the model, and the service passes each question on", "BentoML writes an `__init__` for every class that has none, and loads the model there", "The model is loaded by the client program, which sends it to the service with each prompt"], "a": 1, "why": "Ollama is a service too. This service asks it and passes the answer back. Services often ask other services.", "at": "bentoml.html#talker"},
  {"id": "b3_06", "unit": "bentoml", "d": 0.0, "type": "fill", "q": "Ollama runs as a service on your computer. At which port does it wait, if you have not changed it? Type the number.", "answers": ["11434"], "why": "Ollama stays on in the background and waits at port 11434. A program asks it at an address such as `http://localhost:11434/api/generate`.", "at": "bentoml.html#talker"},
  {"id": "b3_07", "unit": "bentoml", "d": 0.4, "type": "mcq", "q": "A Python program sends a request to Ollama at `/api/generate`. The request holds `\"stream\": False`. What does that ask for?", "o": ["An answer in plain text, with no punctuation and no line breaks", "An answer that Ollama does not save anywhere on the disk", "A model that runs without the internet, on this computer", "The whole answer at once, and not word by word"], "a": 3, "why": "With the whole answer in one reply, `reply.json()[\"response\"]` holds the text.", "at": "bentoml.html#talker"},
  {"id": "b3_08", "unit": "bentoml", "d": 0.5, "type": "mcq", "q": "One AI model returns a number between 0 and 1. Another AI model returns a sentence. A BentoML service can serve either of them. What is the difference for the program that asks?", "o": ["A program can use a number at once: `if answer > 0.5`. A sentence is for a person to read", "BentoML can serve a method that returns a number, but a sentence needs a second library too", "A sentence is always more correct than a number, so the second model is the better one", "A number can travel over the network as JSON, but a sentence has to be sent as a file"], "a": 0, "why": "BentoML serves both in the same way. What differs is who can use the answer.", "at": "bentoml.html#talker"},
  {"id": "b4_01", "unit": "bentoml", "d": 0.6, "type": "mcq", "q": "In this BentoML service, `FORM` is the text of a web page with a form. Why does that web page come from a plain function, `form_page`, fixed on with `@bentoml.asgi_app`, and not from a method with `@bentoml.api`?", "code": "def form_page(request):\n    return HTMLResponse(FORM)\n\n@bentoml.service\n@bentoml.asgi_app(Starlette(routes=[Route(\"/\", form_page)]), path=\"/shop\")\nclass Shop:\n    @bentoml.api\n    def order(self, dish: str, bowls: int) -> HTML:\n        total = MENU[dish] * bowls\n        line = str(bowls) + \" bowls of \" + dish + \" cost \" + str(total) + \".\"\n        return \"<h1>\" + line + \"</h1><p><a href='/shop/'>Ask again</a></p>\"", "o": ["A BentoML API can only return numbers, and a form is a long piece of text", "The form is sent as JSON, and a browser cannot show JSON as a web page", "A browser opens a page with GET, and a BentoML API answers only POST", "Starlette came with BentoML, so every page of a service has to be written with it"], "a": 2, "why": "A browser opens a page with a GET request, and a BentoML API answers only POST requests. The form itself is sent with `method=\"post\"`, so an ordinary API can read it.", "at": "bentoml.html#webshop"},
  {"id": "b4_02", "unit": "bentoml", "d": 0.3, "type": "mcq", "q": "A web form sends everything as text, so the browser sends `\"3\"` to the BentoML API `order(self, dish: str, bowls: int)`. What turns it into the number 3?", "o": ["The browser, before it sends the form", "The hint `bowls: int`", "A line `int(bowls)` that you write in `order`", "Nothing: the method gets the text `\"3\"`"], "a": 1, "why": "BentoML reads the type hint, and it checks a form in the same way as it checks JSON. A word in place of the number gets status 400.", "at": "bentoml.html#webshop"},
  {"id": "b4_03", "unit": "bentoml", "d": 0.2, "type": "mcq", "q": "How does BentoML know which field of the form goes to which value of `order(self, dish: str, bowls: int)`?", "o": ["By the order of the fields in the form: first field, first value", "By a table that you write in the file `bentofile.yaml`", "By type: the text goes to `dish` and the number to `bowls`", "By name: the fields are `name=\"dish\"` and `name=\"bowls\"`"], "a": 3, "why": "That is all the wiring there is. BentoML hands each field to the value with the same name.", "at": "bentoml.html#webshop"},
  {"id": "b4_04", "unit": "bentoml", "d": 0.4, "type": "mcq", "q": "With the Python library `httpx`, one program calls `httpx.post(address, json=order)` and another calls `httpx.post(address, data=form)`. Both send to the same BentoML API. What is the difference?", "o": ["`json=` sends JSON, and `data=` sends a form. The API takes both", "`data=` is for numbers, and `json=` is for text. The API takes only one", "`data=` makes a GET request, and `json=` makes a POST request", "There is none: they are two names for one thing, and both send JSON"], "a": 0, "why": "`data=` does what a browser does when you press the button of a form. A BentoML API reads a form in the same way as it reads JSON.", "at": "bentoml.html#webshop"},
  {"id": "b4_05", "unit": "bentoml", "d": -0.3, "type": "mcq", "q": "A BentoML service runs on your computer at `http://localhost:3000`. A phone on the same Wi-Fi opens that address. Why does it not reach the service?", "o": ["The phone has no Python, and BentoML needs it", "Port 3000 is closed on every phone, by its maker", "On the phone, `localhost` means the phone", "BentoML does not work with phones, only with computers"], "a": 2, "why": "`localhost` is the name every device has for itself. Another device needs your computer's address on the local network, which looks like `192.168.1.20`.", "at": "bentoml.html#network"},
  {"id": "b4_06", "unit": "bentoml", "d": 0.4, "type": "mcq", "q": "What does `--host 127.0.0.1` do in `bentoml serve webshop:Shop --host 127.0.0.1`?", "o": ["It opens the service to the whole internet", "It keeps the service to your own computer", "It changes the port from 3000 to 127", "It makes the service answer faster"], "a": 1, "why": "`127.0.0.1` is `localhost` written as a number. Without the option, BentoML listens on every network that the computer is on.", "at": "bentoml.html#network"},
  {"id": "b4_07", "unit": "bentoml", "d": 0.2, "type": "mcq", "q": "A service runs on your computer. The computer's address on your Wi-Fi is `192.168.1.20`. A friend in another house types that address. Why does it not work?", "o": ["The address is wrong by one number", "Your friend needs BentoML to open it", "The service takes only one visitor", "A local address stops at your router"], "a": 3, "why": "The local network is the devices on the same Wi-Fi. An address such as `192.168.1.20` means nothing outside that network.", "at": "bentoml.html#network"},
  {"id": "b4_08", "unit": "bentoml", "d": 0.5, "type": "mcq", "q": "A service runs on your computer at port 3000. What does the Tailscale command `tailscale funnel 3000` give you?", "o": ["A public address for the service at port 3000", "An address that begins with 100, for your own devices only", "A copy of the service on Tailscale's computers", "A password for the service"], "a": 0, "why": "Tailscale Funnel gives the service a public address, and anyone who has it can open it without Tailscale. Ctrl+C stops the command and closes the address.", "at": "bentoml.html#tailscale"},
  {"id": "re_01", "unit": "reverse_engineer", "d": -0.8, "type": "mcq", "q": "What is reverse engineering of a program?", "o": ["Writing a program a second time, in a faster programming language", "Running a program many times, to find the inputs that make it stop", "Starting from the finished program, and working back to how it is built", "Making a program smaller, so that it loads faster in a web browser"], "a": 2, "why": "The author goes from a plan to a finished program. Reverse engineering goes the other way: from what was shipped back to how it works.", "at": "reverse_engineer.html#overview"},
  {"id": "re_02", "unit": "reverse_engineer", "d": -0.5, "type": "mcq", "q": "A tool does static analysis of a program. What does that mean?", "o": ["It studies the program by reading its files, and does not run it", "It runs the program one time and records every line that is used", "It changes the program's files, so that the program runs faster", "It asks the author of the program for the plan and the comments"], "a": 0, "why": "Static means that nothing moves: the tool only reads the files. That is safe for a program you do not trust, but it cannot see what is decided while the program runs.", "at": "reverse_engineer.html#overview"},
  {"id": "re_03", "unit": "reverse_engineer", "d": 0.2, "type": "mcq", "q": "A tool reads this JavaScript without running it. It can say where `\"Sawasdee\"` is written. Why can it not say that `greet` returns that word?", "code": "const WORDS = { th: \"Sawasdee\", en: \"Hello\" };\n\nfunction greet(name, language) {\n  return WORDS[language] + \", \" + name + \"!\";\n}", "o": ["Because the word is inside quotes, and a tool cannot read what is in quotes", "Because `WORDS` is a constant, and a constant cannot be followed by a tool", "Because the function has two parameters, and such a tool can follow only one", "Because the word that is taken depends on `language`, which has a value only at run time"], "a": 3, "why": "`WORDS[language]` picks a word with a value that exists only while the program runs. A tool that reads without running cannot know that value, and a careful one says so.", "at": "reverse_engineer.html#overview"},
  {"id": "re_04", "unit": "reverse_engineer", "d": -0.1, "type": "mcq", "q": "A reverse engineering tool marks one of its findings as inferred, not as observed. What does that tell you?", "o": ["The tool saw this finding in the files, at a place that it names", "The tool worked this finding out from what it saw, and it can be wrong", "The tool could not find anything, and it gives no answer at all", "The tool ran the program, and it watched this finding happen"], "a": 1, "why": "Observed means seen in the files. Inferred means worked out from what was seen. An inferred finding is the first one to check.", "at": "reverse_engineer.html#step2"},
  {"id": "re_07", "unit": "reverse_engineer", "d": 0.3, "type": "mcq", "q": "A tool looks at a compiled Mac program and lists `_printf` as an import from `libSystem`. What is an import of a compiled program?", "o": ["A function that the program holds itself and shows to other programs", "A function that the program uses but does not hold: a library holds it", "A file that the program reads from the disk each time it starts", "A text that the program prints on the screen while it is running"], "a": 1, "why": "The program calls `printf`, but the code of `printf` is in the library. The list of imports shows what a program takes from each library it loads.", "at": "reverse_engineer.html#step7"},
  {"id": "re_08", "unit": "reverse_engineer", "d": 0.1, "type": "fill", "q": "A Mac app looks like one file, but it is a folder whose name ends in `.app`. What is such a folder called? Type one word.", "answers": ["bundle"], "why": "The folder is a bundle. The compiled program is one file inside it, beside the app's card, its designs and its other files.", "at": "reverse_engineer.html#step4"},
  {"id": "re_09", "unit": "reverse_engineer", "d": 0.2, "type": "mcq", "q": "A Mac program is signed by its developer. Which two things can macOS check with the signature?", "o": ["How fast the program runs, and how much memory the program needs", "Which language the program is written in, and how many files it has", "That the program is free, and that its source code is public", "Who made the program, and that nobody has changed it since"], "a": 3, "why": "Only the maker can put the signature on. If one byte of the program is changed later, the signature no longer fits.", "at": "reverse_engineer.html#step6"},
  {"id": "re_10", "unit": "reverse_engineer", "d": 0.5, "type": "mcq", "q": "In a Mac app, the design of a menu holds an action for a menu item. What does the action say?", "o": ["Which method to call when the item is chosen, and on which class", "Which colour and which size the text of the menu item has", "How many times the user has chosen that item since the app started", "Which other apps are allowed to open the same menu on the screen"], "a": 0, "why": "The action links what the user does to the code: the name of a method, and the class it belongs to. It is the first half of the answer to the question of what a menu item does.", "at": "reverse_engineer.html#step8"},
  {"id": "re_11", "unit": "reverse_engineer", "d": 0.5, "type": "mcq", "q": "You check tool A's answer with tool B, and they agree. Then you learn that tool A gets its answer by running tool B. How much is the agreement worth?", "o": ["A lot: two tools that agree are always right about the answer", "Nothing at all: it proves that both of the tools are broken", "Little: answers from the same source agree even when they are wrong", "A lot: it proves that tool B was written by the makers of tool A"], "a": 2, "why": "A second opinion counts only when it comes from a different place. The best one is the source code of the program, when you have it.", "at": "reverse_engineer.html#step9"},
  {"id": "re_12", "unit": "reverse_engineer", "d": 0.0, "type": "mcq", "q": "You want to reverse engineer a program that somebody else wrote. What do you check first?", "o": ["How many files the program has, to see how long the work will take", "The licence of the program, to see if you are allowed to do it", "The price of the program, because only free programs can be read", "The age of the program, because an old program has fewer secrets"], "a": 1, "why": "Many licences forbid reverse engineering, and an open-source licence such as MIT allows it. Read the licence before you start.", "at": "reverse_engineer.html#app"},
  {"id": "flask_01", "unit": "flask", "d": -0.8, "type": "mcq", "q": "What does `@app.route(\"/\")` mean?", "o": ["Call this function for a visit to the front page", "Start the server and keep it running on port 5000", "Reload the file each time you save a change to it", "Tell Flask which folder holds your HTML templates"], "a": 0, "why": "The route is the address. A single slash means the front page, and the function under it gives what the visitor sees.", "at": "flask.html#hello"},
  {"id": "flask_02", "unit": "flask", "d": -0.4, "type": "fill", "q": "Flask looks for HTML files in one folder, and the folder name matters. What is the folder called? Type it.", "answers": ["templates", "templates/"], "why": "Flask keeps HTML in a folder called `templates`. It looks for that name and nothing else.", "at": "flask.html#templates"},
  {"id": "flask_03", "unit": "flask", "d": -0.3, "type": "mcq", "q": "What do the angle brackets do in `@app.route(\"/hello/<name>\")`?", "o": ["They mark a page that only the named visitor may open", "They catch part of the address for the function", "They make Flask answer 404 for any address not listed", "They tell the template which blank it has to fill in"], "a": 1, "why": "The brackets are a hole in the address. Flask fills it in and hands it to the function as `name`.", "at": "flask.html#routes"},
  {"id": "flask_04", "unit": "flask", "d": 0.0, "type": "mcq", "q": "A Flask route shows a form that has `method=\"post\"`. The route has `methods=[\"GET\", \"POST\"]`. You take this list out. What happens when a visitor sends the form?", "o": ["The form gets 404", "The form is sent twice", "The form gets 405", "The boxes arrive empty"], "a": 2, "why": "GET shows the form and POST acts on it, so the route must accept both. Without the list, Flask does not accept the POST and the form gets 405.", "at": "flask.html#form"},
  {"id": "flask_05", "unit": "flask", "d": 0.2, "type": "mcq", "q": "A Flask site shows a name that the visitor typed. Why is a template with `{{ name }}` safer than joining the name into a string of HTML?", "o": ["It keeps your passwords out of the Python file", "It stops visitors from opening other addresses", "It signs the text so a visitor cannot edit it", "It turns a typed tag into harmless plain words"], "a": 3, "why": "A joined string sends a typed `<img ...>` back as a real tag that the browser would run. A template turns the same text into `&lt;img ...&gt;`, which is only words.", "at": "flask.html#templates"},
  {"id": "flask_06", "unit": "flask", "d": 0.4, "type": "mcq", "q": "A Flask app speaks text with Piper voices. `PiperVoice.load` is slow, and every request calls `get_voice`. Why does this code keep voices in the dictionary `loaded`?", "code": "loaded = {}\n\ndef get_voice(path):\n    if path not in loaded:\n        loaded[path] = PiperVoice.load(path)\n    return loaded[path]", "o": ["So each voice is loaded once, not on every visit", "So each visitor gets a separate conversation list", "So the browser does not replay the old sound file", "So Flask can serve the sound with no route from you"], "a": 0, "why": "Loading a voice is slow and speaking is fast. The dictionary loads a voice the first time it is asked for, then keeps it for every later request.", "at": "flask.html#speak"},
  {"id": "flask_07", "unit": "flask", "d": 0.6, "type": "mcq", "q": "This Python loop is a chat with a model in Ollama. `ask` sends the whole list `messages` to the model. You comment out the last line. What happens in the chat?", "code": "while True:\n    said = input(\"you: \").strip()\n    messages.append({\"role\": \"user\", \"content\": said})\n    reply = ask(messages)\n    print(\"ai:\", reply)\n    messages.append({\"role\": \"assistant\", \"content\": reply})", "o": ["It forgets your messages but remembers its own", "It forgets its own answers but remembers yours", "It forgets everything said before each new turn", "It crashes, because the list has no system line"], "a": 1, "why": "The model has no memory. It only knows what is in the list you send, and now the list holds your messages but not its replies.", "at": "flask.html#memory"},
  {"id": "flask_08", "unit": "flask", "d": 0.8, "type": "mcq", "q": "A Flask chat app keeps each visitor's messages in `session`. Why does this code store only the newest 12 messages and not the whole list?", "code": "REMEMBER = 12          # how many past messages to keep\n\nmessages = session.get(\"messages\", [])\nmessages.append({\"role\": \"user\", \"content\": request.form[\"said\"]})\nmessages.append({\"role\": \"assistant\", \"content\": ask(messages)})\nsession[\"messages\"] = messages[-REMEMBER:]", "o": ["The model cannot read more than twelve messages", "The secret key can sign only a short list of data", "A browser throws away a cookie that is too large", "Flask deletes a session after twelve requests to it"], "a": 2, "why": "Flask packs the whole session into one cookie, and a browser silently throws away a cookie over about 4000 bytes.", "at": "flask.html#session"},
  {"id": "gradio_01", "unit": "gradio", "d": -0.8, "type": "mcq", "q": "What does Gradio do?", "o": ["Turns a web page into a Python function", "Draws a window on this computer only", "Trains an AI model from your own data", "Turns a Python function into a web page"], "a": 3, "why": "Gradio turns a Python function into a web page. You write the function and Gradio draws everything around it.", "at": "gradio.html"},
  {"id": "gradio_02", "unit": "gradio", "d": -0.5, "type": "mcq", "q": "In Gradio, what does `demo.launch()` do?", "o": ["Saves a web page as an HTML file on the disk", "Starts a small web server, prints its address", "Opens a tunnel to everyone on the internet", "Installs Gradio and prints its version number"], "a": 1, "why": "`launch()` starts a small web server and prints its address. You open that address in a browser, and Ctrl-C stops it.", "at": "gradio.html#step1"},
  {"id": "gradio_03", "unit": "gradio", "d": -0.3, "type": "mcq", "q": "In Gradio, what does a `gr.Checkbox` hand to your function?", "o": ["`True` or `False`", "The text of its label", "A number from 0 to 1", "A list of chosen words"], "a": 0, "why": "Every Gradio control becomes one argument of the function. A `gr.Checkbox` hands over `True` or `False`, with no parsing.", "at": "gradio.html#step5"},
  {"id": "gradio_04", "unit": "gradio", "d": 0.1, "type": "mcq", "q": "This `reply` function is given to Gradio's `gr.ChatInterface`, and `talk` is then sent to Ollama. Why does `reply` pass each turn's content through `plain()`?", "code": "def plain(content):\n    if isinstance(content, str):\n        return content\n    return \"\".join(part.get(\"text\", \"\") for part in content\n                   if part.get(\"type\") == \"text\")\n\ndef reply(message, history):\n    talk = [{\"role\": turn[\"role\"], \"content\": plain(turn[\"content\"])}\n            for turn in history]\n    talk.append({\"role\": \"user\", \"content\": message})\n    ...", "o": ["Gradio gives one string, but Ollama wants a list of parts", "Ollama cannot read text that holds Thai letters", "Gradio gives a list of parts, but Ollama wants one string", "Gradio forgets the history unless it is copied"], "a": 2, "why": "Gradio hands each turn's content over as a list of parts, so a turn can hold a picture too. Ollama wants a string, and `plain()` takes the words out.", "at": "gradio.html#step3"},
  {"id": "gradio_05", "unit": "gradio", "d": 0.4, "type": "mcq", "q": "This function is given to Gradio's `gr.ChatInterface` and streams a model's answer. You change `yield so_far` so that it yields only the new piece. What do you see in the browser?", "code": "def reply(message, history):\n    ...\n    so_far = \"\"\n    with urllib.request.urlopen(request, timeout=300) as response:\n        for line in response:\n            piece = json.loads(line)\n            so_far = so_far + piece[\"message\"][\"content\"]\n            yield so_far", "o": ["The answer appears twice on the screen", "Nothing shows until the whole answer is ready", "Gradio shows an error and the server stops", "Each piece flickers past; nothing stays"], "a": 3, "why": "Gradio replaces what is on screen with whatever you yield. It does not add, so you must yield the whole answer so far.", "at": "gradio.html#step4"},
  {"id": "gradio_06", "unit": "gradio", "d": 0.2, "type": "fill", "q": "Which argument of `demo.launch()`, when it is set to `True`, makes Gradio open a tunnel and print a public address that anyone on the internet can open? Type the argument's name.", "answers": ["share", "share=true", "share = true"], "why": "`share=True` puts the program on your computer on the internet. Use `auth` with it, and never leave it running.", "at": "gradio.html#step6"},
  {"id": "gradio_07", "unit": "gradio", "d": 0.6, "type": "mcq", "q": "A Gradio quiz app keeps each player's score in `gr.State` and not in a plain global variable. Why?", "o": ["A global cannot hold a dictionary of numbers", "Each browser gets its own copy of the score", "The score must be shown on screen in a box", "A global is deleted after every button click"], "a": 1, "why": "A Gradio app is on a web server, and two people can open it at once. A global is shared by all of them, but `gr.State` gives each browser its own copy.", "at": "gradio.html#step7"},
  {"id": "gradio_08", "unit": "gradio", "d": 0.7, "type": "mcq", "q": "This smolagents agent is asked for the capital of Thailand, and the model tries to call a tool named `web_search`. Why can it not call it?", "code": "agent = ToolCallingAgent(tools=[coin_price], model=llm)\nanswer = agent.run(message, max_steps=6)", "o": ["`web_search` is not in the `tools` list", "`max_steps=6` stops every call to a tool", "The question has no coin name in it", "Ollama blocks every search on the internet"], "a": 0, "why": "The list of tools is the whole permission system. A tool that is not in the list cannot be called.", "at": "gradio.html#step7"},
  {"id": "rag_llm_01", "unit": "rag_llm", "d": -0.8, "type": "mcq", "q": "What does RAG change?", "o": ["The numbers inside the model itself", "The prompt that the model reads", "The speed of the processor", "The licence of the model"], "a": 1, "why": "RAG changes no number of the model. It puts the passages it found into the prompt, in front of the question.", "at": "rag_llm.html"},
  {"id": "rag_llm_02", "unit": "rag_llm", "d": -0.5, "type": "mcq", "q": "A user asks \"Is there somewhere to park?\" A document says \"There are four spaces for cars behind the building.\" Why does a search for the words of the question find nothing in the document?", "o": ["The document has no passage about cars at all", "The question is too short for any search", "The embedding model can read only Thai text", "The two share no word that matters"], "a": 3, "why": "The question and the passage share no word that matters. That is why RAG compares meanings, with embeddings, and not words.", "at": "rag_llm.html#search"},
  {"id": "rag_llm_03", "unit": "rag_llm", "d": -0.3, "type": "mcq", "q": "Two embeddings get a cosine similarity score near 0. What does that mean?", "o": ["The two texts have little to do with each other", "The two texts mean nearly the same thing", "The search is not sure about its answer", "One of the two texts is too long for the model to read"], "a": 0, "why": "Near 1, two texts mean nearly the same. Near 0, they have little to do with each other.", "at": "rag_llm.html#step2"},
  {"id": "rag_llm_04", "unit": "rag_llm", "d": 0.1, "type": "fill", "q": "A RAG program first cuts a document into small passages, for example at every empty line. What is this cutting called? Type the word.", "answers": ["chunking"], "why": "Cutting a document into pieces is called chunking. It decides more than anything else how well a search can work, because a piece about two subjects gets an embedding that is about neither.", "at": "rag_llm.html#step3"},
  {"id": "rag_llm_05", "unit": "rag_llm", "d": 0.4, "type": "mcq", "q": "In the `transformers` library, you pass `documents` to `apply_chat_template`, but the model's chat template has no place for documents. What happens?", "o": ["Python stops with an error about the chat template", "The passages go into the system instruction", "The model refuses to answer every question", "No error comes, and no passage is in the prompt"], "a": 3, "why": "Such a template drops the documents and says nothing. So print the prompt once for every new model, and check that the passages are in it.", "at": "rag_llm.html#step4"},
  {"id": "rag_llm_06", "unit": "rag_llm", "d": 0.0, "type": "mcq", "q": "In this call of the `transformers` library, what does `do_sample=False` do?", "code": "output = model.generate(prompt_tokens, max_new_tokens=40, do_sample=False)", "o": ["The same prompt gets the same reply on every run", "The model stops writing after 40 new tokens", "The model answers without reading any documents", "The reply is printed without its special tokens"], "a": 0, "why": "`do_sample=False` makes the model take its most likely token every time, so the same prompt gets the same reply on every run.", "at": "rag_llm.html#step5"},
  {"id": "rag_llm_07", "unit": "rag_llm", "d": 0.6, "type": "mcq", "q": "A RAG program prints the titles of the passages that its search found. For the question \"Can I pay by card?\" the first title is `HOW TO PAY`, and that passage says that the shop does not take credit cards. The model still answers \"Yes, you can pay by card\". What went wrong?", "o": ["The search did not find the right passage at all", "The search found it; the model read it badly", "The documents have no passage about paying", "The embedding model gave too low a score"], "a": 1, "why": "Found is not the same as read. The printed titles show that the search did its work, so the mistake is the model's.", "at": "rag_llm.html#step5"},
  {"id": "rag_llm_08", "unit": "rag_llm", "d": 0.3, "type": "mcq", "q": "A RAG system gives the model the 3 passages that a search found. You change it so that the whole document goes into every prompt. What is sure to change?", "o": ["The model must be trained again on the whole document", "The embedding model must be a much bigger one", "Each prompt has more tokens, so answers are slower", "The model now refuses when it has no answer to give"], "a": 2, "why": "A longer prompt has more tokens and takes longer to read. In one run, the whole document gave no better score than the 3 found passages, and each answer took about three times as long.", "at": "rag_llm.html#step6"},
  {"id": "rag_llm_09", "unit": "rag_llm", "d": 0.5, "type": "mcq", "q": "The search of a RAG system finds the right passage for almost every question. But the model often gives a wrong answer from that passage. What is the best thing to try?", "o": ["A vector database to hold the passages", "A second search that looks for words", "More found passages in every prompt", "A bigger language model as the reader"], "a": 3, "why": "When the passage is found and the answer is still wrong, the mistake is the reader's, so a better search cannot help. In one test, a reader 4.6 times the size read every found passage right.", "at": "rag_llm.html#step7"},
  {"id": "rag_llm_10", "unit": "rag_llm", "d": -0.3, "type": "mcq", "q": "A RAG program reads a text file of facts and embeds it each time it starts. One fact changes: a price goes up. What must you do?", "o": ["Write new examples and train the model again", "Download the embedding model one more time", "Edit the text file; the next answer uses it", "Delete the virtual environment and reinstall"], "a": 2, "why": "With RAG the fact is in a text file, not in the model. You edit the line and save, and the next question gets the new price with no training.", "at": "rag_llm.html#step8"},
  {"id": "keyestudio_smart_home_01", "unit": "keyestudio_smart_home", "d": -0.8, "type": "mcq", "q": "Two Python tools are used with a MicroPython board such as an ESP32: `esptool` and `mpremote`. What is `mpremote` for?", "o": ["Writing the MicroPython firmware into the board's memory", "Joining the board to your Wi-Fi and printing its address", "Reading the sensors and giving them back as a dictionary", "Sending your files to the board and showing its output"], "a": 3, "why": "`esptool` puts MicroPython on the board. `mpremote` sends your files to the board and shows what the board prints.", "at": "keyestudio_smart_home.html#step1"},
  {"id": "keyestudio_smart_home_02", "unit": "keyestudio_smart_home", "d": -0.5, "type": "mcq", "q": "In this MicroPython code a light is joined to pin 12 of the board. What does `led.value(1)` do?", "code": "from machine import Pin\n\nled = Pin(12, Pin.OUT)\nled.value(1)", "o": ["Switches the power on, so the light shines", "Reads the pin and gives your code back 0 or 1", "Makes the program wait for one second", "Gives your code pin 1 as an output"], "a": 0, "why": "`Pin.OUT` makes the pin an output. `value(1)` switches its power on and the light shines. `value(0)` switches it off.", "at": "keyestudio_smart_home.html#step2"},
  {"id": "keyestudio_smart_home_03", "unit": "keyestudio_smart_home", "d": -0.3, "type": "fill", "q": "In MicroPython, a button is set up with `Pin(16, Pin.IN, Pin.PULL_UP)`. Which number does `value()` read while the button is pressed? Type the number.", "answers": ["0", "zero"], "why": "`Pin.PULL_UP` holds the pin at 1 while the button is at rest. So the button reads 1 when nobody touches it and 0 while it is pressed.", "at": "keyestudio_smart_home.html#step3"},
  {"id": "keyestudio_smart_home_04", "unit": "keyestudio_smart_home", "d": 0.1, "type": "mcq", "q": "A servo is a motor that turns to an angle and stays there. In MicroPython, how does your code tell a servo which angle to turn to?", "o": ["By the number of pulses in each second", "By the length of each pulse it sends", "By sending 1 for open and 0 for closed", "By giving power to one of its two wires"], "a": 1, "why": "A servo gets 50 pulses a second from a `PWM` pin, and the length of each pulse, set with `duty(...)`, tells it the angle.", "at": "keyestudio_smart_home.html#step4"},
  {"id": "keyestudio_smart_home_05", "unit": "keyestudio_smart_home", "d": 0.3, "type": "mcq", "q": "This MicroPython code is part of a small web server on a board. It sends back `status` and then `reply` as JSON. A request for the path `/garden` arrives. What does the server send back?", "code": "def do(path):\n    if path == \"/read\":\n        return read_house()\n    if path == \"/light/on\":\n        led.value(1)\n    elif path == \"/light/off\":\n        led.value(0)\n    else:\n        return None\n    return {\"done\": path}\n\nreply = do(path)\nstatus = \"200 OK\"\nif reply is None:\n    status = \"404 Not Found\"\n    reply = {\"error\": \"the house does not know \" + path}", "o": ["Status 200 and the sensor readings", "Nothing; it closes the connection", "Status 404 and a JSON error message", "Status 200 and `{\"done\": \"/garden\"}`"], "a": 2, "why": "`do` returns `None` for a path it does not know, so the status becomes `404 Not Found` and the reply is an error in JSON. 404 means that the thing asked for is not there.", "at": "keyestudio_smart_home.html#step6"},
  {"id": "keyestudio_smart_home_06", "unit": "keyestudio_smart_home", "d": 0.8, "type": "mcq", "q": "A MicroPython web server on a board runs `led.value(1)` for the path `/light/on`, and this code for `/read`. A program asks for `/light/on`, then for `/read`. The second reply shows `'light': 1`. Why?", "code": "led = Pin(12, Pin.OUT)\nmotion = Pin(14, Pin.IN)\n\ndef read_house():\n    return {\n        \"motion\": motion.value(),\n        \"light\": led.value(),\n    }", "o": ["The first request switched it on, and it stayed on", "The light sensor measured a bright room at that moment", "A pin reads 1 at rest when it is set with `Pin.PULL_UP`", "The `/read` path switches the light on before it reads"], "a": 0, "why": "`led.value()` with nothing between the brackets reads the pin. The first request set it to 1, and it stays 1 until another request sets it to 0.", "at": "keyestudio_smart_home.html#step7"},
  {"id": "keyestudio_smart_home_07", "unit": "keyestudio_smart_home", "d": 0.4, "type": "mcq", "q": "A MicroPython board runs a web server. It reads its sensors and switches a fan when a request asks. This program runs on your computer and sends the requests. You want the fan to start at 35 degrees, not 28. What do you change?", "code": "HOT = 28    # degrees Celsius\n\nreadings = ask(\"/read\")\nif readings[\"temperature\"] >= HOT:\n    ask(\"/fan/on\")\nelse:\n    ask(\"/fan/off\")", "o": ["The server file, then send it to the board again", "The Wi-Fi settings that the board joins with", "The `main.py` file that runs when the board starts", "Only `HOT` in this program, on your own computer"], "a": 3, "why": "The rule is in the program on your computer, not on the board. You change that program, and the board keeps running the same server file.", "at": "keyestudio_smart_home.html#step8"},
  {"id": "thai_speech_to_text_01", "unit": "thai_speech_to_text", "d": -0.7, "type": "mcq", "q": "A Python script uses the `faster-whisper` library to turn a recording into text. Where is the recording turned into text?", "o": ["On a free website in your browser", "On OpenAI's servers, after an upload", "On a phone app linked to the PC", "On your own computer, with no upload"], "a": 3, "why": "`faster-whisper` runs the Whisper model on your own computer. Nothing is uploaded. The first run downloads the model, once.", "at": "thai_speech_to_text.html"},
  {"id": "thai_speech_to_text_02", "unit": "thai_speech_to_text", "d": -0.3, "type": "mcq", "q": "This line uses a `faster-whisper` model. What does `language=\"th\"` do?", "code": "lines, info = model.transcribe(\"recording.wav\", language=\"th\", beam_size=5)", "o": ["It tells the model not to guess the language", "It translates the Thai speech into English text", "It downloads a smaller Thai-only model", "It adds Thai timestamps to every line"], "a": 0, "why": "Without `language`, Whisper guesses the language from the audio. `language=\"th\"` tells it the speech is Thai, so it does not guess.", "at": "thai_speech_to_text.html"},
  {"id": "thai_speech_to_text_03", "unit": "thai_speech_to_text", "d": 0.1, "type": "mcq", "q": "In `faster-whisper`, what does `beam_size=5` in `model.transcribe(...)` make the model do?", "o": ["Cut the recording into five equal parts first", "Weigh five possible sentences, keep the best", "Run the recording five times and average the text", "Print five lines of text for each second"], "a": 1, "why": "With `beam_size=5` the model weighs five possible sentences as it goes and keeps the best, not just the first.", "at": "thai_speech_to_text.html"},
  {"id": "thai_speech_to_text_04", "unit": "thai_speech_to_text", "d": 0.4, "type": "mcq", "q": "Whisper comes in sizes. How does the `large-v3` model compare with the `tiny` model on Thai speech?", "o": ["Smaller download and more characters wrong", "Same download and the same error rate", "Bigger download and far fewer characters wrong", "Bigger download and a lot more characters wrong"], "a": 2, "why": "`tiny` is the smallest download and gets the most characters wrong. `large-v3` is the biggest download and gets the fewest wrong.", "at": "thai_speech_to_text.html"},
  {"id": "thai_speech_to_text_05", "unit": "thai_speech_to_text", "d": 0.6, "type": "mcq", "q": "A checker reads a speech-to-text transcript. It puts a flag on each line you should listen to again. One line gets no flag. What does that tell you?", "o": ["It does not prove the line is right", "It proves the line is correct Thai", "It shows the line has no speech in it", "It shows the model skipped that line"], "a": 0, "why": "A checker like this only says where to listen again. A line with no flag can still be wrong.", "at": "thai_speech_to_text.html#res"},
  {"id": "python_to_exe_01", "unit": "python_to_exe", "d": -0.8, "type": "mcq", "q": "What does PyInstaller do with your script?", "o": ["It rewrites the script in a faster language than Python.", "It copies the script and Python itself into an app.", "It installs Python on the other person's computer for you.", "It signs the script so that Windows always trusts the app."], "a": 1, "why": "PyInstaller works out everything the script needs and copies all of it, Python itself included, into one folder or one file.", "at": "python_to_exe.html"},
  {"id": "python_to_exe_02", "unit": "python_to_exe", "d": -0.5, "type": "mcq", "q": "You build `hello.py` with PyInstaller and no extra options. In `dist` you get a folder `hello` with `hello.exe` inside. Why must you give someone the whole folder and not only the `.exe`?", "o": ["Windows blocks every `.exe` that arrives without a folder.", "The folder holds the `hello.spec` recipe that the app reads.", "The `.exe` is only a shortcut to the script `hello.py`.", "The `.exe` cannot run without the `_internal` folder."], "a": 3, "why": "The `_internal` folder beside the `.exe` holds everything the program needs, so the two always travel together.", "at": "python_to_exe.html#step3"},
  {"id": "python_to_exe_03", "unit": "python_to_exe", "d": -0.4, "type": "mcq", "q": "PyInstaller built your app as a folder. Why do you send it to someone as a zip file?", "o": ["A zip is signed, so no warning box appears.", "A zip makes the app start faster on a Mac.", "A folder cannot be attached to a message.", "A zip lets the app run on Windows and on a Mac."], "a": 2, "why": "A folder cannot be attached to a message, and a zip keeps all its files in one piece.", "at": "python_to_exe.html#step11"},
  {"id": "python_to_exe_04", "unit": "python_to_exe", "d": 0.0, "type": "mcq", "q": "A Python script prints one line with `print`. PyInstaller packs it with `--windowed`. What happens to that line when the app runs?", "o": ["It is shown in a box when the app closes.", "It is saved in a file inside the `dist` folder.", "It goes nowhere, because there is no console.", "It appears in the title bar of the window."], "a": 2, "why": "A windowed app has no console to show printed text, so a program must put its messages in its own window.", "at": "python_to_exe.html#step5"},
  {"id": "python_to_exe_05", "unit": "python_to_exe", "d": 0.2, "type": "mcq", "q": "What does an app built with `--onefile` do each time it starts?", "o": ["It unpacks itself into a temporary folder.", "It downloads Python from the internet.", "It builds the `_internal` folder beside it.", "It asks PyInstaller to build it again."], "a": 0, "why": "A one-file app first unpacks itself into a temporary folder, runs from there, and deletes the folder when you close it. That is why it starts more slowly.", "at": "python_to_exe.html#step5"},
  {"id": "python_to_exe_06", "unit": "python_to_exe", "d": 0.1, "type": "mcq", "q": "On Windows, why can it be safer to type `python -m PyInstaller` than the short command `pyinstaller`?", "o": ["The short command builds an app without Python inside it.", "The short command is a new `.exe` Windows can refuse.", "The short command only works after you sign your script.", "The short command cannot read the options `--onefile`."], "a": 1, "why": "The short command is a small `.exe` that pip creates, and Windows can refuse to run a brand-new `.exe`. `python -m PyInstaller` starts nothing but Python.", "at": "python_to_exe.html#step2"},
  {"id": "python_to_exe_07", "unit": "python_to_exe", "d": 0.3, "type": "mcq", "q": "On Windows 11, Smart App Control blocks an app that you built a moment ago with PyInstaller. What is a safe way forward?", "o": ["Click the \"Run anyway\" button in the box.", "Switch Smart App Control off, then start it.", "Install Python again and start the script.", "Build again, or wait and try the app later."], "a": 3, "why": "A new build is a new file and gets a fresh answer, and a blocked app may be allowed later. The box has no \"Run anyway\" button, and you do not need to switch the protection off.", "at": "python_to_exe.html#step3"},
  {"id": "python_to_exe_08", "unit": "python_to_exe", "d": 0.6, "type": "mcq", "q": "A script has an `import` of a big AI library inside one function, and that line runs only sometimes. You build the script with PyInstaller from a virtual environment that has the library installed. What happens?", "o": ["The build stops, because the import is not at the top.", "The library is left out until the line really runs.", "The library is packed in, and the app gets much bigger.", "The app downloads the library the first time it starts."], "a": 2, "why": "PyInstaller packs every import that its Python can satisfy, whether or not the line ever runs. In one run, the same script came out at 25 MB from a plain environment and 563 MB from one that had the library.", "at": "python_to_exe.html#step8"},
  {"id": "python_to_exe_09", "unit": "python_to_exe", "d": 0.7, "type": "mcq", "q": "The `llama_cpp` library loads `.dll` files by name, and no import line mentions them. Which PyInstaller option tells it to take those files?", "o": ["`--hidden-import llama_cpp`", "`--collect-binaries llama_cpp`", "`--onefile`", "`--windowed`"], "a": 1, "why": "PyInstaller finds libraries by reading import lines. `--collect-binaries llama_cpp` tells it to take every binary file that belongs to the library.", "at": "python_to_exe.html#step9"},
  {"id": "python_to_exe_10", "unit": "python_to_exe", "d": 0.8, "type": "mcq", "q": "A windowed app built with PyInstaller has an error in a second thread. What does the user see?", "o": ["Nothing: the window stays open and looks fine.", "A box titled \"Unhandled exception in script\".", "A black console window with the error in it.", "The box from Smart App Control, with one button."], "a": 0, "why": "An error in the main part shows a box, but an error in a second thread shows nothing. So code in a second thread should catch its errors and show them in the app's own window.", "at": "python_to_exe.html#step7"},
  {"id": "fine_tune_llm_01", "unit": "fine_tune_llm", "d": -0.8, "type": "mcq", "q": "What is fine-tuning of a language model?", "o": ["More training of a trained model, on new examples", "Writing a longer prompt with a few solved examples in it", "Letting a model look up facts in documents as it answers", "Building a new model from the start with your own text"], "a": 0, "why": "Fine-tuning is training a model that has already been trained, on new examples, to teach it one job. The model keeps what it already knows about language.", "at": "fine_tune_llm.html"},
  {"id": "fine_tune_llm_02", "unit": "fine_tune_llm", "d": -0.5, "type": "mcq", "q": "Fine-tuning a language model in Python often uses three libraries: `torch`, `transformers` and `peft`. What does `peft` add?", "o": ["The arithmetic that changes the numbers", "LoRA, which trains only a few numbers", "Turning text into tokens and back", "The download of the model's own file"], "a": 1, "why": "`torch` does the arithmetic, `transformers` downloads and runs the model, and `peft` adds LoRA. Its name is short for parameter-efficient fine-tuning.", "at": "fine_tune_llm.html#step1"},
  {"id": "fine_tune_llm_03", "unit": "fine_tune_llm", "d": -0.3, "type": "mcq", "q": "Before you fine-tune a language model for a job, it is wise to try a long, careful prompt with a few solved examples in it. Why?", "o": ["The model cannot be trained until it has seen a prompt", "The prompt is saved and used as the first training example", "It is much cheaper, and if it works you are finished", "A long prompt makes the training run faster afterwards"], "a": 2, "why": "Fine-tuning costs time and care. A careful instruction with solved examples is often enough, and then there is nothing to train.", "at": "fine_tune_llm.html#step3"},
  {"id": "fine_tune_llm_04", "unit": "fine_tune_llm", "d": 0.0, "type": "mcq", "q": "In each training example for a chat model, the answer ends with the model's end-of-turn token, such as `<|im_end|>`. Why is it there?", "o": ["It teaches the model to stop writing", "It marks where the user's message ends", "It tells PyTorch not to grade that token", "It makes the answer valid JSON for a program"], "a": 0, "why": "The token means the turn is finished. A model that never practised writing it would never learn to stop.", "at": "fine_tune_llm.html#step5"},
  {"id": "fine_tune_llm_05", "unit": "fine_tune_llm", "d": 0.2, "type": "mcq", "q": "This PyTorch code is from a training loop for a language model. What does the value `-100` in `graded_tokens` do?", "code": "example_tokens = torch.tensor([question_tokens + answer_tokens])\ngraded_tokens = torch.tensor(\n    [[-100] * len(question_tokens) + answer_tokens])\nloss = model(input_ids=example_tokens, labels=graded_tokens).loss", "o": ["Marks the answer's tokens so they are graded twice", "Marks the question's tokens so they are skipped", "Sets how far one training round may move each number", "Tells the model to stop writing at that position"], "a": 1, "why": "PyTorch's loss skips a position marked -100, so the model is graded on the answer only.", "at": "fine_tune_llm.html#step6"},
  {"id": "fine_tune_llm_06", "unit": "fine_tune_llm", "d": -0.4, "type": "fill", "q": "One kind of fine-tuning freezes all the numbers of the model and trains only a small set of new numbers beside them, called an adapter. What is the four-letter name of this kind of fine-tuning? Type it.", "answers": ["lora", "low-rank adaptation", "low rank adaptation"], "why": "LoRA is short for low-rank adaptation. It leaves the model as it is and trains a small add-on. Full fine-tuning lets training change every number.", "at": "fine_tune_llm.html#step6"},
  {"id": "fine_tune_llm_07", "unit": "fine_tune_llm", "d": 0.1, "type": "mcq", "q": "You fine-tune a language model with LoRA, using `peft`, and save the result with `model.save_pretrained(\"tuned_lora\")`. What does the folder `tuned_lora` hold?", "o": ["A complete model, twice the size of the download", "The training examples and the files for testing", "Only the add-ons; it needs the original model", "The original model with every number changed"], "a": 2, "why": "The folder does not contain a model. It contains only the add-ons and is useless without the original model beside it. That is why it is so small.", "at": "fine_tune_llm.html#step8"},
  {"id": "fine_tune_llm_08", "unit": "fine_tune_llm", "d": 0.7, "type": "mcq", "q": "A fine-tuned model is nearly perfect on test messages made by the same program as its training examples. It misses many messages that a person wrote, with words and spellings the examples never used. What would fix these misses?", "o": ["Training for many more rounds", "Full fine-tuning, not LoRA", "A smaller learning rate", "Better training examples"], "a": 3, "why": "A model becomes as good as its examples and no better. Misses on words the examples never showed are fixed by better examples, not by longer training or by changing every number.", "at": "fine_tune_llm.html#step10"},
  {"id": "fine_tune_llm_09", "unit": "fine_tune_llm", "d": 0.8, "type": "mcq", "q": "A model is fine-tuned on 400 examples. Each one is a customer's food order, with the order written as JSON. Then somebody asks it `What time do you close today?`, and it answers with a food order as JSON. Why?", "o": ["The add-ons were merged into the model", "Every training example was an order", "The reply was cut off at 50 tokens", "Training wiped out what it knew before"], "a": 1, "why": "The model learned that the reply is always an order. No example showed it a message that is not one. To handle such messages, some training examples must be such messages.", "at": "fine_tune_llm.html#step11"},
  {"id": "fine_tune_llm_10", "unit": "fine_tune_llm", "d": 0.4, "type": "mcq", "q": "A shop's prices and opening hours change often, and a language model must answer questions about them. What is the best thing to do with facts like these?", "o": ["Fine-tune the model again each week", "Put them in the LoRA add-on file", "Add them to the test file by hand", "Keep them in documents to look up"], "a": 3, "why": "A model that learned facts in training goes on repeating the old ones. Keep facts in documents and let the model look them up (retrieval, or RAG).", "at": "fine_tune_llm.html#when"},
  {"id": "m1_01", "unit": "python_to_mac_app", "d": -0.6, "type": "mcq", "q": "Why is a `.py` file not a program by itself?", "o": ["It is too small: macOS will only start a file that is bigger than one megabyte in size", "It needs a computer with Python, in a suitable version, and every library it imports", "macOS cannot read text files, so a script has to be turned into a picture first", "It has no icon, and the Finder refuses to open any file that has no icon of its own"], "a": 1, "why": "Most people you would give it to have none of that. PyInstaller copies all of it, Python included, into one thing you can hand over.", "at": "python_to_mac_app.html#first"},
  {"id": "m1_02", "unit": "python_to_mac_app", "d": 0.0, "type": "mcq", "q": "On a Mac, what do you get from `venv/bin/python -m PyInstaller hello.py`, with no options?", "o": ["A Mac app, `dist/hello.app`, with an icon, that opens with a double-click", "One single file, `dist/hello`, that unpacks itself each time it starts", "A folder, `dist/hello`, with the program and its supporting files", "A disk image, `hello.dmg`, with the app and a link to Applications"], "a": 2, "why": "This is PyInstaller's default, `--onedir`. The other person would have to know which file in the folder to start.", "at": "python_to_mac_app.html#step2"},
  {"id": "m1_03", "unit": "python_to_mac_app", "d": 0.1, "type": "fill", "q": "Which option of PyInstaller makes a Mac app, `dist/hello.app`, that opens with a double-click? Type it with its two dashes.", "answers": ["--windowed", "windowed", "-w", "--noconsole"], "why": "With `--windowed` the Finder shows the app as one thing with an icon, and it opens with no Terminal.", "at": "python_to_mac_app.html#step3"},
  {"id": "m1_04", "unit": "python_to_mac_app", "d": 0.5, "type": "mcq", "q": "A script prints one line with `print(...)` and opens a tkinter window. PyInstaller builds it into a Mac app with `--windowed`, and the app is opened with a double-click. Where does the printed line go?", "o": ["Into the window, in a line under the last button, where the user can read it", "Into a file named after the app, which PyInstaller puts beside the app", "Into the Terminal, which macOS opens behind the window for every app", "Nowhere. A program built this way has to put its messages in its own window"], "a": 3, "why": "The app opens with no Terminal, so there is no place for `print` to write.", "at": "python_to_mac_app.html#step3"},
  {"id": "m1_05", "unit": "python_to_mac_app", "d": 0.4, "type": "mcq", "q": "What does a program built with PyInstaller's `--onefile` do each time it starts?", "o": ["It unpacks itself into a temporary folder, runs from there, and removes the folder at the end", "It downloads the right version of Python from the internet, and keeps it for the next start", "It builds itself again from the script, which is why the script must stay beside the file", "It asks the user for a folder, installs itself there, and then starts from that folder"], "a": 0, "why": "While such a program is open, a folder whose name begins with `_MEI` is in the temporary folder. It is gone after the program stops.", "at": "python_to_mac_app.html#step3"},
  {"id": "m1_06", "unit": "python_to_mac_app", "d": 0.3, "type": "mcq", "q": "PyInstaller builds the same small script two ways. The `--onefile` build is 9.7 MB, and the default folder build is 26.4 MB. Why is the one file smaller?", "o": ["It leaves Python out, and uses the Python that the Mac already has", "The file is compressed, and it pays for that at each start", "It leaves tkinter out, because one file cannot open a window", "It holds only the starter, and fetches the rest when it runs"], "a": 1, "why": "Smaller to hand over, and slower to start, because it unpacks first.", "at": "python_to_mac_app.html#step3"},
  {"id": "m1_07", "unit": "python_to_mac_app", "d": 0.6, "type": "mcq", "q": "You want a Mac app that opens with a double-click. Which PyInstaller options fit best?", "o": ["`--onefile` and `--windowed` together", "`--onefile` without `--windowed`", "`--windowed` without `--onefile`", "No options at all, the default build"], "a": 2, "why": "PyInstaller's documentation says that the two together are possible on a Mac but not recommended, because the app then unpacks itself on every run.", "at": "python_to_mac_app.html#step3"},
  {"id": "m2_01", "unit": "python_to_mac_app", "d": 0.0, "type": "mcq", "q": "Does PyInstaller turn your Python into another language?", "o": ["Yes, into C, which is then built into one small program for the Mac's kind of chip", "Yes, into Swift, the language that Mac apps are written in, and then into an app", "Yes, into machine code for the Apple chip, so that no copy of Python has to go into the app", "No. It makes a small starter program and puts a copy of Python and your code beside it"], "a": 3, "why": "Its own log says so, one stage at a time.", "at": "python_to_mac_app.html#how"},
  {"id": "m2_02", "unit": "python_to_mac_app", "d": 0.3, "type": "mcq", "q": "A library is installed in `venv`, but your script never imports it. Does it go into the app?", "o": ["No. PyInstaller packs what your code imports, not everything that is installed", "Yes. Everything in `venv` is copied into the app, whether it is used or not", "Only on a Mac with an Intel chip, where PyInstaller cannot read the imports", "Only with `--onefile`, because one file has to hold the whole of `venv`"], "a": 0, "why": "PyInstaller reads your script and follows its imports.", "at": "python_to_mac_app.html#step4"},
  {"id": "m2_03", "unit": "python_to_mac_app", "d": 0.6, "type": "mcq", "q": "A library loads something by a name that is worked out while the program runs. PyInstaller cannot read that import in the code. Which option helps?", "o": ["`--windowed`", "`--hidden-import`", "`--onefile --windowed`", "`--exclude-module`"], "a": 1, "why": "PyInstaller can only follow imports it can read in the code. The rest needs your help.", "at": "python_to_mac_app.html#step4"},
  {"id": "m2_04", "unit": "python_to_mac_app", "d": 0.2, "type": "mcq", "q": "In PyInstaller's log, each line begins with a number, such as `3054 INFO:`. What is the number?", "o": ["The number of files that have been copied into the app so far", "The line of your script that PyInstaller is reading at that moment", "The time since it started, in thousandths of a second", "The size of the app so far, in thousands of bytes"], "a": 2, "why": "So `3054` is a little over three seconds into the build.", "at": "python_to_mac_app.html#step4"},
  {"id": "m2_05", "unit": "python_to_mac_app", "d": -0.2, "type": "mcq", "q": "What is a `.app` on a Mac?", "o": ["One compressed file, which macOS unpacks each time the app is opened", "A link to a Python file, which macOS runs with the Python it has", "A disk image, which the Finder opens as if it were a small disk", "A folder. Everything is in one folder inside it, named `Contents`"], "a": 3, "why": "Control-click the app and choose Show Package Contents to look inside.", "at": "python_to_mac_app.html#step5"},
  {"id": "m2_06", "unit": "python_to_mac_app", "d": 0.4, "type": "mcq", "q": "You double-click a Mac app that PyInstaller made from a Python script. What does macOS do first?", "o": ["It reads `Info.plist` and starts the program in the folder `MacOS`", "It runs the script with the Mac's own Python, found in `Frameworks`", "It unpacks the app into the temporary folder, and starts it there", "It builds the app again from the script, which takes a few seconds"], "a": 0, "why": "That starter loads the Python library from `Frameworks`, and Python then runs your script.", "at": "python_to_mac_app.html#step5"},
  {"id": "m2_07", "unit": "python_to_mac_app", "d": 0.5, "type": "mcq", "q": "PyInstaller packs a script, `hello.py`, into a Mac app. Is `hello.py` inside the app as a file you can open and read?", "o": ["Yes. It is in `Contents/Resources`, beside the icon of the app", "No. It is in there as compiled Python, in the package inside the starter", "Yes, but only in an app that was built with `--windowed`", "No. It is not in the app at all, and the app fetches it when it starts"], "a": 1, "why": "The app holds your code, but not as the text you wrote.", "at": "python_to_mac_app.html#step5"},
  {"id": "m3_01", "unit": "python_to_mac_app", "d": 0.3, "type": "mcq", "q": "`codesign` prints `Signature=adhoc` and `TeamIdentifier=not set`. What does that mean?", "o": ["Apple has checked the app and found it safe", "The app has no signature of any kind at all", "The app is signed by nobody in particular", "The app is signed by PyInstaller's makers"], "a": 2, "why": "An Apple chip will not run a program with no signature at all, so PyInstaller gives it this empty one.", "at": "python_to_mac_app.html#step6"},
  {"id": "m3_02", "unit": "python_to_mac_app", "d": 0.5, "type": "mcq", "q": "For a Mac app that you built yourself, `spctl --assess` answers `rejected`, and yet the app opens on the Mac that built it. Why?", "o": ["`spctl` is wrong about apps that you build, and Gatekeeper itself would have said yes", "Gatekeeper checks only apps from the App Store, and it lets every other app open freely", "The app was built in the Terminal, and Gatekeeper trusts everything the Terminal makes", "Gatekeeper is only asked about files with `com.apple.quarantine`, a label on downloads"], "a": 3, "why": "A file you built yourself has no such label: `xattr` prints nothing for it. A browser puts the label on what it downloads.", "at": "python_to_mac_app.html#step6"},
  {"id": "m3_03", "unit": "python_to_mac_app", "d": 0.6, "type": "mcq", "q": "You made a Mac app with PyInstaller. What is the way to an app that opens on somebody else's Mac with no extra step?", "o": ["Sign it with a certificate from Apple's paid developer programme, and have Apple check it", "Put it in a disk image, because macOS trusts an app that the user drags into Applications", "Build it with `--onefile`, because a single file is not an app and Gatekeeper lets it through", "Zip it with `ditto`, which keeps the signature that PyInstaller gave it, so macOS accepts it"], "a": 0, "why": "Apple calls the check notarization. PyInstaller's option for the signing is `--codesign-identity`.", "at": "python_to_mac_app.html#step6"},
  {"id": "m3_04", "unit": "python_to_mac_app", "d": 0.3, "type": "mcq", "q": "A Mac app is best zipped with macOS's own `ditto`, and not with an ordinary zip program. Why?", "o": ["`ditto` makes a smaller file than a zip does", "`ditto` keeps the links inside the app as links", "`ditto` signs the app, so that Gatekeeper lets it open", "An ordinary zip program cannot hold folders, only files"], "a": 1, "why": "A Mac app holds links: names that point at a file kept somewhere else in the app. An ordinary zip program can break them.", "at": "python_to_mac_app.html#step7"},
  {"id": "m3_05", "unit": "python_to_mac_app", "d": 0.2, "type": "mcq", "q": "A Mac app made with PyInstaller is released in files with names like `hello-1.0-macos-arm64.zip`. Why is `arm64` in the name?", "o": ["It is the version of Python that is inside the app, which the user must match", "It is the version of macOS that the app needs before it will open", "The app runs on the kind of chip that built it, so the name says which", "GitHub asks for it, and refuses a release file with no chip in its name"], "a": 2, "why": "`arm64` is a Mac with an Apple chip, and `x86_64` is a Mac with an Intel chip.", "at": "python_to_mac_app.html#step7"},
  {"id": "m3_06", "unit": "python_to_mac_app", "d": 0.5, "type": "mcq", "q": "A Mac app has only the ad-hoc signature that PyInstaller gives it. Does putting it in a disk image (`.dmg`) or an installer (`.pkg`) get it past Gatekeeper?", "o": ["Yes. Both are made by macOS's own programs, so it trusts them", "Only the installer, because it asks for an administrator's password", "Only the disk image, because the app inside it is not a download", "No. macOS rejects both for the same reason: no usable signature"], "a": 3, "why": "They are ways to hand the app over. They are not a way round Gatekeeper.", "at": "python_to_mac_app.html#step7"},
  {"id": "m3_07", "unit": "python_to_mac_app", "d": 0.0, "type": "mcq", "q": "A release on GitHub often has a file named `SHA256SUMS.txt` beside its downloads. What is it for?", "o": ["It holds a fingerprint of each file, so a download can be checked to have arrived whole", "It is the licence, which says what a person is allowed to do with the files they download", "It tells macOS who made the app, so that Gatekeeper lets it open without a question", "It lists the libraries inside the app, so that someone can build the same app again"], "a": 0, "why": "A fingerprint is 64 letters and digits that change if one byte of the file changes.", "at": "python_to_mac_app.html#step8"},
  {"id": "m3_08", "unit": "python_to_mac_app", "d": 0.4, "type": "fill", "q": "Which program, on every Mac, checks downloaded files against `SHA256SUMS.txt`? Type its name.", "answers": ["shasum", "shasum -a 256 -c sha256sums.txt", "shasum -a 256"], "why": "The full command is `shasum -a 256 -c SHA256SUMS.txt`. It prints `OK` for each file that arrived whole.", "at": "python_to_mac_app.html#step8"},
  {"id": "m4_01", "unit": "python_to_mac_app", "d": 0.0, "type": "mcq", "q": "A script with an Agno agent, which asks a model in Ollama, is packed into one file with PyInstaller. What is in the file, and what is not?", "o": ["The model is in it, and Agno is not", "Agno is in it, and the model is not", "Both Agno and the model are in it", "Only the script is in it, with no library"], "a": 1, "why": "PyInstaller packs the libraries the script imports. The model stays in Ollama, and the program asks it over port 11434 of the same computer.", "at": "python_to_mac_app.html#step9"},
  {"id": "m4_02", "unit": "python_to_mac_app", "d": 0.3, "type": "mcq", "q": "A Python script has no window: it prints its answer in the Terminal. Why is it built with PyInstaller's `--onefile`, and without `--windowed`?", "o": ["`--windowed` would leave out the libraries that the script imports", "`--windowed` makes a bigger file, and takes far longer to build", "With `--windowed`, what the program prints would go nowhere", "`--windowed` is for Windows, and does nothing at all on a Mac"], "a": 2, "why": "An app made with `--windowed` opens with no Terminal, so there is no place for `print` to write.", "at": "python_to_mac_app.html#step9"},
  {"id": "m4_03", "unit": "python_to_mac_app", "d": 0.4, "type": "mcq", "q": "A script imports `agno`, a library that does not come with Python. Before PyInstaller packs the script, `agno` is installed into the Python that runs PyInstaller. Why?", "o": ["PyInstaller is a part of Agno, and it comes in together with it", "It makes the packed file smaller than it would be without it", "It downloads the model that the agent is going to ask", "PyInstaller packs only libraries that the building Python has"], "a": 3, "why": "PyInstaller builds the app out of the Python that runs it. If that Python lacks a library, PyInstaller cannot find it to pack.", "at": "python_to_mac_app.html#step9"},
  {"id": "m4_04", "unit": "python_to_mac_app", "d": 0.5, "type": "mcq", "q": "A program packed with PyInstaller stops, and its last line is `Failed to execute script 'cashier' due to unhandled exception!` Where is that line from?", "o": ["From the starter that PyInstaller puts in front of your script", "From Python itself, which prints it after every traceback it shows", "From Gatekeeper, which stops a program with no signature from Apple", "From the Terminal, which prints it for a program that ends with code 1"], "a": 0, "why": "It is how a packed program reports any error the script did not catch. The real reason is in the lines above it.", "at": "python_to_mac_app.html#step9"},
  {"id": "m4_05", "unit": "python_to_mac_app", "d": 0.3, "type": "mcq", "q": "A program that asks a model in Ollama is packed with PyInstaller's `--onefile`. Its first question takes 76 seconds. The same question again takes 20, and so does the plain Python script. What made the first one slow?", "o": ["The one file was still unpacking itself the first time", "The first time, Ollama had to load the model into memory", "The second answer was saved from the first, so no model ran", "The Mac was asleep, and had to wake up for the first question"], "a": 1, "why": "Loading the model is paid one time. A one-file program unpacks at every start, so unpacking cannot explain a slow first question only.", "at": "python_to_mac_app.html#step9"},
  {"id": "m4_06", "unit": "python_to_mac_app", "d": 0.5, "type": "mcq", "q": "A script with an agent asks a model in Ollama. You pack the script with PyInstaller. What happens to the time that one answer takes?", "o": ["It falls a lot, because the packed file is compressed", "It rises a lot, because the model is unpacked at each start", "It hardly changes: most of the time is the model answering", "It falls by half, because PyInstaller turns Python into C"], "a": 2, "why": "The model runs in Ollama, outside the packed program. In one test the packed and the plain script both took about 20 seconds for the same question.", "at": "python_to_mac_app.html#step9"},
  {"id": "m4_07", "unit": "python_to_mac_app", "d": 0.2, "type": "mcq", "q": "You have no Mac, and you want to build a Mac app with PyInstaller. Which way works?", "o": ["Run PyInstaller on Windows with the option `--target-architecture arm64`", "Build a Windows `.exe`, and rename the file so that it ends in `.app`", "Build it on Linux, because macOS can run every Linux program as it is", "Run the build on a Mac from GitHub Actions, with `runs-on: macos-15`"], "a": 3, "why": "PyInstaller is not a cross-compiler: it builds for the kind of computer it runs on. GitHub Actions can run your commands on a Mac of GitHub's.", "at": "python_to_mac_app.html#nomac"},
  {"id": "m4_08", "unit": "python_to_mac_app", "d": 0.5, "type": "mcq", "q": "A GitHub Actions workflow builds a Mac app on one of GitHub's Macs (`runs-on: macos-15`). Under `on: push: paths:` it lists only the Python files, so a push that changes other files does not start it. What is a good reason for that?", "o": ["A minute on one of GitHub's Macs costs about ten times a minute on its smallest Linux machine", "GitHub's Macs can only read Python files, so any other changed file would stop the run with an error", "A workflow may run only one time a day on a Mac, so each of the runs has to be saved for the code", "Without `paths`, a workflow never starts at all, because GitHub does not know which files to watch"], "a": 0, "why": "One price list gave $0.062 a minute for the Mac and $0.006 for the Linux machine. `paths` keeps the runs to the changes that matter.", "at": "python_to_mac_app.html#nomac"},
  {"id": "space_invaders_01", "unit": "space_invaders", "d": -0.7, "type": "mcq", "q": "You installed the library with `pip3 install pygame-ce`. What do you type in your code to use it?", "o": ["`import pygame_ce`", "`import pygame-ce`", "`from ce import pygame`", "`import pygame`"], "a": 3, "why": "In your code you still type `import pygame`. Only the install name is different.", "at": "space_invaders.html#step0"},
  {"id": "space_invaders_02", "unit": "space_invaders", "d": -0.4, "type": "mcq", "q": "What does the line `if __name__ == \"__main__\":` before `main()` mean?", "o": ["Run `main()` only when I start this file myself.", "Run `main()` each time another file imports it.", "Run `main()` 60 times every second, forever.", "Run `main()` only if the file is named `main`."], "a": 0, "why": "It means: run `main()` only when I start this file myself. If another file imports this one, `main()` does not run.", "at": "space_invaders.html#step1"},
  {"id": "space_invaders_03", "unit": "space_invaders", "d": -0.3, "type": "mcq", "q": "A pygame game sets `FPS = 60` and makes `clock = pygame.time.Clock()`. The last line of the game loop is `clock.tick(FPS)`. What does that line do?", "o": ["It counts the score for each frame.", "It keeps the loop at 60 frames every second.", "It closes the window after 60 seconds.", "It makes the ship move 60 pixels at a time."], "a": 1, "why": "Without the clock, the loop runs as fast as the computer can go. The clock keeps it at 60 frames every second.", "at": "space_invaders.html#step3"},
  {"id": "space_invaders_04", "unit": "space_invaders", "d": 0.0, "type": "mcq", "q": "This pygame class moves a bullet. The player's bullet is made with speed `-10`. An enemy's bullet is made with speed `5`. Which way do they go on the screen?", "code": "class Bullet:\n    def __init__(self, x, y, speed, colour):\n        self.x = x\n        self.y = y\n        self.speed = speed\n        self.colour = colour\n\n    def update(self):\n        self.y += self.speed", "o": ["Both go up, but at different speeds.", "Both go down, but at different speeds.", "The player's goes up, the enemy's goes down.", "The player's goes down, the enemy's goes up."], "a": 2, "why": "In a pygame window, `y` is 0 at the top and grows down the screen. So a negative speed moves a bullet up and a positive speed moves it down.", "at": "space_invaders.html#step6"},
  {"id": "space_invaders_05", "unit": "space_invaders", "d": 0.2, "type": "mcq", "q": "In a Python game, one enemy is slow and tough. The next one is fast and weak. Both are made from one class, `Creep`. What makes them different?", "o": ["A new subclass, written for each kind of enemy.", "A copy of the `update` method, changed a little.", "A different game loop, one for each enemy.", "Different numbers, passed to `__init__`."], "a": 3, "why": "A class is written one time. Each object gets its own numbers in `__init__`: same class, different behaviour.", "at": "space_invaders.html#step8"},
  {"id": "space_invaders_06", "unit": "space_invaders", "d": 0.4, "type": "mcq", "q": "Look at this pygame function. The picture file is missing from the folder. What does the function do?", "code": "def load_image(name, size, colour):\n    path = IMAGE_DIR / name\n    if path.exists():\n        picture = pygame.image.load(str(path)).convert_alpha()\n        return pygame.transform.smoothscale(picture, size)\n    box = pygame.Surface(size, pygame.SRCALPHA)\n    box.fill(colour)\n    return box", "o": ["It returns a coloured box in its place.", "It stops the game with an error.", "It downloads the picture again.", "It returns `None` for that picture."], "a": 0, "why": "When `path.exists()` is false, the function skips the loading. It makes a `Surface`, fills it with the colour and returns that box, so the game still works.", "at": "space_invaders.html#step12"},
  {"id": "space_invaders_07", "unit": "space_invaders", "d": 0.5, "type": "mcq", "q": "This Python code loops over `list(my_bullets)`, not over `my_bullets`. Why?", "code": "for bullet in list(my_bullets):\n    for creep in list(creeps):\n        if bullet.rect().colliderect(creep.rect()):\n            my_bullets.remove(bullet)\n            creep.hp -= 1\n            if creep.hp <= 0:\n                creeps.remove(creep)\n            break", "o": ["A list copy is faster to walk through than the list.", "The loop removes bullets, so it walks a copy.", "`my_bullets` is not a list until you call `list`.", "The copy puts the bullets in order, oldest first."], "a": 1, "why": "`list(...)` makes a copy. You must not remove things from a list while you walk through it.", "at": "space_invaders.html#step10"},
  {"id": "chiang_mai_map_01", "unit": "chiang_mai_map", "d": -0.8, "type": "fill", "q": "This Python code makes a map you can drag and zoom, and saves it as an HTML file. Which library is missing at `____`? Type its name.", "code": "import ____\n\nthe_map = ____.Map(location=(18.7883, 98.9853), zoom_start=13, tiles=\"OpenStreetMap\")\nthe_map.save(\"map.html\")", "answers": ["folium"], "why": "`folium` makes a real map with `folium.Map(...)` and saves it as an HTML file. You open that file in your browser.", "at": "chiang_mai_map.html#step0"},
  {"id": "chiang_mai_map_02", "unit": "chiang_mai_map", "d": -0.5, "type": "mcq", "q": "In folium, in what order do you write the two numbers of a `location`?", "o": ["The smaller number first", "Longitude first, then latitude", "The bigger number first", "Latitude first, then longitude"], "a": 3, "why": "Always latitude first, longitude second. If you swap them, the map shows a spot in a very different part of the world.", "at": "chiang_mai_map.html#step2"},
  {"id": "chiang_mai_map_03", "unit": "chiang_mai_map", "d": -0.3, "type": "mcq", "q": "A place has a longitude of -105.07. What does the minus mean?", "o": ["It is west of the zero line", "It is south of the equator line", "It is far from the north pole", "It is below sea level"], "a": 0, "why": "For longitude, minus means west. Everywhere in the Americas has a minus longitude.", "at": "chiang_mai_map.html#step2"},
  {"id": "chiang_mai_map_04", "unit": "chiang_mai_map", "d": 0.0, "type": "mcq", "q": "On a folium `Marker`, what is the difference between `tooltip` and `popup`?", "o": ["`tooltip` is the colour; `popup` is the icon", "`tooltip` is in English; `popup` is in Thai", "`tooltip` shows on hover; `popup` shows on click", "`tooltip` shows on click; `popup` shows on hover"], "a": 2, "why": "On a folium `Marker`, `tooltip` shows when you hover and `popup` shows when you click.", "at": "chiang_mai_map.html#step4"},
  {"id": "chiang_mai_map_05", "unit": "chiang_mai_map", "d": 0.2, "type": "mcq", "q": "A folium map needs fourteen pins. Why is one class for a place, and a list of fourteen objects, better than fourteen `Marker` blocks written out?", "o": ["A class makes the map load faster", "Written out, you cannot sort or count them", "folium only accepts pins that come from a class", "A class gets the coordinates from OpenStreetMap"], "a": 1, "why": "Pins written out one by one are many lines of code, and the program cannot sort or count them. Objects in a list can be sorted, counted and added in one loop.", "at": "chiang_mai_map.html#step5"},
  {"id": "chiang_mai_map_06", "unit": "chiang_mai_map", "d": 0.6, "type": "mcq", "q": "The haversine formula says a temple on a mountain is 7.7 km from a city gate. Why is the drive much longer?", "o": ["The formula forgets that the earth is a ball", "The coordinates are close, not exact", "The formula counts in miles, not km", "It gives the straight line; the road bends"], "a": 3, "why": "The haversine formula gives the straight line, the way a bird flies. A road bends around the mountain, so the drive is longer.", "at": "chiang_mai_map.html#step8"},
  {"id": "chiang_mai_map_07", "unit": "chiang_mai_map", "d": 0.4, "type": "mcq", "q": "A folium `Circle` has `radius=5000`. What happens to it when you zoom the map out?", "o": ["It grows, so it always looks the same size", "It stays 5 km wide on the ground", "It disappears until you zoom in again", "It changes into a pin at its centre"], "a": 1, "why": "`radius=5000` means 5000 metres. The circle is a real distance on the ground and stays 5 km wide at any zoom.", "at": "chiang_mai_map.html#step9"},
  {"id": "grokking_algorithms_01", "unit": "grokking_algorithms", "d": -0.8, "type": "mcq", "q": "What does Big-O notation describe?", "o": ["How many seconds the code takes on your own laptop", "How the number of steps grows as the input grows", "How many lines of code the algorithm needs", "How much the input must be sorted before a run"], "a": 1, "why": "Big-O does not measure seconds. It measures how the number of steps grows when you give the algorithm more input.", "at": "grokking_algorithms.html#foundations"},
  {"id": "grokking_algorithms_02", "unit": "grokking_algorithms", "d": -0.5, "type": "mcq", "q": "A recursive function has no base case. What happens when you call it?", "o": ["It returns `1` and stops at once", "It runs one time and returns `None`", "Python turns it into a `for` loop for you", "It calls itself until Python crashes"], "a": 3, "why": "Without a base case the function never stops calling itself. Python ends with `RecursionError: maximum recursion depth exceeded`.", "at": "grokking_algorithms.html#foundations"},
  {"id": "grokking_algorithms_03", "unit": "grokking_algorithms", "d": -0.4, "type": "mcq", "q": "What must be true of a list before you use binary search on it?", "o": ["The list must be sorted", "The list must be short", "The list must hold only numbers", "The list must have no repeats"], "a": 0, "why": "Binary search throws half of the list away at each step. That only works when the list is sorted.", "at": "grokking_algorithms.html#searching"},
  {"id": "grokking_algorithms_04", "unit": "grokking_algorithms", "d": 0.0, "type": "fill", "q": "What does this Python code print? Type it.", "code": "def binary_search(arr, target):\n    lo, hi = 0, len(arr) - 1\n    while lo <= hi:\n        mid = (lo + hi) // 2\n        if arr[mid] == target:\n            return mid\n        if arr[mid] < target:\n            lo = mid + 1\n        else:\n            hi = mid - 1\n    return None\n\nprint(binary_search([1, 3, 5, 7, 9, 11, 13, 15], 10))", "answers": ["none"], "why": "10 is not in the list. The search runs out of places to look, the `while` loop ends, and the function returns `None`.", "at": "grokking_algorithms.html#searching"},
  {"id": "grokking_algorithms_05", "unit": "grokking_algorithms", "d": 0.5, "type": "mcq", "q": "A quicksort always uses the first item as the pivot. Which input gives it the slow O(n²) worst case?", "o": ["A list with only one item", "A list in random order", "A list that is already sorted", "A list with an even number of items"], "a": 2, "why": "On a sorted list the first item splits the rest into an empty pile and one big pile. So there are n levels of recursion, not log n.", "at": "grokking_algorithms.html#sorting"},
  {"id": "grokking_algorithms_06", "unit": "grokking_algorithms", "d": -0.1, "type": "mcq", "q": "In a hash table, what is a collision?", "o": ["One key is stored in two different slots", "Two keys hash to the same slot", "The table has no empty slots left", "A lookup asks for a missing key"], "a": 1, "why": "A collision is when two different keys hash to the same slot. One fix is separate chaining: each slot keeps a small list of its keys and values.", "at": "grokking_algorithms.html#data-structures"},
  {"id": "grokking_algorithms_07", "unit": "grokking_algorithms", "d": 0.3, "type": "mcq", "q": "This Python code is a breadth-first search. `queue` is a `deque` and `graph` maps each node to its neighbours. You change `queue.popleft()` to `queue.pop()`. What do you get?", "code": "def bfs(start, target):\n    queue = deque([start])\n    seen  = {start}\n    while queue:\n        node = queue.popleft()\n        if node == target:\n            return True\n        for neighbour in graph[node]:\n            if neighbour not in seen:\n                seen.add(neighbour)\n                queue.append(neighbour)\n    return False", "o": ["A faster breadth-first search", "Dijkstra's algorithm with weights", "An error, because the queue is empty", "Depth-first search instead of BFS"], "a": 3, "why": "`popleft()` takes from the front, so the `deque` works as a queue, and a queue gives BFS. `pop()` takes from the end, so it works as a stack, and a stack gives depth-first search.", "at": "grokking_algorithms.html#graphs"},
  {"id": "grokking_algorithms_08", "unit": "grokking_algorithms", "d": 0.5, "type": "mcq", "q": "A graph has some negative edge weights. What does Dijkstra's algorithm do on this graph?", "o": ["It silently gives wrong answers", "It stops with a clear error message", "It works, but it needs more memory", "It becomes the same as BFS"], "a": 0, "why": "Dijkstra is only correct with non-negative weights. With negative weights it gives wrong answers and no error. Use Bellman-Ford for such a graph.", "at": "grokking_algorithms.html#graphs"},
  {"id": "grokking_algorithms_09", "unit": "grokking_algorithms", "d": 0.1, "type": "mcq", "q": "Why is memoized Fibonacci much faster than plain recursive Fibonacci?", "o": ["It uses a loop and never calls itself", "It sorts the numbers before adding them", "It stores each answer and reuses it", "It guesses the answer and checks it once"], "a": 2, "why": "Plain recursion computes the same numbers again and again. Memoization remembers each answer the first time and looks it up after that.", "at": "grokking_algorithms.html#paradigms"},
  {"id": "grokking_algorithms_10", "unit": "grokking_algorithms", "d": 0.7, "type": "mcq", "q": "In k-NN, weight goes from 100 to 1000 and diameter from 0.05 to 0.10. You do not scale them. What happens?", "o": ["Both features count the same in the distance", "Weight dominates and diameter is almost ignored", "The code stops with a distance error", "Diameter dominates because its numbers are small"], "a": 1, "why": "Euclidean distance treats every feature the same, so the feature with big numbers dominates. Normalize every feature before you use k-NN.", "at": "grokking_algorithms.html#ml"},
  {"id": "make_a_flashcard_app_01", "unit": "make_a_flashcard_app", "d": -0.8, "type": "mcq", "q": "In a tkinter program, why must `root.mainloop()` be the last line?", "o": ["It deletes every widget made before it.", "It can only be called one time per file.", "Code after it waits until the window closes.", "It needs all of the fonts to be loaded before it."], "a": 2, "why": "`mainloop()` is tkinter's loop and it waits for the person. Nothing after it runs until the window closes.", "at": "make_a_flashcard_app.html#step1"},
  {"id": "make_a_flashcard_app_02", "unit": "make_a_flashcard_app", "d": -0.5, "type": "mcq", "q": "In tkinter, what is wrong with `command=flip()` on a `Button`? `flip` is a function of your own.", "o": ["`flip` runs at once and the button is dead.", "Python cannot find the function `flip`.", "The button calls `flip` twice on each click.", "The window closes when the button is clicked."], "a": 0, "why": "Brackets mean \"run this now\". Python runs `flip` once, immediately, and the button then does nothing. Write `command=flip`.", "at": "make_a_flashcard_app.html#step3"},
  {"id": "make_a_flashcard_app_03", "unit": "make_a_flashcard_app", "d": -0.1, "type": "mcq", "q": "You ask tkinter for a font that is not on the computer. What happens?", "o": ["The program stops with a font error.", "You quietly get a different font.", "The label shows empty boxes for text.", "tkinter downloads the font for you."], "a": 1, "why": "Asking for a font that is not there does not crash. You just quietly get a different one. `tkinter.font.families()` tells you which fonts the computer really has.", "at": "make_a_flashcard_app.html#step2"},
  {"id": "make_a_flashcard_app_04", "unit": "make_a_flashcard_app", "d": 0.3, "type": "mcq", "q": "What is the rule for `pack()` and `grid()` in a tkinter program?", "o": ["Use `pack()` for buttons and `grid()` for labels.", "Call `pack()` first, and then call `grid()`.", "Every widget needs both, one after the other.", "Do not mix the two in the same container."], "a": 3, "why": "Pick one and stay with it. Mixing `pack()` and `grid()` in the same container makes tkinter stop with an error.", "at": "make_a_flashcard_app.html#step5"},
  {"id": "make_a_flashcard_app_05", "unit": "make_a_flashcard_app", "d": 0.4, "type": "fill", "q": "Which argument of `json.dumps`, when it is set to `False`, makes Python write Thai so that a person can read it in the file? Type the argument's name.", "answers": ["ensure_ascii", "ensure_ascii=false", "ensure_ascii = false"], "why": "Without `ensure_ascii=False` the file still works, but nobody can read or fix the Thai by hand.", "at": "make_a_flashcard_app.html#step6"},
  {"id": "make_a_flashcard_app_06", "unit": "make_a_flashcard_app", "d": 0.7, "type": "mcq", "q": "In `self.words.sort(key=lambda w: -w[\"misses\"])`, what does the minus sign do?", "o": ["It removes the words you never missed.", "It puts the most missed words first.", "It takes one miss away from every word.", "It puts the easy words before the hard ones."], "a": 1, "why": "The minus sign sorts from big to small, so the words with the most misses are shown first.", "at": "make_a_flashcard_app.html#step7"},
  {"id": "make_a_homework_app_01", "unit": "make_a_homework_app", "d": -0.8, "type": "mcq", "q": "In Python, after `sqlite3.connect(\"homework.db\")` runs, a new file `homework.db` appears in the folder. What is that file?", "o": ["A copy of the Python program itself.", "A list of the commands you have typed.", "The whole database, in one file.", "A library that Python had to install."], "a": 2, "why": "SQLite is one file on your disk. That file is your whole database.", "at": "make_a_homework_app.html#step1"},
  {"id": "make_a_homework_app_02", "unit": "make_a_homework_app", "d": -0.5, "type": "mcq", "q": "What does `IF NOT EXISTS` do in `CREATE TABLE IF NOT EXISTS jobs`?", "o": ["It makes the table the first time only.", "It deletes the old table before each run.", "It stops rows with an empty student name.", "It saves the changes without `commit()`."], "a": 0, "why": "It makes the table the first time and does nothing every other time, so you can run the program twice safely.", "at": "make_a_homework_app.html#step2"},
  {"id": "make_a_homework_app_03", "unit": "make_a_homework_app", "d": -0.1, "type": "mcq", "q": "This Python `sqlite3` line puts `?` in the SQL and gives the values separately. Why not glue the values into the SQL text yourself?", "code": "conn.execute(\"INSERT INTO jobs (student, title) VALUES (?, ?)\", (name, title))", "o": ["It makes the database save rows faster.", "It lets one line insert many rows at once.", "It saves the row without a `commit()`.", "A name like O'Brien breaks glued text."], "a": 3, "why": "The `'` in O'Brien ends the text early when you glue the name in yourself. With `?` Python puts the values in safely, whatever the name is.", "at": "make_a_homework_app.html#step3"},
  {"id": "make_a_homework_app_04", "unit": "make_a_homework_app", "d": 0.2, "type": "fill", "q": "With Python's `sqlite3` you add rows, but after the next run they are gone. Which method of the connection did you forget to call? Type its name.", "answers": ["commit", "commit()", "conn.commit()", "conn.commit"], "why": "SQLite holds your changes to one side until you say \"yes, save it\". `conn.commit()` is you saying yes.", "at": "make_a_homework_app.html#step4"},
  {"id": "make_a_homework_app_05", "unit": "make_a_homework_app", "d": 0.5, "type": "mcq", "q": "After this Python `sqlite3` line, `result.rowcount` is 0. What does that tell you?", "code": "result = conn.execute(\"UPDATE jobs SET done = 1 WHERE id = ?\", (args.job_id,))", "o": ["The job was already marked as done.", "There is no job with that id.", "Every row in the table was changed.", "The change was not saved yet."], "a": 1, "why": "`rowcount` says how many rows changed. Zero means that no row has that id.", "at": "make_a_homework_app.html#step6"},
  {"id": "make_a_homework_app_06", "unit": "make_a_homework_app", "d": 0.7, "type": "mcq", "q": "A Python program opens its database with `sqlite3.connect(\"homework.db\")`. You run it from another folder and all your rows seem to be gone. Why, and what is the fix?", "o": ["`connect` made a new file where you stand; tie it to the program.", "The table was dropped by `IF NOT EXISTS`; create the table again.", "The rows were never committed; add `conn.commit()` after `SELECT`.", "The file is read-only from other folders; open it with mode `\"w\"`."], "a": 0, "why": "`sqlite3.connect(\"homework.db\")` makes the file where you are standing, not where the program is. `Path(__file__).with_name(\"homework.db\")` ties it to the program.", "at": "make_a_homework_app.html#step8"},
  {"id": "make_a_school_board_01", "unit": "make_a_school_board", "d": -0.7, "type": "mcq", "q": "In Flask, what does `@app.route(\"/\")` above a function mean?", "o": ["Save this function into the database", "Send this function to every phone", "Run this function for the front page", "Run this function when the server stops"], "a": 2, "why": "A route is an address joined to a function. When someone asks for the front page, Flask runs that function and the browser shows what it returns.", "at": "make_a_school_board.html#step1"},
  {"id": "make_a_school_board_02", "unit": "make_a_school_board", "d": -0.4, "type": "mcq", "q": "A terminal program and a Flask web app are two Python files. Both open a SQLite database by the plain file name `homework.db`. Why should the two files sit, and run, in the same folder?", "o": ["So both programs open the same database file", "So Flask can install itself into that folder", "So a phone can open the terminal program too", "So the terminal program can show HTML pages"], "a": 0, "why": "A plain file name is looked up in the folder where the program runs. In one folder, both programs open the same `homework.db`, so the data lives in one place.", "at": "make_a_school_board.html#step0"},
  {"id": "make_a_school_board_03", "unit": "make_a_school_board", "d": 0.0, "type": "mcq", "q": "A person fills in a web form that adds a new row to a database. Which HTTP method should the browser use, and why?", "o": ["GET, because it shows something", "POST, because it changes something", "GET, because it reads the form", "POST, because it shows a page"], "a": 1, "why": "GET means \"show me something\". POST means \"here is some data, change something\". Adding a row is a change, so it uses POST.", "at": "make_a_school_board.html#step3"},
  {"id": "make_a_school_board_04", "unit": "make_a_school_board", "d": 0.4, "type": "mcq", "q": "In a Flask app, you send a form with POST and then press refresh in the browser. The same row is added again. What is the cure?", "o": ["Delete the second row from the database", "Change the form from POST to GET", "Turn off the refresh button in the HTML", "Redirect to a fresh GET after the POST"], "a": 3, "why": "Refresh repeats the last thing the browser did, which was a POST. After `return redirect(url_for(...))` the last thing is a GET, so refresh only reloads a page.", "at": "make_a_school_board.html#step4"},
  {"id": "make_a_school_board_05", "unit": "make_a_school_board", "d": 0.3, "type": "mcq", "q": "In Flask, which value of `host` in `app.run` lets anyone on the same wifi open the app?", "o": ["`127.0.0.1`", "`localhost`", "`0.0.0.0`", "`255.255.255.0`"], "a": 2, "why": "`localhost` means this machine and nobody else. `host=\"0.0.0.0\"` means listen to the whole network, so any device on the same wifi can reach the app.", "at": "make_a_school_board.html#step6"},
  {"id": "make_a_school_board_06", "unit": "make_a_school_board", "d": 0.6, "type": "mcq", "q": "A small Flask app runs with `app.run(host=\"0.0.0.0\")` on a shared wifi. It has no login, and its Jinja template shows what people type. What is the real risk?", "o": ["It loses all the data when the server stops", "It sends all the data out to the internet", "Anyone on the wifi can change the data", "A `<script>` typed into a form will run"], "a": 2, "why": "With no login, the app cannot tell who is who, so anyone on the wifi can type any name and change any row. That is not safe for marks or anything private. The template shows `<script>` as text, so it does not run.", "at": "make_a_school_board.html#step7"},
  {"id": "is_the_air_safe_01", "unit": "is_the_air_safe", "d": -0.8, "type": "mcq", "q": "What does the PM2.5 number count?", "o": ["The hours of smoke in one day", "The number of fires near the city", "The heat of the air above the city", "The tiny bits of dirt in the air"], "a": 3, "why": "PM2.5 counts the tiny bits of dirt in the air that are small enough to go deep into your lungs.", "at": "is_the_air_safe.html"},
  {"id": "is_the_air_safe_02", "unit": "is_the_air_safe", "d": -0.5, "type": "mcq", "q": "A Python program runs `reply = requests.get(...)` and prints `reply.status_code`. You see `200`. What does that mean?", "o": ["The request worked and the website answered.", "The website has nothing at that address.", "The website took 200 seconds to answer.", "The website sent 200 readings of data."], "a": 0, "why": "A `status_code` of 200 means the request worked. 404 means \"no such thing\". Look at it before you trust the answer.", "at": "is_the_air_safe.html#step1"},
  {"id": "is_the_air_safe_03", "unit": "is_the_air_safe", "d": -0.1, "type": "fill", "q": "A Python program has a list of times and a list of numbers, side by side. Which Python function walks down the two lists together? Type its name.", "answers": ["zip", "zip()"], "why": "`zip` walks down two lists together: `for when, value in zip(times, values)` gives one pair each turn.", "at": "is_the_air_safe.html#step6"},
  {"id": "is_the_air_safe_04", "unit": "is_the_air_safe", "d": 0.1, "type": "mcq", "q": "A Python program asks a website for data with `requests.get(API, timeout=20)`. Why set `timeout`?", "o": ["So the data is never more than 20 minutes old.", "So a sleeping website cannot hang the program.", "So the program asks the website 20 times.", "So the reply holds only the next 20 hours."], "a": 1, "why": "Without a timeout, a sleeping website makes your program hang for ever, with no message.", "at": "is_the_air_safe.html#step7"},
  {"id": "is_the_air_safe_05", "unit": "is_the_air_safe", "d": 0.4, "type": "mcq", "q": "An air-quality API takes a latitude and a longitude. Two places a few kilometres apart give exactly the same numbers, but a city far away gives different ones. Why?", "o": ["The API gives the same data to every caller.", "The air is always the same inside one country.", "Both places sit in one grid square of the data.", "The API rounds each number to the nearest ten."], "a": 2, "why": "Such data comes from a grid of squares, for example about 11 km wide. Two places in one square get the same numbers. The data tells you about an area, not a street.", "at": "is_the_air_safe.html#step5"},
  {"id": "is_the_air_safe_06", "unit": "is_the_air_safe", "d": 0.5, "type": "mcq", "q": "Look at the code. What word does `band_for(40)` return?", "code": "BANDS = [\n    (12.0,  \"GOOD\",      \"Go outside. The air is clean.\"),\n    (35.4,  \"OK\",        \"Most people are fine. Play outside.\"),\n    (55.4,  \"NOT GOOD\",  \"If you cough or have asthma, stay in.\"),\n    (150.4, \"BAD\",       \"Everyone should stay inside. Close the windows.\"),\n    (250.4, \"VERY BAD\",  \"Do not go outside. Wear an N95 mask if you must.\"),\n    (99999, \"DANGEROUS\", \"Stay inside. Keep the windows shut. Use a filter.\"),\n]\n\ndef band_for(pm25):\n    for limit, word, advice in BANDS:\n        if pm25 <= limit:\n            return word, advice", "o": ["`GOOD`", "`OK`", "`BAD`", "`NOT GOOD`"], "a": 3, "why": "The function walks down the list until the number fits. 40 is more than 12.0 and 35.4, but it is not more than 55.4, so the row is `NOT GOOD`.", "at": "is_the_air_safe.html#step3"},
  {"id": "teach_the_computer_to_speak_01", "unit": "teach_the_computer_to_speak", "d": -0.7, "type": "mcq", "q": "Why do you need wifi to use `edge-tts`?", "o": ["The library needs a paid account", "The program must send a password", "The `.mp3` file is played online", "The voices are made on a server"], "a": 3, "why": "The voices are made on a server, so you need wifi. You never need a key, an account or a password.", "at": "teach_the_computer_to_speak.html#step0"},
  {"id": "teach_the_computer_to_speak_02", "unit": "teach_the_computer_to_speak", "d": -0.4, "type": "mcq", "q": "This `edge-tts` program speaks English. You change the text to Thai words. What else must change so that the Thai is spoken properly?", "code": "import asyncio\nimport edge_tts\n\nasync def speak(text, voice, out_path):\n    talker = edge_tts.Communicate(text, voice)\n    await talker.save(out_path)\n\nasyncio.run(speak(\"Good morning, teacher.\",\n                  \"en-GB-LibbyNeural\",\n                  \"hello.mp3\"))", "o": ["The library that is imported", "The voice name string", "The body of `speak`", "The call to `asyncio.run`"], "a": 1, "why": "Only the voice name changes, for example to `th-TH-PremwadeeNeural`. The words and the voice must match, and no new library or function is needed.", "at": "teach_the_computer_to_speak.html#step2"},
  {"id": "teach_the_computer_to_speak_03", "unit": "teach_the_computer_to_speak", "d": 0.1, "type": "mcq", "q": "A text-to-speech program uses this Python function to pick a voice. How does the function choose the language of a text?", "code": "def detect_language(text):\n    thai = sum(1 for c in text if \"\\u0e00\" <= c <= \"\\u0e7f\")\n    han  = sum(1 for c in text if \"\\u4e00\" <= c <= \"\\u9fff\")\n    if thai > han and thai > 0:\n        return \"th\"\n    if han > 0:\n        return \"zh\"\n    return \"en\"", "o": ["It counts the Thai and the Chinese characters", "It asks the server which language the text is in", "It looks only at the first letter of the text", "It reads the language from the name of the file"], "a": 0, "why": "Every character has a number behind it, and Thai and Chinese sit in different ranges. The function counts the characters in each range, and everything else is English.", "at": "teach_the_computer_to_speak.html#step4"},
  {"id": "teach_the_computer_to_speak_04", "unit": "teach_the_computer_to_speak", "d": 0.1, "type": "fill", "q": "Which option of `edge_tts.Communicate` slows the voice down for a beginner? Type its name.", "answers": ["rate", "rate=", "--rate"], "why": "`rate` is written as a percent with a plus or minus sign. `rate=\"-25%\"` is a good speed for a beginner.", "at": "teach_the_computer_to_speak.html#step5"},
  {"id": "teach_the_computer_to_speak_05", "unit": "teach_the_computer_to_speak", "d": 0.6, "type": "mcq", "q": "A Python program saves speech with `edge-tts` by `await talker.save(out_path)`. Then it checks that the size of the file is more than 0. Why?", "o": ["A big file cannot be played on a phone", "The server sends the size before the sound", "A file can exist and still be empty", "The file must be smaller than the last one"], "a": 2, "why": "A file can exist and still be empty. Your program will not notice an empty file unless it looks at the size.", "at": "teach_the_computer_to_speak.html#step6"},
  {"id": "teach_the_computer_to_speak_06", "unit": "teach_the_computer_to_speak", "d": 0.4, "type": "mcq", "q": "A program makes speech with `edge-tts`, a free online voice service. Sometimes a request fails with `NoAudioReceived`, and the same request works two seconds later. What should the program do?", "o": ["Stop at once and delete the empty file", "Try again, and wait longer each time", "Change to another voice and go on", "Ask the user to type the text again"], "a": 1, "why": "Free services are sometimes busy. The program should try again a few times, and wait a little longer each time.", "at": "teach_the_computer_to_speak.html#step6"},
  {"id": "robot_python_01", "unit": "robot_python", "d": -0.8, "type": "mcq", "q": "What is the job of the Python library `bleak`?", "o": ["It draws pictures on a small LED screen", "It installs Python on your computer", "It speaks Bluetooth for your program", "It plays sound through the speakers"], "a": 2, "why": "`bleak` is a Bluetooth library for Python. It finds a Bluetooth device, connects to it and sends it bytes.", "at": "robot_python.html"},
  {"id": "robot_python_02", "unit": "robot_python", "d": -0.6, "type": "fill", "q": "In Python, inside an `async` function, which keyword means \"wait here for this slow thing, then go on\"? Type it.", "code": "async def main():\n    devices = ____ BleakScanner.discover(timeout=10)", "answers": ["await"], "why": "`async` marks a function that can wait. `await` means \"wait here, then go on\". Bluetooth is slow, so the program must wait for it.", "at": "robot_python.html"},
  {"id": "robot_python_03", "unit": "robot_python", "d": -0.3, "type": "mcq", "q": "A Python program sends `b\"G#\"` to a Bluetooth device. What does the `b` in front of the text mean?", "o": ["It is bytes, not letters", "It makes the text bold", "It sends the text twice", "It reads the text backwards"], "a": 0, "why": "The letter `b` makes a bytes value, not a string of letters. Radios send bytes, so a Bluetooth write needs bytes.", "at": "robot_python.html"},
  {"id": "robot_python_04", "unit": "robot_python", "d": 0.0, "type": "mcq", "q": "This Python helper uses `bleak` to send one command to a small Bluetooth robot. Why does it sleep for 0.05 seconds after each command?", "code": "async def say(car, command):\n    await car.write_gatt_char(BOX, command.encode() + b\"#\")\n    await asyncio.sleep(0.05)", "o": ["To give the batteries time to rest", "To let Bluetooth connect again each time", "To keep the laptop from getting hot", "To stop fast messages getting lost"], "a": 3, "why": "A small device reads one command at a time. Fast messages get lost, and a small rest after each one fixes that.", "at": "robot_python.html"},
  {"id": "robot_python_05", "unit": "robot_python", "d": 0.3, "type": "mcq", "q": "This Python code drives a robot car. `say` sends one command. Why is the stop command inside `finally`?", "code": "try:\n    await say(car, \"A\")      # forward\n    await asyncio.sleep(1)\nfinally:\n    await say(car, \"0\")      # stop", "o": ["`finally` makes the stop command run much faster", "`finally` always runs, even if the code breaks", "`finally` runs only when there is no error", "`finally` reconnects Bluetooth after a failure"], "a": 1, "why": "`finally` always runs. So if the code in `try` breaks, the car still stops.", "at": "robot_python.html"},
  {"id": "robot_python_06", "unit": "robot_python", "d": 0.2, "type": "mcq", "q": "A small Bluetooth robot is on and close by. Your Python program with `bleak` still cannot find it. What should you check?", "o": ["That the program calls `await` two times", "That the robot has a longer device name", "That no phone app is connected to it", "That the robot's lights are switched off"], "a": 2, "why": "A small Bluetooth device like this talks to one thing at a time. If a phone app holds it, Python cannot find it.", "at": "robot_python.html#scripts"},
  {"id": "robot_python_07", "unit": "robot_python", "d": 0.5, "type": "mcq", "q": "This Python code stops a robot car over Bluetooth. `say` sends one command, and `\"0\"` means stop. Why does it send the stop command three times, not once?", "code": "finally:\n    for i in range(3):\n        await say(car, \"0\")\n        await asyncio.sleep(0.15)", "o": ["The car needs three commands to brake", "Each command stops one of the wheels", "The first two only switch the lights off", "One lost message would leave it driving"], "a": 3, "why": "A Bluetooth message can get lost. If the only stop message is lost, the car keeps driving, so the code sends it more than once.", "at": "robot_python.html#scripts"},
  {"id": "robot_python_08", "unit": "robot_python", "d": 0.6, "type": "mcq", "q": "`SONG` is `\"1155665443322 1\"`, and `say` sends one command to a robot. What does this loop do when `note` is the space?", "code": "for note in SONG:\n    if note == \" \":\n        await asyncio.sleep(0.4)\n        continue\n    await say(car, note)\n    await say(car, COLOUR[note])", "o": ["It waits, and sends no command", "It plays note 1 with a red light", "It stops the loop and ends the song", "It turns every light off for good"], "a": 0, "why": "For a space the loop sleeps, then `continue` jumps to the next character without calling `say`.", "at": "robot_python.html"},
  {"id": "control_your_phone_01", "unit": "control_your_phone", "d": -0.7, "type": "mcq", "q": "Which tool opens a window on your computer that shows the screen of an Android phone?", "o": ["`adb`", "`scrcpy`", "`uiautomator2`", "`pip3`"], "a": 1, "why": "`adb` is the road between the two machines, `scrcpy` is the window that shows the phone, and `uiautomator2` is the Python library that presses the buttons.", "at": "control_your_phone.html#step2"},
  {"id": "control_your_phone_02", "unit": "control_your_phone", "d": -0.5, "type": "mcq", "q": "How do you make the hidden developer menu appear on an Android phone?", "o": ["Tap Build number seven times", "Hold the power button for ten seconds", "Install `adb` on the phone", "Plug in a USB cable twice"], "a": 0, "why": "In Settings, About phone, you tap Build number seven times. Then Developer options is in Settings, System.", "at": "control_your_phone.html#step1"},
  {"id": "control_your_phone_03", "unit": "control_your_phone", "d": 0.2, "type": "mcq", "q": "`adb pair` worked, but `adb connect` says `failed to connect`. What is the most common mistake here?", "o": ["You typed the six-digit code too slowly", "You ran `scrcpy` before `adb devices`", "You used the pairing port to connect", "You forgot to install `uiautomator2`"], "a": 2, "why": "The pairing port is not the same as the connecting port. Use the port at the top of the Wireless debugging screen.", "at": "control_your_phone.html#step3"},
  {"id": "control_your_phone_04", "unit": "control_your_phone", "d": 0.0, "type": "mcq", "q": "`adb devices` shows the word `unauthorized` next to your phone. What should you do?", "o": ["Run `adb kill-server` on the Mac", "Turn on the phone's own hotspot", "Plug in a USB cable and wait", "Look at the phone and accept"], "a": 3, "why": "The phone is asking permission and nobody answered. Look at the phone, tick Always allow and accept.", "at": "control_your_phone.html#step3"},
  {"id": "control_your_phone_05", "unit": "control_your_phone", "d": 0.4, "type": "mcq", "q": "A `uiautomator2` script taps a row on an Android phone with `d(text=\"Wi-Fi\").click()`, and not with a coordinate on the screen. Why is a name better?", "o": ["A coordinate is slower for the phone to find", "A name still works after the layout changes", "A coordinate needs a USB cable to be sent", "A name lets the script run without `adb`"], "a": 1, "why": "A coordinate works today and breaks tomorrow: on a bigger screen, or after an update moves the row. Asking by name survives.", "at": "control_your_phone.html#step6"},
  {"id": "control_your_phone_06", "unit": "control_your_phone", "d": 0.6, "type": "mcq", "q": "A wireless `adb` connection to an Android phone worked yesterday. Today `adb connect` with the same address and port fails. Why?", "o": ["Wireless debugging picked a new port", "The pairing code is only good for a day", "The helper app was removed overnight", "The computer has two copies of `scrcpy`"], "a": 0, "why": "Wireless debugging picks a new port every time it restarts. The pairing is remembered, the port is not, so read the new port and connect again.", "at": "control_your_phone.html#step8"},
  {"id": "control_your_phone_07", "unit": "control_your_phone", "d": 0.5, "type": "mcq", "q": "There is no `adb` for an iPhone. What does Python talk to when it scripts an iPhone?", "o": ["QuickTime Player", "iPhone Mirroring", "WebDriverAgent", "AirPlay Receiver"], "a": 2, "why": "WebDriverAgent is a small app you build in Xcode and leave on the phone. Python then talks to that app.", "at": "control_your_phone.html#step9"},
  {"id": "crewai_autogen_01", "unit": "crewai_autogen", "d": -0.7, "type": "mcq", "q": "In CrewAI, what does every agent get?", "o": ["A `name`, a `model_client` and a `system_message`", "A `role`, a `goal` and a `backstory`", "A `@tool` and a `final_answer()`", "A `prompt`, a `seed` and a `temperature`"], "a": 1, "why": "CrewAI describes agents the way a manager describes staff: a role (a job title), a goal, and a backstory that sets its manner.", "at": "crewai_autogen.html#crewai"},
  {"id": "crewai_autogen_02", "unit": "crewai_autogen", "d": -0.4, "type": "mcq", "q": "In CrewAI, a later `Task` must see the output of two earlier tasks, named `spec` and `build`. How do you give it both?", "o": ["Make its agent with `verbose=False`", "Make the crew with `tracing=False`", "Save both outputs in the same text file", "Give the task `context=[spec, build]`"], "a": 3, "why": "In CrewAI each task receives the earlier ones through `context=[...]`. A task that lists both `spec` and `build` can read both outputs.", "at": "crewai_autogen.html#crewai"},
  {"id": "crewai_autogen_03", "unit": "crewai_autogen", "d": 0.4, "type": "mcq", "q": "In CrewAI, what happens if you leave `tracing=False` out of the `Crew`?", "o": ["CrewAI stops at the end and asks about traces", "CrewAI runs the tasks in a random order", "The agents can no longer see each other's work", "The crew's result is saved with no text in it"], "a": 0, "why": "Without it, CrewAI stops once the work is done and asks in the terminal whether you want to see execution traces. It looks like a script that has hung.", "at": "crewai_autogen.html#crewai"},
  {"id": "crewai_autogen_04", "unit": "crewai_autogen", "d": 0.5, "type": "mcq", "q": "A CrewAI crew has a programmer agent that writes an HTML file and a tester agent that checks it against a checklist. Why should you still open the file in a browser yourself?", "o": ["The tester agent deletes the file after its check", "The programmer agent never sees the checklist", "The tester only re-reads the HTML as text", "The crew cannot save any file to the disk"], "a": 2, "why": "A tester agent is not a test suite. It re-reads the HTML against the checklist and opens no browser, so nothing proves that the file renders.", "at": "crewai_autogen.html#crewai"},
  {"id": "crewai_autogen_05", "unit": "crewai_autogen", "d": -0.3, "type": "mcq", "q": "How does AutoGen's `RoundRobinGroupChat` decide who speaks?", "o": ["A manager agent picks the next speaker each time", "The agents take turns, then the list starts again", "All agents answer the task at the same moment", "The agent with the fewest messages so far goes next"], "a": 1, "why": "It is the simplest team: the agents take turns, and when the list runs out it starts again.", "at": "crewai_autogen.html#autogen"},
  {"id": "crewai_autogen_06", "unit": "crewai_autogen", "d": 0.5, "type": "mcq", "q": "In an AutoGen group chat, the first message holds some numbers that one agent must not use until the other agents have spoken. The agent uses them on its first turn. Writing \"do not use them yet\" again, in capital letters, changes nothing. What is the fix?", "o": ["Repeat the instruction in every later message", "Let the model make up the numbers by itself", "Use a lower temperature for that one agent", "Give it the numbers only when it needs them"], "a": 3, "why": "A model uses what is in front of it. So the fix is not a firmer instruction but a smaller context: keep the numbers out of the chat until the agent needs them.", "at": "crewai_autogen.html#autogen"},
  {"id": "crewai_autogen_07", "unit": "crewai_autogen", "d": 0.0, "type": "mcq", "q": "You have several jobs in a fixed order, and each is done by an agent with a job title. Which Python agent library is shaped for this?", "o": ["CrewAI", "AutoGen", "smolagents", "LangChain"], "a": 0, "why": "CrewAI is for several jobs in a fixed order, each done by an agent with a title. The order is yours, written down.", "at": "crewai_autogen.html#pick"},
  {"id": "langchain_01", "unit": "langchain", "d": -0.8, "type": "mcq", "q": "In LangChain, what does the pipe `|` do in `prompt | model | StrOutputParser()`?", "o": ["It runs the three pieces at the same time.", "It sends the output of one piece to the next.", "It picks the best answer of the three pieces.", "It saves each piece to a file on your machine."], "a": 1, "why": "You join a prompt, a model and a parser with `|`. What comes out of one goes into the next, like a production line.", "at": "langchain.html#s1"},
  {"id": "langchain_02", "unit": "langchain", "d": -0.4, "type": "mcq", "q": "In LangChain, what do you get from a chain when `StrOutputParser()` is its last piece?", "o": ["A pydantic object with named fields.", "A list of messages from an agent.", "A message object from the model.", "Plain text, not a message object."], "a": 3, "why": "Without `StrOutputParser()` you get a message object. With it you get plain text.", "at": "langchain.html#s2"},
  {"id": "langchain_03", "unit": "langchain", "d": -0.1, "type": "mcq", "q": "This line makes a LangChain model that is used inside a chain. Why does `reasoning=False` matter there?", "code": "model = ChatOllama(model=\"qwen3\", base_url=\"http://localhost:11434\",\n                   num_ctx=8192, reasoning=False)", "o": ["So its thinking is not passed to the next step.", "So the model answers without using the internet.", "So the model is allowed to call a tool by itself.", "So the model gives the same answer every time."], "a": 0, "why": "qwen3 normally writes its thinking before the answer, and a chain hands that thinking straight to the next step. `reasoning=False` turns the thinking off.", "at": "langchain.html#s1"},
  {"id": "langchain_04", "unit": "langchain", "d": 0.3, "type": "mcq", "q": "You ask a LangChain model for structured output with `with_structured_output()` and a pydantic class. Every field comes back filled, but one value is wrong. What does this show?", "o": ["Structured output only works with a paid model.", "The class needs one more field to be right.", "Structured output guarantees only the shape.", "The model must think first to fill the fields."], "a": 2, "why": "Structured output guarantees the shape and nothing else. The values can still be wrong, so check them like any other answer.", "at": "langchain.html#s3"},
  {"id": "langchain_05", "unit": "langchain", "d": 0.5, "type": "mcq", "q": "In LangChain, what is the difference between a chain and an agent?", "o": ["A chain needs a key; an agent runs on a free model.", "A chain gives objects; an agent gives plain text.", "A chain runs the same steps; an agent decides.", "A chain uses one prompt; an agent uses none."], "a": 2, "why": "A chain always runs the same steps. An agent chooses whether to call the tool before it answers.", "at": "langchain.html#s5"},
  {"id": "langchain_06", "unit": "langchain", "d": 0.7, "type": "mcq", "q": "Your job is a fixed set of steps: explain, then translate, then shorten. Which library is built around joining fixed steps with a pipe `|`?", "o": ["LangChain", "smolagents", "CrewAI", "AutoGen"], "a": 0, "why": "LangChain joins a prompt, a model and a parser with `|`. A fixed pipe is easy to read and test, because it does the same thing every time.", "at": "langchain.html#s6"},
  {"id": "face_recognition_01", "unit": "face_recognition", "d": -0.8, "type": "mcq", "q": "People say \"face recognition\" for more than one job. Which of these jobs is the hard one for a program?", "o": ["Finding where a face is in a photo", "Naming the person in a photo", "Changing a photo from colour to grey", "Drawing a box around each face"], "a": 1, "why": "Finding a face is old, fast and works. Naming a face is much harder: light, angle and people the program has never seen all cause mistakes.", "at": "face_recognition.html#step1"},
  {"id": "face_recognition_02", "unit": "face_recognition", "d": -0.5, "type": "mcq", "q": "For face recognition in Python with OpenCV 4, why install the contrib build, `opencv-contrib-python`?", "o": ["It is the only build that opens photos", "It sends photos to a faster server", "It downloads a trained AI model", "It also carries the `cv2.face` module"], "a": 3, "why": "The contrib build carries the face finder and also `cv2.face`, the part that names faces (for example `LBPHFaceRecognizer_create`).", "at": "face_recognition.html#step2"},
  {"id": "face_recognition_03", "unit": "face_recognition", "d": -0.3, "type": "mcq", "q": "OpenCV's Haar cascade face detector is called with `detectMultiScale`. What happens when you raise its `minNeighbors` value?", "o": ["It gives fewer false alarms", "It catches more faces", "It ignores all small faces", "It keeps the photo in colour"], "a": 0, "why": "`minNeighbors` sets how fussy the detector is. Raise it for fewer false alarms; lower it to catch more faces.", "at": "face_recognition.html#step3"},
  {"id": "face_recognition_04", "unit": "face_recognition", "d": 0.0, "type": "mcq", "q": "OpenCV's Haar cascade face detector sometimes draws a box around a round printed logo. Why?", "o": ["It counts each real face two or three times", "It was trained on photos of printed logos", "It only matches patterns of light and dark", "It boxes every shape with a bright colour"], "a": 2, "why": "The detector slides simple light-and-dark patterns over a grey picture. A round shape with two dark marks fits those patterns, so it is reported as a face.", "at": "face_recognition.html#step3"},
  {"id": "face_recognition_05", "unit": "face_recognition", "d": 0.2, "type": "mcq", "q": "This Python code uses an OpenCV LBPH face recognizer. A face is found in the photo, and `model.predict` gives a distance of 64.3. Which name does `who` return?", "code": "THRESHOLD = 56\n\ndef who(model, names, path):\n    \"\"\"Name the face in one photo, or say it does not know.\"\"\"\n    face = face_from(path)\n    if face is None:\n        return \"no face found\", None\n    label, distance = model.predict(face)\n    if distance > THRESHOLD:\n        return \"I do not know\", distance\n    return names[label], distance", "o": ["`I do not know`", "`no face found`", "The closest name", "An error message"], "a": 0, "why": "A low distance is a close match. Here 64.3 is above the threshold of 56, so the code refuses to give a name.", "at": "face_recognition.html#step4"},
  {"id": "face_recognition_06", "unit": "face_recognition", "d": 0.6, "type": "mcq", "q": "A face recognizer gives a distance for each photo, and a low distance is a close match. In one test, people it knew scored from 34.6 to 77.8, and strangers scored from 52.1 to 88.7. Why can no single threshold separate the two groups?", "o": ["The distance changes on every run", "It was trained on too many people", "The two distance ranges overlap", "Strangers always score lower"], "a": 2, "why": "The two ranges overlap, so any threshold makes both kinds of mistake: it turns away some known people and it lets some strangers in.", "at": "face_recognition.html#step5"},
  {"id": "face_recognition_07", "unit": "face_recognition", "d": 0.7, "type": "mcq", "q": "A Python program uses OpenCV to find faces in a live camera picture. Its writer could add a recognizer that names each face, but leaves it out. What is a good reason?", "o": ["A camera picture is too small to name", "A wrong name is worse than no name", "Naming needs a model from the internet", "OpenCV cannot name faces in a video"], "a": 1, "why": "Naming is the half that often gets it wrong. A program that quietly puts a name on a stranger is worse than one that says nothing at all.", "at": "face_recognition.html#step6"},
  {"id": "physics_with_python_01", "unit": "physics_with_python", "d": -0.8, "type": "mcq", "q": "This Python function simulates a stone that falls from `HEIGHT`. It does not use the formula ½gt². What does it use?", "code": "def simulate(dt, stop):\n    y, v, t = HEIGHT, 0.0, 0.0\n    while t < stop - 1e-12:\n        y = y - v * dt\n        v = v + G * dt\n        t = t + dt\n    return y", "o": ["The height of the stone at every second", "The time the stone needs to reach the ground", "The mass of the stone and the density of the air", "Gravity changes speed; speed changes position"], "a": 3, "why": "A simulation like this knows only two things, repeated in small jumps of time: gravity changes the speed, and the speed changes the position.", "at": "physics_with_python.html#step2"},
  {"id": "physics_with_python_02", "unit": "physics_with_python", "d": -0.4, "type": "mcq", "q": "A Python simulation of a falling stone repeats two lines: `y = y - v * dt`, then `v = v + G * dt`. You make `dt` ten times smaller. What happens to the error of the result?", "o": ["It gets ten times smaller.", "It stays exactly the same size.", "It gets ten times bigger.", "It becomes exactly zero."], "a": 0, "why": "With this simple method the error grows in line with `dt`. In one run, 0.1 s gave an error of 0.9810 m and 0.01 s gave 0.0981 m. The simulation is approximate, and you can shrink the error on purpose.", "at": "physics_with_python.html#step2"},
  {"id": "physics_with_python_03", "unit": "physics_with_python", "d": 0.5, "type": "mcq", "q": "A simulation of a falling stone has one wrong sign: `y + v*dt` in place of `y - v*dt`. It runs with no error and the stone goes up. What is the reliable way to catch a mistake like this?", "o": ["Wait for Python to report a sign error.", "Run it again with a smaller `dt`.", "Check it against an answer known on paper.", "Run it twice and compare the two results."], "a": 2, "why": "A simulation can agree with itself and still be completely wrong. Check it against something you did not compute with it, such as the formula ½gt².", "at": "physics_with_python.html#step2"},
  {"id": "physics_with_python_04", "unit": "physics_with_python", "d": -0.3, "type": "mcq", "q": "When does a falling ball reach terminal velocity?", "o": ["When it has fallen for ten seconds", "When the air's push equals its weight", "When the air becomes too thin to push it", "When gravity stops pulling it down"], "a": 1, "why": "The air pushes harder as the ball gets faster. When the push equals the weight, the ball stops speeding up.", "at": "physics_with_python.html#step3"},
  {"id": "physics_with_python_05", "unit": "physics_with_python", "d": 0.1, "type": "mcq", "q": "A ball falls through air. Why do you simulate the fall in Python, and not just use a formula?", "o": ["A formula is too slow for Python to run.", "The formula needs a library that is not installed.", "The formula for the final speed is wrong.", "There is no tidy formula for the whole fall."], "a": 3, "why": "There is a tidy formula for the final speed, √(mg/K), but none for the rest of the fall. When the algebra runs out, you simulate.", "at": "physics_with_python.html#step3"},
  {"id": "physics_with_python_06", "unit": "physics_with_python", "d": 0.0, "type": "mcq", "q": "With no air, a ball thrown at 45 degrees goes furthest. What happens to the best angle when you add air resistance to the simulation?", "o": ["It becomes lower than 45 degrees.", "It becomes higher than 45 degrees.", "It stays at exactly 45 degrees.", "It becomes exactly 90 degrees."], "a": 0, "why": "45 degrees is the right answer only for a world with no air. In one simulation of a hard throw of a tennis ball with air, the best angle was 39 degrees.", "at": "physics_with_python.html#step4"},
  {"id": "physics_with_python_07", "unit": "physics_with_python", "d": 0.7, "type": "mcq", "q": "The textbook formula for the period of a pendulum is T = 2π√(L/g). How good is this formula?", "o": ["It is wrong by the same amount at every angle.", "It is right only when the pendulum is one metre.", "It is good for small swings, worse for big ones.", "It is exact, and only a simulation has an error."], "a": 2, "why": "The formula does not contain the angle, but the real period grows with the swing. In one simulation the formula was 0.00% off at 1 degree and 18.04% off at 90 degrees.", "at": "physics_with_python.html#step5"},
  {"id": "telescope_data_01", "unit": "telescope_data", "d": -0.8, "type": "mcq", "q": "In space telescope data, what is MAST?", "o": ["A Python library that draws pictures of galaxies.", "A password you need before you can download files.", "The archive where NASA keeps telescope pictures.", "The newest space telescope, which started in 2022."], "a": 2, "why": "NASA keeps the pictures in one big archive called MAST, and you can ask it a question from Python without an account.", "at": "telescope_data.html"},
  {"id": "telescope_data_02", "unit": "telescope_data", "d": -0.5, "type": "mcq", "q": "What is the Python library `astroquery` for?", "o": ["It knows how to ask archives such as MAST.", "It turns raw files into finished pictures.", "It prints a table in neat, lined-up columns.", "It reads what you typed after the file name."], "a": 0, "why": "`astropy` knows about the sky, and `astroquery` knows how to ask archives. MAST is one of them.", "at": "telescope_data.html#step1"},
  {"id": "telescope_data_03", "unit": "telescope_data", "d": -0.3, "type": "mcq", "q": "This line uses `astroquery` to ask the MAST archive. What does `radius=\"0.01 deg\"` mean?", "code": "obs = Observations.query_object(\"M51\", radius=\"0.01 deg\")", "o": ["How many rows the archive may send back.", "How long the telescope's shutter stayed open.", "How large the downloaded file is allowed to be.", "How wide a circle to search around the object."], "a": 3, "why": "The radius says how wide a circle to search. A bigger radius gives more results.", "at": "telescope_data.html#step3"},
  {"id": "telescope_data_04", "unit": "telescope_data", "d": 0.0, "type": "mcq", "q": "This Python loop counts how many rows each name has. Why does it use `tally.get(name, 0)` and not `tally[name]`?", "code": "tally = {}\nfor row in obs:\n    name = str(row[\"obs_collection\"])\n    tally[name] = tally.get(name, 0) + 1", "o": ["`tally[name]` would sort the names in the wrong order.", "`tally[name]` stops with a `KeyError` when the name is new.", "`tally[name]` would count every name twice in the loop.", "`tally[name]` only works on a list, never on a dictionary."], "a": 1, "why": "Every name is missing the first time the loop meets it. Square brackets on a missing key raise a `KeyError`, but `.get(name, 0)` hands back the spare 0.", "at": "telescope_data.html#step4"},
  {"id": "telescope_data_05", "unit": "telescope_data", "d": 0.4, "type": "mcq", "q": "`obs` is an `astropy` table. Why does this filter join its two tests with `&` and not with `and`?", "code": "pics = obs[(obs[\"obs_collection\"] == \"HST\") &\n           (obs[\"dataproduct_type\"] == \"image\")]", "o": ["`and` is slower than `&` on a large table.", "`and` would also change the table `obs`.", "Each test gives one answer for every row.", "`&` keeps a row when only one test is True."], "a": 2, "why": "On a table, `==` gives a True or False for every row at once. That is why the table needs `&`, not `and`, with each test in its own brackets.", "at": "telescope_data.html#step5"},
  {"id": "telescope_data_06", "unit": "telescope_data", "d": 0.1, "type": "fill", "q": "A list has five items. How many items does the slice `[:3]` give you? Type the number.", "answers": ["3", "three"], "why": "Counting starts at 0 and the end number is not included, so `[:3]` gives items 0, 1 and 2.", "at": "telescope_data.html#step6"},
  {"id": "telescope_data_07", "unit": "telescope_data", "d": 0.6, "type": "mcq", "q": "This `astroquery` code downloads from the MAST archive. It keeps only `PREVIEW` files, sorts them by size and takes `[:1]`. What is this for?", "code": "products = Observations.get_product_list(pics[:1])\nsmall = products[products[\"productType\"] == \"PREVIEW\"]\nsmall.sort(\"size\")\nresult = Observations.download_products(small[:1], download_dir=\"sky\")", "o": ["So that the archive does not ask for a password.", "So that the download is a finished press picture.", "So that the file is saved in the folder `sky`.", "So that you never grab a huge file by mistake."], "a": 3, "why": "Preview files are small, `sort(\"size\")` puts the smallest first and `[:1]` takes one. Raw science files can be 100 MB.", "at": "telescope_data.html#step10"},
  {"id": "telescope_data_08", "unit": "telescope_data", "d": 0.7, "type": "mcq", "q": "With `astroquery`, `Observations.query_object(\"Saturn\", radius=\"0.01 deg\")` gives a `ResolverError`. Why?", "o": ["The radius of the search circle is too small.", "Planets move; the name finder knows fixed things.", "No telescope in the archive ever looked at Saturn.", "The name must be written in capital letters."], "a": 1, "why": "The name finder only knows fixed things, and planets move. Use stars, galaxies or nebulas, or give numbers for the position.", "at": "telescope_data.html#step11"},
  {"id": "cloakbrowser_01", "unit": "cloakbrowser", "d": -0.8, "type": "mcq", "q": "Why does a plain `requests.get` call get `403 Forbidden` from a big commercial site?", "o": ["The site only answers users who have paid.", "The `requests` library cannot open HTTPS pages.", "Bot protection decides the request is a script.", "The HTML is too big for Python to download."], "a": 2, "why": "The bot-protection service scores how human the request looks. A bare `requests` call fails almost every check, so it gets the challenge page.", "at": "cloakbrowser.html"},
  {"id": "cloakbrowser_02", "unit": "cloakbrowser", "d": -0.5, "type": "mcq", "q": "In CloakBrowser, as in Playwright, what does `page.content()` give you?", "o": ["The current HTML of the web page as a string", "A picture of the web page as an image file", "A list of every link found on the web page", "The status code that the server sent back"], "a": 0, "why": "`page.content()` returns the current HTML as a Python string, the same HTML a human would see in the browser.", "at": "cloakbrowser.html"},
  {"id": "cloakbrowser_03", "unit": "cloakbrowser", "d": -0.6, "type": "fill", "q": "Which function of the Python package `cloakbrowser` starts the stealth Chromium browser? Type its name.", "answers": ["launch", "launch()", "browser = launch()", "browser=launch()", "cloakbrowser.launch", "cloakbrowser.launch()"], "why": "`launch()` starts the browser process and hands you a browser object.", "at": "cloakbrowser.html"},
  {"id": "cloakbrowser_04", "unit": "cloakbrowser", "d": -0.1, "type": "mcq", "q": "You scrape several URLs in one run with CloakBrowser. Starting a browser is expensive, and going to a new URL in a page that is still loading can fail. What is the right way to do it?", "o": ["Launch a new browser for each of the URLs.", "Reuse one page for all of the URLs.", "Open all URLs at once in one tab.", "Launch once, open a new page per URL."], "a": 3, "why": "Starting a browser is expensive, so launch once. Reusing the same page across navigations causes \"navigation is changing\" races, so call `browser.new_page()` for each URL.", "at": "cloakbrowser.html"},
  {"id": "cloakbrowser_05", "unit": "cloakbrowser", "d": 0.2, "type": "mcq", "q": "Why should a web scraper save the raw HTML to disk before it parses it?", "o": ["So the site cannot see who fetched the HTML.", "So you can parse again without a new fetch.", "So the browser needs less memory to run.", "So the HTML file loads faster in Chrome."], "a": 1, "why": "Fetching is the slow, fragile part and parsing is redone many times. With the raw HTML on disk you scrape once and re-parse for free.", "at": "cloakbrowser.html"},
  {"id": "cloakbrowser_06", "unit": "cloakbrowser", "d": 0.4, "type": "mcq", "q": "A scraper should read the JSON-LD block of a shopping site first, and use a regex on the visible text only as a fallback. Why?", "o": ["JSON-LD is found on every single web page.", "JSON-LD needs no JavaScript to load.", "JSON-LD is more stable than the markup.", "JSON-LD is hidden from bot protection."], "a": 2, "why": "Sites embed JSON-LD so Google can read it. It is far more stable than the visible markup, which changes constantly.", "at": "cloakbrowser.html"},
  {"id": "cloakbrowser_07", "unit": "cloakbrowser", "d": 0.7, "type": "mcq", "q": "An AI agent searches the web and ranks the results. At the end, the Python program prints the result links from its own saved data. It does not let the model write them. Why?", "o": ["Models copy long addresses badly.", "The model has no tool for printing.", "Python can search the web much faster.", "Search sites hide links from models."], "a": 0, "why": "The model judges and Python keeps the facts. Models copy long addresses badly, so the links come from the data that the program saved.", "at": "cloakbrowser.html#agent"},
  {"id": "cloakbrowser_08", "unit": "cloakbrowser", "d": 0.5, "type": "mcq", "q": "Why do you run a CloakBrowser scraper as a plain `.py` script and not in a Jupyter notebook?", "o": ["A notebook cannot install packages with `pip`.", "A notebook cannot save HTML files to the disk.", "A notebook is blocked by Cloudflare much faster.", "`launch()` refuses to start inside Jupyter's loop."], "a": 3, "why": "`launch()` is a synchronous API. It refuses to start inside Jupyter's already-running asyncio event loop; a plain script has no running loop.", "at": "cloakbrowser.html#getit"},
  {"id": "rust_start_01", "unit": "rust_start", "d": -0.8, "type": "mcq", "q": "What does `cargo check` do?", "o": ["It builds the program and then runs it.", "It makes a new project folder for you.", "It builds a fast program for real use.", "It checks if the code would build."], "a": 3, "why": "`cargo check` asks the compiler \"would this build?\" without building it, so it is fast.", "at": "rust_start.html#s4"},
  {"id": "rust_start_02", "unit": "rust_start", "d": -0.6, "type": "fill", "q": "In Rust a variable made with `let` cannot change. Which word do you add after `let` so that it can change? Type the word.", "answers": ["mut", "let mut"], "why": "Variables are immutable by default. `let mut` tells the compiler, and the reader, that this value changes.", "at": "rust_start.html#s5"},
  {"id": "rust_start_03", "unit": "rust_start", "d": -0.3, "type": "mcq", "q": "In this function, what happens if you add a semicolon after `a + b`?", "code": "fn add(a: i32, b: i32) -> i32 {\n    a + b\n}", "o": ["The function returns the sum two times.", "Nothing changes; the semicolon is optional.", "It returns nothing, so it does not compile.", "The function returns the value of `a` only."], "a": 2, "why": "The last thing in the block, with no semicolon, is what comes back. With a semicolon the block gives back nothing, and the compiler refuses the function because it promised an `i32`. It is a very common first mistake.", "at": "rust_start.html#s7"},
  {"id": "rust_start_04", "unit": "rust_start", "d": 0.4, "type": "mcq", "q": "Why can `main` still use `name` on the last line?", "code": "fn shout(text: &String) -> String {\n    text.to_uppercase()\n}\n\nfn main() {\n    let name = String::from(\"Chiang Mai\");\n    println!(\"{}\", shout(&name));\n    println!(\"{} is still here\", name);\n}", "o": ["`shout` makes a copy of every `String` it gets.", "The `&` only lends `name`, so `main` keeps it.", "`name` was made with `let mut`, so it can move back.", "`println!` gives `name` back after it prints it."], "a": 1, "why": "Passing a `String` without `&` moves it, and you cannot use it again. With `&` the function only borrows it and you keep it.", "at": "rust_start.html#s9"},
  {"id": "rust_start_05", "unit": "rust_start", "d": 0.0, "type": "mcq", "q": "A Rust function returns `Option<u32>`. What does that type mean?", "o": ["Maybe a number, maybe nothing: `Some` or `None`.", "It worked or it failed: `Ok` or `Err`.", "A number that the caller is allowed to change.", "A list of numbers that the caller can choose from."], "a": 0, "why": "Rust has no null. If something might be missing the type says so: `Option<u32>` is always `Some(number)` or `None`.", "at": "rust_start.html#s10"},
  {"id": "rust_start_06", "unit": "rust_start", "d": 0.6, "type": "mcq", "q": "What happens when this Rust program runs?", "code": "fn to_number(text: &str) -> Result<i32, std::num::ParseIntError> {\n    let number: i32 = text.parse()?;\n    Ok(number * 2)\n}\n\nfn main() {\n    match to_number(\"cat\") {\n        Ok(n) => println!(\"double is {}\", n),\n        Err(e) => println!(\"bad number: {}\", e),\n    }\n}", "o": ["The program panics and stops at the `?` line.", "The function returns `Ok(0)` and prints \"double is 0\".", "The code does not build, because \"cat\" is text.", "The `?` passes the error back, and `main` prints it."], "a": 3, "why": "`?` gives the value if it worked. If it failed, it stops there and passes the error back to the caller, where the `Err` arm of the `match` handles it.", "at": "rust_start.html#s12"},
  {"id": "rust_start_07", "unit": "rust_start", "d": 0.7, "type": "mcq", "q": "In this Rust loop, `parse_line` returns a `Result<Student, String>`. Three of the five lines are broken, so they give an `Err`. What does the program do with them?", "code": "for line in lines {\n    match parse_line(line) {\n        Ok(student) => {\n            println!(\"ok      {} scored {}\", student.name, student.score);\n            good.push(student);\n        }\n        Err(problem) => println!(\"skipped {}\", problem),\n    }\n}", "o": ["It stops at the first broken line and prints nothing more.", "It counts each broken line as a score of zero.", "It skips each one, says why, and carries on.", "It panics, because a `match` cannot hold an `Err`."], "a": 2, "why": "The `match` handles both sides of the `Result`: each `Ok` line is kept, and each `Err` prints \"skipped\" with the reason. Nothing crashes.", "at": "rust_start.html#s13"}
 ]
}
