348 lines
11 KiB
Markdown
348 lines
11 KiB
Markdown
# 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
|
|
|
|
```json
|
|
{
|
|
"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)
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
1. Menu option 7 inside the app (writes zeroed-out progress)
|
|
2. Delete the progress file directly:
|
|
```bash
|
|
rm ~/.aware/progress/deckname_*.json # single deck
|
|
rm -rf ~/.aware/progress/ # all decks
|
|
```
|
|
On next launch, the app finds no file and starts from zero.
|
|
|
|
|
|
## 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`:
|
|
|
|
```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.
|
|
|
|
```json
|
|
{
|
|
"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.
|