diff --git a/CLAUDE.md b/CLAUDE.md index bb76d6d..dbace15 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,6 +53,10 @@ journalctl --user -u battery-tracker -f - Unretire: sets status back to `available` - Unassign with `next` form field → redirects to that URL (must start with `/`); falls back to dashboard - Logging a charge entry → sets `battery.battery_percentage = 100` +- Retired batteries are excluded from `needs_attention` and `low_pct` warnings — both use the `active` list (`status in ("available", "installed")`); the template-side `low_pct` filter and the in-table ⚠ badge also guard against retired status + +### Dashboard filtering +The dashboard route builds an `active` list (`status in ("available", "installed")`) used for all warning logic. Client-side filtering uses `data-status` attributes on each table row and `applyFilters()` in JS. The default filter state is `"active"` (retired rows hidden on page load); the Reset button restores `"active"`, not an empty filter. Column visibility choices are stored in `localStorage`. ### Home Assistant integration (optional) `ha_client.py` wraps the HA REST API (`GET /api/states/`). `ha_poller.py` runs a daemon thread started in `create_app` only when `HOMEASSISTANT_URL` and `HOMEASSISTANT_API_KEY` are set. The poller queries all `Device` rows with `ha_entity_id IS NOT NULL`, fetches the current percentage from HA, and writes it to `battery_percentage` on each installed battery in that device. The poller uses its own `sessionmaker` session (not the request-scoped `scoped_session`). When HA is not configured the app behaves exactly as before — all HA UI is gated on `ha_enabled` passed to templates. diff --git a/MIGRATION.md b/MIGRATION.md index 38d3e4c..d3cdf27 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,81 @@ # Schema Migrations +## Adding battery metadata fields, device type, and history tables + +These columns and tables were added over several feature commits. `create_all()` does not add columns to existing tables, so existing installations need the `ALTER TABLE` commands below. New tables (`capacity_test`, `charge_log`) are created automatically by `create_all()` on next restart for SQLite — the explicit SQL below is provided for reference and for MariaDB operators who manage schema manually. + +**Always snapshot first:** +```bash +cp batteries.db batteries.db.$(date +%Y-%m-%d).snapshot +``` + +**SQLite:** +```bash +sqlite3 batteries.db "ALTER TABLE device ADD COLUMN device_type VARCHAR(50);" +sqlite3 batteries.db "ALTER TABLE battery ADD COLUMN size VARCHAR(20);" +sqlite3 batteries.db "ALTER TABLE battery ADD COLUMN chemistry VARCHAR(20);" +sqlite3 batteries.db "ALTER TABLE battery ADD COLUMN capacity_mah INTEGER;" +sqlite3 batteries.db "ALTER TABLE battery ADD COLUMN tested_capacity_mah INTEGER;" +sqlite3 batteries.db "ALTER TABLE battery ADD COLUMN tested_date VARCHAR(10);" +sqlite3 batteries.db "ALTER TABLE battery ADD COLUMN charge_cycles INTEGER;" +sqlite3 batteries.db "ALTER TABLE battery ADD COLUMN purchase_date VARCHAR(10);" +sqlite3 batteries.db "ALTER TABLE battery ADD COLUMN storage_location VARCHAR(100);" +``` + +New tables (auto-created on restart, shown here for reference): +```python +import sqlite3 +conn = sqlite3.connect('batteries.db') +conn.execute('''CREATE TABLE IF NOT EXISTS capacity_test ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + battery_id INTEGER NOT NULL REFERENCES battery(id) ON DELETE CASCADE, + tested_capacity_mah INTEGER NOT NULL, + tested_date VARCHAR(10) NOT NULL, + notes TEXT +)''') +conn.execute('''CREATE TABLE IF NOT EXISTS charge_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + battery_id INTEGER NOT NULL REFERENCES battery(id) ON DELETE CASCADE, + charged_date VARCHAR(10) NOT NULL, + increment_cycles INTEGER NOT NULL DEFAULT 0, + notes TEXT +)''') +conn.commit(); conn.close() +``` + +**MariaDB / MySQL:** +```sql +ALTER TABLE device ADD COLUMN device_type VARCHAR(50) NULL; +ALTER TABLE battery ADD COLUMN size VARCHAR(20) NULL; +ALTER TABLE battery ADD COLUMN chemistry VARCHAR(20) NULL; +ALTER TABLE battery ADD COLUMN capacity_mah INT NULL; +ALTER TABLE battery ADD COLUMN tested_capacity_mah INT NULL; +ALTER TABLE battery ADD COLUMN tested_date VARCHAR(10) NULL; +ALTER TABLE battery ADD COLUMN charge_cycles INT NULL; +ALTER TABLE battery ADD COLUMN purchase_date VARCHAR(10) NULL; +ALTER TABLE battery ADD COLUMN storage_location VARCHAR(100) NULL; + +CREATE TABLE IF NOT EXISTS capacity_test ( + id INT AUTO_INCREMENT PRIMARY KEY, + battery_id INT NOT NULL, + tested_capacity_mah INT NOT NULL, + tested_date VARCHAR(10) NOT NULL, + notes TEXT, + CONSTRAINT fk_ct_battery FOREIGN KEY (battery_id) REFERENCES battery(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS charge_log ( + id INT AUTO_INCREMENT PRIMARY KEY, + battery_id INT NOT NULL, + charged_date VARCHAR(10) NOT NULL, + increment_cycles INT NOT NULL DEFAULT 0, + notes TEXT, + CONSTRAINT fk_cl_battery FOREIGN KEY (battery_id) REFERENCES battery(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +--- + ## Adding battery_pct_log table This table was added to track battery percentage change history. diff --git a/README.md b/README.md index 01704d9..9b91081 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,13 @@ Battery Tracker gives you a single source of truth: ### Dashboard - Overview stats (total / available / installed / retired) + **Low Battery** count when any battery is below 20% +- **Needs Attention** panel — highlights batteries with low capacity (< 80% of rated) or low percentage (< 20%, requires HA) - Charge summary: total charge events and events in the last year -- Full battery table with client-side filtering by status, brand, size, storage location, and free-text search +- Full battery table with client-side filtering by status, brand, size, storage location, and free-text search; **retired batteries hidden by default** (change the Status filter to show them) +- Sortable columns - Column picker (Chemistry, Capacity, Storage, Purchase Date, Cycles, Battery %) 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 +- Bulk actions: Unassign, Retire, Delete, Set Field (storage location or brand), Install in Device, Log Charged ### History & Charts - **Capacity history** — track tested capacity over time with a trend chart; full log in a modal with year filter and pagination @@ -79,7 +81,7 @@ Battery Tracker gives you a single source of truth: | Database | SQLite (dev) / MariaDB (prod) | | WSGI server | Waitress | | Process manager | systemd user service | -| Tests | pytest (80 acceptance tests) | +| Tests | pytest (82 tests) | ---