Production-oriented CLI for ENSAM SchoolApp with semester namespaces:
schoolapp s3 ...schoolapp s4 ...
- Semester-first commands (
s3,s4) - Watch mode with snapshot diffing
- Interactive weighted calculator
- Module listing per semester
- Optional SMTP notifications
- Rich output (tables, panels, color logs)
Fabricated data: the module codes and coefficients are the real published ones, so the règlement walkthrough uses the coefficients it would actually use, but every mark, ranking and change below is invented.
Who you are, the latest results, the change feed, and how stale each cached resource is. No network involved.
schoolapp explain API421. An element sent to rattrapage, walked through
article by article. The rows marked calculé are the ones the portal has not
published yet, computed from the official text rather than waited for.
Commands are grouped by what they cost: reads that never leave the machine, fetches that log into the portal, and the server the plasmoid and the Android app read.
cd /home/notadil/Projects/schoolapp_watcher
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e .python3 -m pip install --user -e .If schoolapp is not found, ensure your user bin path is in PATH.
schoolapp # dashboard: profile, latest results, freshness, recent changes
schoolapp dashboard # …the same, explicitly
schoolapp changes # change history - the same feed the email digests narrate
schoolapp explain API421 # règlement walkthrough for one element (SO/SR/decision + articles)
schoolapp csv grades -o m.csv # export marks/rankings as CSV for a spreadsheet
schoolapp show grades # offline view of any cached resource (mirrors the API)
schoolapp fetch-all # log in, fetch everything, write the store
schoolapp serve # expose the store over the API the plasmoid/Android app read
schoolapp s3 list | calc | watch
schoolapp s4 list | calc | watch | table
Commands are grouped in --help by panel: Read (offline, from the store),
Live (logs into the portal), Server, Semesters, Catalog.
schoolapp explain <CODE> and the s3/s4 table views apply the official ENSAM
evaluation règlement (CE 2019‑10‑22 / CU 2019‑12‑25): after a rattrapage the
element moyenne is max(SO, max(0.3·CC + 0.7·Rat, Rat)) (Art. 7) and a module
validated on rattrapage is capped at the pass bar - 11/20 in the prépa years,
12/20 in the cycle ingénieur (Art. 9). Values the tool computes while the portal
has not yet published them are shown with a * marker.
schoolapp csv grades --latest -o grades.csv # current-year element marks
schoolapp csv stats -o rankings.csv # NoteRAT/exam ranking stats (needs fetch-all --stats)Resources: elements/grades, modules, semesters, years, courses,
absences, stats. The same rows are available over HTTP at
GET /api/csv/<resource>.
schoolapp promo API421 --eval rat,moy,ex,cc -o out/ # one element
schoolapp promo-sem S1 -o out/ # a whole semester
schoolapp promo-year 2A -o out/ # a whole yearRecovers every student's note across the whole promotion - without names,
which the portal never exposes. It works because the stats endpoint answers
rank(note) for any hypothetical note, so sweeping that value traces the full
grade distribution. Endpoints: /notes-stat/elemevalsat (element),
/notes-stat/semsat (semester), /notes-stat/anneesat (année). Target a
semester by its label (S1…S4) and a year by its niveau (1A, 2A); the
année universitaire and filière are resolved from your own rows automatically.
Each run writes, per evaluation, into --out-dir (default .), all keyed by
the code (API421, S1, 2A):
| File | Contents |
|---|---|
promo_<code>_<eval>_hist.csv |
binned histogram - how many got each mark (bin_low,bin_high,count,percent,cumulative) |
promo_<code>_<eval>_dist.csv |
exact reconstructed distribution (note,count,rank_top) |
promo_<code>_<eval>_students.csv |
one anonymized row per student (rank,note) |
promo_<code>_<eval>_summary.csv |
effectif, moyenne promo, min/max/écart-type, median, your note/rank |
promo_<code>_<eval>_hist.png |
histogram figure (mean + your-note markers); needs matplotlib, --no-plot to skip |
Fires many rate-limited requests (--min-interval, default 0.25 s) - expect
a few minutes per evaluation. --bin sets the histogram bin width (default 1.0).
The reconstruction is cached and served at GET /api/promo/<code>.
schoolapp --help
schoolapp --version
schoolapp --install-completion # shell tab-completion
schoolapp s3 --help
schoolapp s4 --helpschoolapp s3 list
schoolapp s4 listInteractive:
schoolapp s3 calc
schoolapp s4 calcFrom JSON marks file:
schoolapp s4 calc --marks-file marks.jsonExample marks.json:
{
"API411.cc": 12,
"API411.ex": 14,
"API421.cc": 10,
"API421.ex": 13
}Single run:
schoolapp s4 watch --once --email "you@example.com" --password "secret"Continuous polling:
schoolapp s3 watch --interval 300 --email "you@example.com" --password "secret"Watch mode polls every --interval seconds (default 300) all day, slowing to
30 minutes between 04:00 and 06:00. The session is reused between checks
(re-login only on expiry). Changes are detected per-element on real mark
fields, so cosmetic page changes don't trigger notifications. With
--notify-email, repeated failures for over an hour send a single warning
email, followed by a recovery email when checks succeed again.
Each check also watches, in the same session and email:
student/absence/bilan- new absences (justified or not) and justification changesstudent/absence/sanctions- sanction level changes (e.g. Avertissement)student/notesmod-encours- official module moyennes/decisions as publishedstudent/notessem- semester moyenne, decision and class ranking
With email notification:
schoolapp s3 watch --interval 900 --notify-email --email "you@example.com" --password "secret"Global options:
--debug--version--config PATH--env-file PATH
If --env-file is not provided, the CLI will try to auto-load an env file from
the current working directory or project root: it checks .env first and then
.env.example. If .env is missing but .env.example exists, the CLI will
create .env by copying .env.example (permissions set to 600 where possible).
You can also pass --env-file .env.example explicitly.
Optional config file: ~/.schoolapp/config.toml
[schoolapp]
email = "you@example.com"
password = "secret"
[smtp]
host = "smtp.gmail.com"
port = 587
username = "you@gmail.com"
password = "app-password"
from = "you@gmail.com"
to = "you@gmail.com"Supported env vars:
SCHOOLAPP_EMAIL,SCHOOLAPP_PASSWORDSMTP_HOST,SMTP_PORT,SMTP_USERNAME,SMTP_PASSWORD,SMTP_FROM,SMTP_TO
Symptoms: ModuleNotFoundError (e.g. requests)
Fix:
source .venv/bin/activate
pip install -r requirements.txt
pip install -e .
schoolapp s4 listRun commands via venv interpreter directly:
/home/notadil/Projects/schoolapp_watcher/.venv/bin/schoolapp s3 list- Check SMTP host/port/credentials
- Use app passwords where required (e.g. Gmail)
- Test once with
--oncebefore running long watch mode
0: success / no change in watch-once1: change detected in watch-once2: runtime error (validation/network/login/etc)


