bloombase/docs/superpowers/specs/2026-05-27-plantdb-design.md
Stephan Kerkman 5b2cc87540 Add PlantDB design spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 19:23:20 +02:00

228 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PlantDB — Design Spec
**Date:** 2026-05-27
**Stack:** Django 5 + Bootstrap 5 + HTMX
**Deployment:** Docker Compose on homelab
---
## 1. Overview
PlantDB is a mobile-first family plant and flower registry. It tracks both indoor and outdoor plants, stores care information per species, surfaces a pruning calendar, and integrates with the Perenual API to auto-populate species data. No user accounts — a single shared instance for the whole family.
---
## 2. Architecture
```
Browser (mobile-first)
│ Bootstrap 5 + HTMX (partial page updates)
Nginx (reverse proxy + static/media files)
Gunicorn + Django 5
SQLite (single file, Docker volume)
└── Perenual API (external, cached on first lookup)
```
**Key principles:**
- Server-rendered Django templates throughout; HTMX handles dynamic interactions (species search live results, pruning log updates) without a separate API layer.
- Perenual API is called only once per species — results are cached in the `Species` table. No recurring API cost after initial lookups.
- All uploaded media (personal plant photos) stored in a Docker volume and served by Nginx.
---
## 3. Data Model
### `Species`
Populated from Perenual API or entered manually. One row per plant species.
| Field | Type | Notes |
|---|---|---|
| `id` | PK | |
| `common_name` | CharField | |
| `scientific_name` | CharField | |
| `description` | TextField | |
| `watering` | CharField | e.g. "Weekly", "Twice a week" |
| `sunlight` | CharField | e.g. "Full sun", "Partial shade" |
| `shadow_tolerance` | CharField | e.g. "Low", "Medium", "High" |
| `max_height_cm` | IntegerField | nullable |
| `growth_rate` | CharField | e.g. "Slow", "Moderate", "Fast" |
| `growth_season` | CharField | e.g. "SpringSummer" |
| `pruning_months` | JSONField | Default schedule, e.g. `[2, 6, 8]` — pre-fills the Plant field |
| `api_image_url` | URLField | Image from Perenual, nullable |
| `perenual_id` | IntegerField | Unique, nullable — prevents duplicate fetches |
### `Plant`
One row per physical plant owned by the family.
| Field | Type | Notes |
|---|---|---|
| `id` | PK | |
| `name` | CharField | User's nickname, e.g. "Big rose on back fence" |
| `species` | FK → Species | Nullable — plant can exist without a species link |
| `location` | CharField | Free text, e.g. "Living room windowsill" |
| `is_indoor` | BooleanField | |
| `pruning_months` | JSONField | This plant's actual pruning schedule — pre-filled from species, editable per plant |
| `photo` | ImageField | Personal photo, stored in media volume, nullable |
| `notes` | TextField | Free text, nullable |
| `date_added` | DateField | Auto-set on creation |
### `PruningLog`
One row per pruning event, logged by the family.
| Field | Type | Notes |
|---|---|---|
| `id` | PK | |
| `plant` | FK → Plant | |
| `pruned_on` | DateField | |
| `notes` | TextField | nullable |
---
## 4. Pages & Views
### Dashboard (home `/`)
- Summary tiles: total plant count, number of plants due for pruning this month.
- Upcoming pruning list: overdue plants first (red), then due this month (amber).
- Buttons: "View all plants", "Add plant".
- HTMX: none needed — static server render on load.
### Plant List (`/plants/`)
- Scrollable list of all plants, each showing name, location, indoor/outdoor badge, and thumbnail.
- Search bar at top — HTMX live search filters the list on each keystroke (targets the list partial).
- Filter chips: All / Indoor / Outdoor.
- FAB-style "+" button links to Add Plant.
### Plant Detail (`/plants/<id>/`)
- Full-width personal photo hero (falls back to API species image if no personal photo).
- Name, scientific name (italic), indoor/outdoor badge.
- Location line.
- Care chips: 💧 watering · ☀️ sunlight · 📏 max height.
- Species reference image (small, below care info) if API image available and personal photo also exists.
- Pruning strip: recommended months + last pruned date. Amber highlight if due this month, red if overdue.
- "Log pruning" button → inline HTMX form (date + optional notes), updates pruning strip on success.
- Pruning history accordion: list of all `PruningLog` entries for this plant.
- Edit and Delete actions.
### Add Plant (`/plants/add/`)
Two-step flow:
**Step 1 — Species search (optional)**
- Search input with HTMX live lookup against Perenual API (debounced, 300ms).
- Results show common name, scientific name, thumbnail.
- Selecting a result caches the species in the DB (if not already cached) and pre-fills the form.
- "Skip — enter manually" link bypasses to step 2 with a blank form.
**Step 2 — Plant form**
- Nickname field (required).
- Location field (free text).
- Indoor/Outdoor toggle.
- Care fields (watering, sunlight, etc.) — pre-filled from species if selected, all editable.
- Pruning months — pre-filled from species, shown as a 12-checkbox month picker.
- Photo section: "Take photo" (camera capture via `<input type="file" accept="image/*" capture="environment">`). The species API image, if available, is always shown automatically on the plant detail as a reference — no separate action needed.
- Notes textarea.
- Save button.
### Edit Plant (`/plants/<id>/edit/`)
Same form as Add, pre-populated.
### Pruning Calendar (`/pruning/`)
- Grouped by urgency:
1. **Overdue** (red) — plants with a recommended pruning month in the past and no log entry since.
2. **This month** (amber) — plants with current month in `pruning_months`.
3. **Next month** (grey) — plants due next month.
4. **Later this year** — collapsed section, expandable.
- Each entry: plant name, location, recommended months, last pruned date. Tap to open plant detail.
- Month strip at top (scrollable) to jump to any month and see what's scheduled.
### Species Detail (`/species/<id>/`) — optional read-only
- Shows all species info as reference. Linked from plant detail. Not editable directly (edit via the plant form).
---
## 5. Perenual API Integration
- **Endpoint used:** `GET https://perenual.com/api/species-list?q=<name>&key=<API_KEY>`
- **API key:** stored in Django settings via environment variable `PERENUAL_API_KEY`.
- **Cache strategy:** on first successful lookup, a `Species` row is created with `perenual_id` set. Any subsequent search that returns a species already in the DB skips the API entirely.
- **Free tier limit:** 100 requests/day. Given that lookups only happen when adding new plants (not on every page view), this is not a practical constraint for a family app.
- **Fallback:** if the API is unreachable or returns an error, the add-plant form stays open with an empty species form for manual entry. No hard dependency on the API.
---
## 6. Pruning Logic
A plant is considered **overdue** for pruning if:
- It has at least one `pruning_month` that fell within the past 12 months, AND
- There is no `PruningLog` entry with `pruned_on` falling within that month's window.
A plant is **due this month** if the current month is in `Plant.pruning_months` and not yet logged this month.
Using a 12-month rolling window (rather than calendar-year) correctly handles plants due in December when checked in January.
The pruning calendar view computes this in a Django queryset annotated with the current date — no background tasks or scheduled jobs needed.
---
## 7. Deployment
### Docker Compose
```
services:
web: Django + Gunicorn
nginx: Reverse proxy + static/media serving
volumes:
db: SQLite file
media: Uploaded plant photos
```
- Accessible on the local network via homelab IP or a local hostname (e.g. `plantdb.home`).
- No SSL required for LAN use.
- `docker compose up -d` starts everything; `docker compose down` stops it.
- Database backup: copy the SQLite volume file. Simple enough to add to an existing homelab backup routine.
### Environment Variables
| Variable | Purpose |
|---|---|
| `PERENUAL_API_KEY` | Perenual API key |
| `SECRET_KEY` | Django secret key |
| `DEBUG` | `False` in production |
| `ALLOWED_HOSTS` | Homelab hostname/IP |
---
## 8. Static & Media Files
- Static files (CSS, JS, Bootstrap): collected via `manage.py collectstatic`, served by Nginx.
- Media files (plant photos): stored in `/media/` inside the container, mounted as a Docker volume, served by Nginx under `/media/`.
---
## 9. Dependencies
| Package | Purpose |
|---|---|
| `django` | Web framework |
| `gunicorn` | WSGI server |
| `django-htmx` | HTMX integration helpers |
| `pillow` | Image handling for `ImageField` |
| `requests` | Perenual API calls |
No frontend build step — Bootstrap 5 and HTMX loaded via CDN or bundled as static files.
---
## 10. Out of Scope
- User authentication / accounts
- Watering or feeding reminders
- Push notifications
- Multi-language support
- Public internet access / SSL (LAN only)
- Offline / PWA mode