4.7 KiB
Daily Blackout Check
Checks the official SAAPA planned-outage API and sends Persian notifications to an Eitaa chat when an outage is added, changed, cancelled, or restored.
The checker is designed for an hourly Gitea Actions workflow. Its deduplication
state is stored in state/outages.json and committed back to this repository.
Behavior
- Queries each configured bill ID from today through five days ahead.
- Uses Tehran time regardless of the runner container's timezone.
- Sends all upcoming outages when starting with empty state.
- Treats an existing outage number with changed time, address, date, reason, or planned status as an update.
- Reports a future outage as cancelled only after two consecutive successful checks omit it.
- Records a notification only after Eitaa accepts it. This favors delivery over perfect deduplication: a failed state push can cause a duplicate next hour.
- Rejects malformed responses and snapshots larger than the configured safety limit without changing that bill's state.
Local setup
Python 3.11 or newer is required. The Gitea runner image uses Debian's Python 3.11.
python -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[test]'
cp .env.example .env
Fill in the required values in .env, then preview the next run without sending
messages or changing state:
python -m blackout_notifier check --dry-run
Run the live checker with:
python -m blackout_notifier check
Configuration
| Variable | Required | Default | Purpose |
|---|---|---|---|
BARGHEMAN_TOKEN |
Yes | — | SAAPA bearer token |
EITAAYAR_TOKEN |
Yes | — | EitaaYar bot token |
CHAT_ID |
Yes | — | Destination Eitaa chat |
BILL_IDS |
Yes | — | Comma-separated decimal bill IDs |
STATE_FILE |
No | state/outages.json |
Durable notification state |
LOOKAHEAD_DAYS |
No | 5 |
Query horizon, from 1 to 30 days |
REQUEST_TIMEOUT_SECONDS |
No | 15 |
HTTP timeout, from 1 to 120 seconds |
MAX_EVENTS_PER_BILL |
No | 25 |
Response safety limit, from 1 to 100 |
Shell and Gitea-provided variables take precedence over .env.
Gitea deployment
This repository targets Gitea 1.25.x and a Docker-based runner advertising the
ubuntu-latest label. Both workflows run inside the locally built
daily-blackout-check-runner:latest image. It contains Node for the checkout
action plus Python, Git, timezone data, application dependencies, pytest, and
Ruff. Scheduled jobs therefore do not install Python or download Python packages.
Build the image on the Docker host used by act_runner:
cd /path/to/daily-blackout-check
docker build \
--tag daily-blackout-check-runner:latest \
runner-image
docker image inspect daily-blackout-check-runner:latest >/dev/null
Only runner-image/ is sent as the Docker build context, so local secrets and
outage state never enter the build context or image.
The image is local rather than registry-hosted, matching the deployment pattern
used by mahak-api-docs. Build it on every runner host that can claim this job.
Rebuild it whenever runner-image/Dockerfile, runner-image/requirements.lock,
or dependency declarations in pyproject.toml change. The image build requires
Docker Hub and PyPI access; normal workflow runs only need the Gitea instance,
gitea.com, SAAPA, and EitaaYar.
- Build
daily-blackout-check-runner:lateston the runner's Docker host. - Enable Actions in the repository settings.
- Confirm an online Docker runner advertises
ubuntu-latest. - Add these repository Actions secrets:
BARGHEMAN_TOKEN,EITAAYAR_TOKEN,CHAT_ID, andBILL_IDS. - Run the test workflow manually.
- Run Hourly blackout check manually once and inspect the notification and resulting state commit.
- Leave the
@hourlyschedule enabled.
The workflow uses fully qualified https://gitea.com/actions/... actions rather
than resolving actions through GitHub. Gitea 1.25 ignores workflow permissions
and concurrency, so the notifier workflow deliberately does not rely on them.
Its checkout credential must retain the default ability to push to the current
repository. Scheduled jobs should not be manually overlapped; in the rare event
of a push race, the job fails and unsaved notices may be repeated on the next run.
If a workflow tries to pull the local image instead of using it, ensure the image
exists in the same Docker daemon used by act_runner and set
container.force_pull: false in the runner's generated config.yaml, then
restart the runner.
Development
ruff check .
pytest
Tests mock both external services and never send real messages.
Historical state from the previous implementation is retained at
archive/blackouts-1404.json for reference only and is not imported.