Automate something in your actual life. The student selects a real, repeated, tedious task they face, then ships the tool that automates it.
The brief
Pick one task you actually repeat. Not a hypothetical example from a textbook; not a tutorial exercise. Something you, the student, would do again next week if the tool did not exist. Build a Python CLI tool that does that task. Ship it with tests, documentation, and a Git history. Present it to the cohort in five minutes plus Q&A.
The constraint is the only one that matters: the tool must be something you would actually use after this course ends. If you would not run it next month, pick a different task.
What you ship
A Git repository containing:
- The tool itself: one or more
.pyfiles implementing your automation argparseinterface: every program option is documented in--helpREADME.md: how to install, how to run, one worked exampletests/directory: at least threepytesttests; one test must cover an edge case you found by breaking the tool- A Git history with at least five commits: each commit message a sentence; no "fix" or "wip" or "update"
- A 5-minute demo + a 5-minute Q&A at the capstone presentation block
Repository naming convention: fnd102-capstone-{your-name} (lowercase, hyphens, no spaces). Example: fnd102-capstone-jamie-smith.
What "automation" means here
A task counts as automation-worthy if it has these three properties:
- You do it more than once. Not a one-time chore. Something you repeat weekly, monthly, or every time a triggering condition occurs.
- The steps are precise. You can write them down as a recipe. A human could follow your recipe and get the right answer without judgment calls.
- It involves data that lives on your computer or on the public internet. No tasks that require physical action (no Raspberry-Pi robotics here; that is HW-101). No tasks that need a paid API or scraping a site that forbids it.
Tasks that DO fit:
- Renaming photos from a trip into a consistent format using EXIF dates
- Pulling sports stats from a public API and writing them into a CSV for spreadsheet analysis
- Generating a weekly chore-rotation schedule for a household with N people
- Backing up a directory to cloud storage with SHA-256 verification of every file
- Scanning a directory of receipts and generating a monthly expense summary
- Building a personal RSS-feed digest that emails you a weekly summary
- Generating a grocery list from a meal plan stored as a JSON file
- Renaming downloaded e-books to a consistent
Author - Title.extformat
Tasks that do NOT fit:
- Anything that needs a paid API (no Twitter, no GPT-4, no commercial APIs costing money)
- Anything that needs JavaScript to render the page (Selenium / headless browser is out of scope)
- Anything that violates a site's terms of service
- "Build a web app"; that is a different course (and a different effort budget)
- "Train a machine learning model"; out of scope; that is AI-101
- A toy reimplementation of something
sortorgrepalready does cleanly
Exemplar capstones (three full specs)
You may pick one of these verbatim, modify one of them for your situation, or invent your own. Most students do the third. These are concrete enough that a student can scope and ship one in the available time.
Exemplar 1: Photo Renamer
Real task: I have ~200 photos from each trip dumped into a folder with names like IMG_20240601_142337.JPG. I want them named 2024-06-01_paris_001.jpg, 2024-06-01_paris_002.jpg, etc.
Tool: photo-renamer.py SOURCE_DIR --trip-name "paris" [--dry-run]
Behavior:
- Walks SOURCE_DIR for JPEG / HEIC / PNG files
- Reads the EXIF "DateTimeOriginal" tag (use Pillow library, freely installable via pip)
- Renames each file to
{date}_{trip-name}_{seq:03d}.{ext} --dry-runprints what it would do without renaming- Skips non-image files; warns on missing EXIF dates and falls back to file mtime
Tests:
- Test 1 (happy path): given a directory of 3 fake JPEGs with known EXIF dates, the rename produces the expected filenames
- Test 2 (no-EXIF fallback): given a JPEG with no EXIF tag, the rename uses the file's modification time
- Test 3 (edge case): given a file that already has the target name, the rename is a no-op (not a duplicate-key error)
Why this is a fit: real recurring task, precise steps, files-only (no APIs).
Exemplar 2: Weekly Chore Scheduler
Real task: my household has 4 people and 6 weekly chores (dishes, trash, vacuum, bathrooms, kitchen, lawn). I want a fair rotation that does not repeat the same person on the same chore two weeks in a row.
Tool: chore-scheduler.py --config chores.json --week WEEK_NUMBER [--print|--csv]
Behavior:
- Reads
chores.jsonwith the people list and chores list - Computes a rotation that gives each person ~1.5 chores per week (rounding up where N chores does not divide evenly into M people)
- Avoids assigning a person their previous week's chore (use the deterministic seed
WEEK_NUMBERso re-runs give the same answer) --printshows a human-readable table;--csvemits a row per (week, person, chore)
Tests:
- Test 1: with 4 people and 6 chores over 4 weeks, every person gets 6 chores total and no person repeats their previous chore
- Test 2: the same
WEEK_NUMBERalways produces the same assignment (deterministic) - Test 3: a config with N people and N chores produces exactly one chore per person per week
Why this is a fit: the student actually has chores; the script answers a real weekly question.
Exemplar 3: Sports-Stats Scraper
Real task: I track my favorite NBA team's stats every week during the season. I currently copy-paste from a stats website into a spreadsheet, which takes 15 minutes weekly.
Tool: nba-stats.py --team-id TEAM_ID --season SEASON --out FILE.csv
Behavior:
- Uses a free NBA stats API (e.g.
https://www.balldontlie.io/; verify the terms-of-service before relying on it; or the official NBA stats endpoints athttps://stats.nba.com/) - Fetches the team's most recent N games (default 10)
- Writes one row per game: date, opponent, score, win/loss, top scorer, assists leader, rebounds leader
- Appends to FILE.csv if it exists (does not duplicate rows; uses game ID as the key)
Tests:
- Test 1: a mocked API response with 3 games produces a 3-row CSV with the expected columns
- Test 2: re-running with the same data does not produce duplicates
- Test 3: an empty API response (no games yet) produces a CSV with only the header row
Why this is a fit: the student actually wants this data; the manual workflow is concrete.
Two-tier grading rubric
| Tier | Weight | What is graded |
|---|---|---|
| Code quality + craft | 40% | Functions are well-named and have docstrings; the code reads like prose; no dead code; argparse --help reads like documentation; exit codes are correct |
| Tests + docs | 30% | pytest tests cover the tool's documented behavior, including one regression test for a bug you found; README answers what / install / run in under 200 words |
| Reflective depth | 30% | The demo includes one paragraph on the bug you found and fixed, and one paragraph on what you would do differently next time |
Reflective depth is weighted as heavily as testing because the academy is teaching practitioners. A capstone that ships clean code but cannot describe its own failure modes is half-finished.
Success criteria
Your capstone is graded on three things:
- It works. Running it from a fresh clone with your README's instructions produces the documented output.
- It is yours. The task is one you actually have. The instructor can tell when a student picked a textbook exercise off Real Python and rebadged it; the reflection paragraph in the demo always gives it away.
- It is shippable. A peer in the cohort can pull your repo, follow the README, and run your tool without asking you a question.
There is no minimum tool complexity. A 50-line, well-tested, well-documented photo-renamer earns full credit. A 500-line, undocumented, untested everything-app does not.
What the capstone does NOT require
- No web framework (Flask, Django, FastAPI). Out of scope.
- No database (SQLite, Postgres). Use JSON / CSV files for state.
- No machine learning. Out of scope.
- No GUI. CLI only.
- No deployment. Ship to GitHub / GitLab; no server hosting required.
- No custom CI. The graders run your tests with
pytestfrom a fresh clone.
Timeline
| When | What |
|---|---|
| Week 13 | Brainstorm 3-5 candidate tasks during week 13's reflection; bring them to office hours |
| Week 14, day 1-2 | Pick one task; sketch the argparse interface on paper before any code |
| Week 14, day 2 (scope-check meeting with instructor) | 10-minute conversation; instructor pushes back on scope. Trim if needed. |
| Week 14, days 3-5 | Build the happy path; write the README as you go |
| Week 14, days 5-6 | Write at least 3 pytest tests; one must catch a bug you found |
| Week 14, day 6 | Polish: --help output, edge cases, commit history clean-up |
| Capstone presentation block | 5-minute demo + 5-minute Q&A with the cohort |
Submission
Push your capstone repository to GitHub or GitLab and email the URL to interested@virtuscyberacademy.org with subject FND-102 capstone, {your-name}. Include in the email:
- The repository URL
- A one-sentence description of the real task you automated
- The one bug you found and fixed (a sentence; the full version is in the demo)
The course team replies within 7 days with the grade and brief feedback. Outstanding capstones (top decile per cohort) are invited to be added to the academy's public capstone showcase with the student's permission.
After the capstone
A finished FND-102 capstone is a portfolio piece. It demonstrates: Python fluency, CLI design sense, testing discipline, Git workflow, documentation, and the practitioner habit of automating real work. Students applying to internships or first jobs after the academy cite their capstone repository in cover letters and bring up the "one bug I found" story in interviews. The exercise of articulating the bug and the fix in plain English is, in many cases, the most directly career-relevant thing you do all course.
Capstone specification v0.1.