Project: the delivery robot
A small project taken through analysis, design, implementation, testing and evaluation, the way the NEA is written up.
Do this lesson in the simulatorEvery A level in computer science ends with a programming project, the non-exam assessment (NEA), worth 20% of the qualification. You choose a problem, usually for a real user, and take it through analysis, design, implementation, testing and evaluation, writing up each stage. This lesson works a small robot project through those stages the way a project write-up does, then you build it.
The robot here is small on purpose. A real NEA is much larger and needs more complex techniques; what carries over is the shape: every stage follows from the one before, and the success criteria written in analysis are what testing and evaluation check.
1. Analysis
The problem. Staff carry parcels from the office to a delivery bay beside the charging wall. The client, the site manager, wants the robot to do it.
Stakeholders and research. The site manager (safety, charging), office staff (sending), and teachers (collecting). An interview with the site manager established that the robot must never touch the wall, must park where the collection shelf can reach it, and must make its arrival obvious to someone who is not looking at it.
Success criteria, numbered and measurable:
| No. | Criterion | Evidence |
|---|---|---|
| SC1 | Stops with a gap of 17 to 23 cm to the wall before moving across to the bay | the gap read by distance() |
| SC2 | Is inside the delivery bay within 30 s of starting | the time from clock() |
| SC3 | On arrival, LED green and an 880 Hz note | observed, and the task's check |
| SC4 | Never touches the wall | observed |
Limitations agreed with the client: one fixed route, no people in the way (the obstacle behaviour of lesson A14.8 is for a later iteration).
2. Design
Decomposition, as a structure chart written as an outline:
delivery
run_unit_tests tests gap_ok before the robot moves
approach(target) returns the gap once parked
sidestep(cm)
signal
evaluate(gap, elapsed)
gap_ok(gap) returns True if the gap meets SC1
Algorithms. The approach must be fast but must not overshoot. Driving in large steps until the wall is near, then small ones, gives both:
SUBROUTINE approach(target)
WHILE distance() > target + 10
drive forward 5 cm at speed 60
ENDWHILE
WHILE distance() > target + 1
drive forward 1 cm at speed 30
ENDWHILE
RETURN distance()
ENDSUBROUTINE
Test plan. Designed now, from the criteria. gap_ok is a pure function, so it can be unit tested with boundary data before the robot ever moves:
| Test | Data | Kind | Expected |
|---|---|---|---|
| gap_ok below the band | 16.9 | boundary | False |
| gap_ok lowest in band | 17 | boundary | True |
| gap_ok middle | 20 | normal | True |
| gap_ok highest in band | 23 | boundary | True |
| gap_ok above the band | 23.1 | boundary | False |
| full run | robot from the start | system | SC1 to SC4 all met |
3. Implementation
Built iteratively, one module at a time, each tested before the next was started, and each working step committed to version control with a message:
gap_okand its unit tests. Commit: "gap check with boundary tests".approach, run on its own and the gap printed. The first version used 5 cm steps all the way and stopped at 16 cm: a failed test, fixed by adding the slow final stage. Commit: "two-speed approach".sidestepandsignal. Commit: "move to the bay and signal".evaluate, which prints each criterion as met or not met. Commit: "self-evaluation".
In a write-up, each step is evidenced with the code, the test output, and a note of what went wrong and how it was fixed. That record of problems solved is valued, not hidden.
Good implementation also means the things examiners look for in code: meaningful names, constants for the limits (GAP_LOW, GAP_HIGH), subroutines with clear interfaces, comments where the reason is not obvious, and validation where data comes in.
4. Testing
Iterative testing happened in step 3 above. Post-development testing runs the test plan on the finished program:
# unit tests for gap_ok, the way the finished program runs them
GAP_LOW, GAP_HIGH = 17, 23
def gap_ok(gap):
return GAP_LOW <= gap <= GAP_HIGH
tests = [(16.9, False), (17, True), (20, True), (23, True), (23.1, False)]
passed = 0
for value, expected in tests:
ok = gap_ok(value) == expected
passed += ok
print(f"test gap_ok({value}): {'pass' if ok else 'FAIL'}")
print(f"unit tests: {passed} of {len(tests)} passed")
For the NEA, final testing should also include the user: the site manager watching the robot deliver and confirming the criteria, a form of acceptance testing.
5. Evaluation
The evaluation goes back to each success criterion, says whether it was met, and points to the evidence. Where one was not fully met, it says why and what would fix it. It also considers user feedback, and how the system could be maintained and extended:
SC1 was met: the robot parked with a gap of 21 cm (test output, run 3). SC2 was met, arriving after 20 s. SC3 and SC4 were met. The site manager asked for the robot to wait if a person is in the corridor; this was out of scope for version 1 and is planned for version 2, using the decision rules of lesson A14.8. Because the approach and the evaluation are separate subroutines, that change will not affect the criteria checks. A limitation is that the route is fixed: a new bay would need code changes rather than a setting, so a future version should read the route from a file.
That paragraph is what top-band evaluation looks like: every claim tied to a criterion and to evidence, honest about limits, and forward-looking.
Law and ethics in the project
A real project should consider the issues of this module too. If the robot logged who collected each parcel, the analysis would note the data protection principles (lesson A14.7), and the design would include retention and pseudonymisation. If it used a camera near students, the evaluation should discuss consent and bias (lesson A14.8).
Task: the delivery robot
Build the designed program. The robot starts facing the charging wall, 71 cm away. The delivery bay is to the right of where it parks.
Write these subroutines, and use them in this order:
gap_ok(gap):gapis a number of cm; returnsTrueif it is fromGAP_LOWtoGAP_HIGHinclusive, otherwiseFalse.run_unit_tests(): testsgap_okwith exactly these five values, in this order: 16.9 (expectedFalse), 17 (True), 20 (True), 23 (True) and 23.1 (False). For each, printtest gap_ok(<value>): passortest gap_ok(<value>): FAIL, then printunit tests: <passed> of 5 passed.approach(target):targetis the gap in cm to park at; drive towards the wall and return the gap read withdistance()once stopped. Call it asapproach(20).sidestep(cm): move rightcmcentimetres. Call it assidestep(30).signal(): LED green and an 880 Hz note.evaluate(gap, elapsed):gapis the valueapproachreturned andelapsedisclock()read aftersignal(). PrintSC1 gap <gap> cm: <result>usinggap_ok,SC2 in the bay after <elapsed> s: <result>(met ifelapsedis at mostTIME_LIMIT), andSC3 signal: met, where each<result>ismetornot met. Then printcriteria met: <n> of 3, counting the criteria met.
The whole program prints exactly ten lines. SC4 is checked by watching the run, so the program does not print it: do not touch the wall.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
GAP_LOW, GAP_HIGH = 17, 23 # SC1, in cm
TIME_LIMIT = 30 # SC2, in seconds
def gap_ok(gap):
pass
Challenges
- Add SC5, "never closer than 15 cm to the wall at any time", and a way for the program to measure it while it drives.
- Write the evaluation paragraph for your own run, using your printed output as evidence.
- Plan version 2 with the site manager's request. Which methodology from lesson A14.2 suits adding it, and why?