AI & Computer Vision, lesson 51 of 52
Demo Day: Your Live Product Showcase
Introduction
Opening night. In five minutes your team shows what you built, what it can do, and what it can't, live if you possibly can. Today you'll plan a demo that survives things going wrong, present an honest result, and get ready for the hardest question in the room.
By the end of this lesson you'll be able to:
- Plan a five-minute demo that adds up and puts the live product at the centre
- Choose the demo mode before going on stage, using a fallback ladder
- State the measured result with its limits, including one thing that went wrong
- Prepare answers to the questions your own evidence invites
- Keep to time with a cue from a helper
- Give kind and specific feedback to other teams
Working principle
What a good five minutes looks like
| Part | Time | What it does |
|---|---|---|
| Hook | 30 s | One sentence about a real person with a real problem |
| Live demo | 120 s | The product doing its job on the real input |
| How it works | 60 s | Camera, detection, trigger, output: one diagram |
| Honest result | 60 s | Your numbers with their ranges, and one thing that went wrong |
| Close | 30 s | What it is for, what it is not for, what next |
The checker warns when the plan is over time, when the live demo is under a third of it, when the honest result is missing, and when any part is too short to say anything.
Live matters most
People came to see it work. Do not spend three minutes on slides before the product appears. Start the live product early and talk over it. A small, working thing beats a big, broken thing every time.
The fallback ladder
Things break on demo day: the camera is busy, the projector changes the resolution, the Wi-Fi drops. So you decide in advance:
| Rung | When you use it |
|---|---|
| Live camera | Everything works |
| A recorded clip through the live product | The camera fails but the software works. Say "this is a recording" |
| A recorded video of a good run | The software fails. Say so |
| Slides with screenshots | Nothing runs. Say so |
Always say aloud which rung you are on. Nobody minds a recording that is called a recording. People mind a recording that is passed off as live.
Ten minutes before your slot, run the checks. The first rung that works is your mode. Do not decide on stage.
The honest result
Your acceptance report (lesson 50) already did the hard thinking. Put its decision on one slide: what you aimed for, what you measured, the range, and what failed. A team that says "we caught 10 of 10 goals in one match, so we are fairly sure but not certain, and our false alarms need work" sounds more trustworthy than a team that says "it works".
Questions
Good listeners ask where the evidence is thin. The question builder reads your brief and your verdicts and gives you what a sharp person would ask: why a criterion failed, why one was not measured, how sure you are when the range was wide, what happens when each risk happens, whether it could be misused, and what happened to the pictures. Write an answer to each. The answers you did not expect are the interesting ones, and "we did not test that" is a fine answer when it is true.
Timing
A helper holds up cards. The timer says how far you are from the plan and what to do: on time, shorten the next part, slow down, wrap up, stop. Ahead of time you can rehearse it with a pretend clock in a few seconds. The rule on stage is that you finish when the card says stop, even in the middle of a sentence. Plan the last sentence so it is a good place to stop.
Ethics in the room
Say who was filmed, that they agreed (lesson 14), and what happens to the pictures. If the demo shows people, show only those who agreed. Say what the product is not for. This takes ten seconds and it is the part adults remember.
Giving feedback
Each team gets scores on a short rubric and two sentences from the audience: one thing that worked and one question. Specific beats nice: "your fallback to the recorded clip was smooth" is useful; "good job" is not.
Honest limits
A five-minute demo shows a slice. It cannot show everything that works, nor everything that does not. Say that the report holds the rest.
What you'll need
- Your capstone product, tested in lesson 50
- A recording of it working, as a fallback
- Your acceptance report and slides
Build it
Put demo_day.py and your brief JSON (lesson 47) in one folder. Everything here runs with no camera and no internet. On the day, Cell 5 is run by the helper who keeps time.
Cell 1: the demo tools
"""Demo day tools: a five-minute plan that adds up, a timer with cues, a fallback ladder chosen BEFORE you walk on,
questions the audience is likely to ask (taken from your own evidence), and a score tally for peers."""
import time
PLAN = [("hook", 30, "One sentence: who has this problem?"),
("live demo", 120, "The product doing its job, live, on the real input"),
("how it works", 60, "Camera, detection, trigger, output: one slide or one diagram"),
("honest result", 60, "Your measured numbers with their error bars, and one thing that went wrong"),
("close", 30, "What it is for, what it is not for, what you would do next")]
LIMIT = 300
def check_plan(plan, limit=LIMIT):
"""Does the plan add up, and does it spend its time well?"""
warns = []
total = sum(s for _, s, _ in plan)
if total > limit:
warns.append(f"plan is {total - limit} s over the {limit} s limit: cut something")
if total < limit - 45:
warns.append(f"plan uses only {total} s of {limit} s: you can show more, or leave time for questions")
names = [n for n, _, _ in plan]
if "live demo" not in names:
warns.append("no live demo segment")
elif dict((n, s) for n, s, _ in plan)["live demo"] < 0.3 * total:
warns.append("the live demo is under a third of the time: the audience came to see it working")
if "honest result" not in names:
warns.append("no honest result segment: say what you measured and what went wrong")
if any(s < 15 for _, s, _ in plan):
warns.append("a segment under 15 s is too short to say anything")
return warns
class DemoTimer:
"""Tracks elapsed time against the plan. Give it a clock so tests (and rehearsals) can run fast."""
def __init__(self, plan=PLAN, limit=LIMIT, clock=time.monotonic):
self.plan, self.limit, self.clock = plan, limit, clock
self.start_time = None
self.index = 0
self.seg_start = None
self.behind_at_start = 0.0
def start(self):
self.start_time = self.seg_start = self.clock()
def next_segment(self):
"""Call when you move on. Remembers how far ahead or behind the plan you are."""
planned_start = sum(s for _, s, _ in self.plan[:self.index + 1])
self.index = min(self.index + 1, len(self.plan) - 1)
self.seg_start = self.clock()
self.behind_at_start = (self.seg_start - self.start_time) - planned_start
def status(self):
now = self.clock()
elapsed = now - self.start_time
name, seg_len, _ = self.plan[self.index]
in_seg = now - self.seg_start
overrun = max(0.0, in_seg - seg_len)
behind = self.behind_at_start + overrun
remaining = self.limit - elapsed
if remaining <= 0:
cue = "STOP: time is up"
elif remaining <= 30:
cue = "wrap up now"
elif behind > 20:
cue = f"{behind:.0f} s behind: shorten the next segment"
elif behind < -20:
cue = f"{-behind:.0f} s ahead: you can slow down"
else:
cue = "on time"
return {"elapsed": round(elapsed, 1), "segment": name, "segment_left": round(seg_len - in_seg, 1),
"remaining": round(remaining, 1), "behind": round(behind, 1), "cue": cue}
# The fallback ladder: the highest rung that works, decided ten minutes before, not on stage.
LADDER = ["live camera", "recorded clip through the live product", "recorded video of a good run", "slides with screenshots"]
def choose_mode(checks):
"""checks: {rung: function returning True/False}. A check that raises counts as failed. Returns (rung, why_not_higher)."""
why = []
for rung in LADDER:
fn = checks.get(rung)
try:
ok = bool(fn()) if fn else False
except Exception as e:
ok, err = False, f"{type(e).__name__}: {e}"
why.append(f"{rung}: failed ({err})")
continue
if ok:
return rung, why
why.append(f"{rung}: not available")
return "say it without the demo", why
def hard_questions(brief, verdicts, limit=6):
"""Questions a sharp listener would ask, built from YOUR evidence. Prepare an answer to each."""
qs = []
for v in verdicts:
if v["status"] == "FAIL":
qs.append(f"You aimed for '{v['metric']}' {v['op']} {v['target']} and it failed ({v['note']}). Why, and what would you change?")
elif v["status"] == "NOT MEASURED":
qs.append(f"You promised '{v['metric']}' but did not measure it. Why not?")
for v in verdicts:
if v["status"] == "LIKELY":
qs.append(f"'{v['metric']}' passed, but the note says: {v['note']}. How sure are you?")
for r in brief.get("risks", []):
qs.append(f"What happens when this goes wrong: {r['risk']}?")
if brief.get("not_for"):
qs.append("Could someone use this for something you said it is not for? How would you stop that?")
qs.append("Who did you film, and what happens to the pictures now?")
seen, out = set(), []
for q in qs:
if q not in seen:
seen.add(q); out.append(q)
return out[:limit]
def tally(scores, weights):
"""Peer or teacher scores of 1 to 3 per criterion; weights must add to 100. Returns a percentage."""
if sum(weights.values()) != 100:
raise ValueError("weights must add to 100")
if set(scores) != set(weights):
raise ValueError("scores and weights must name the same criteria")
if any(s not in (1, 2, 3) for s in scores.values()):
raise ValueError("each score must be 1, 2 or 3")
return round(sum(weights[k] * scores[k] / 3 for k in weights), 1)Cell 2: does your plan add up?
from demo_day import PLAN, check_plan
for name, seconds, what in PLAN:
print(f"{seconds:>4} s {name:<14} {what}")
print(check_plan(PLAN) or "plan is fine")
mine = [("hook", 40, ""), ("live demo", 90, ""), ("how it works", 90, ""), ("close", 100, "")] # a plan with problems
for w in check_plan(mine):
print("-", w)Run the second plan and read the warnings: it is over time, the live demo is too small a part, and the honest result is missing.
Cell 3: choose the mode ten minutes before, not on stage
You write one small function per rung of the ladder. Each says yes or no. The first rung that works is your mode.
import os
from demo_day import choose_mode
def camera_works():
import cv2
cap = cv2.VideoCapture(0)
ok, _ = cap.read()
cap.release()
return ok
checks = {
"live camera": camera_works,
"recorded clip through the live product": lambda: os.path.exists("backup_clip.mp4"),
"recorded video of a good run": lambda: os.path.exists("good_run.mp4"),
"slides with screenshots": lambda: os.path.exists("slides.pdf"),
}
mode, why = choose_mode(checks)
print("We demo with:", mode)
for w in why:
print(" not higher because", w)Prepare all four rungs the night before. Falling to the second rung is not failure; it is the plan working.
Cell 4: questions you should expect
This builds the questions from your own brief and your acceptance report (lesson 50). Bring the verdicts from acceptance.py.
import json
from demo_day import hard_questions
brief = json.load(open("example_brief.json"))
verdicts = [
{"metric": "shot recall", "op": ">=", "target": 0.9, "status": "LIKELY", "note": "passes (10 of 10), but the 95 percent range 0.72 to 1.00 crosses the target"},
{"metric": "false counts per 10 minutes", "op": "<=", "target": 1, "status": "FAIL", "note": "measured 3, needed <= 1"},
]
for q in hard_questions(brief, verdicts):
print("-", q)Write one honest sentence under each question. A good answer to "why did it fail?" begins with what you saw and ends with what you would try.
Cell 5: rehearse with the timer
The timer takes a clock, so you can rehearse in a few seconds here, and use the real clock on the day.
from demo_day import DemoTimer
t = [0.0]
timer = DemoTimer(clock=lambda: t[0]) # a pretend clock; for the real day use DemoTimer()
timer.start()
for second, action in [(25, None), (40, "next"), (170, "next"), (200, None), (240, "next"), (270, None), (300, None)]:
t[0] = second
if action == "next":
timer.next_segment()
s = timer.status()
print(f"{s['elapsed']:>5.0f} s {s['segment']:<14} {s['cue']}")On the real day a classmate holds up a card with the cue. Practise taking the cue calmly.
Cell 6: score a demo
from demo_day import tally
weights = {"works live": 30, "honest result": 25, "clarity": 20, "answers": 15, "ethics": 10}
print(tally({"works live": 3, "honest result": 2, "clarity": 3, "answers": 2, "ethics": 3}, weights), "percent")Cell 7: the tests
import json
from demo_day import *
n = 0
def ok(name, cond):
global n
print(("PASS " if cond else "FAIL ") + name); n += bool(cond)
if not cond: raise SystemExit(1)
ok("default plan adds up to 300 and is clean", sum(s for _, s, _ in PLAN) == 300 and check_plan(PLAN) == [])
ok("over time warned", any("over the 300" in w for w in check_plan(PLAN + [("extra", 60, "")])))
ok("too short warned", any("only" in w for w in check_plan([("live demo", 120, ""), ("honest result", 60, "")])))
ok("no live demo warned", any("no live demo" in w for w in check_plan([("hook", 150, ""), ("honest result", 150, "")])))
ok("small live demo warned", any("under a third" in w for w in check_plan([("hook", 100, ""), ("live demo", 60, ""), ("honest result", 120, "")])))
ok("no honest result warned", any("honest result" in w for w in check_plan([("hook", 100, ""), ("live demo", 200, "")])))
ok("tiny segment warned", any("under 15" in w for w in check_plan(PLAN[:4] + [("close", 10, "")])))
t = [0.0]
tm = DemoTimer(clock=lambda: t[0]); tm.start()
t[0] = 10; s = tm.status()
ok("start: on time in hook", s["segment"] == "hook" and s["cue"] == "on time" and s["remaining"] == 290)
t[0] = 55; s = tm.status() # hook was 30 s but we are at 55: 25 s over
ok("overrunning a segment is behind", s["behind"] == 25 and "behind" in s["cue"])
tm.next_segment(); s = tm.status()
ok("next segment keeps the debt", s["segment"] == "live demo" and s["behind"] == 25)
t[0] = 55 + 100; s = tm.status()
ok("catching up inside the segment does not erase the debt", s["behind"] == 25 and s["segment_left"] == 20)
tm.next_segment(); s = tm.status()
ok("live demo ended 20 s early: debt shrinks", s["behind"] == 5 and s["cue"] == "on time")
t[0] = 275; s = tm.status(); ok("last 30 seconds: wrap up", s["cue"] == "wrap up now" or "behind" in s["cue"])
t[0] = 301; ok("past the limit: stop", tm.status()["cue"] == "STOP: time is up")
t2 = [0.0]; tm2 = DemoTimer(clock=lambda: t2[0]); tm2.start(); t2[0] = 5; tm2.next_segment()
ok("running ahead is noticed", tm2.status()["behind"] == -25 and "ahead" in tm2.status()["cue"])
for i in range(10): tm2.next_segment()
ok("advancing past the last segment stays on it", tm2.status()["segment"] == "close")
ok("best rung wins", choose_mode({r: (lambda: True) for r in LADDER})[0] == "live camera")
mode, why = choose_mode({"live camera": lambda: False, "recorded clip through the live product": lambda: True})
ok("falls to the clip and says why", mode == "recorded clip through the live product" and "live camera: not available" in why[0])
def boom(): raise RuntimeError("camera busy")
mode, why = choose_mode({"live camera": boom, "recorded clip through the live product": boom, "recorded video of a good run": lambda: True})
ok("a crashing check counts as failed", mode == "recorded video of a good run" and "RuntimeError: camera busy" in why[0])
ok("nothing works: say it", choose_mode({})[0] == "say it without the demo")
brief = json.load(open("example_brief.json"))
V = [{"metric": "shot recall", "op": ">=", "target": 0.9, "status": "LIKELY", "note": "passes (10 of 10), but the 95 percent range 0.72 to 1.00 crosses the target"},
{"metric": "false counts", "op": "<=", "target": 1, "status": "FAIL", "note": "measured 3, needed <= 1"},
{"metric": "delay", "op": "<=", "target": 1.5, "status": "NOT MEASURED", "note": "no value"}]
qs = hard_questions(brief, V, limit=20)
ok("fail comes first", "failed" in qs[0] and "false counts" in qs[0])
ok("not measured is asked", any("did not measure it" in q for q in qs))
ok("likely is asked with its note", any("How sure are you" in q for q in qs))
ok("each risk is asked", sum("What happens when this goes wrong" in q for q in qs) == len(brief["risks"]))
ok("misuse and consent questions are asked", any("not for" in q for q in qs) and any("Who did you film" in q for q in qs))
ok("limit respected", len(hard_questions(brief, V, limit=4)) == 4)
ok("no duplicates", len(qs) == len(set(qs)))
ok("a perfect result still gets risk and consent questions", len(hard_questions(brief, [], limit=20)) >= 4)
W = {"works live": 30, "honest result": 25, "clarity": 20, "answers": 15, "ethics": 10}
ok("tally all threes is 100", tally({k: 3 for k in W}, W) == 100.0)
ok("tally all ones", tally({k: 1 for k in W}, W) == 33.3)
def bad(f):
try: f(); return False
except ValueError: return True
ok("weights must add to 100", bad(lambda: tally({"a": 3}, {"a": 50})))
ok("scores must be 1 to 3", bad(lambda: tally({k: 4 for k in W}, W)))
ok("scores must match weights", bad(lambda: tally({"works live": 3}, W)))
print(f"\n{n} checks passed")Expected: 33 checks passed.
If it goes wrong
| Error | Cause | Fix |
|---|---|---|
| The camera does not start on stage | Another app has it, or the permission changed | Run the mode checks 10 minutes before. Use the next rung and say so |
| The demo runs but nothing is detected | The light in the room is different from the test | Have a recorded clip ready as the second rung |
| Five minutes is gone after the slides | No time plan | Run check_plan, and put the live demo first |
| A question is hard and the team freezes | The answer was not prepared | Run hard_questions and write answers beforehand |
| A recording is passed off as live | Embarrassment | Always say which rung you are on. It builds trust |
| The projector shows a different resolution | The display changed when plugged in | Test beforehand and keep windows small enough to adjust |
| One team overruns | No timekeeper | Use the timer cards, and stop at the card |
Try it
- Run your own question list on your acceptance report and answer it for another team
- Use the default plan unchanged
Check what you learnt
Why choose your demo mode ten minutes before you present?
Why say out loud which rung of the ladder you're on?
What goes on the honest result slide?
Which question is most useful to prepare for?
What does 'ethics in the room' mean on Demo Day?
Conclusion
A great demo is short, live if possible, and honest. Plan a fallback ladder (live, recording, slides) and say which rung you're on, show what you aimed for and what you measured, including what failed, prepare for the hard question, and only show footage people agreed to.
What next?
Next: Retrospective & what’s next. Last lesson: look back, and plan what's next.

