Contributing
Local setup
git clone https://github.com/ChunSikPark/TeamOverbyeWeather.git
cd TeamOverbyeWeather
pip install -e package/ # the SDK, editable
pip install -r docs/requirements.txt
Run the backend locally if you are changing it:
cd backend
pip install -r requirements.txt
uvicorn main:app --reload
Point the client at it:
client = WeatherClient(base_url="http://localhost:8000")
The backend needs GDRIVE_CREDENTIALS_JSON_CONTENT set to a service-account JSON
with Viewer access on the data folders. Without it the catalog will be empty but
the app still starts.
Building the docs
sphinx-build -b html docs docs/_build/html
Open docs/_build/html/index.html.
Pushing to main rebuilds and publishes the site to
https://chunsikpark.github.io/TeamOverbyeWeather/ via
.github/workflows/docs.yml. A .readthedocs.yaml is also checked in if you
would rather host on Read the Docs, which adds versioned docs and PDF output.
Adding a data source
Five places, in this order:
backend/catalog.py— add the folder id to_DEFAULT_FOLDERS, write a_build_<source>(), call it frombuild_catalog(), add the key to_empty_catalog()backend/download.py— add_SOURCE_LOOKUPand_FILENAME_PATTERNSentriespackage/TeamOverbyeWeather/registry.py— add to_API_KEYSand_TYPESfrontend/main.js— add toTYPE_DEFSandgetApiSourceKey()docs/guides/catalog.md— document the new type and its date-key format
Check the real filenames before writing a regex:
curl "<backend>/api/debug/folder?folder_id=<id>&limit=50"
Filename conventions vary between folders more than you would expect. Guessing wastes time; the debug endpoint takes seconds.
Things that will bite you
pww_io.py exists twice. backend/pww_io.py is the original;
package/TeamOverbyeWeather/pww_io.py is a copy. Change one, copy to the other.
Bounding box ordering is (lat_max, lon_min, lat_min, lon_max) everywhere —
north, west, south, east. Not the ordering most GIS libraries use.
The longitude axis descends. Index 0 is lon_max. Latitude ascends.
Always .copy() after slicing before writing a PWW. Numpy slices are views.
255 is the missing-data sentinel, not a value.
Times are OLE Automation days in the file, Unix epoch seconds in the API.
crop_to_timerange takes epoch seconds; header["date_min"] is OLE days. Do not
pass one where the other is expected — they are both floats, so nothing will stop
you, and the result is a nonsensical date rather than an error.
HRRR history regexes are loose on the extension on purpose. Older archives
are .pww.gz; do not tighten to \.zip$.
Do not reintroduce get_file_url in download.py. Fetching large Drive
files by URL hits a virus-scan interstitial and silently returns HTML instead of
data. Everything goes through the service account.
Testing a change to the crop path
Compare server output against local output — they should agree exactly:
from TeamOverbyeWeather import WeatherClient, pww_io, localcrop
client = WeatherClient(show_progress=False)
bbox = (36.5, -106.6, 25.8, -93.5)
server = client.download("hrrr", type="current", dates="2026-07-21",
region="TX", dest="./tmp")[0]
raw = client._plain("hrrr_history_current", "2026-07-21",
__import__("pathlib").Path("./tmp"), False)
local = localcrop.crop_file(raw, "./tmp/local.pww", bbox=bbox)
for p in (server, local):
_, _, arr = pww_io.read_pww(open(p, "rb").read())
print(p.name, arr.shape)
Both must report 96 time steps for a 15-minute day. A result of 24 means the four quarter files were not stitched.
Publishing
Package — bump version in package/pyproject.toml and __version__ in
__init__.py (keep them equal), then build and upload:
cd package
rm -rf dist build
python -m build
python -m twine check dist/*
python -m twine upload dist/*
A version number on PyPI can never be reused, so run twine check first and
confirm the metadata (especially [project.urls]) is right before uploading.
Verify afterwards in a clean environment:
python -m venv /tmp/check && /tmp/check/bin/pip install TeamOverbyeWeather
Backend and frontend — push to main; both auto-deploy. Refresh the catalog
afterwards:
curl <backend>/api/catalog/refresh
Style
Match the surrounding code. Google-style docstrings — they are what the API reference is generated from, so a function without one shows up bare in the docs.