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=...:
- Look up a saved row in
mosque_prayer_timesfor that mosque+date. - If none, and the mosque has
latitude/longitudeset, call the AlAdhan API in real time (core/aladhan.py:fetch_prayer_times) and return that,source: "aladhan"— not cached, not persisted. - If neither, return all-null fields (
sourcedefaults to"mosque"even though there is no data — the response'swarning/null fields are the actual signal of "nothing available," not thesourcevalue).
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
| Endpoint | Purpose | Access |
|---|---|---|
GET /prayer-times/location | Real-time AlAdhan lookup by raw lat/lng, no mosque context | public, no auth required |
GET /prayer-times/{mosque_id}/saved-dates | Which dates in a range have a saved row | mosque admin / superadmin |
GET /prayer-times/{mosque_id}/coverage | Latest saved date and days remaining before coverage runs out | mosque 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.