Skip to main content

Prayer Timing

:::info Status Implemented (the resolution/storage model below). A separate proposal document (docs/prayer-timing-architecture.md) describes a much larger system — see Proposed vs. implemented before relying on anything not in this section. :::

Resolves and stores per-mosque prayer times, and is the source of the db / aladhan / none distinction referenced by Azaan Broadcast's offline scheduler.

Data model

One table, mosque_prayer_times (core/db.py:ensure_mosque_prayer_times_table): one row per (mosque_id, prayer_date), columns for every prayer's adhan and iqamah time plus sunrise, sunset, ishraq, zawal, suhur, iftar, jumuah (core/prayer_times_fields.py:PRAYER_TIME_COLUMNS). No approval workflow, no per-prayer override table, no audit table — a row is simply admin-entered or absent.

Resolution

GET /prayer-times/{mosque_id}?date=...:

  1. Look up a saved row in mosque_prayer_times for that mosque+date.
  2. If none, and the mosque has latitude/longitude set, call the AlAdhan API in real time (core/aladhan.py:fetch_prayer_times) and return that, source: "aladhan" — not cached, not persisted.
  3. If neither, return all-null fields (source defaults to "mosque" even though there is no data — the response's warning/null fields are the actual signal of "nothing available," not the source value).

The Asr calculation school can be supplied either as the mosque's school text column (Hanafi/Maliki/Shafi'i/Hanbali, mapped to AlAdhan's 0/1 school parameter) or as a raw AlAdhan school int on the location-based endpoint — Hanafi uses the later shadow-length method.

This is the same db / aladhan (and the offline scheduler's own third state, none) vocabulary Azaan Broadcast uses when deciding whether it has anything to play — that module calls this resolution logic (or its own equivalent lookup) to decide.

Writing times

PUT /prayer-times/{mosque_id} — mosque admin or superadmin only (_ensure_can_edit_prayer_times) — upserts one full day's row. No optimistic locking, no field-level override/reason tracking; a PUT replaces the row for that date.

Other endpoints

EndpointPurposeAccess
GET /prayer-times/locationReal-time AlAdhan lookup by raw lat/lng, no mosque contextpublic, no auth required
GET /prayer-times/{mosque_id}/saved-datesWhich dates in a range have a saved rowmosque admin / superadmin
GET /prayer-times/{mosque_id}/coverageLatest saved date and days remaining before coverage runs outmosque admin / superadmin

Proposed vs. implemented

docs/prayer-timing-architecture.md (titled "Hybrid Prayer Timing System: Full Architecture") describes a considerably larger system: separate prayer_times_generated / prayer_time_overrides / mosque_special_timings / prayer_timing_audit / timing_notifications / prayer_generation_runs tables, an APScheduler-driven daily/monthly generation job, Redis caching, per-prayer manual overrides with an approval workflow, staleness detection, and an email/push notification schedule.

None of that is present in the codebase as of this writing — no jobs/ directory, no timing_resolver.py, no cache.py, no Redis dependency, and none of the six proposed tables exist in core/db.py's ensure_* functions or in the Alembic migrations. Despite its title, that document describes a proposal, not the system's current architecture — treat it as forward-looking design material (candidate for Roadmaps, not a "what exists today" reference), and rely on this page for the actual current behavior.