Reliability-focused, cross-platform BIU grade monitoring for Windows and Linux.
whenTheAnswer is a Playwright-based monitoring utility designed around a deliberately conservative failure model. The interesting part of the project is not aggressive automation; it is the reliability layer around browser state, request pacing, persistence, rate limiting, recovery, and explicit stop conditions.
The watcher does not attempt to bypass CAPTCHAs, spoof fingerprints, evade anti-bot controls, or continue hammering an endpoint after a block. When the site signals blocking, rate limiting, or human verification, the system transitions into a persistent protection state and pauses work.
flowchart TD
A[CLI / configuration] --> B[Single-instance lock]
B --> C[Persistent browser profile]
C --> D[Authentication / session state]
D --> E[Request gate]
E --> F[Grade check / keepalive]
F --> G{Response classification}
G -->|success| H[Snapshot comparison]
H --> I[Cross-platform notification]
G -->|transient failure| J[Backoff controller]
G -->|429 / block / challenge| K[Persistent circuit breaker]
J --> K
K -->|cooldown elapsed| L[Single HALF_OPEN recovery workflow]
L --> F
K --> M[protection_state.json]
H --> N[grade_snapshot.json]
The project intentionally separates request permission from request execution: the request gate controls pacing and serialization, while the protection controller decides whether activity is allowed at all.
The implementation is also separated by responsibility: browser/session integration is isolated from reliability state, snapshot comparison, configuration, notifications, and runtime orchestration. biu_grade_watcher.py remains a thin compatibility CLI so existing commands and imports continue to work.
The protection controller uses three explicit states:
CLOSED— normal operation;OPEN— checks and keepalives are paused until cooldown expires;HALF_OPEN— one serialized recovery workflow is allowed to test whether normal access has returned.
The state is persisted to disk, so restarting the process does not erase a cooldown caused by a block or rate-limit event.
The watcher enforces:
- one running process per user;
- no overlapping top-level BIU operations;
- minimum spacing between top-level requests;
- an hourly top-level request budget;
- jitter around recurring schedules;
- conservative lower bounds for grade and keepalive intervals.
The watcher distinguishes between:
- authentication/session expiry;
- transient server/network failures;
- explicit blocking statuses;
- rate limiting;
- blocking / CAPTCHA / human-verification page text.
Retry-After is honored when available.
Transient failures use exponential backoff. Explicit protection events open the circuit for a longer cooldown. Once that cooldown expires, the system allows one controlled recovery workflow instead of immediately resuming normal polling.
- Persistent Chromium profile and authenticated session
- Single-instance process lock
- Serialized top-level BIU operations
- Minimum request spacing
- Hourly request budget
- Randomized scheduling jitter
- Detection of HTTP
403,406,418,423, and429 - Detection of blocking, CAPTCHA, and rate-limit text
Retry-Afterparsing- Persistent
CLOSED / OPEN / HALF_OPENcircuit breaker - Exponential backoff for transient failures
- Controlled recovery probe after cooldown
- Authentication-attempt budget
- No repeated automatic OTP attempts
- Persistent state across restarts
- Cross-platform notifications
- No stealth/fingerprint-spoofing browser flags
whenTheAnswer/
├── biu_grade_watcher.py # thin CLI + compatibility import surface
├── watcher/
│ ├── config.py # constants, paths, safety limits
│ ├── errors.py # typed failure/protection events
│ ├── reliability.py # lock, request gate, auth budget, circuit breaker
│ ├── snapshot.py # grade identity, persistence, diffing
│ ├── browser.py # Playwright auth/navigation/extraction boundary
│ ├── runtime.py # watcher orchestration + keepalive loop
│ └── utils.py # logging, notifications, JSON/time helpers
├── tests/ # deterministic reliability tests
├── .github/workflows/ci.yml # cross-version Python validation
├── env.example # local configuration template
├── requirements.txt
├── LICENSE
└── README.md
This split keeps deterministic reliability and state-management logic independent from the live browser integration. The external-site boundary remains in watcher/browser.py, while orchestration is coordinated from watcher/runtime.py.
The deterministic test suite covers core reliability behavior without contacting BIU or launching a browser:
- text normalization;
Retry-Afterparsing for seconds and HTTP dates;- transient failures opening the persistent circuit after the configured threshold;
- rate-limit/protection events recording status and cooldown state;
- state persistence across controller reloads;
- successful recovery resetting the circuit;
- rejection of activity while the circuit remains open.
Run locally:
python -m pip install -r requirements.txt
python -m unittest discover -s tests -vGitHub Actions also runs compile validation and these tests across multiple supported Python versions.
Browser integration remains intentionally separate because it requires external authentication and live website behavior.
py -3.10 -m pip install -r requirements.txt
py -3.10 -m playwright install chromiumpython3 -m pip install -r requirements.txt
python3 -m playwright install chromium
python3 -m playwright install-deps chromiumDesktop notifications on Debian/Ubuntu:
sudo apt install libnotify-binCreate .env next to the script:
BIU_ID=123456789
BIU_PHONE=0501234567The OTP is entered interactively and is not stored.
py -3.10 biu_grade_watcher.py `
--auth-method direct `
--interval 10 `
--keepalive 2python3 biu_grade_watcher.py \
--auth-method direct \
--interval 10 \
--keepalive 2Actual execution includes small random jitter so recurring operations do not always occur on an exact fixed boundary.
python3 biu_grade_watcher.py \
--auth-method direct \
--force-login \
--once \
--print-rowsOn Windows, replace python3 with the Python launcher/runtime available on the machine.
python3 biu_grade_watcher.py --protection-statusExample:
{
"circuit_state": "OPEN",
"consecutive_failures": 0,
"opened_count": 1,
"cooldown_until": "2026-07-24T18:00:00+00:00",
"last_reason": "grade check returned HTTP 429.",
"last_status": 429,
"seconds_until_probe": 1620
}Only clear the persistent state after confirming the site is accessible normally in a regular browser:
python3 biu_grade_watcher.py --clear-protection-state%LOCALAPPDATA%\BIUGradeWatcher
$XDG_DATA_HOME/biu-grade-watcher
or:
~/.local/share/biu-grade-watcher
Runtime files include:
browser_profile/
grade_snapshot.json
protection_state.json
watcher.log
watcher.lock
Default behavior is intentionally conservative:
Grade check interval: 10 minutes
Keepalive interval: 2 minutes
Minimum top-level request gap: 12 seconds
Maximum top-level operations: 45 per hour
The script refuses a grade interval below 5 minutes, a keepalive interval below 2 minutes, or more than 60 top-level operations per hour.
- Respect external controls: a block is a stop condition, not a challenge to bypass.
- Persist failure state: process restarts should not erase safety behavior.
- Serialize recovery: recovery should happen through one controlled workflow.
- Fail visibly: protection state, reason, status and cooldown remain inspectable.
- Separate external I/O from deterministic logic: browser behavior is isolated from state, pacing, and snapshot rules.
- Keep compatibility at the boundary: the original CLI remains stable while internals are modularized.
- Live browser behavior depends on an external university website and can change independently of this repository.
- Authentication and full end-to-end browser integration cannot be reproduced in public CI without real credentials and live external access.
- Browser selectors and authentication flows remain coupled to the current BIU site and require maintenance when the external UI changes.
- This is a personal monitoring utility, not an official BIU integration.
MIT — see LICENSE.