119 lines
4.6 KiB
Markdown
119 lines
4.6 KiB
Markdown
# Roster Formatter
|
||
|
||
A local browser app that turns the current one-page roster PDF into the established formatted PNG. The web app and command-line tool use the same renderer.
|
||
|
||
## Start with Docker Compose
|
||
|
||
From this folder:
|
||
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Open <http://127.0.0.1:8787>, upload the roster PDF, choose its month, and select **Format roster**.
|
||
|
||
Common commands:
|
||
|
||
```bash
|
||
docker compose restart # restart it later
|
||
docker compose logs -f app # watch logs
|
||
docker compose down # stop it
|
||
docker compose up -d --build # rebuild after code changes
|
||
```
|
||
|
||
The Compose service restarts automatically unless you explicitly stop it. Its direct browser port binds only to `127.0.0.1`.
|
||
|
||
The service also joins the existing external `tabernet` Docker network, so Caddy can reach it at `roster-formatter:8787`. For a Caddy hostname, add that hostname to `.env`:
|
||
|
||
```dotenv
|
||
ROSTER_ALLOWED_HOSTS=roster.example.com
|
||
```
|
||
|
||
Then a minimal Caddy route is:
|
||
|
||
```caddyfile
|
||
roster.example.com {
|
||
reverse_proxy roster-formatter:8787
|
||
}
|
||
```
|
||
|
||
The `tabernet` network must already exist. Because roster images contain personal information, add authentication in Caddy before exposing this service outside your private network.
|
||
|
||
## Saved files and automatic cleanup
|
||
|
||
Every successful PNG is returned to the browser with a **Download PNG** button and saved in [`output/`](output). The Compose container mounts that folder, so saved PNGs survive rebuilds and restarts.
|
||
|
||
Layout defaults are persistent too. Change values under **Advanced adjustments**, then select **Save as defaults** to prefill them for future rosters. **Reset changes** restores the currently saved values, while **Use original values** loads the production geometry without saving it. The server stores these defaults in `output/.roster-layout-defaults.json`, inside the same mounted volume, and the roster cleanup never removes that settings file.
|
||
|
||
On startup, every six hours, and after each successful format, the app removes only filenames matching its roster pattern, such as `MARCH 2026 Roster.png`, when either its roster month or saved-file age is outside the three-calendar-month window. The file-age check prevents an accidentally future-dated filename from bypassing cleanup. Other files are not removed. Cleanup is permanent.
|
||
|
||
Change the retention window or browser port with a `.env` file beside `compose.yaml`:
|
||
|
||
```dotenv
|
||
ROSTER_RETENTION_MONTHS=6
|
||
ROSTER_PORT=8788
|
||
TZ=America/Chicago
|
||
```
|
||
|
||
Set `ROSTER_RETENTION_MONTHS=0` to disable cleanup.
|
||
|
||
The image runs as UID/GID `1000:1000`, matching this machine. On a host with a different checkout owner, set `ROSTER_UID` and `ROSTER_GID` in `.env`, then rebuild so the container can write to `output/`:
|
||
|
||
```dotenv
|
||
ROSTER_UID=1001
|
||
ROSTER_GID=1001
|
||
```
|
||
|
||
## Layout guarantees
|
||
|
||
With the advanced fields untouched, the renderer preserves the original production values:
|
||
|
||
| Setting | Default |
|
||
| --- | ---: |
|
||
| Canvas | 792 × 612 PDF points |
|
||
| PNG export | 1650 × 1275 pixels at 150 DPI |
|
||
| Roster vertical shift | 100 |
|
||
| Bottom trim | 60 |
|
||
| Logo position | X 50, Y -10 |
|
||
| Logo scale | 0.27 |
|
||
| Date position | X 460, Y 88 |
|
||
| Date font size | 48 |
|
||
|
||
The web page exposes the layout values in a collapsed **Advanced adjustments** section. Only an intentional field change—or explicitly saving that change as the new default—alters logo/date scale or placement. The canvas size and PNG DPI are not editable. The original production values remain available through **Use original values**.
|
||
|
||
The page theme defaults to **System** and follows the browser/operating-system color preference. The **System / Light / Dark** control remembers the choice in that browser. Theme changes affect only the webpage and never the generated PNG.
|
||
|
||
Input is deliberately limited to a one-page, unrotated, 11 × 8.5 inch landscape PDF. This prevents a differently sized upload from silently changing the established geometry.
|
||
|
||
## Run without Docker
|
||
|
||
The existing `venv` already contains the formatter dependencies. Start the web app with:
|
||
|
||
```bash
|
||
venv/bin/python server.py
|
||
```
|
||
|
||
Or create a fresh environment:
|
||
|
||
```bash
|
||
python3 -m venv .venv
|
||
.venv/bin/pip install -r requirements.txt
|
||
.venv/bin/python server.py
|
||
```
|
||
|
||
The original CLI workflow is still available and now accepts an explicit input path:
|
||
|
||
```bash
|
||
venv/bin/python main.py --input input/original.pdf --month "July 2026"
|
||
```
|
||
|
||
## Tests
|
||
|
||
Run the standard-library test suite with:
|
||
|
||
```bash
|
||
PYTHONDONTWRITEBYTECODE=1 venv/bin/python -m unittest discover -v
|
||
```
|
||
|
||
Uploads and intermediate PDFs are created in isolated temporary directories and removed immediately after formatting. Only the final PNG is retained in `output/`.
|