Skip to content

← Final exam model papers

Final Exam Model Paper 8

Theory paper: 50 marksTime: 2 hours

Practical exam: 25 marksTime: 1 hour 30 minutes

All questions are compulsory.

A full practice run of the Grade 10 final exam: the theory paper (50 marks, 2 hours) and, below it, the practical exam (25 marks, 1 hour 30 minutes, at a computer, marked by the school). In the theory paper, work out on paper what each program does, as in the exam hall: its programs have no Run button. In the practical exam, the programs run on this page.

A practice paper by Science Playground, made to its own course’s grid; not an official exam paper.

Write your answers on paper first. Then open each answer and its marking scheme, and give yourself one mark for each point you got. In multiple choice, your first choice counts.

Section A

10 × 1 = 10

1. Choose the correct option for each of the following questions.

a.

When a function uses a name, in what order does Python look for it?

Pick an answer to see why.

b.

A health worker’s map of the trails between health posts, as a dictionary of neighbors:

posts = {    "Ramechhap": ["Manthali", "Doramba"],    "Manthali": ["Ramechhap", "Sunapati", "Khimti"],    "Doramba": ["Ramechhap", "Gokulganga"],    "Sunapati": ["Manthali"],    "Khimti": ["Manthali", "Gokulganga"],    "Gokulganga": ["Doramba", "Khimti"],}

A breadth-first search starts at Ramechhap, taking places from the front of its list and adding new neighbors at the back, in the order the lists give them. Which health post is the fourth to be visited?

Pick an answer to see why.

c.

A health post’s database keeps each patient’s phone number, such as 9841234567 or +977 9851234567. Which data type should the field have?

Pick an answer to see why.

d.

A fitness page on social media asks its followers: “How many days a week do you exercise?” Of the 2,000 who answered, most said 5 days or more. Its owner writes: “Most people in Nepal exercise 5 days a week.” Which judgment is right?

Pick an answer to see why.

e.

A hospital’s appointment app sends a text message to confirm each booking. One morning the text message service fails. Which design is an example of graceful degradation?

Pick an answer to see why.

f.

What is a package manager?

Pick an answer to see why.

g.

What is pretraining a language model?

Pick an answer to see why.

h.

Nabin asks a chatbot, with no search tool, which vaccines the government’s campaign last month offered. It describes an older campaign as if it were the latest. What is the most likely reason?

Pick an answer to see why.

i.

Sushma works for a district health office. Each week she cleans the data sent by the health posts, looks for patterns in the cases of diarrhea and fever, and makes honest charts for the officers who decide where to send medicines. Which job is this?

Pick an answer to see why.

j.

A student’s proposal for a law on telemedicine says: “Every hospital that gives consultations by video must use the app HealthLink, version 3.” Which judgment is right?

Pick an answer to see why.

Section B

10 × 2 = 20

Write short answers to the following questions.

2.
[2]

A district health office offers an API (made up for this paper). Its documentation gives this example, which asks for the health posts in Mugu that give vaccinations:

https://api.health.example.np/posts?district=Mugu&service=vaccines
  • i.Which part of the address are its query parameters, and what is each one?
  • ii.Write the address that asks for the health posts in Humla that give dental care, if the documentation calls that service dental.
Answer
  • i.Everything after the ?: district=Mugu&service=vaccines. They are name=value pairs joined by &: district with the value Mugu, and service with the value vaccines. They are the request’s arguments, like a function’s, and the API’s documentation lists the names it understands.
  • ii.The same address, with the values changed:
https://api.health.example.np/posts?district=Humla&service=dental
Marking scheme
  • i. The part after the ?: name=value pairs joined by &, district=Mugu and service=vaccines.
  • ii. …/posts?district=Humla&service=dental.
3.
[2]

A doctor at the hospital in Jumla holds a video consultation with a heart specialist in Kathmandu. The call’s data crosses many routers on the way.

  • i.How does a router decide where to send each piece of data next?
  • ii.A cable between two routers on the way is cut during the call, but the call goes on. Explain, in terms of the graph, how that is possible.
Answer
  • i.Many routers keep a graph of the network: routers are nodes, links are edges, with a cost on each link. From it the router works out the best route to the destination (with Dijkstra’s algorithm, nearest first by cost) and sends the data to the first router on that route, the best next hop.
  • ii.The cut cable is an edge removed from the graph. The routers learn that the link is gone, work out the best routes again on the changed graph, and send the data by another path, if one exists. A network with more than one route between places keeps working when one link fails.
Marking scheme
  • i. The router keeps a graph of the network with a cost on each link and works out the best next hop (with Dijkstra’s algorithm).
  • ii. The cut cable is an edge taken out; the routes are worked out again and the data goes another way, because the graph has another route.
4.
[2]

A hospital keeps two tables:

doctors (doctor_id, name, ward)

visits (visit_id, doctor_id, patient, day)

Write an SQL query that lists every visit on 2026-10-12, with the doctor’s name and the patient’s name, ordered by the doctor’s name.

Answer
SELECT doctors.name, visits.patientFROM visitsJOIN doctors ON visits.doctor_id = doctors.doctor_idWHERE visits.day = '2026-10-12'ORDER BY doctors.name;

The JOIN puts each visit beside its doctor’s row, matched by doctor_id; WHERE keeps that day’s visits.

Marking scheme
  • A JOIN of visits and doctors ON visits.doctor_id = doctors.doctor_id, selecting the doctor’s name and the patient.
  • WHERE visits.day = '2026-10-12' and ORDER BY doctors.name.
5.
[2]

A health post in Ramechhap measured the blood pressure of eight patients one morning. The health post sends anyone whose systolic (upper) number is 140 or more for a check-up with the doctor.

bp.csv

patient,age,systolic1,45,1282,62,1513,38,1184,70,1655,55,1396,49,1427,66,1208,58,140
  • i.Write pandas lines that read the file, print the rows of the patients to be sent for a check-up, and then print how many of the patients they are.
  • ii.Write what the lines print.
Answer

i.

import pandas as pd bp = pd.read_csv("bp.csv")high = bp[bp["systolic"] >= 140]print(high)print(len(high), "of", len(bp), "need a check-up")

ii. The comparison gives True or False for each row, and the outer brackets keep the rows that are True, with their index numbers from the file’s order:

Output

   patient  age  systolic1        2   62       1513        4   70       1655        6   49       1427        8   58       1404 of 8 need a check-up

Patient 8, at exactly 140, is included because the rule is >=; patient 5, at 139, is not.

Marking scheme
  • i. Reads the file, keeps the rows with bp[bp["systolic"] >= 140] (with >=, not >), prints them and their number with len().
  • ii. Patients 2, 4, 6 and 8 (with their rows), and 4 of 8.
6.
[2]

Dawa has used a word processor for years. At the health post he must now keep the monthly reports in a spreadsheet program he has never opened.

  • i.Give two ideas he already knows from the word processor that work the same way in the spreadsheet, and say why knowing them helps.
  • ii.He plans to “learn the spreadsheet program” by watching a two-hour video. Why is that a poor way to start, and what should he do instead?
Answer
  • i.Any two: save, save as and export (a report saved as a PDF); undo (Ctrl + Z), so he can try things freely; select, then act; the menus and the search for commands; help (F1); shortcuts such as Ctrl + C and Ctrl + V. Most programs share these ideas, so he only has to learn the two or three of the spreadsheet’s own, such as cells and formulas.
  • ii.“Learn the program” is too big a job to know when it is done, and watching someone else feels like learning but isn’t. He should start with a small, real task (this month’s patients by ward, with a total), read the built-in help, follow a tutorial doing each step on a copy of the file, and check what he learned by doing it again a few days later without help.
Marking scheme
  • i. Two shared ideas (save and export, undo, select then act, menus, help, shortcuts), so only the spreadsheet’s own ideas are new.
  • ii. Watching a long video is too big and passive; start with a small real task, doing each step on a copy, and check by doing it again without help.
7.
[2]

Kabita’s program for a clinic stops with:

Error message

Traceback (most recent call last):  File "C:\Users\Kabita\Desktop\clinic\report.py", line 12, in <module>    with open("patients.xlsx", "w") as f:PermissionError: [Errno 13] Permission denied: 'patients.xlsx'
  • i.Write what she should type into a search engine.
  • ii.Which parts of the message should she leave out, and why?
Answer

i. The program’s language and the exact error, in quotes:

python "PermissionError: [Errno 13] Permission denied"

ii. Her folders and file names (C:\Users\Kabita\Desktop\clinic\report.py, patients.xlsx) and line 12: nobody else has them, so they only hide the pages that would help, and the path shows her name. (The usual answer she will find: the file is open in another program, such as a spreadsheet, or she isn’t allowed to write there.)

Marking scheme
  • i. The exact error message in quotes, with “python”.
  • ii. Leave out her own path, file names and line number: only she has them (and they show her name).
8.
[2]

A language model’s tokenizer could cut text into single letters, or into whole words. Explain why each would be a bad choice, and what real tokenizers do instead.

Answer
  • Single letters: the vocabulary would be tiny, but every sentence would be very long in tokens, and the model must look back over everything written so far at each step, so every step would be much more work; and a single letter means almost nothing on its own.
  • Whole words: each token would mean something, but the vocabulary would be enormous (गर्छ, गर्छन्, गरेको, गर्दै…, every name, every misspelling, every word of every language), and a word not in the list, such as a new village’s name, couldn’t be read at all.

Real tokenizers do something in between: common words are one token each, and rarer or longer words are cut into a few pieces that appear in other words too, so any word can still be read, at worst letter by letter.

Marking scheme
  • Letters make texts very long in tokens (more work at every step), and a letter means little; words make the vocabulary enormous, and an unknown word can’t be read.
  • Real tokenizers: common words as one token, rarer words as a few common pieces.
9.
[2]

A website says that in a five-minute test, judges chatting by text with a person and with its chatbot chose the chatbot as the human more often than the real person. Rajan says: “So the chatbot thinks and understands, just like us.” Judge his claim.

Answer

The test doesn’t show that. Turing’s game measures whether a machine can pass for a person in a short written chat, which is not the same as thinking or understanding: ELIZA, a few rules that turned people’s words back as questions, made people feel understood. People disagree about whether a model understands (Searle’s Chinese room says rule-following isn’t understanding; others say predicting this well needs some kind of it), and it is very different from us: no body or goals of its own, and parameters frozen after training. A fair claim: “It writes in a way people can’t tell from a person, in five minutes.”

Marking scheme
  • Passing for a person in a short chat shows fluent imitation, not thinking or understanding (people are easily fooled, as with ELIZA).
  • Whether it understands is disputed (the Chinese room), and it differs from us (no body or goals, doesn’t keep learning); a fair claim is about passing for a person.
10.
[2]

In Nepal, what is the difference between an Act and the rules or directives made under it?

Answer
  • An Act is passed or amended by the Federal Parliament: the law itself.
  • Rules, directives and guidelines are made under an Act by the government, a ministry or a regulator (such as Nepal Rastra Bank or the Nepal Telecommunications Authority). They fill in the details and are quicker to change, but must stay within what the Act allows.
Marking scheme
  • An Act is passed or amended by the Federal Parliament.
  • Rules and directives are made under an Act by the government or a regulator, and are quicker to change.
11.
[2]

A stranger messages Suresh, a student who freelances: “Let money for my business come into your bank account. Send it on to the account I give you, and keep 10% for yourself. Easy money!” Judge this offer.

Answer

It is a trap. Suresh would be a money mule: someone whose account is used to pass on money, usually the money of scams, theft or other crime, so that the criminals stay hidden. The bank and the police trace the money to his account, under his name: he could have the account frozen and be charged, even though he “only passed it on”. Honest businesses don’t need a stranger’s account. He should refuse, not reply, and tell his bank or the police if he has already received money.

Marking scheme
  • He would be a money mule, passing on money from crime for criminals who stay hidden.
  • The money leads to his account and name: frozen account, criminal charges; refuse and report.

Section C

5 × 4 = 20

Write long answers to the following questions.

12.
[4]

A clinic in Nepalgunj’s team wants to show each patient’s body mass index (BMI) in its program. They find a third-party library on PyPI whose documentation says (the library is made up for this question):

bmi(weight_kg, height_m, *, digits=1)    Body mass index: weight_kg divided by the square of height_m,    rounded to `digits` decimal places.    Returns a float.    Raises ValueError if weight_kg or height_m is not more than 0.
  • i.Before they install it with pip, write two things they should check, and why. (1)
  • ii.Which of these calls work, and which gives an error? Give the reason. (1)
a. bmi(60, 1.6)          b. bmi(60, 1.6, 2)          c. bmi(60, 1.6, digits=2)
  • iii.Write lines that print each patient’s BMI from the list patients = [("Sita", 52, 1.55), ("Ram", 70, 0)], printing “Check Ram’s height” instead of stopping when the library raises its error. (1)
  • iv.Why might the library’s writer have made digits keyword-only? (1)
Answer
  • i.Any two: that it is widely used and looked after (a recent version, documentation, problems answered), or its mistakes won’t be fixed; that it is the real one, its name copied letter by letter from the official page, since harmful packages use names one letter off and installing runs their code; that its license allows their use; and whether they need it at all, since BMI is one line they could write and test themselves.
  • ii.a and c work. b gives a TypeError: the * in the signature means everything after it must be given by name, so 2 can’t be passed as a third value.

iii.

from bmi_tools import bmi       # the library’s module patients = [("Sita", 52, 1.55), ("Ram", 70, 0)]for name, weight, height in patients:    try:        print(name, bmi(weight, height))    except ValueError:        print("Check " + name + "’s height")

It prints Sita 21.6 (52 ÷ 1.55² = 21.64…) and then Check Ram’s height.
iv. So that a call says what each number is: bmi(60, 1.6, digits=2) can’t be mistaken, while bmi(60, 1.6, 2) could be read as a third measurement. It also lets the writer add or reorder options later without breaking anyone’s calls.

Marking scheme
  • i. Two checks with reasons: looked after (recent, documented), the real name (copied exactly), the license, or whether it is needed at all.
  • ii. a and c work; b fails because digits is keyword-only (after *).
  • iii. A loop that calls bmi in try, catches ValueError and prints the message.
  • iv. Keyword-only makes calls clear (no number mistaken for another) and lets the library change later without breaking calls.
13.
[4]

A simplified, made-up map of the trails between villages with health posts in Sankhuwasabha, as a dictionary of neighbors:

trails = {    "Khandbari": ["Tumlingtar", "Chainpur", "Mane Bhanjyang"],    "Tumlingtar": ["Khandbari", "Chainpur"],    "Chainpur": ["Khandbari", "Tumlingtar", "Bala"],    "Mane Bhanjyang": ["Khandbari", "Num"],    "Num": ["Mane Bhanjyang", "Seduwa"],    "Seduwa": ["Num", "Hatiya", "Makalu"],    "Bala": ["Chainpur", "Hatiya"],    "Hatiya": ["Seduwa", "Bala", "Kimathanka"],    "Makalu": ["Seduwa"],    "Kimathanka": ["Hatiya"],}
  • i.Do a breadth-first search from Khandbari by hand. Write a row for each visit: the place visited, its steps, the new places added (with their steps), and the list still to visit. (2)
  • ii.In how few trails can a health worker from Khandbari reach Kimathanka? Write the route, from the notes of where each place was first reached from. (1)
  • iii.Which places are exactly 3 trails from Khandbari? (1)
Answer

i.

Visit Steps New places (steps) List still to visit, front first
(start) Khandbari (0) Khandbari
Khandbari 0 Tumlingtar, Chainpur, Mane Bhanjyang (1) Tumlingtar, Chainpur, Mane Bhanjyang
Tumlingtar 1 none Chainpur, Mane Bhanjyang
Chainpur 1 Bala (2) Mane Bhanjyang, Bala
Mane Bhanjyang 1 Num (2) Bala, Num
Bala 2 Hatiya (3) Num, Hatiya
Num 2 Seduwa (3) Hatiya, Seduwa
Hatiya 3 Kimathanka (4) Seduwa, Kimathanka
Seduwa 3 Makalu (4) Kimathanka, Makalu
Kimathanka 4 none Makalu
Makalu 4 none (empty)
  • ii.4 trails. Kimathanka was first reached from Hatiya, Hatiya from Bala, Bala from Chainpur, Chainpur from Khandbari: Khandbari → Chainpur → Bala → Hatiya → Kimathanka.
  • iii.Hatiya and Seduwa, the third ring.
Marking scheme
  • i. The visits in the right order: Khandbari; Tumlingtar, Chainpur, Mane Bhanjyang; Bala, Num; Hatiya, Seduwa; Kimathanka, Makalu.
  • i. Correct steps (as above), with the list as a queue and no place added twice.
  • ii. 4 trails, Khandbari → Chainpur → Bala → Hatiya → Kimathanka.
  • iii. Hatiya and Seduwa.
14.
[4]

The district health office of Mugu keeps the medicines at its health posts in two tables. An expiry date not yet recorded is empty (NULL).

medicines

med_id name kind
1 Paracetamol tablet
2 ORS sachet
3 Amoxicillin capsule
4 Zinc tablet
5 Iron tablet

stock

stock_id med_id post qty expires
1 1 Gamgadhi 500 2027-03
2 2 Gamgadhi 120 2026-12
3 1 Mangri 80 2026-11
4 3 Mangri 0 2027-01
5 4 Pulu 200 NULL
6 2 Pulu 40 2026-11
7 5 Gamgadhi 300 NULL

Write what each query gives.

  • i.SELECT post, SUM(qty) FROM stock GROUP BY post ORDER BY SUM(qty) DESC; (1)
  • ii.(1)
SELECT medicines.name, stock.post, stock.qtyFROM stockJOIN medicines ON stock.med_id = medicines.med_idWHERE medicines.kind = 'tablet' AND stock.qty < 250ORDER BY stock.qty;
  • iii.SELECT stock_id, post FROM stock WHERE expires IS NULL; (1)
  • iv.A clerk writes SELECT stock_id, post FROM stock WHERE expires = NULL; and gets no rows. Why? (1)
Answer

i. Each post’s quantities added up, the biggest first:

post SUM(qty)
Gamgadhi 920
Pulu 240
Mangri 80

ii. The tablets with fewer than 250 left, the fewest first (Paracetamol at Gamgadhi and Iron have more):

name post qty
Paracetamol Mangri 80
Zinc Pulu 200
  • iii.The rows with no expiry date recorded: stock 5 (Pulu) and stock 7 (Gamgadhi).
  • iv.NULL means “no value, unknown”, so nothing is equal to it, not even another NULL: expires = NULL is never true, for any row. Empty fields must be found with IS NULL.
Marking scheme
  • i. Gamgadhi 920, Pulu 240, Mangri 80.
  • ii. Paracetamol, Mangri, 80; Zinc, Pulu, 200.
  • iii. Stock 5 (Pulu) and 7 (Gamgadhi).
  • iv. NULL is an unknown value, so = NULL is never true; use IS NULL.
15.
[4]

A health camp in Okhaldhunga registers patients at desks before they see a doctor. Registering takes 2 to 4 minutes, and about 20 people arrive an hour, at random, from 10:00 for 4 hours. A volunteer writes a simulation:

import random def one_day(per_hour, desks):    """One made-up morning at a health camp's registration desks.    Gives back each person's wait in line, in minutes."""    free_at = [0] * desks    waits = []    now = 0    while True:        now = now + random.expovariate(per_hour / 60)        if now > 240:            break        k = free_at.index(min(free_at))        start = max(now, free_at[k])        waits.append(start - now)        free_at[k] = start + random.uniform(2, 4)    return waits for desks in [1, 2]:    waits = one_day(20, desks)    print(desks, "desk(s):", len(waits), "people, longest wait", round(max(waits)), "minutes")
  • i.What do lines 10 and 11 do? (1)
  • ii.With 2 desks, free_at starts as [0, 0]. The first person arrives at minute 2.5 and takes 3.2 minutes; the second arrives at minute 4.0 and takes 2.6 minutes. For each, which desk (0 or 1, its place in the list) does line 13 choose, how long does the person wait, and what is free_at afterwards? (1)
  • iii.At the real camp, most people register in 2 to 4 minutes, but a few, such as elderly patients who have lost their papers, take 10 minutes or more. Which line models the time, and how would this make the simulation’s results mislead the organizers? (1)
  • iv.With one desk, the longest waits keep growing through the day. Explain why, from the numbers in the question. (1)
Answer
  • i.Line 10 moves the clock to when the next person arrives: the gap is random, about 3 minutes on average for 20 people an hour (60 ÷ 20). Line 11 stops when the time passes 240 minutes, 14:00: no more new people.
  • ii.Line 13 picks the desk free first (the first one when they tie), and line 14 starts the person at the later of arriving and the desk being free.
Person Arrives Desk Starts Waits free_at afterwards
1 2.5 0 (both free at 0; the first is taken) 2.5 0 [5.7, 0]
2 4.0 1 (free at 0, before desk 0’s 5.7) 4.0 0 [5.7, 6.6]

(With one desk, the second person would have waited 5.7 − 4.0 = 1.7 minutes.)

  • iii.Line 16: random.uniform(2, 4) never gives more than 4 minutes, so the slow registrations are left out of the model. Every slow person holds up everyone behind them, so the real waits, the longest ones above all, would be longer than the simulation says, and the organizers might set up too few desks. A simulation is only as good as its model: they should time real registrations at a camp and use those times.
  • iv.One desk can register about one person every 3 minutes (2 to 4, 3 on average), 20 an hour, and 20 an hour arrive: the desk is busy almost all the time, so whenever several people come close together a line builds up, and the desk never has spare time to clear it. A second desk can serve about 40 an hour, so lines clear quickly.
Marking scheme
  • i. Line 10 adds a random gap to reach the next arrival; line 11 ends the arrivals after 240 minutes.
  • ii. Person 1 to desk 0, person 2 to desk 1, neither waits; free_at becomes [5.7, 0], then [5.7, 6.6].
  • iii. Line 16 never gives more than 4 minutes, so slow registrations are left out and the waits shown are too short; time real registrations.
  • iv. One desk serves about 20 an hour, as many as arrive, so it has no spare time and the line grows; two desks have spare time.
16.
[4]

Read the given passage and answer the questions that follow.

A hospital in Biratnagar keeps its patients’ records in a database on a server in its own building. Each change is copied within seconds to a second server in the same room. The hospital plans to rent space from a cloud company for a third copy, in a data center in another city, and to make a backup every night.

  • i.What is it called when a database is copied, change by change, to other servers? Why is it done? (1)
  • ii.Why is a second server in the same room not enough? What does a cloud company’s “zone” give instead? (1)
  • iii.What is a backup? Why do they need backups even with three copies? (1)
  • iv.Before changing the program that all the servers run, why should they change it on one server first, and keep a way to undo it? (1)
Answer
  • i.Replication. If one copy is lost (a failed disk, a server down), another copy already has everything, so the records aren’t lost and the system keeps running.
  • ii.Copies help only if they don’t fail together: a power cut, fire, flood or earthquake in that building takes both servers. A zone is a separate building, a few kilometers away, with its own power, cooling and network (a region is a different city or country).
  • iii.A backup is a copy kept apart, from an earlier time. Replication copies every change, the mistakes too: if someone deletes records by accident (or malware scrambles them), every copy loses them within seconds. Only a backup from before the mistake still has them.
  • iv.A change sent to every server at once can break every copy at once, since they all run the same program. A staged rollout (a few servers first, watched, then the rest) limits the harm, and a rollback (a way to undo the change) brings it back quickly.
Marking scheme
  • i. Replication, so that if one copy is lost another has everything.
  • ii. Both servers in one building fail together (power, fire, earthquake); a zone is a separate building with its own power and network.
  • iii. A backup is a copy kept apart from an earlier time; replication copies mistakes to every copy.
  • iv. A change can break every copy at once; change a few first (staged rollout) and keep a rollback.

Practical exam

25 marks

Marked by the school. Work alone at a computer, with no internet and no AI, in front of the teacher, for 1 hour 30 minutes. Write and run each program, show it working, and answer the teacher’s questions. On this page, press Edit on a program to type in it, and Run to run it.

P1.
[7]

Token times. At a free eye camp, patients get numbered tokens. The doctor starts at 10:00 and sees one patient every 6 minutes. From 13:00 to 13:30 is lunch, so any patient whose time would be 13:00 or later is seen 30 minutes later.

At the computer, make two files in one folder:

  • camp.py, a module with a function seen_at(token) that returns the time a token is seen as text such as "10:24" or "13:30", and raises a ValueError for a token below 1, with tests at the end that run only when camp.py itself is run;
  • main.py, which imports it, asks for 3 token numbers, and prints the time each is seen.

Run camp.py, then main.py with tokens 5, 31 and 50. (Hint: f"{m:02}" writes a number with at least two digits, so 6 becomes 06.)

Answer

camp.py

"""When each token is seen at the eye camp.""" START = 10 * 60          # 10:00, in minutes after midnightEACH = 6                 # minutes for each patientLUNCH = 13 * 60          # lunch starts at 13:00LUNCH_LENGTH = 30        # minutes  def seen_at(token):    """The time token number `token` is seen, as text such as "10:24".    Raises ValueError for a token below 1."""    if token < 1:        raise ValueError("tokens start at 1")    minutes = START + (token - 1) * EACH    if minutes >= LUNCH:        minutes = minutes + LUNCH_LENGTH    return f"{minutes // 60}:{minutes % 60:02}"  if __name__ == "__main__":    # tests: the first token, and either side of lunch    print(seen_at(1), "should be 10:00")    print(seen_at(30), "should be 12:54")    print(seen_at(31), "should be 13:30")

Run by itself, camp.py prints its tests:

Output

10:00 should be 10:0012:54 should be 12:5413:30 should be 13:30

main.py

import camp for i in range(3):    token = int(input("Token number? "))    print("Token", token, "is seen at", camp.seen_at(token))

Output

Token number? 5Token 5 is seen at 10:24Token number? 31Token 31 is seen at 13:30Token number? 50Token 50 is seen at 15:24

Token 50: 49 × 6 = 294 minutes after 10:00 is 14:54, plus 30 for lunch, 15:24.

Marking scheme
  • camp.py has a function seen_at(token) that returns the time as text (it doesn’t print it).
  • 6 minutes for each token before it, from 10:00 ((token - 1) * 6).
  • 30 minutes added from 13:00 on (>= 13:00, so token 31 is 13:30).
  • The time made from // 60 and % 60, with two digits for the minutes.
  • Raises a ValueError for a token below 1.
  • Tests under if __name__ == "__main__":, either side of lunch (30 and 31), which don’t run when it is imported.
  • main.py imports camp, asks three times with int(input(…)), and prints 10:24, 13:30 and 15:24 in front of the teacher.
P2.
[6]

The blood donation camp. A youth club in Dharan runs blood donation camps and keeps its donors in a database. This program makes it and gives you ask(), which prints what a query gives. Run it, then add one ask() for each question:

  • i.The donations of 350 ml: their donation_id, donor_id and day, the most recent first. (2)
  • ii.Each ward, with the number of donors who live in it, in order of ward. (2)
  • iii.Everyone who gave blood on 2026-09-05: their name and blood group, in alphabetical order of name. (2)
import sqlite3 db = sqlite3.connect(":memory:")db.execute("CREATE TABLE donors (donor_id INTEGER PRIMARY KEY, name TEXT, blood_group TEXT, ward INTEGER)")db.execute("""CREATE TABLE donations (donation_id INTEGER PRIMARY KEY,              donor_id INTEGER REFERENCES donors(donor_id),              day TEXT, ml INTEGER)""")db.executemany("INSERT INTO donors VALUES (?, ?, ?, ?)", [    (1, "Anil Rai", "O+", 4),    (2, "Sarita Limbu", "A+", 7),    (3, "Bikash Shrestha", "B+", 4),    (4, "Puja Tamang", "O−", 12),    (5, "Rajesh Yadav", "AB+", 7),    (6, "Nirmala Rai", "O+", 9),])db.executemany("INSERT INTO donations VALUES (?, ?, ?, ?)", [    (1, 1, "2026-01-15", 450),    (2, 2, "2026-01-15", 350),    (3, 3, "2026-04-20", 450),    (4, 1, "2026-05-02", 450),    (5, 4, "2026-05-02", 450),    (6, 6, "2026-08-11", 350),    (7, 1, "2026-09-05", 450),    (8, 2, "2026-09-05", 450),    (9, 5, "2026-09-05", 350),])  def ask(sql):    """Print each row the query gives, then a blank line."""    for row in db.execute(sql):        print(row)    print()  ask("SELECT * FROM donors")
Answer

The same program, with these three lines in place of the last one:

import sqlite3 db = sqlite3.connect(":memory:")db.execute("CREATE TABLE donors (donor_id INTEGER PRIMARY KEY, name TEXT, blood_group TEXT, ward INTEGER)")db.execute("""CREATE TABLE donations (donation_id INTEGER PRIMARY KEY,              donor_id INTEGER REFERENCES donors(donor_id),              day TEXT, ml INTEGER)""")db.executemany("INSERT INTO donors VALUES (?, ?, ?, ?)", [    (1, "Anil Rai", "O+", 4),    (2, "Sarita Limbu", "A+", 7),    (3, "Bikash Shrestha", "B+", 4),    (4, "Puja Tamang", "O−", 12),    (5, "Rajesh Yadav", "AB+", 7),    (6, "Nirmala Rai", "O+", 9),])db.executemany("INSERT INTO donations VALUES (?, ?, ?, ?)", [    (1, 1, "2026-01-15", 450),    (2, 2, "2026-01-15", 350),    (3, 3, "2026-04-20", 450),    (4, 1, "2026-05-02", 450),    (5, 4, "2026-05-02", 450),    (6, 6, "2026-08-11", 350),    (7, 1, "2026-09-05", 450),    (8, 2, "2026-09-05", 450),    (9, 5, "2026-09-05", 350),])  def ask(sql):    """Print each row the query gives, then a blank line."""    for row in db.execute(sql):        print(row)    print()  ask("SELECT donation_id, donor_id, day FROM donations WHERE ml = 350 ORDER BY day DESC")ask("SELECT ward, COUNT(*) FROM donors GROUP BY ward ORDER BY ward")ask("""SELECT donors.name, donors.blood_group FROM donations       JOIN donors ON donations.donor_id = donors.donor_id       WHERE donations.day = '2026-09-05' ORDER BY donors.name""")

Output

(9, 5, '2026-09-05')(6, 6, '2026-08-11')(2, 2, '2026-01-15') (4, 2)(7, 2)(9, 1)(12, 1) ('Anil Rai', 'O+')('Rajesh Yadav', 'AB+')('Sarita Limbu', 'A+')

Dates written year-month-day sort correctly as text, so ORDER BY day DESC puts the latest first. (The donors are made up for this paper.)

Marking scheme
  • i. WHERE ml = 350, selecting donation_id, donor_id and day.
  • i. ORDER BY day DESC, giving donations 9, 6 and 2.
  • ii. SELECT ward, COUNT(*) FROM donors with GROUP BY ward.
  • ii. Ordered by ward: 4 and 7 have 2 donors, 9 and 12 have 1.
  • iii. A JOIN of donations and donors ON donations.donor_id = donors.donor_id.
  • iii. Only 2026-09-05, ordered by name: Anil Rai O+, Rajesh Yadav AB+, Sarita Limbu A+.
P3.
[6]

Flood warnings by radio. When the Koshi rises, a warning is passed by radio from station to station; each pair below can talk to each other directly (a made-up network). Write a function add_link(graph, a, b) that adds a two-way link to a dictionary of neighbors, and build the graph from the list. Then write fewest_hops(graph, start, goal), which returns the stations on a route with the fewest hops, as a list, and print:

  • the route from Chatara to Rajbiraj, and the number of hops;
  • the route from Dharan to Hanumannagar, and the number of hops.

Then add a new station, Biratnagar, linked to Itahari, and print the route from Chatara to Biratnagar.

links = [("Chatara", "Barahakshetra"), ("Barahakshetra", "Prakashpur"),         ("Prakashpur", "Inaruwa"), ("Chatara", "Dharan"), ("Dharan", "Itahari"),         ("Itahari", "Inaruwa"), ("Inaruwa", "Rajbiraj"),         ("Prakashpur", "Hanumannagar"), ("Hanumannagar", "Rajbiraj")] # Write add_link and fewest_hops here
Answer
links = [("Chatara", "Barahakshetra"), ("Barahakshetra", "Prakashpur"),         ("Prakashpur", "Inaruwa"), ("Chatara", "Dharan"), ("Dharan", "Itahari"),         ("Itahari", "Inaruwa"), ("Inaruwa", "Rajbiraj"),         ("Prakashpur", "Hanumannagar"), ("Hanumannagar", "Rajbiraj")]  def add_link(graph, a, b):    """Add a two-way link between a and b."""    if a not in graph:        graph[a] = []    if b not in graph:        graph[b] = []    graph[a].append(b)    graph[b].append(a)  def fewest_hops(graph, start, goal):    """The stations on a route with the fewest hops, or None."""    came_from = {start: None}    to_visit = [start]    while len(to_visit) > 0:        station = to_visit.pop(0)        if station == goal:            route = []            while station is not None:                route.append(station)                station = came_from[station]            route.reverse()            return route        for nxt in graph[station]:            if nxt not in came_from:                came_from[nxt] = station                to_visit.append(nxt)    return None  radio = {}for a, b in links:    add_link(radio, a, b) for start, goal in [("Chatara", "Rajbiraj"), ("Dharan", "Hanumannagar")]:    route = fewest_hops(radio, start, goal)    print(" → ".join(route), "(" + str(len(route) - 1), "hops)") add_link(radio, "Biratnagar", "Itahari")print(" → ".join(fewest_hops(radio, "Chatara", "Biratnagar")))

Output

Chatara → Barahakshetra → Prakashpur → Inaruwa → Rajbiraj (4 hops)Dharan → Chatara → Barahakshetra → Prakashpur → Hanumannagar (4 hops)Chatara → Dharan → Itahari → Biratnagar

Chatara to Rajbiraj by Hanumannagar, and Dharan to Hanumannagar by Itahari and Inaruwa, are also 4 hops; any route with the fewest hops is right.

Marking scheme
  • add_link adds each station to the other’s list, making a list for a station not yet in the dictionary.
  • Builds the graph by calling it for every pair in the list.
  • A breadth-first search with a queue (pop(0), append()), marking a station as seen when it is added.
  • Follows the came_from notes back from the goal and reverses them to give the route.
  • Prints both routes with 4 hops.
  • Adds Biratnagar with add_link and prints Chatara → Dharan → Itahari → Biratnagar.
P4.
[6]

Viva. The teacher asks each student about their own work, for example:

  • i.In camp.py, what does f"{minutes % 60:02}" do? What would token 2’s time look like without :02? (2)
  • ii.In your third query, why do you write donors.name and donations.donor_id, with the table’s name in front? What would SQLite say to SELECT donor_id FROM donations JOIN donors ON donations.donor_id = donors.donor_id? (2)
  • iii.Chatara to Rajbiraj has three routes of 4 hops. Why does your function print the one by Barahakshetra, Prakashpur and Inaruwa? What could make it print another? (2)
Answer
  • i.minutes % 60 is the minutes past the hour (the remainder after the whole hours), and :02 writes it with at least two digits, adding a 0 in front when needed. Token 2 is seen at 10:06; without :02 it would be shown as 10:6, which reads like 10:60 or is easily misread.
  • ii.After a JOIN, the result has the columns of both tables, and both have a column called donor_id, so the table’s name says which one is meant. With a bare donor_id, SQLite can’t tell and stops with an error: ambiguous column name: donor_id. (name is only in donors, so donors.name isn’t required, but it makes the query clearer.)
  • iii.Breadth-first search adds each station’s neighbors in the order of its list, and the first note made in came_from is kept. Chatara’s list starts with Barahakshetra (its link came first), so Prakashpur is added before Itahari, and Prakashpur’s list has Inaruwa before Hanumannagar, so Inaruwa is visited first and Rajbiraj is noted as reached from it. Giving the links in another order (for example Prakashpur–Hanumannagar before Prakashpur–Inaruwa) changes the lists’ order, and the function would print another route, still of 4 hops.
Marking scheme
  • i. % 60 gives the minutes past the hour; :02 pads it to two digits.
  • i. Without it, 10:6 instead of 10:06.
  • ii. Both tables have donor_id, so the table’s name says which column is meant.
  • ii. A bare donor_id gives an error (ambiguous column name): SQLite can’t tell which table’s column is meant.
  • iii. Neighbors are added in list order and the first note is kept, so the route through the earliest-listed stations wins.
  • iii. Another order of links (or of the lists) would give another 4-hop route.

Your score

0 / 50 theory

0 / 25 practical

29 questions not marked yet

Section A–Full marks 10
Section B–Full marks 20
Section C–Full marks 20
Practical exam–Full marks 25

Marks lost, by unit

Mark a question to see your score here.

Score 0 of 0 0 of 29 marked Time left: 2:00:00