From b192424978c7ae20e4945aaf2a7880a116d805b7 Mon Sep 17 00:00:00 2001 From: Darek Date: Wed, 10 Jun 2026 12:35:18 -0500 Subject: [PATCH] Update CLAUDE.md models and HA sections for sub-component hierarchy --- CLAUDE.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index dbace15..f573207 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,8 +41,9 @@ journalctl --user -u battery-tracker -f ### Models - `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, battery_percentage -- `Device`: name (unique), battery_slots, device_type, notes, ha_entity_id -- Helper methods: `Battery.is_available/installed/retired()`, `Device.installed_count()`, `Device.installed_brands()`, `Device.has_mixed_brands()` +- `Device`: name, battery_slots, device_type, battery_size, location, notes, ha_entity_id, parent_id (self-FK, `ondelete=SET NULL`), parent_key (`-1` for top-level, else parent_id; uniqueness is `UNIQUE(parent_key, name)` so names are scoped per parent). Sub-components are one level deep only; parents have battery_slots=0, battery_size/device_type/location NULL. +- History/log models: `ChargeLog`, `CapacityTest`, `BatteryPctLog` (per-battery, FK), `Logbook` (attached to a battery or device via association tables, cascade delete-orphan) +- Helper methods: `Battery.is_available/installed/retired()`, `Device.installed_count()`, `Device.installed_brands()`, `Device.has_mixed_brands()`, `Device.has_children()`, `Device.is_subcomponent()`, `Device.effective_installed_count()/effective_slots()` (aggregate over children), `Device.installed_batteries()` (installed batteries incl. sub-components) ### Business rules (enforced in routes, not DB constraints) - Assigning a retired battery → hard block with flash error @@ -59,7 +60,7 @@ journalctl --user -u battery-tracker -f 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. +`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 (clamped to 0–100), and writes it to `battery_percentage` on each installed battery in that device — including batteries installed in its sub-components (`Device.installed_batteries()`). 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. ### Adding new columns to existing DB `create_all()` won't add columns to existing tables. Run via Python: