11 KiB
AWARE (Aviation Weighted Active Recall Engine) — Developer Documentation
Overview
A cross-platform, text-based flashcard quiz application written in Python 3. No external dependencies beyond the standard library. Optional text-to-speech uses OS-native tools (no pip packages required).
Core features:
- 5-bucket weighted spaced repetition
- Cross-platform TTS (Linux, macOS, Windows)
- Multi-deck support with per-deck progress tracking
- Category filtering within decks
- Editable pronunciation glossary for TTS
File Layout
aware/ ← project root (SCRIPT_DIR)
├── aware.py ← main application
├── tts_glossary.json ← TTS pronunciation glossary
├── decks/ ← deck storage
│ ├── g700_oral_study.json ← 299 questions, 15 categories
│ ├── g700_abbreviations.json ← 131 questions, 6 categories
│ └── your_deck.json ← any .json file here is auto-discovered
│
~/.aware/ ← user data (created automatically)
├── progress/ ← per-deck progress files
│ ├── g700_oral_study_a1b2c3d4e5f6.json
│ └── your_deck_f6e5d4c3b2a1.json
└── config.json ← optional TTS override config
Path Resolution
| Item | Location | Notes |
|---|---|---|
SCRIPT_DIR |
Directory containing aware.py |
All relative paths anchor here |
DECKS_DIR |
SCRIPT_DIR/decks/ |
Auto-scanned for .json files on startup |
GLOSSARY_FILE |
SCRIPT_DIR/tts_glossary.json |
Optional; TTS works without it |
PROGRESS_DIR |
~/.aware/progress/ |
Created automatically on first save |
CONFIG_FILE |
~/.aware/config.json |
Optional; overrides TTS backend |
Progress files are named {deck_stem}_{md5_hash}.json where the hash is
derived from the deck's absolute file path. This means moving a deck file
to a different directory creates a new progress file (old progress is
orphaned but not lost).
Deck File Format
Decks are JSON files placed in the decks/ directory. The app supports
two formats: categorized (with topic groupings) and flat (just a list).
Minimal Flat Format
{
"title": "My Study Deck",
"questions": [
{
"question": "What is the answer to this?",
"answer": "This is the answer."
},
{
"question": "Another question?",
"answer": "Another answer."
}
]
}
Required fields per question: question, answer.
Everything else is optional and auto-populated at load time.
Categorized Format (single category example)
{
"title": "My Study Deck",
"categories": [
{
"name": "My Topic",
"questions": [
{
"question": "What is the answer to this?",
"answer": "This is the answer."
},
{
"question": "Another question?",
"answer": "Another answer."
}
]
}
]
}
To add more categories, add more objects to the categories array. Empty
categories are allowed (the app ignores them until populated).
Optional Question Fields
| Field | Type | Default | Purpose |
|---|---|---|---|
id |
int | Auto-assigned (1, 2, 3...) | Tracks progress per question |
ref |
string | "" |
Source reference, displayed after the answer |
category |
string | Parent category name | Set automatically from the category structure |
cas |
string or array | none | CAS message(s) displayed above the question |
detail |
string | none | Extended explanation displayed after the answer |
CAS and Detail Example
{
"question": "What actions would you take?",
"answer": "REFER TO CHECKLIST.",
"ref": "AFM, APU Fire (U)",
"cas": [
"\u001b[1;31;40mAPU FIRE (U)\u001b[0m",
"\u001b[1;33;40m>APU Fail\u001b[0m"
],
"detail": "The APU will automatically shut down when fire is detected. You must still discharge the fire bottle into the APU enclosure by selecting FIRE EXT on the Overhead Panel."
}
cas accepts a single string or an array of strings. Each message is
displayed on its own line above the question, and ANSI color codes
are supported for realistic CAS message presentation. The TTS engine
strips ANSI before speaking but includes the CAS text as context.
detail is displayed after the answer and ref, providing space for
deeper explanation, mnemonics, or study notes without cluttering the
core answer you're trying to memorize.
If you supply your own id values, they must be unique within the deck.
If omitted, IDs are assigned sequentially at load time. Note: auto-assigned
IDs are positional, so inserting questions in the middle of a deck will
shift IDs and orphan existing progress. If you plan to actively edit a deck
while studying it, assign explicit IDs.
Progress Files
Progress is stored as JSON in ~/.aware/progress/. Each deck gets
its own file. The structure:
{
"buckets": {
"1": 3,
"42": 5,
"103": 1
},
"stats": {
"total_sessions": 12,
"total_correct": 85,
"total_wrong": 30,
"total_partial": 45
}
}
buckets maps question ID (as string) to bucket number (1-5). Questions
not present in the map default to bucket 1.
Resetting Progress
Two ways:
- Menu option 7 inside the app (writes zeroed-out progress)
- Delete the progress file directly:
On next launch, the app finds no file and starts from zero.rm ~/.aware/progress/deckname_*.json # single deck rm -rf ~/.aware/progress/ # all decks
Weighted Repetition System
Questions are assigned to 5 buckets. Higher buckets mean better retention. Weighted random selection ensures weak material appears more frequently.
Bucket Weights
| Bucket | Label | Selection Weight | Meaning |
|---|---|---|---|
| 1 | New / No Clue | 8x | Haven't seen it or can't answer at all |
| 2 | Some | 5x | Getting some of the answer |
| 3 | Half | 3x | Getting about half right |
| 4 | Most | 2x | Getting most but not all |
| 5 | Nailed It | 1x | Consistently correct |
A bucket 1 question is 8x more likely to be selected than a bucket 5 question.
Grading
After revealing an answer, the user grades their recall:
| Input | Alias | Action | Description |
|---|---|---|---|
n or Enter |
- |
Set to bucket 1 | Didn't get the answer at all |
1 |
Set to bucket 2 | Got some of the answer | |
2 |
Set to bucket 3 | Got about half right | |
3 |
Set to bucket 4 | Got most of the answer | |
y |
+ |
Promote by 1 (max 5) | Nailed it |
Grades n through 3 set the bucket absolutely — a question in bucket 5
graded as 1 drops to bucket 2. Grade y is the only relative operation:
it promotes by one. This means reaching bucket 5 requires nailing the
question while already in bucket 4, enforcing the "getting it right
regularly" pattern from physical flashcard study.
Other Controls During Quiz
| Input | Action |
|---|---|
v |
Toggle text-to-speech on/off |
q |
Quit session (progress is saved) |
Text-to-Speech
Platform Autodetection
| Platform | Backend | Command |
|---|---|---|
| Linux | espeak-ng | espeak-ng -v en-us -s 160 "text" |
| macOS | say | say -v Alex -r 180 "text" |
| Windows | SAPI via PowerShell | powershell -Command "...SpeechSynthesizer..." |
Detection is by sys.platform. The TTS engine runs as a background
subprocess — it starts when a question is displayed, and is killed when the
user presses Enter to reveal the answer.
If the TTS tool is not installed, the app runs fine without voice. The
v toggle will show an install hint for the current platform.
Custom TTS Override
Create ~/.aware/config.json:
{
"tts_command": "piper",
"tts_args": ["--model", "en_US-lessac-medium", "--output-raw", "{text}"]
}
{text} is replaced with the spoken text at runtime. The custom command
must accept text as an argument and play audio. If the command needs a
pipeline (e.g., piper piping to aplay), wrap it in a shell script and
point tts_command at the script.
Pronunciation Glossary
tts_glossary.json controls how abbreviations are spoken. It sits next
to aware.py and is loaded once at startup.
{
"abbreviations": {
"APU": "A-P-U",
"DU": "display unit",
"FADEC": "FADEC",
"KCAS": "K-CAS"
},
"symbols": {
">=": "greater than or equal to",
"°": " degrees ",
"\n": ". "
}
}
Abbreviations are matched using word-boundary regex (\b), so DU
won't corrupt words like DURING or PROCEDURE. Matches are sorted
longest-first at compile time, so KCAS is caught before CAS.
Symbols are replaced with simple string substitution, sorted by
length (longest first) so >= is matched before >.
If the glossary file is missing, TTS still works — text passes through without any abbreviation expansion.
Menu Structure
Startup
└── Deck Selection (if multiple decks exist; auto-selects if only one)
└── Deck Menu
├── 1) Quick 10
├── 2) Session 25
├── 3) Full 50
├── 4) Custom count
├── 5) By category (only shown if deck has multiple categories)
├── 6) Review missed only (bucket 1 questions)
├── v) Toggle voice
├── 7) Reset deck progress
├── d) Switch deck
└── q) Quit
Key Functions
| Function | Purpose |
|---|---|
main() |
Entry point. Creates decks dir, migrates legacy files, runs deck selection loop |
deck_menu(deck_path, tts) |
Main menu for a loaded deck. Returns "quit" or "switch" |
run_quiz(questions, deck_path, progress, count, title, tts) |
Core quiz loop |
discover_decks() |
Scans decks/ for valid .json files, returns metadata list |
load_deck(path) |
Parses a deck file, returns (questions, categories, title) |
pick_weighted(questions, progress) |
Selects a question using bucket-weighted random |
grade_question(progress, qid, grade) |
Applies a grade, returns new bucket number |
load_progress(deck_path) |
Loads progress JSON or returns fresh defaults |
save_progress(deck_path, progress) |
Writes progress JSON to PROGRESS_DIR |
select_categories(categories) |
Interactive category picker, returns list or None |
review_missed(...) |
Filters to bucket 1 questions and runs a quiz on them |
TTS Class Methods
| Method | Purpose |
|---|---|
TTS() |
Init: autodetect platform, load glossary, check availability |
.toggle() |
Flip on/off. Returns new state. Returns False if unavailable |
.speak(text) |
Clean text and speak in background. Kills any prior speech |
.stop() |
Kill running speech subprocess |
.install_hint() |
Returns platform-specific install instructions string |
ANSI Color
The C class holds ANSI escape codes. C.init() is called at startup and
on Windows attempts to enable ANSI processing via os.system("") (works
on Win10+). If that fails, all color attributes are set to empty strings,
producing uncolored but functional output.
Auto-Migration
On first run, if decks/ is empty, the app scans SCRIPT_DIR for any
.json files that look like valid decks and copies them into decks/.
This handles the case where a user drops a deck file next to aware.py
instead of inside the subdirectory.