# Story 1.1: Project Scaffold & Verified Entry Points Status: ready-for-dev ## Story As a developer, I want a verified project scaffold with the `src/planemapper/` layout, both console entry points installable, all module stubs in place, systemd unit files, and `pytest` running without error, So that every subsequent story has a consistent, working foundation to build on. ## Acceptance Criteria 1. **Given** the repository is cloned on a Pi Zero 2W running Raspberry Pi OS Bookworm **When** `pip install -e .` is run **Then** it completes without errors and both `planemapper-provision` and `planemapper-radar` commands are available on PATH **And** running either command logs "not implemented" and exits with code 0 2. **Given** the project is installed **When** `pytest` is run **Then** the test suite discovers tests and exits with 0 failures (empty stubs acceptable) 3. **Given** the project structure **When** a developer inspects the repository **Then** all files from the Architecture directory structure exist: `src/planemapper/` with `__init__.py`, `constants.py`, `models.py`, `main.py`, `provision.py`, `fetcher.py`, `gpio_ctrl.py`, `display.py`, `provisioning/` (7 modules), `renderer/` (8 modules), `data/airports.csv`; `systemd/` with both `.service` files; `pyproject.toml`, `requirements.txt`, `requirements-dev.txt` **And** `src/planemapper/data/airports.csv` is accessible via `importlib.resources` **And** `ruff check .` passes with zero violations ## Tasks / Subtasks - [ ] Task 1: Create `pyproject.toml` (AC: #1, #3) - [ ] 1.1 Set `[build-system]` to use `setuptools` with `find_packages` - [ ] 1.2 Set `requires-python = ">=3.11"` and list pinned runtime dependencies (Pillow==12.2.0, gpiozero==2.0.1, Flask==3.1.3, requests==2.33.1) - [ ] 1.3 Add `[project.scripts]` with `planemapper-radar = "planemapper.main:main"` and `planemapper-provision = "planemapper.provision:main"` - [ ] 1.4 Add `[tool.setuptools.package-data]` entry so `planemapper/data/airports.csv` is included in the installed package - [ ] 1.5 Add `[tool.ruff]` section: `line-length = 100`, `target-version = "py311"`, and an import boundary rule preventing `planemapper.main` from importing `planemapper.provisioning.*` - [ ] Task 2: Create `requirements.txt` and `requirements-dev.txt` (AC: #1, #3) - [ ] 2.1 `requirements.txt`: pin Pillow==12.2.0, gpiozero==2.0.1, Flask==3.1.3, requests==2.33.1 - [ ] 2.2 `requirements-dev.txt`: pin pytest==9.0.3, ruff==0.15.11, add `gpiozero[mock]` - [ ] Task 3: Create top-level `src/planemapper/` module stubs (AC: #1, #3) - [ ] 3.1 `src/planemapper/__init__.py` — empty or version string only - [ ] 3.2 `src/planemapper/constants.py` — stub with module docstring and placeholder constants - [ ] 3.3 `src/planemapper/models.py` — stub with module docstring - [ ] 3.4 `src/planemapper/fetcher.py` — stub with module docstring - [ ] 3.5 `src/planemapper/gpio_ctrl.py` — stub with module docstring - [ ] 3.6 `src/planemapper/display.py` — stub with module docstring - [ ] 3.7 `src/planemapper/main.py` — `main()` function that logs "not implemented" and returns; must NOT import from `planemapper.provisioning.*` - [ ] 3.8 `src/planemapper/provision.py` — `main()` function that logs "not implemented" and returns - [ ] Task 4: Create `provisioning/` subpackage stubs (AC: #3) - [ ] 4.1 `src/planemapper/provisioning/__init__.py` - [ ] 4.2 `src/planemapper/provisioning/portal.py` - [ ] 4.3 `src/planemapper/provisioning/location.py` - [ ] 4.4 `src/planemapper/provisioning/tiles.py` - [ ] 4.5 `src/planemapper/provisioning/airspace.py` - [ ] 4.6 `src/planemapper/provisioning/wifi.py` - [ ] 4.7 `src/planemapper/provisioning/config.py` - [ ] Task 5: Create `renderer/` subpackage stubs (AC: #3) - [ ] 5.1 `src/planemapper/renderer/__init__.py` - [ ] 5.2 `src/planemapper/renderer/renderer.py` - [ ] 5.3 `src/planemapper/renderer/projection.py` - [ ] 5.4 `src/planemapper/renderer/basemap.py` - [ ] 5.5 `src/planemapper/renderer/aircraft.py` - [ ] 5.6 `src/planemapper/renderer/airspace.py` - [ ] 5.7 `src/planemapper/renderer/colours.py` - [ ] 5.8 `src/planemapper/renderer/icons.py` - [ ] Task 6: Bundle `airports.csv` data file (AC: #3) - [ ] 6.1 Download `airports.csv` from OurAirports (https://ourairports.com/data/airports.csv) and place at `src/planemapper/data/airports.csv` - [ ] 6.2 Confirm `pyproject.toml` package-data entry covers `data/airports.csv` - [ ] 6.3 Smoke-test `importlib.resources` access in a scratch script or test to confirm the file is reachable after `pip install -e .` - [ ] Task 7: Create `systemd/` unit files (AC: #3) - [ ] 7.1 `systemd/planemapper-provision.service`: `Type=oneshot`, runs `planemapper-provision`; intended to run at first boot / post-reset; include `[Install]` target - [ ] 7.2 `systemd/planemapper-radar.service`: `Restart=always`, `After=planemapper-provision.service`; runs `planemapper-radar` - [ ] Task 8: Create `tests/` structure (AC: #2) - [ ] 8.1 `tests/conftest.py` — empty or with a minimal shared fixture comment - [ ] 8.2 `tests/fixtures/aircraft_sample.json` — minimal valid JSON stub (empty list acceptable) - [ ] 8.3 `tests/fixtures/airspace_sample.geojson` — minimal valid GeoJSON stub - [ ] 8.4 Top-level test stubs: `test_fetcher.py`, `test_models.py`, `test_projection.py`, `test_colours.py`, `test_icons.py`, `test_renderer.py`, `test_pipeline.py`, `test_gpio_ctrl.py` — each contains at least one `pass`-body test function so pytest can discover them - [ ] 8.5 `tests/provisioning/__init__.py` (empty) plus `test_location.py`, `test_tiles.py`, `test_config.py`, `test_provision_loop.py` with stub test functions - [ ] Task 9: Verify quality gates pass (AC: #1, #2, #3) - [ ] 9.1 Run `pip install -e .` and confirm both entry-point commands exist on PATH - [ ] 9.2 Run `planemapper-radar` and `planemapper-provision`; confirm each logs "not implemented" and exits 0 - [ ] 9.3 Run `pytest` and confirm zero failures - [ ] 9.4 Run `ruff check .` and confirm zero violations - [ ] 9.5 Run `ruff format --check .` and confirm zero formatting issues ## Dev Notes ### Critical Context **Architecture constraints:** - Package layout uses `src/` layout: `src/planemapper/` — ensure `pyproject.toml` specifies `package-dir = {"" = "src"}` so `pip install -e .` finds the package. - The `main.py` entry point **must not** import anything from `planemapper.provisioning.*`. This boundary is enforced by ruff; any violation will cause `ruff check .` to fail. - `airports.csv` must be bundled as package data and accessed via `importlib.resources`, not via a raw file path relative to the source tree, so it works correctly after installation. - All stub `main()` functions must log "not implemented" (using Python `logging`, not `print`) and return (exit code 0). Do not raise `NotImplementedError`. **Pinned dependency versions (do not deviate):** - Runtime: `Pillow==12.2.0`, `gpiozero==2.0.1`, `Flask==3.1.3`, `requests==2.33.1` - Dev: `pytest==9.0.3`, `ruff==0.15.11`, `gpiozero[mock]` - Python: `>=3.11` **Systemd unit design:** - `planemapper-provision.service`: `Type=oneshot` — runs once and exits; guards `planemapper-radar.service` startup - `planemapper-radar.service`: `Restart=always`, `After=planemapper-provision.service` — long-running radar loop **ruff configuration:** - `line-length = 100` - `target-version = "py311"` - Import boundary rule: `planemapper.main` may not import from `planemapper.provisioning.*` (use `ruff`'s `flake8-tidy-imports` or equivalent `banned-module-level-imports` setting) **Testing stance for this story:** - Tests are stubs only — each file must have at least one discoverable test function (even if it just calls `pass`) so pytest exits 0 with no collection errors. - No actual logic needs to be tested in Story 1.1; that begins in Story 1.2 onwards.