Add /feed page with tabbed forms for Give Feed and Purchase Feed: - GET /feed renders page with tabs (Give Feed default, Purchase Feed) - POST /actions/feed-given records feed given to a location - POST /actions/feed-purchased records feed purchases to inventory Also adopts idiomatic FastHTML/HTMX pattern: - Add hx-boost to base template for automatic AJAX on forms - Refactor egg form to use action/method instead of hx_post Spec §22 compliance: - Integer kg only, min=1 - Warn if inventory negative (but allow) - Toast + stay on page after submit - Location/type stick, amount resets to default bag size 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
419 lines
16 KiB
Markdown
419 lines
16 KiB
Markdown
# AnimalTrack Implementation Plan
|
|
|
|
Check off items as completed. Each phase builds on the previous.
|
|
|
|
---
|
|
|
|
## Phase 1: Foundation
|
|
|
|
### Step 1.1: Project Structure & Configuration
|
|
- [x] Create Python package structure (`src/animaltrack/`)
|
|
- [x] Create `pyproject.toml` with dependencies
|
|
- [x] Create `flake.nix` with dev environment
|
|
- [x] Create `.envrc`, `.gitignore`
|
|
- [x] Implement `config.py` with all env vars from spec §18
|
|
- [x] Write tests for config loading and validation
|
|
- [x] **Commit checkpoint** (61f704c)
|
|
|
|
### Step 1.2: Database Connection & Pragmas
|
|
- [x] Create `db.py` with connection factory
|
|
- [x] Set pragmas: WAL, synchronous=FULL, foreign_keys=ON, busy_timeout=5000
|
|
- [x] Create transaction context manager (BEGIN IMMEDIATE)
|
|
- [x] Create `constants.py` with END_OF_TIME_UTC
|
|
- [x] Write tests for pragmas and transactions
|
|
- [x] **Commit checkpoint** (d8910d6)
|
|
|
|
### Step 1.3: Migration Framework
|
|
- [x] Create `migrations.py` using FastMigrate patterns
|
|
- [x] Create `cli.py` with `migrate` and `create-migration` commands
|
|
- [x] Create initial migration for `schema_migrations` table
|
|
- [x] Write tests for migration discovery and application
|
|
- [x] **Commit checkpoint** (7d7cd2b)
|
|
|
|
### Step 1.4: Reference Tables Schema
|
|
- [x] Create migration for species, locations, products, feed_types, users tables
|
|
- [x] Create Pydantic models for each reference type
|
|
- [x] Write tests for model validation and constraints
|
|
- [x] **Commit checkpoint** (2e28827)
|
|
|
|
### Step 1.5: Reference Data Repository & Seeds
|
|
- [x] Create repositories for each reference type (CRUD operations)
|
|
- [x] Create `seeds.py` with idempotent seeder
|
|
- [x] Seed: users (ppetru, ines, guest), locations (Strip 1-4, Nursery 1-4)
|
|
- [x] Seed: species (duck, goose active; sheep inactive)
|
|
- [x] Seed: products (egg.duck, meat/offal/fat/bones/feathers/down)
|
|
- [x] Seed: feed types (starter, grower, layer - 20kg default)
|
|
- [x] Create `id_gen.py` for ULID generation
|
|
- [x] Write tests for repositories and seeding idempotency
|
|
- [x] **Commit checkpoint** (4579229)
|
|
|
|
---
|
|
|
|
## Phase 2: Event Infrastructure
|
|
|
|
### Step 2.1: Events Table Schema
|
|
- [x] Create migration for events, event_revisions, event_tombstones
|
|
- [x] Create migration for idempotency_nonces, event_animals tables
|
|
- [x] Create Pydantic models for Event, EventRevision, EventTombstone
|
|
- [x] Write tests for table constraints (ULID checks, JSON validation)
|
|
- [x] **Commit checkpoint** (262739d)
|
|
|
|
### Step 2.2: Event Store Core
|
|
- [x] Create `events/store.py` with append_event, get_event, list_events
|
|
- [x] Implement nonce validation (reject duplicates)
|
|
- [x] Implement clock skew guard (reject ts_utc > now + 5 min)
|
|
- [x] Create `events/types.py` with event type constants
|
|
- [x] Write tests for event storage, nonce rejection, clock skew
|
|
- [x] **Commit checkpoint** (29d5d3f)
|
|
|
|
### Step 2.3: Event Type Payloads
|
|
- [x] Create Pydantic models for all event payloads (spec §6):
|
|
- [x] LocationCreated, LocationRenamed, LocationArchived
|
|
- [x] AnimalCohortCreated, AnimalPromoted, AnimalMoved
|
|
- [x] AnimalAttributesUpdated, AnimalTagged, AnimalTagEnded
|
|
- [x] HatchRecorded, AnimalOutcome
|
|
- [x] ProductCollected, ProductSold
|
|
- [x] FeedPurchased, FeedGiven
|
|
- [x] AnimalMerged, AnimalStatusCorrected
|
|
- [x] Create `events/validation.py` for payload validation
|
|
- [x] Implement tag normalization (spec §20)
|
|
- [x] Write tests for each payload model
|
|
- [x] **Commit checkpoint** (80784ff)
|
|
|
|
### Step 2.4: Projection Infrastructure
|
|
- [x] Create `projections/base.py` with Projection base class
|
|
- [x] Create ProjectionRegistry
|
|
- [x] Create `events/processor.py` with process_event function
|
|
- [x] Write tests for projection registration and processing
|
|
- [x] **Commit checkpoint** (42cb1ed)
|
|
|
|
---
|
|
|
|
## Phase 3: Animal Domain
|
|
|
|
### Step 3.1: Animal Registry Schema
|
|
- [x] Create migration for animal_registry table with indexes
|
|
- [x] Create migration for live_animals_by_location table
|
|
- [x] Create migration for animal_aliases table
|
|
- [x] Create Pydantic models: Animal, enums (Sex, ReproStatus, LifeStage, Status, Origin)
|
|
- [x] Write tests for table constraints and unique nickname constraint
|
|
- [x] **Commit checkpoint** (739b7bf)
|
|
|
|
### Step 3.2: Interval Projections Schema
|
|
- [x] Create migration for animal_location_intervals
|
|
- [x] Create migration for animal_tag_intervals
|
|
- [x] Create migration for animal_attr_intervals
|
|
- [x] Create Pydantic models for interval types
|
|
- [x] Write tests for CHECK constraints (end_utc > start_utc)
|
|
- [x] **Commit checkpoint** (e3d6528)
|
|
|
|
### Step 3.3: Animal Cohort Creation
|
|
- [x] Create `projections/animal_registry.py` for cohort creation
|
|
- [x] Create `projections/intervals.py` for location and attribute intervals
|
|
- [x] Create `services/animal.py` with create_cohort function
|
|
- [x] Populate animal_registry, live_animals_by_location, event_animals
|
|
- [x] Create initial location and attribute intervals
|
|
- [x] Write tests: cohort validation, correct animal count, all projections updated
|
|
- [x] **Commit checkpoint** (876e817)
|
|
|
|
### Step 3.4: Animal Movement
|
|
- [x] Update projections for AnimalMoved event
|
|
- [x] Close old location interval, open new one
|
|
- [x] Update animal_registry and live_animals_by_location
|
|
- [x] Add move_animals to services/animal.py
|
|
- [x] Write tests: move validation (to!=from, single from_location), interval updates
|
|
- [x] **Commit checkpoint** (b89ea41)
|
|
|
|
### Step 3.5: Animal Attributes Update
|
|
- [x] Update projections for AnimalAttributesUpdated event
|
|
- [x] Close old attribute intervals, open new ones for changed attrs
|
|
- [x] Add update_attributes to services/animal.py
|
|
- [x] Write tests: only changed attrs create new intervals
|
|
- [x] **Commit checkpoint** (dc7700d)
|
|
|
|
### Step 3.6: Animal Tagging
|
|
- [x] Create migration for tag_suggestions table
|
|
- [x] Create `projections/tags.py` for tag intervals and suggestions
|
|
- [x] Update live_animals_by_location tags JSON
|
|
- [x] Add add_tag, end_tag to services/animal.py
|
|
- [x] Write tests: tag normalization, interval open/close, no-op handling
|
|
- [x] **Commit checkpoint** (0511ed7)
|
|
|
|
---
|
|
|
|
## Phase 4: Products & Feed
|
|
|
|
### Step 4.1: Feed Inventory Schema & Purchase
|
|
- [x] Create migration for feed_inventory table
|
|
- [x] Create `projections/feed.py` with FeedInventoryProjection
|
|
- [x] Implement apply_feed_purchased (increment purchased_kg, update price)
|
|
- [x] Implement revert_feed_purchased
|
|
- [x] Create `services/feed.py` with purchase_feed function
|
|
- [x] Write tests: purchase increments, price stored as cents, revert works
|
|
- [x] **Commit checkpoint** (5c10a75)
|
|
|
|
### Step 4.2: Feed Given Event
|
|
- [x] Implement apply_feed_given (increment given_kg, decrement balance)
|
|
- [x] Implement revert_feed_given
|
|
- [x] Add give_feed to services/feed.py
|
|
- [x] Block if no purchase <= ts_utc
|
|
- [x] Write tests: give updates inventory, blocked without purchase, revert
|
|
- [x] **Commit checkpoint** (2eb0ca7)
|
|
|
|
### Step 4.3: Product Collection Event
|
|
- [x] Create `projections/products.py` for ProductCollected
|
|
- [x] Create `services/products.py` with collect_product function
|
|
- [x] Create `selection/resolver.py` with basic resolve_selection
|
|
- [x] Populate event_animals for each resolved animal
|
|
- [x] Write tests: event created, event_animals populated
|
|
- [x] **Commit checkpoint** (d53decd)
|
|
|
|
### Step 4.4: 30-Day Stats Computation
|
|
- [x] Create migration for egg_stats_30d_by_location table
|
|
- [x] Implement bird-days calculation from intervals
|
|
- [x] Implement layer-eligible days filtering
|
|
- [x] Implement feed proration formula
|
|
- [x] Implement cost calculations (integer cents)
|
|
- [x] Create `services/stats.py` with get_egg_stats (compute on read)
|
|
- [x] Write tests: verify E2E test #1 baseline values (spec §21)
|
|
- [x] **Commit checkpoint** (c08fa47)
|
|
|
|
### Step 4.5: Product Sold Event
|
|
- [x] Implement apply_product_sold projection
|
|
- [x] Add sell_product to services/products.py
|
|
- [x] Calculate unit_price_cents = floor(total/qty)
|
|
- [x] Write tests: event stored, unit price calculated
|
|
- [x] **Commit checkpoint** (b48fab5)
|
|
|
|
---
|
|
|
|
## Phase 5: Selection & Historical Queries
|
|
|
|
### Step 5.1: Selection Filter DSL Parser
|
|
- [x] Create `selection/parser.py` for filter parsing
|
|
- [x] Support: AND (default), OR (|), negate (-), quotes
|
|
- [x] Fields: location, species, sex, life_stage, identified, tag
|
|
- [x] Create `selection/ast.py` for filter AST nodes
|
|
- [x] Write tests for all filter syntax variations
|
|
- [x] **Commit checkpoint**
|
|
|
|
### Step 5.2: Historical State Resolver
|
|
- [x] Update `selection/resolver.py` for point-in-time resolution
|
|
- [x] Use interval tables for historical state (spec §7 query pattern)
|
|
- [x] Create `selection/hash.py` with roster hash (xxhash64)
|
|
- [x] Write tests: resolver returns correct animals before/after events
|
|
- [x] **Commit checkpoint**
|
|
|
|
### Step 5.3: Optimistic Locking
|
|
- [x] Create `selection/validation.py` for selection validation
|
|
- [x] Re-resolve on submit, compute hash, return diff on mismatch
|
|
- [x] Create SelectionContext and SelectionDiff models
|
|
- [x] Write tests: mismatch detected, diff correct, confirmed bypasses
|
|
- [x] **Commit checkpoint**
|
|
|
|
---
|
|
|
|
## Phase 6: Event Lifecycle
|
|
|
|
### Step 6.1: Event Editing
|
|
- [x] Create `events/edit.py` with edit_event function
|
|
- [x] Store old version in event_revisions
|
|
- [x] Increment version
|
|
- [x] Implement projection updates using revert/apply pattern
|
|
- [x] Write tests: revision stored, version incremented, projections updated
|
|
- [x] Write test: E2E test #5 (edit egg event)
|
|
- [x] **Commit checkpoint**
|
|
|
|
### Step 6.2: Event Deletion
|
|
- [x] Create `events/delete.py` with delete_event function
|
|
- [x] Create tombstone, trigger replay/revert
|
|
- [x] Create `events/dependencies.py` for finding dependents
|
|
- [x] Implement recorder vs admin rules (spec §10)
|
|
- [x] Write tests: tombstone created, projections updated
|
|
- [x] Write tests: recorder blocked if dependents (409), admin cascade
|
|
- [x] Write test: E2E test #6
|
|
- [x] **Commit checkpoint** (282d3d0)
|
|
|
|
### Step 6.3: Animal Lifecycle Events
|
|
- [x] Implement HatchRecorded (creates hatchlings)
|
|
- [x] Implement AnimalOutcome (death/harvest/sold with yields)
|
|
- [x] Implement AnimalPromoted (identified=true, nickname)
|
|
- [x] Implement AnimalMerged (status=merged_into, aliases)
|
|
- [x] Implement AnimalStatusCorrected (admin-only with reason)
|
|
- [x] Write tests for each event type
|
|
- [x] Write test: E2E test #7 (harvest with yields)
|
|
- [x] **Commit checkpoint** (1153f6c)
|
|
|
|
---
|
|
|
|
## Phase 7: HTTP API
|
|
|
|
### Step 7.1: FastHTML App Shell
|
|
- [x] Create `web/app.py` with FastHTML setup
|
|
- [x] Configure HTMX extensions (head-support, preload, etc.)
|
|
- [x] Create `web/middleware.py`:
|
|
- [x] Auth middleware (X-Oidc-Username, TRUSTED_PROXY_IPS)
|
|
- [x] CSRF middleware (cookie + header + Origin/Referer)
|
|
- [x] Request logging (NDJSON format)
|
|
- [x] Request ID generation
|
|
- [x] Create `web/auth.py` with get_current_user, require_role
|
|
- [x] Write tests: auth extraction, CSRF validation, untrusted IP rejection
|
|
- [x] **Commit checkpoint** (84225d8)
|
|
|
|
### Step 7.2: Health & Static Assets
|
|
- [x] Create `web/routes/health.py` with /healthz (DB writable check)
|
|
- [x] /metrics with Prometheus format (enabled by default, env gated)
|
|
- [x] Configure static file serving (/static/vN/... with immutable cache)
|
|
- [x] Vendor MonsterUI assets (via Theme.slate.headers() CDN)
|
|
- [x] Create `web/templates/base.py` with base HTML template
|
|
- [x] Create bottom nav component (Egg • Feed • Move • Registry)
|
|
- [x] Write tests: healthz returns 200/503, static cache headers
|
|
- [x] **Commit checkpoint** (6cdf48f)
|
|
|
|
### Step 7.3: Egg Quick Capture ✓
|
|
- [x] Create `web/routes/eggs.py`:
|
|
- [x] GET / - Egg Quick Capture form
|
|
- [x] POST /actions/product-collected
|
|
- [x] Create `web/templates/eggs.py` with form
|
|
- [x] Implement UX defaults (integer only, min=1, location sticks, qty clears)
|
|
- [x] Write tests: form renders, POST creates event, validation errors (422)
|
|
- [x] **Commit checkpoint** (e9804cd)
|
|
|
|
### Step 7.4: Feed Quick Capture ✓
|
|
- [x] Create `web/routes/feed.py`:
|
|
- [x] GET /feed - Feed Quick Capture form
|
|
- [x] POST /actions/feed-given
|
|
- [x] POST /actions/feed-purchased
|
|
- [x] Create `web/templates/feed.py` with forms
|
|
- [x] Implement defaults per spec §22
|
|
- [x] Write tests: form renders, POST creates events, blocked without purchase
|
|
- [x] Adopt hx-boost pattern (idiomatic FastHTML/HTMX)
|
|
- [ ] **Commit checkpoint**
|
|
|
|
### Step 7.5: Move Animals
|
|
- [ ] Create `web/routes/move.py`:
|
|
- [ ] GET /move - Move Animals form
|
|
- [ ] POST /actions/animal-move
|
|
- [ ] Create `web/templates/move.py` with selection UI
|
|
- [ ] Implement mismatch confirmation flow
|
|
- [ ] Write tests: form renders, move validation, mismatch (409)
|
|
- [ ] Write test: E2E test #8 (optimistic lock)
|
|
- [ ] **Commit checkpoint**
|
|
|
|
---
|
|
|
|
## Phase 8: Registry & Event Log
|
|
|
|
### Step 8.1: Animal Registry View
|
|
- [ ] Create `web/routes/registry.py`:
|
|
- [ ] GET /registry with filters and pagination
|
|
- [ ] Create `web/templates/registry.py`:
|
|
- [ ] Table: ID, species, sex, life_stage, location, tags, last event, status
|
|
- [ ] Facet sidebar with counts
|
|
- [ ] Create `repositories/animals.py` with list_animals, get_facet_counts
|
|
- [ ] Implement infinite scroll (50/page, cursor=base64)
|
|
- [ ] Write tests: renders with animals, pagination works, filters apply
|
|
- [ ] **Commit checkpoint**
|
|
|
|
### Step 8.2: Event Log Projection & View
|
|
- [ ] Create migration for event_log_by_location table with cap trigger
|
|
- [ ] Create `projections/event_log.py` for event summaries
|
|
- [ ] Create `web/routes/events.py`:
|
|
- [ ] GET /event-log?location_id=...
|
|
- [ ] Create `web/templates/events.py`
|
|
- [ ] Write tests: events appear, capped at 500, ordered by ts_utc DESC
|
|
- [ ] **Commit checkpoint**
|
|
|
|
### Step 8.3: Animal Detail Drawer
|
|
- [ ] Create `web/routes/animals.py`:
|
|
- [ ] GET /animals/{animal_id}
|
|
- [ ] Create `web/templates/animal_detail.py`:
|
|
- [ ] Header summary
|
|
- [ ] Timeline (newest first)
|
|
- [ ] Quick actions
|
|
- [ ] Create `repositories/animal_timeline.py`
|
|
- [ ] Write tests: detail renders, timeline shows events, merge info shown
|
|
- [ ] **Commit checkpoint**
|
|
|
|
---
|
|
|
|
## Phase 9: Remaining Actions
|
|
|
|
### Step 9.1: Animal Actions Routes
|
|
- [ ] POST /actions/animal-attrs
|
|
- [ ] POST /actions/hatch-recorded
|
|
- [ ] POST /actions/animal-outcome
|
|
- [ ] POST /actions/animal-cohort
|
|
- [ ] POST /actions/animal-promote
|
|
- [ ] POST /actions/animal-tag-add
|
|
- [ ] POST /actions/animal-tag-end
|
|
- [ ] POST /actions/animal-status-correct (admin-only)
|
|
- [ ] Create form templates for each
|
|
- [ ] Write tests for each action
|
|
- [ ] **Commit checkpoint**
|
|
|
|
### Step 9.2: Product Sold Route
|
|
- [ ] POST /actions/product-sold
|
|
- [ ] Create form template
|
|
- [ ] Write tests: sale creates event, unit price calculated
|
|
- [ ] **Commit checkpoint**
|
|
|
|
### Step 9.3: User Defaults
|
|
- [ ] Create migration for user_defaults table
|
|
- [ ] Create `repositories/user_defaults.py`
|
|
- [ ] Integrate defaults into form rendering
|
|
- [ ] Write tests: defaults saved and loaded
|
|
- [ ] **Commit checkpoint**
|
|
|
|
---
|
|
|
|
## Phase 10: Polish & E2E
|
|
|
|
### Step 10.1: Full E2E Test Suite
|
|
- [ ] E2E test #1: Baseline eggs+feed+costs
|
|
- [ ] E2E test #2: Mixed group proration
|
|
- [ ] E2E test #3: Split flock, per-location stats
|
|
- [ ] E2E test #4: Backdated eggs use historical roster
|
|
- [ ] E2E test #5: Edit egg event (if not already done)
|
|
- [ ] E2E test #6: Deletes: recorder vs admin cascade (if not already done)
|
|
- [ ] E2E test #7: Harvest with yields (if not already done)
|
|
- [ ] E2E test #8: Optimistic lock with confirm (if not already done)
|
|
- [ ] **Commit checkpoint**
|
|
|
|
### Step 10.2: Location Events & Error Handling
|
|
- [ ] Implement LocationCreated (idempotent for seeding)
|
|
- [ ] Implement LocationRenamed
|
|
- [ ] Implement LocationArchived
|
|
- [ ] Standardize error responses (422, 409, 401/403)
|
|
- [ ] Toast via HX-Trigger
|
|
- [ ] Write tests for location events and error rendering
|
|
- [ ] **Commit checkpoint**
|
|
|
|
### Step 10.3: CLI, Docker & Deployment
|
|
- [ ] Complete CLI: serve, seed, migrate commands
|
|
- [ ] Update flake.nix for Docker image build
|
|
- [ ] Create docker.nix
|
|
- [ ] Document deployment configuration
|
|
- [ ] Write tests: CLI commands work
|
|
- [ ] **Final commit**
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Total steps**: 40 steps across 10 phases
|
|
**Each step**: Self-contained, testable, ends with integration
|
|
|
|
### Key Milestones
|
|
- Phase 1 complete: Project boots, DB works
|
|
- Phase 3 complete: Animals can be created and tracked
|
|
- Phase 4 complete: Feed/egg collection works with costs
|
|
- Phase 7 complete: Web UI is functional
|
|
- Phase 10 complete: Production ready
|
|
|
|
### Notes
|
|
- TDD: Write tests first
|
|
- Each step should be committable
|
|
- Refer to spec.md for authoritative behavior
|
|
- Check CLAUDE.md for framework patterns
|