Thanks for wanting to help! Falak is intentionally tiny β please help keep it that way. This document covers the ground rules, how to run the project locally, the backend module contract, and step-by-step guides for the two most welcome contributions: adding a language and improving a data catalog.
These are the project's identity, not preferences. PRs that break them will be declined regardless of quality:
- One dependency. The backend must stay Python 3 +
ephemonly. No numpy, no flask, no requests, no astropy/skyfield. The web server is the Python standard library. - Offline-first is non-negotiable. No CDN assets, no external JS or
fonts, no telemetry, no API keys, no accounts. Any feature that needs the
network must degrade gracefully without it and disclose where its data
came from and how old it is β the TLE pipeline in
backend/satellites.py(source: network|cache|none+tle_age_hours) is the reference pattern. - Data honesty. Never present an estimate as a measurement. If Falak
can't know something from geometry alone, it labels the limitation (see
backend/tides.py, which returns"type": "astronomical_estimate"). - The frontend stays self-contained. Plain HTML/CSS/JS, no build step, no frameworks, all assets served from disk.
git clone <your-fork> falak && cd falak
pip install -r requirements.txt # just: ephem
python3 backend/server.py # serves http://127.0.0.1:8091Then run the smoke tests β they must pass fully offline (the suite stubs the network itself):
python3 tests/test_smoke.py # or: python3 -m pytest tests/The tests import every backend module, build a Dubai observer at a fixed UTC
instant, call each compute(), and assert the documented response shapes.
If you add a module, key, or reason code, extend the smoke test in the same PR.
Full detail in docs/ARCHITECTURE.md; the short form:
Every compute module in backend/ exposes exactly one entry point:
compute(obs, when_utc) -> dictobsis anephem.Observerbuilt bycore.make_observer(...)βcore.pyis the only place observers are constructed. Beware the classic PyEphem trap: lat/lon/RA/Dec must be assigned as strings (parsed as degrees / sexagesimal); raw floats are read as radians.when_utcis a naivedatetimetreated as UTC. The backend never localizes: all times in responses are ISO 8601 UTC strings; angles are float degrees.tz=is echoed back untouched and applied client-side.- Return a plain, JSON-serializable dict. No
ephem.Date/ephem.Angleobjects may leak out β convert at the boundary withcore.iso()/core.deg(). - PyEphem search calls (
next_rising, horizon tweaks, β¦) mutate observer state. If your module scans, work oncore.clone(obs), never the instance you were handed. - Do not print. Do not raise for expected conditions β return a valid dict
(an empty pass list, an
up: false, β¦).server.pywraps each slot intry/exceptso an unexpected exception poisons only your card, but that is a safety net, not a license. - Keys are language-neutral English snake_case tokens, never sentences β the frontend does all i18n (see below).
- Backend: stdlib style, small functions, comments where the math is non-obvious (cite a source for astronomical formulas or criteria β e.g. Odeh/Yallop for the crescent, depression angles for prayer times).
- Frontend: vanilla JS, no build step. Every human-visible string goes
through
frontend/i18n.jsβ never hard-code display text inindex.html/skymap.js. - Emitted "reasons" are structured, e.g.
{"code": "planet", "planet": "saturn", "alt": 42}, never prose. The frontend fills templates from the active language's token map. The smoke test enforces this shape. - One topic per PR, with a clear description. Small and focused beats big and clever.
The whole UI for a language is one self-contained block in
frontend/i18n.js (window.I18N). Currently: ar, en, fr, es, ur, hi, tr, zh (ar/ur RTL).
- Copy the
enblock to your language code and translate every entry. A language must carry the complete key set β both:- flat keys for static UI text (
s_moon,best_window,err_slot, β¦), and - token maps β the sub-dictionaries keyed by backend tokens:
phases,planets,prayers,cardinal,compass,twilight_names,planet_notes,bw, and the tonight reason templates.
- flat keys for static UI text (
- For right-to-left languages, set
dir: "rtl"in the block β the page and the sky map flip automatically. - Check the rendered result in both themes;
docs/qa/holds per-language screenshots you can compare against. - If your language needs a font the bundled stack (Alexandria, Noto Kufi
Arabic, Inter) doesn't cover, open an issue first β fonts are vendored
locally as
woff2and we keep the payload lean.
Corollary rule for backend contributors: adding a new backend note/reason
code means adding it to every language's token map in the same commit.
The bundled catalogs live in data/ and ship with the repo:
| File | Contents |
|---|---|
cities.json |
Seed cities for the location picker (366 today) |
star_names.json |
Bright stars, incl. traditional Arabic names |
constellation_names.json |
Constellation names |
meteor_showers.json |
Annual shower calendar (activity window, peak, ZHR) |
Guidelines:
- Match the existing entry shape exactly (open the file β they're small).
- Cite a source in the PR description for astronomical data: IMC/IMO for showers, standard catalogs for stars, etc.
- Coordinates in decimal degrees; keep city entries to notable/distinct locations rather than exhaustive coverage.
data/tle_cache.jsonis a runtime cache, not a catalog β don't edit or commit changes to it.
Also especially welcome: corrections to prayer-time methods and crescent criteria (with sources), and astronomy accuracy fixes of any kind.
Use the bug report template. Because everything is computed from your position and time, a useful report includes your approximate latitude/longitude, the UTC time, and expected vs. observed values (a screenshot helps).
-
python3 tests/test_smoke.pypasses, offline - No new runtime dependency, no network call without cache + disclosure
- New strings/tokens present in all 8 language blocks
- Angles in degrees, times in ISO 8601 UTC, keys in snake_case
- One topic, clear description, sources cited for astronomical data
By contributing you agree your work is licensed under the project's MIT License.