# Story 1.1: Project Scaffold & Verified Entry Points

Status: done

## 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

- [x] Task 1: Create `pyproject.toml` (AC: #1, #3)
  - [x] 1.1 Set `[build-system]` to use `setuptools` with `find_packages`
  - [x] 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)
  - [x] 1.3 Add `[project.scripts]` with `planemapper-radar = "planemapper.main:main"` and `planemapper-provision = "planemapper.provision:main"`
  - [x] 1.4 Add `[tool.setuptools.package-data]` entry so `planemapper/data/airports.csv` is included in the installed package
  - [x] 1.5 Add `[tool.ruff]` section: `line-length = 100`, `target-version = "py311"`, and an import boundary rule preventing `planemapper.main` from importing `planemapper.provisioning.*`

- [x] Task 2: Create `requirements.txt` and `requirements-dev.txt` (AC: #1, #3)
  - [x] 2.1 `requirements.txt`: pin Pillow==12.2.0, gpiozero==2.0.1, Flask==3.1.3, requests==2.33.1
  - [x] 2.2 `requirements-dev.txt`: pin pytest==9.0.3, ruff==0.15.11, add `gpiozero[mock]`

- [x] Task 3: Create top-level `src/planemapper/` module stubs (AC: #1, #3)
  - [x] 3.1 `src/planemapper/__init__.py` — empty or version string only
  - [x] 3.2 `src/planemapper/constants.py` — stub with module docstring and placeholder constants
  - [x] 3.3 `src/planemapper/models.py` — stub with module docstring
  - [x] 3.4 `src/planemapper/fetcher.py` — stub with module docstring
  - [x] 3.5 `src/planemapper/gpio_ctrl.py` — stub with module docstring
  - [x] 3.6 `src/planemapper/display.py` — stub with module docstring
  - [x] 3.7 `src/planemapper/main.py` — `main()` function that logs "not implemented" and returns; must NOT import from `planemapper.provisioning.*`
  - [x] 3.8 `src/planemapper/provision.py` — `main()` function that logs "not implemented" and returns

- [x] Task 4: Create `provisioning/` subpackage stubs (AC: #3)
  - [x] 4.1 `src/planemapper/provisioning/__init__.py`
  - [x] 4.2 `src/planemapper/provisioning/portal.py`
  - [x] 4.3 `src/planemapper/provisioning/location.py`
  - [x] 4.4 `src/planemapper/provisioning/tiles.py`
  - [x] 4.5 `src/planemapper/provisioning/airspace.py`
  - [x] 4.6 `src/planemapper/provisioning/wifi.py`
  - [x] 4.7 `src/planemapper/provisioning/config.py`

- [x] Task 5: Create `renderer/` subpackage stubs (AC: #3)
  - [x] 5.1 `src/planemapper/renderer/__init__.py`
  - [x] 5.2 `src/planemapper/renderer/renderer.py`
  - [x] 5.3 `src/planemapper/renderer/projection.py`
  - [x] 5.4 `src/planemapper/renderer/basemap.py`
  - [x] 5.5 `src/planemapper/renderer/aircraft.py`
  - [x] 5.6 `src/planemapper/renderer/airspace.py`
  - [x] 5.7 `src/planemapper/renderer/colours.py`
  - [x] 5.8 `src/planemapper/renderer/icons.py`

- [x] Task 6: Bundle `airports.csv` data file (AC: #3)
  - [x] 6.1 Download `airports.csv` from OurAirports (https://ourairports.com/data/airports.csv) and place at `src/planemapper/data/airports.csv`
  - [x] 6.2 Confirm `pyproject.toml` package-data entry covers `data/airports.csv`
  - [x] 6.3 Smoke-test `importlib.resources` access in a scratch script or test to confirm the file is reachable after `pip install -e .`

- [x] Task 7: Create `systemd/` unit files (AC: #3)
  - [x] 7.1 `systemd/planemapper-provision.service`: `Type=oneshot`, runs `planemapper-provision`; intended to run at first boot / post-reset; include `[Install]` target
  - [x] 7.2 `systemd/planemapper-radar.service`: `Restart=always`, `After=planemapper-provision.service`; runs `planemapper-radar`

- [x] Task 8: Create `tests/` structure (AC: #2)
  - [x] 8.1 `tests/conftest.py` — empty or with a minimal shared fixture comment
  - [x] 8.2 `tests/fixtures/aircraft_sample.json` — minimal valid JSON stub (empty list acceptable)
  - [x] 8.3 `tests/fixtures/airspace_sample.geojson` — minimal valid GeoJSON stub
  - [x] 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
  - [x] 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

- [x] Task 9: Verify quality gates pass (AC: #1, #2, #3)
  - [x] 9.1 Run `pip install -e .` and confirm both entry-point commands exist on PATH
  - [x] 9.2 Run `planemapper-radar` and `planemapper-provision`; confirm each logs "not implemented" and exits 0
  - [x] 9.3 Run `pytest` and confirm zero failures
  - [x] 9.4 Run `ruff check .` and confirm zero violations
  - [x] 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.
