From 86fb342b0d383df4300defa0f757b3e8d386c0fd Mon Sep 17 00:00:00 2001 From: Darek Date: Sun, 12 Apr 2026 22:58:58 -0500 Subject: [PATCH] Add README and update CLAUDE.md with current model and rules --- CLAUDE.md | 19 ++++++-- README.md | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 README.md diff --git a/CLAUDE.md b/CLAUDE.md index b9424a8..70be39b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,15 +40,28 @@ journalctl --user -u battery-tracker -f `config.py` reads `DATABASE_URL` from the environment, falling back to `sqlite:///batteries.db`. Swapping to MariaDB is entirely a matter of setting that env var — no code changes needed. All column types are restricted to `Integer`, `String`, `Text`, `Boolean`, `DateTime` for MariaDB compatibility. Status is stored as `String(20)` (not `Enum`) to avoid DDL differences between SQLite and MariaDB. ### Models -- `Battery`: label (unique), brand, status (`available`/`installed`/`retired`), device_id (FK nullable, `ondelete=SET NULL`), notes -- `Device`: name (unique), battery_slots, notes -- Helper methods on both models (`is_available()`, `installed_count()`, `has_mixed_brands()`) are used directly in templates and route logic. +- `Battery`: label (unique), brand, status (`available`/`installed`/`retired`), device_id (FK nullable, `ondelete=SET NULL`), notes, size, chemistry, capacity_mah, tested_capacity_mah, tested_date, charge_cycles, purchase_date, storage_location +- `Device`: name (unique), battery_slots, device_type, notes +- Helper methods: `Battery.is_available/installed/retired()`, `Device.installed_count()`, `Device.installed_brands()`, `Device.has_mixed_brands()` ### Business rules (enforced in routes, not DB constraints) - Assigning a retired battery → hard block with flash error - Assigning to a full device → hard block with flash error - Mixed brands on same device → flash warning, assignment still proceeds - Deleting a battery → requires GET confirmation page first, then POST +- Bulk install into device: capacity check before write; batteries already in target device are skipped; retired batteries are skipped +- Unretire: sets status back to `available` +- Unassign with `next` form field → redirects to that URL (must start with `/`); falls back to dashboard + +### Adding new columns to existing DB +`create_all()` won't add columns to existing tables. Run via Python: +```python +import sqlite3 +conn = sqlite3.connect('batteries.db') +conn.execute('ALTER TABLE ADD COLUMN ') +conn.commit(); conn.close() +``` +Always snapshot the DB first: `cp batteries.db batteries.db.$(date +%Y-%m-%d).snapshot` ### Testing Tests use a temporary file-based SQLite DB (via `tempfile.mkstemp`) created fresh per test — avoids SQLite in-memory per-connection isolation issues. The `seeded_client` fixture in `conftest.py` pre-populates via HTTP POST calls (not direct DB access), so tests exercise the full stack. Battery IDs in tests are positional (id=1 is always the first battery POSTed by the fixture). diff --git a/README.md b/README.md new file mode 100644 index 0000000..f566f4c --- /dev/null +++ b/README.md @@ -0,0 +1,139 @@ +# Battery Tracker + +A self-hosted web app for tracking rechargeable batteries — where they are, what condition they're in, and which devices they're installed in. + +Built with Flask + SQLAlchemy. Runs on a Raspberry Pi or any Linux box with a systemd user service. + +## Features + +### Batteries +- Add batteries in bulk (auto-generated labels like "Eneloop 001", "Eneloop 002") +- Track status: **Available**, **Installed**, **Retired** +- Store metadata: size (AA/AAA/18650/…), chemistry (NiMH/Li-ion/…), capacity, tested capacity, charge cycles, purchase date, storage location, notes +- Visual health indicator when tested capacity is recorded (good/warn/bad) +- Retire and unretire batteries + +### Devices +- Add devices with a name, battery slot count, type (Remote Control, Flashlight, Sensor, …), and notes +- Install batteries into devices — by brand/quantity or by picking a specific battery +- Bulk install multiple batteries at once from the dashboard +- Mixed-brand warning when batteries from different brands share a device +- Edit device details in-place + +### Dashboard +- Overview stats (total / available / installed / retired) +- Full battery table with client-side filtering by status, brand, size, storage location, and free-text search +- Column picker (Chemistry, Capacity, Storage, Purchase Date, Cycles) stored in localStorage +- Quick-assign available batteries to a device without leaving the page +- Bulk actions: Unassign, Retire, Delete, Set Field (storage location or brand), Install in Device + +### UI +- Mobile-friendly: card-style table rows on small screens, full-width filter controls, stacked form buttons +- Dark mode — follows the OS system preference automatically (`prefers-color-scheme`) +- No JavaScript framework — vanilla JS only + +--- + +## Tech Stack + +| Layer | Technology | +|---|---| +| Web framework | Flask 3.x | +| ORM | SQLAlchemy 2.x (raw session, no Flask-SQLAlchemy) | +| Database | SQLite (dev) / MariaDB (prod) | +| WSGI server | Waitress | +| Process manager | systemd user service | +| Tests | pytest (45 acceptance tests) | + +--- + +## Setup + +### Prerequisites +- Python 3.10+ +- `python3-venv` + +### Install + +```bash +git clone +cd battery-tracker-app + +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +### Run (development) + +```bash +source .venv/bin/activate +flask run +# → http://127.0.0.1:5000 +``` + +### Run (production — systemd user service) + +```bash +bash sbin/install-service.sh # prompts for host/port, installs and enables the service +systemctl --user start battery-tracker +systemctl --user status battery-tracker +journalctl --user -u battery-tracker -f +``` + +### Database + +SQLite is used by default (`batteries.db` in the project root). To switch to MariaDB, set the environment variable before starting: + +```bash +export DATABASE_URL="mysql+pymysql://user:pass@host/dbname" +``` + +See `MIGRATION.md` for migrating an existing SQLite database to MariaDB. + +### Seed data (optional) + +```bash +source .venv/bin/activate +python seed.py +``` + +--- + +## Running Tests + +```bash +source .venv/bin/activate +pytest tests/ -v +``` + +Tests use a fresh temporary SQLite database per test and exercise the full HTTP stack. + +--- + +## Project Structure + +``` +app.py Flask app factory + all routes +models.py SQLAlchemy models (Battery, Device) +config.py Database URL config +seed.py Optional seed data script +migrate_to_mariadb.py SQLite → MariaDB migration script +MIGRATION.md MariaDB migration procedure +requirements.txt +sbin/ + install-service.sh systemd user service installer +templates/ + base.html Layout, CSS variables, dark mode, nav + dashboard.html Battery list with filters and bulk actions + battery_add.html Add batteries form + battery_detail.html Battery detail + edit + battery_delete.html Delete confirmation + assign.html Assign battery to device + device_list.html Device list with type filter + device_add.html Add device form + device_detail.html Device detail + install + edit +tests/ + conftest.py pytest fixtures + test_acceptance.py 45 acceptance tests +```