Skip to content

Commit 4ca3b47

Browse files
travisjneumanclaude
andcommitted
feat: add flashcard system, auto-grader, diagnostics, and progress dashboard
- Spaced repetition flashcards: 7 level decks (175 cards) + Leitner box runner - Auto-grading test runner with pedagogical feedback and concept hints - Diagnostic assessments for Gate A and Levels 0-3 (placement tests) - Progress dashboard with streak tracking and next-step recommendations - Updated PROGRESS.md with practice tracking sections - Updated .gitignore for flashcard review state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 002d3e0 commit 4ca3b47

21 files changed

Lines changed: 3696 additions & 0 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ venv/
55
.env
66
.ipynb_checkpoints/
77
*.ipynb_checkpoints/
8+
9+
# Flashcard review state (personal progress)
10+
practice/flashcards/.review-state.json

PROGRESS.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,74 @@ Check off items as you complete them. This is your personal tracker.
222222
- [ ] 01 Deploy to Railway
223223
- [ ] 02 Deploy with Database
224224
- [ ] 03 Production Checklist
225+
226+
---
227+
228+
## Practice — Active Recall & Skill Building
229+
230+
### Concept Quizzes (after reading each concept doc)
231+
- [ ] What is a Variable quiz
232+
- [ ] How Loops Work quiz
233+
- [ ] Functions Explained quiz
234+
- [ ] Collections Explained quiz
235+
- [ ] Files and Paths quiz
236+
- [ ] Errors and Debugging quiz
237+
- [ ] Types and Conversions quiz
238+
- [ ] How Imports Work quiz
239+
- [ ] Classes and Objects quiz
240+
- [ ] Decorators Explained quiz
241+
- [ ] Virtual Environments quiz
242+
- [ ] The Terminal Deeper quiz
243+
- [ ] HTTP Explained quiz
244+
- [ ] API Basics quiz
245+
- [ ] Async Explained quiz
246+
247+
### Flashcard Review
248+
- [ ] Level 00 deck reviewed (25 cards)
249+
- [ ] Level 0 deck reviewed (25 cards)
250+
- [ ] Level 1 deck reviewed (25 cards)
251+
- [ ] Level 2 deck reviewed (25 cards)
252+
- [ ] Level 3 deck reviewed (25 cards)
253+
- [ ] Level 4 deck reviewed (25 cards)
254+
- [ ] Level 5 deck reviewed (25 cards)
255+
256+
### Coding Challenges — Beginner
257+
- [ ] 01 Swap Variables
258+
- [ ] 02 FizzBuzz
259+
- [ ] 03 Reverse String
260+
- [ ] 04 Count Vowels
261+
- [ ] 05 Palindrome Check
262+
- [ ] 06 Sum of Digits
263+
- [ ] 07 Find Max
264+
- [ ] 08 Remove Duplicates
265+
- [ ] 09 Word Frequency
266+
- [ ] 10 Caesar Cipher
267+
- [ ] 11 Flatten List
268+
- [ ] 12 Matrix Transpose
269+
- [ ] 13 Binary Search
270+
- [ ] 14 Merge Sorted
271+
- [ ] 15 Anagram Check
272+
273+
### Coding Challenges — Intermediate
274+
- [ ] 01 Decorator Timer
275+
- [ ] 02 Context Manager
276+
- [ ] 03 Generator Pipeline
277+
- [ ] 04 Class Registry
278+
- [ ] 05 Retry Decorator
279+
- [ ] 06 LRU Cache
280+
- [ ] 07 Event Emitter
281+
- [ ] 08 Validate Schema
282+
- [ ] 09 Parse Log File
283+
- [ ] 10 Batch Processor
284+
- [ ] 11 Rate Limiter
285+
- [ ] 12 Tree Traversal
286+
- [ ] 13 Async Fetcher
287+
- [ ] 14 CLI Parser
288+
- [ ] 15 Plugin Loader
289+
290+
### Diagnostic Assessments
291+
- [ ] Gate A diagnostic completed
292+
- [ ] Level 0 diagnostic completed
293+
- [ ] Level 1 diagnostic completed
294+
- [ ] Level 2 diagnostic completed
295+
- [ ] Level 3 diagnostic completed

practice/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Practice — Active Recall & Skill Building
2+
3+
This directory contains practice tools that complement the main project ladder. Use these between projects to reinforce concepts and build fluency.
4+
5+
## What's Here
6+
7+
### Flashcards (`flashcards/`)
8+
Spaced repetition flashcard decks for each level. Review cards regularly to retain concepts long-term.
9+
10+
```bash
11+
python practice/flashcards/review-runner.py
12+
```
13+
14+
### Coding Challenges (`challenges/`)
15+
Short, focused exercises (10-30 minutes each) that reinforce specific patterns. Not full projects — just targeted practice reps.
16+
17+
```bash
18+
# Try a challenge
19+
python practice/challenges/beginner/01-swap-variables.py
20+
21+
# Check your solution
22+
python practice/challenges/solutions/beginner/01-swap-variables-solution.py
23+
```
24+
25+
## When to Use These
26+
27+
| Tool | When | Time |
28+
|------|------|------|
29+
| **Flashcards** | Daily, before starting project work | 5-10 min |
30+
| **Challenges** | Between projects, or when you want quick practice | 10-30 min |
31+
| **Concept quizzes** | After reading a concept doc (see `concepts/quizzes/`) | 5-10 min |
32+
33+
## How They Fit the Curriculum
34+
35+
```
36+
Read concept doc → Take quiz → Do projects → Review flashcards → Try challenges
37+
↑ |
38+
└──────────── review when cards appear ────────┘
39+
```
40+
41+
The flashcards use a **Leitner box system** — cards you get right move to higher boxes and appear less often. Cards you get wrong drop back to box 1 for immediate review.

practice/flashcards/README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Flashcards — Spaced Repetition Review
2+
3+
Flashcard decks for each curriculum level. Cards test recall of key concepts, syntax patterns, and common pitfalls.
4+
5+
## Quick Start
6+
7+
```bash
8+
# Review all due cards
9+
python practice/flashcards/review-runner.py
10+
11+
# Review a specific level
12+
python practice/flashcards/review-runner.py --level 0
13+
14+
# See your stats
15+
python practice/flashcards/review-runner.py --stats
16+
```
17+
18+
## How It Works
19+
20+
The runner uses the **Leitner box system**:
21+
22+
| Box | Review Interval | Meaning |
23+
|-----|----------------|---------|
24+
| 1 | Every session | New or recently wrong |
25+
| 2 | Every 2 days | Getting it |
26+
| 3 | Every 4 days | Know it |
27+
| 4 | Every 8 days | Solid |
28+
| 5 | Every 16 days | Mastered |
29+
30+
- **Correct answer** → card moves up one box
31+
- **Wrong answer** → card drops back to box 1
32+
- Cards are shown front-first; you think of the answer, then press Enter to reveal
33+
34+
## Card Decks
35+
36+
| File | Level | Cards | Topics |
37+
|------|-------|-------|--------|
38+
| `level-00-cards.json` | Absolute Beginner | 25 | Variables, print, input, basic types |
39+
| `level-0-cards.json` | Terminal & I/O | 25 | File I/O, string methods, loops, conditionals |
40+
| `level-1-cards.json` | Functions | 25 | def, return, scope, parameters, docstrings |
41+
| `level-2-cards.json` | Collections | 25 | Lists, dicts, sets, comprehensions, sorting |
42+
| `level-3-cards.json` | File Automation | 25 | pathlib, os, shutil, glob, CSV |
43+
| `level-4-cards.json` | JSON & Data | 25 | json module, nested data, schemas, validation |
44+
| `level-5-cards.json` | Exceptions | 25 | try/except, custom exceptions, logging, context managers |
45+
46+
## Card Format
47+
48+
Each card has:
49+
- **front**: The question or prompt
50+
- **back**: The answer
51+
- **concept_ref**: Link to the relevant concept doc
52+
- **difficulty**: 1 (easy), 2 (medium), 3 (hard)
53+
- **tags**: Topic labels for filtering
54+
55+
## Progress File
56+
57+
Your review progress is saved to `practice/flashcards/.review-state.json` (git-ignored). This tracks which box each card is in and when it's due for review.
58+
59+
## Tips
60+
61+
- Review daily, even for just 5 minutes
62+
- Don't peek at the back — test yourself honestly
63+
- If a card is too easy, you'll naturally see it less often
64+
- If you keep getting one wrong, go back to the concept doc
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
{
2+
"deck": "Level 0 — Terminal and Basic I/O",
3+
"description": "File I/O, string methods, terminal interaction, basic data processing",
4+
"cards": [
5+
{
6+
"id": "0-01",
7+
"front": "How do you open and read a file in Python?",
8+
"back": "with open(\"file.txt\") as f:\n content = f.read()\n\nThe with statement automatically closes the file.\nUse \"r\" mode (default) for reading.",
9+
"concept_ref": "concepts/files-and-paths.md",
10+
"difficulty": 1,
11+
"tags": ["files", "reading"]
12+
},
13+
{
14+
"id": "0-02",
15+
"front": "What is the difference between read(), readline(), and readlines()?",
16+
"back": "read() → entire file as one string\nreadline() → one line at a time\nreadlines() → list of all lines\n\nFor large files, iterate directly:\nfor line in f:",
17+
"concept_ref": "concepts/files-and-paths.md",
18+
"difficulty": 2,
19+
"tags": ["files", "reading"]
20+
},
21+
{
22+
"id": "0-03",
23+
"front": "How do you write to a file?",
24+
"back": "with open(\"file.txt\", \"w\") as f:\n f.write(\"Hello\\n\")\n\n\"w\" = write (overwrites)\n\"a\" = append (adds to end)\n\"x\" = create (fails if exists)",
25+
"concept_ref": "concepts/files-and-paths.md",
26+
"difficulty": 1,
27+
"tags": ["files", "writing"]
28+
},
29+
{
30+
"id": "0-04",
31+
"front": "What does .strip() do to a string?",
32+
"back": "Removes whitespace (spaces, tabs, newlines) from both ends.\n\n\" hello \".strip() → \"hello\"\n\"hello\\n\".strip() → \"hello\"\n\n.lstrip() = left only, .rstrip() = right only",
33+
"concept_ref": "concepts/what-is-a-variable.md",
34+
"difficulty": 1,
35+
"tags": ["strings", "methods"]
36+
},
37+
{
38+
"id": "0-05",
39+
"front": "What does .split() do?",
40+
"back": "Splits a string into a list of substrings.\n\n\"hello world\".split() → [\"hello\", \"world\"]\n\"a,b,c\".split(\",\") → [\"a\", \"b\", \"c\"]\n\"one two\".split() → [\"one\", \"two\"] (default splits on any whitespace)",
41+
"concept_ref": "concepts/what-is-a-variable.md",
42+
"difficulty": 1,
43+
"tags": ["strings", "methods"]
44+
},
45+
{
46+
"id": "0-06",
47+
"front": "What does .join() do?",
48+
"back": "Joins a list of strings with a separator.\n\n\", \".join([\"a\", \"b\", \"c\"]) → \"a, b, c\"\n\"\\n\".join(lines) → lines joined by newlines\n\nIt's the opposite of .split()",
49+
"concept_ref": "concepts/what-is-a-variable.md",
50+
"difficulty": 2,
51+
"tags": ["strings", "methods"]
52+
},
53+
{
54+
"id": "0-07",
55+
"front": "How do you check if a string contains a substring?",
56+
"back": "Use the in operator:\n\n\"hello\" in \"hello world\" → True\n\"xyz\" in \"hello world\" → False\n\nAlso: \"hello world\".find(\"hello\") → 0 (index), -1 if not found",
57+
"concept_ref": "concepts/what-is-a-variable.md",
58+
"difficulty": 1,
59+
"tags": ["strings", "operators"]
60+
},
61+
{
62+
"id": "0-08",
63+
"front": "What is the difference between a syntax error and a runtime error?",
64+
"back": "Syntax error: code is written wrong, Python can't even start\n if x == 5 # missing colon\n\nRuntime error: code starts running but fails during execution\n x = 1 / 0 # ZeroDivisionError",
65+
"concept_ref": "concepts/errors-and-debugging.md",
66+
"difficulty": 2,
67+
"tags": ["errors", "debugging"]
68+
},
69+
{
70+
"id": "0-09",
71+
"front": "How do you handle errors with try/except?",
72+
"back": "try:\n result = int(input(\"Number: \"))\nexcept ValueError:\n print(\"That's not a number!\")\n\nCode in try runs normally.\nIf it raises an error, except catches it.",
73+
"concept_ref": "concepts/errors-and-debugging.md",
74+
"difficulty": 2,
75+
"tags": ["errors", "exception-handling"]
76+
},
77+
{
78+
"id": "0-10",
79+
"front": "What does enumerate() do in a for loop?",
80+
"back": "Gives you both the index and the value.\n\nfor i, name in enumerate([\"Alice\", \"Bob\"]):\n print(f\"{i}: {name}\")\n# 0: Alice\n# 1: Bob\n\nAvoids manual counter variables.",
81+
"concept_ref": "concepts/how-loops-work.md",
82+
"difficulty": 2,
83+
"tags": ["loops", "enumerate"]
84+
},
85+
{
86+
"id": "0-11",
87+
"front": "What does break do in a loop?",
88+
"back": "Immediately exits the loop.\n\nfor n in range(100):\n if n > 5:\n break\n print(n)\n# prints 0, 1, 2, 3, 4, 5",
89+
"concept_ref": "concepts/how-loops-work.md",
90+
"difficulty": 1,
91+
"tags": ["loops", "control-flow"]
92+
},
93+
{
94+
"id": "0-12",
95+
"front": "What does continue do in a loop?",
96+
"back": "Skips the rest of the current iteration and goes to the next.\n\nfor n in range(5):\n if n == 2:\n continue\n print(n)\n# prints 0, 1, 3, 4 (skips 2)",
97+
"concept_ref": "concepts/how-loops-work.md",
98+
"difficulty": 1,
99+
"tags": ["loops", "control-flow"]
100+
},
101+
{
102+
"id": "0-13",
103+
"front": "How do you sort a list?",
104+
"back": "Two ways:\n\nsorted(my_list) → returns a NEW sorted list\nmy_list.sort() → sorts IN PLACE (modifies original)\n\nBoth accept reverse=True for descending order.",
105+
"concept_ref": "concepts/collections-explained.md",
106+
"difficulty": 2,
107+
"tags": ["lists", "sorting"]
108+
},
109+
{
110+
"id": "0-14",
111+
"front": "What is a boolean and what values can it have?",
112+
"back": "A boolean is a True/False value.\n\nTrue and False (capitalized in Python)\n\nComparisons return booleans:\n5 > 3 → True\n5 == 3 → False",
113+
"concept_ref": "concepts/types-and-conversions.md",
114+
"difficulty": 1,
115+
"tags": ["types", "booleans"]
116+
},
117+
{
118+
"id": "0-15",
119+
"front": "What are truthy and falsy values in Python?",
120+
"back": "Falsy: False, 0, 0.0, \"\", [], {}, None\nTruthy: everything else\n\nif my_list: # True if list is not empty\nif name: # True if string is not empty",
121+
"concept_ref": "concepts/types-and-conversions.md",
122+
"difficulty": 3,
123+
"tags": ["types", "booleans", "truthy"]
124+
},
125+
{
126+
"id": "0-16",
127+
"front": "How do you get just the file name from a full path?",
128+
"back": "from pathlib import Path\n\npath = Path(\"/home/user/data.txt\")\npath.name → \"data.txt\"\npath.stem → \"data\"\npath.suffix → \".txt\"\npath.parent → Path(\"/home/user\")",
129+
"concept_ref": "concepts/files-and-paths.md",
130+
"difficulty": 2,
131+
"tags": ["files", "pathlib"]
132+
},
133+
{
134+
"id": "0-17",
135+
"front": "What does .replace() do on a string?",
136+
"back": "Returns a new string with replacements made.\n\n\"hello world\".replace(\"world\", \"Python\")\n\"hello Python\"\n\nStrings are immutable — replace() returns a NEW string.",
137+
"concept_ref": "concepts/what-is-a-variable.md",
138+
"difficulty": 1,
139+
"tags": ["strings", "methods"]
140+
},
141+
{
142+
"id": "0-18",
143+
"front": "What is None in Python?",
144+
"back": "None represents \"no value\" or \"nothing\".\n\nFunctions without return give None.\nUsed as a default/placeholder.\n\nCheck with: if x is None (not ==)",
145+
"concept_ref": "concepts/types-and-conversions.md",
146+
"difficulty": 2,
147+
"tags": ["types", "None"]
148+
},
149+
{
150+
"id": "0-19",
151+
"front": "How do you check if a file exists before opening it?",
152+
"back": "from pathlib import Path\n\nif Path(\"data.txt\").exists():\n # safe to open\n\nAlso: .is_file(), .is_dir()",
153+
"concept_ref": "concepts/files-and-paths.md",
154+
"difficulty": 2,
155+
"tags": ["files", "pathlib"]
156+
},
157+
{
158+
"id": "0-20",
159+
"front": "What does this print?\n\nfor i in range(3):\n for j in range(2):\n print(f\"{i},{j}\", end=\" \")",
160+
"back": "0,0 0,1 1,0 1,1 2,0 2,1\n\nNested loops: inner loop runs completely for each outer iteration.\n3 × 2 = 6 total prints.",
161+
"concept_ref": "concepts/how-loops-work.md",
162+
"difficulty": 3,
163+
"tags": ["loops", "nested"]
164+
},
165+
{
166+
"id": "0-21",
167+
"front": "What is string slicing?",
168+
"back": "Extract parts of a string with [start:stop:step].\n\ns = \"Python\"\ns[0:3] → \"Pyt\"\ns[2:] → \"thon\"\ns[::-1] → \"nohtyP\" (reversed)\n\nstart is inclusive, stop is exclusive.",
169+
"concept_ref": "concepts/what-is-a-variable.md",
170+
"difficulty": 2,
171+
"tags": ["strings", "slicing"]
172+
},
173+
{
174+
"id": "0-22",
175+
"front": "How do you convert a list to a string?",
176+
"back": "\", \".join([\"apple\", \"banana\", \"cherry\"])\n\"apple, banana, cherry\"\n\nAll items must be strings. Convert first if needed:\n\", \".join(str(x) for x in [1, 2, 3])",
177+
"concept_ref": "concepts/what-is-a-variable.md",
178+
"difficulty": 2,
179+
"tags": ["strings", "lists", "conversion"]
180+
},
181+
{
182+
"id": "0-23",
183+
"front": "What is the with statement used for?",
184+
"back": "Automatic resource management (like closing files).\n\nwith open(\"file.txt\") as f:\n data = f.read()\n# f is automatically closed here\n\nPrevents resource leaks even if errors occur.",
185+
"concept_ref": "concepts/files-and-paths.md",
186+
"difficulty": 2,
187+
"tags": ["files", "context-manager"]
188+
},
189+
{
190+
"id": "0-24",
191+
"front": "How do you run a Python script from the terminal?",
192+
"back": "python script.py\n\nOr with arguments:\npython script.py arg1 arg2\n\nAccess args in code:\nimport sys\nprint(sys.argv) # [\"script.py\", \"arg1\", \"arg2\"]",
193+
"concept_ref": "concepts/the-terminal-deeper.md",
194+
"difficulty": 1,
195+
"tags": ["terminal", "running"]
196+
},
197+
{
198+
"id": "0-25",
199+
"front": "What does .upper(), .lower(), and .title() do?",
200+
"back": "\"hello\".upper() → \"HELLO\"\n\"HELLO\".lower() → \"hello\"\n\"hello world\".title() → \"Hello World\"\n\nAll return new strings (strings are immutable).",
201+
"concept_ref": "concepts/what-is-a-variable.md",
202+
"difficulty": 1,
203+
"tags": ["strings", "methods"]
204+
}
205+
]
206+
}

0 commit comments

Comments
 (0)