feat: add animal registry schema and models

Add database tables for animal tracking:
- animal_registry: main snapshot table with all animal attributes
- live_animals_by_location: denormalized view for fast roster queries
- animal_aliases: merge tracking for when animals are discovered to be same

Includes Pydantic models and comprehensive tests for all constraints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-28 18:59:24 +00:00
parent d8259f4371
commit 739b7bfe32
4 changed files with 901 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
# ABOUTME: Pydantic models for animal tracking tables.
# ABOUTME: Includes Animal, LiveAnimal, and AnimalAlias for registry system.
from pydantic import BaseModel, Field
from animaltrack.events.enums import AnimalStatus, LifeStage, Origin, ReproStatus, Sex
class Animal(BaseModel):
"""Full animal record from animal_registry table.
Represents the current state of an animal including all attributes,
location, and lifecycle status.
"""
animal_id: str = Field(..., min_length=26, max_length=26)
species_code: str
identified: bool = False
nickname: str | None = None
sex: Sex
repro_status: ReproStatus
life_stage: LifeStage
status: AnimalStatus
location_id: str = Field(..., min_length=26, max_length=26)
origin: Origin
born_or_hatched_at: int | None = None
acquired_at: int | None = None
first_seen_utc: int
last_event_utc: int
class LiveAnimal(BaseModel):
"""Animal record from live_animals_by_location table.
Denormalized view of active animals for fast roster queries.
Only includes animals with status='alive'.
"""
animal_id: str = Field(..., min_length=26, max_length=26)
location_id: str = Field(..., min_length=26, max_length=26)
species_code: str
identified: bool = False
nickname: str | None = None
sex: Sex
repro_status: ReproStatus
life_stage: LifeStage
first_seen_utc: int
last_move_utc: int | None = None
tags: list[str] = []
class AnimalAlias(BaseModel):
"""Merge tracking record from animal_aliases table.
When two animals are discovered to be the same individual,
one becomes an alias pointing to the survivor.
"""
alias_animal_id: str = Field(..., min_length=26, max_length=26)
survivor_animal_id: str = Field(..., min_length=26, max_length=26)
merged_at_utc: int