Terminal exam simulator for the French civics test (naturalization / CSP residency permit).
40 questions, 45-minute countdown, dynamic difficulty tracking across sessions.
ec-in-cli-proto-01.mp4
uv is the fastest way to get started — no manual venv, no pip.
cd ec-in-cli/
uv sync # install deps, create venv (like npm install)
uv run civ --sample # demo mode
uv run civ # full dataset
# Optional: enable Convex sync
uv sync --extra sync # also installs httpx + python-dotenvuv sync reads pyproject.toml and pins exact versions in uv.lock.
pip install richPython 3.10 or higher is required — the app uses union type syntax (X | Y) introduced in 3.10. It will not run on 3.9 or below.
No database required. Sync with Convex is optional — httpx and python-dotenv are only needed if you enable it.
cd ec-in-cli/
# Demo mode — uses the bundled sample dataset
python -m civ --sample
# Full dataset (see Dataset section below)
python -m civ| Flag | Default | Description |
|---|---|---|
--sample |
off | Use dataset_sample.json (40 QA-ed questions, simulates a real exam draw) |
--dataset FILE / -d |
unified_dataset_complete.json |
Path to a custom dataset |
--mode MODE / -m |
interactive | Skip mode selection: super-hard or marathon |
--tutorial |
off | Force-show the tutorial even if already seen |
python3 -m civ --sample
python3 -m civ --dataset ../unified_dataset_yours.json
python3 -m civ --mode marathon
python3 -m civ --sample --tutorialsuper-hard — 40 questions, 45 minutes, 80% threshold (32/40 to pass).
marathon — multiple 40-question blocks back-to-back (you pick the number of blocks).
Both modes let you filter by theme before starting.
| Key | Action |
|---|---|
↑ / ↓ or 1–4 |
Navigate / select answer |
← / → |
Previous / next question |
M |
Flag question for review (FLAGGED) |
Tab |
Mark answer as low-confidence |
Enter |
Confirm selection |
S |
Submit exam (opens review loop for flagged/unanswered) |
Q |
Quit |
At submission, you can navigate flagged and unanswered questions with ← / → before finalising.
unified_dataset_complete.json is the default dataset and is expected inside ec-in-cli/. If it's missing, copy or symlink it from the project root:
ln -s ../unified_dataset_yours.json unified_dataset_complete.json
python3 -m civIf the default dataset file is missing, the app exits with a red error and points you to --sample.
Any JSON file that follows the schema below will work. Pass it with --dataset:
[
{
"id": "conn-0001",
"source": "connaissances",
"theme": "Principes et valeurs de la République",
"type": "connaissance",
"question": "Quelle est la devise de la République française ?",
"answers": [
"Liberté, Égalité, Fraternité",
"Travail, Famille, Patrie",
"Liberté, Égalité, Solidarité",
"Unité, Fraternité, Progrès"
],
"correct_index": 0,
"explication": "La devise officielle est gravée sur les frontons des bâtiments publics depuis la Révolution.",
"options_generees_ia": false,
"needs_manual_review": false,
"qa_flag_type": null
}
]Rules:
correct_indexmust be0— the app shuffles answers at display time.- Each question needs exactly 4 entries in
answers. Incomplete questions are silently skipped. thememust be one of the 5 official themes (see below) or it will be excluded from theme filtering.
- Principes et valeurs de la République
- Système institutionnel et politique
- Droits et devoirs
- Histoire, géographie et culture
- Vivre dans la société française
After each session, per-question stats are persisted in civ_stats.json (created automatically):
score = error_rate×0.5 + speed_norm×0.3 + views×0.2
< 0.3 → standard 0.3–0.6 → hard > 0.6 → piège
The startup screen shows your difficulty distribution and the last 3 sessions.
ec-in-cli/
├── civ/
│ ├── __main__.py # entry point
│ ├── cli.py # argument parsing
│ ├── loader.py # JSON loading + validation
│ ├── menus.py # theme / mode selection
│ ├── engine.py # exam loop, timer, navigation
│ ├── models.py # Question, AnswerSlot, ExamState
│ ├── ui.py # Rich console, TRICOLOR theme, getch
│ ├── results.py # scoring, session persistence, results screen
│ ├── stats.py # difficulty scoring
│ └── tutorial.py # first-launch tutorial
├── dataset_sample.json # 40 QA-ed questions (28 conn. + 12 mise-sit.) — one real exam draw
└── README.md