<!-- FOR AI AGENTS - Human readability is a side effect, not a goal -->
<!-- Managed by agent: keep sections and order; edit content, not structure -->
<!-- Last updated: 2026-05-06 | Last verified: 2026-05-06 -->

# AGENTS.md

**tipsharks-elo-api** — Elo-style multi-entity ratings engine with REST API and Web UI for harness/thoroughbred racing.

**Precedence:** this file overrides root `AGENTS.md` for files in `tipsharks-elo-api/`.

## Commands
> Source: pyproject.toml + pre-commit config

<!-- AGENTS-GENERATED:START commands -->
| Task | Command | ~Time |
|------|---------|-------|
| Install | `pip install -e ".[dev]"` | ~30s |
| Lint | `ruff check .` | ~3s |
| Lint fix | `ruff check . --fix` | ~3s |
| Format | `black .` | ~5s |
| Format check | `black --check .` | ~5s |
| Typecheck | `mypy packages/ apps/` | ~10s |
| Test (all) | `pytest` | ~30s |
| Test (single) | `pytest tests/test_file.py` | ~5s |
| Test (pattern) | `pytest -k "pattern"` | ~5s |
| Test (coverage) | `pytest --cov=packages --cov=apps` | ~30s |
| Dev server | `uvicorn apps.api.main:app --reload` | - |
| CLI worker | `python -m apps.worker.cli` | - |
| Alembic migrate | `alembic upgrade head` | ~5s |
| Alembic new migration | `alembic revision --autogenerate -m "desc"` | ~5s |
<!-- AGENTS-GENERATED:END commands -->

## Stack
- **Language**: Python 3.12+
- **Framework**: FastAPI (async)
- **Web UI**: Vanilla HTML/CSS/JS + Bootstrap 5 + Chart.js
- **Database**: PostgreSQL 16 + SQLAlchemy 2.0 ORM
- **Migrations**: Alembic
- **HTTP Client**: httpx (async)
- **CLI**: Click + Rich
- **Testing**: pytest + pytest-asyncio + respx
- **Linting**: ruff + black + mypy
- **Deployment**: Docker + Docker Compose
- **CI/CD**: GitHub Actions

## File Map
<!-- AGENTS-GENERATED:START filemap -->
```
tipsharks-elo-api/
├── apps/
│   ├── api/                 # FastAPI REST API service
│   │   └── main.py          # API endpoints
│   ├── web/                 # Web UI
│   │   ├── templates/       # HTML pages
│   │   └── static/          # CSS, JS, assets
│   └── worker/              # CLI worker
│       └── cli.py           # Click commands (ingest, recompute, info)
├── packages/
│   ├── common/              # Utilities and configuration
│   │   ├── settings.py      # Pydantic settings
│   │   ├── logging.py       # Structured JSON logging
│   │   └── utils.py         # Date parsing, bucketing
│   ├── tab_client/          # TAB API integration
│   │   └── client.py        # Retries, rate limiting
│   ├── hrnz_scraper/        # HRNZ data scraper
│   ├── ratings/             # Core rating engine
│   │   ├── engine.py        # Multi-runner Elo algorithm
│   │   └── recompute.py     # Batch computation
│   └── storage/             # Database layer
│       ├── models.py        # SQLAlchemy ORM models
│       ├── repositories.py  # Data access layer
│       ├── ingestion.py     # Orchestration
│       └── database.py      # Session management
├── tests/                   # Test suite
├── scripts/                 # Standalone utilities
│   └── evaluate.py          # Accuracy evaluation
├── docs/                    # Documentation
│   ├── architecture.md      # System design
│   ├── data_model.md        # Database schema
│   ├── rating_math.md       # Elo mathematics
│   └── ops.md               # Operations guide
├── infrastructure/          # Deployment configs
├── alembic/                 # Database migrations
├── pyproject.toml           # Python project config
└── docker-compose.yml       # Multi-container orchestration
```
<!-- AGENTS-GENERATED:END filemap -->

## Overview
Elo-style multi-entity ratings engine with REST API and Web UI for harness/thoroughbred racing. Computes ratings for horses, drivers, and trainers using pairwise logistic models.

## Setup
```bash
pip install -e ".[dev]"
cp .env.example .env
# Edit .env with your configuration
docker compose up -d
alembic upgrade head
```

**Dependencies**: Python 3.12+, PostgreSQL
**Docker**: `docker compose up -d` starts PostgreSQL
**Mock mode**: Set `TAB_MOCK_MODE=true` for testing without API access

## Code style
- snake_case for files/functions, PascalCase for classes
- ruff + black for linting/formatting (line-length: 100)
- mypy for type checking
- Repository pattern for all database operations
- Pydantic models for API responses
- Structured JSON logging with contextual fields

## Security
- Never commit `.env` or credentials
- No user tracking or personal data in API requests
- All TAB API requests must be anonymous
- Logs must be sanitized (no IP addresses, session data)
- Admin endpoints require Bearer token authentication

## Checklist
Before committing:
- [ ] `pytest` passes
- [ ] `ruff check .` passes
- [ ] `black --check .` passes
- [ ] Zero-sum property preserved (rating changes)
- [ ] No direct DB queries (use repositories)

## Examples
<!-- AGENTS-GENERATED:START golden-samples -->
| For | Reference | Key patterns |
|-----|-----------|--------------|
| Repository | `packages/storage/repositories.py` | Upsert with ON CONFLICT, static methods |
| Rating engine | `packages/ratings/engine.py` | Multi-entity Elo, zero-sum property |
| API endpoint | `apps/api/main.py` | FastAPI + Depends(), Pydantic responses |
| Settings | `packages/common/settings.py` | Pydantic BaseSettings singleton |
| Test | `tests/test_rating_engine.py` | pytest fixtures, mock data |
| CLI command | `apps/worker/cli.py` | Click commands, Rich output |
<!-- AGENTS-GENERATED:END golden-samples -->

## When stuck
- Check `docs/` for architecture, data model, rating math, and ops guides
- Review `ALGORITHM.md` for rating algorithm details
- Run `python -m apps.worker.cli info` to check system status
- Use `pytest -v` for verbose test output
- Check `TODO.md` for project roadmap

## Utilities
<!-- AGENTS-GENERATED:START utilities -->
| Need | Use | Location |
|------|-----|----------|
| Settings | `get_settings()` singleton | `packages/common/settings.py` |
| DB session | `get_session()` context manager | `packages/storage/database.py` |
| Repositories | Static method pattern | `packages/storage/repositories.py` |
| TAB client | Async httpx with retries | `packages/tab_client/client.py` |
| Rating engine | `RatingEngine` class | `packages/ratings/engine.py` |
| Logging | Structured JSON logging | `packages/common/logging.py` |
<!-- AGENTS-GENERATED:END utilities -->

## Heuristics
<!-- AGENTS-GENERATED:START heuristics -->
| When | Do |
|------|-----|
| Adding DB table | Add model + migration + repository |
| Adding API endpoint | Add route + Pydantic response + test |
| Modifying rating algorithm | Preserve zero-sum + add tests + run recompute |
| Adding config param | Add to Pydantic settings + `.env.example` |
| Adding dependency | Ask first - we minimize deps |
| Unsure about pattern | Check Golden Samples above |
<!-- AGENTS-GENERATED:END heuristics -->

## Repository Settings
<!-- AGENTS-GENERATED:START repo-settings -->
- **Package manager**: pip
- **Python version**: >=3.12
- **Test framework**: pytest + pytest-asyncio
- **Lint**: ruff + black
- **Type check**: mypy
- **CI**: GitHub Actions
<!-- AGENTS-GENERATED:END repo-settings -->

## Key Conventions

### Repository Pattern
All database operations go through repositories with static methods:
```python
class EntityRepository:
    @staticmethod
    def upsert(session, data) -> Entity: ...
    @staticmethod
    def get_by_id(session, id) -> Optional[Entity]: ...
```

### Idempotent Operations
All writes use `INSERT ... ON CONFLICT DO UPDATE`.

### Rating Engine
- Zero-sum property: all rating changes in a race sum to zero
- Multi-entity formula: `r_eff = r_horse + α × r_driver + β × r_trainer + adj_barrier + adj_handicap`
- Deterministic: same input always produces same output

### API Conventions
- RESTful endpoints with Pydantic response models
- FastAPI `Depends()` for database sessions
- Pagination: `limit` (max 500) and `offset`
- Admin endpoints require Bearer token

## Boundaries

### Always Do
- Use repository pattern for all database operations
- Use Pydantic models for API responses (never return ORM models directly)
- Preserve zero-sum property in rating calculations
- Run `pytest` + `ruff check .` + `black --check .` before committing
- Use structured logging with contextual fields
- Handle errors gracefully with appropriate HTTP status codes

### Never Do
- Query database directly without repositories
- Return ORM models from API endpoints
- Modify rating calculations without preserving zero-sum
- Commit secrets or credentials
- Use raw SQL without idempotency guarantees
- Change rating parameters without documentation
- Push directly to main/master branch

## Privacy Requirements
- No user tracking or personal data in API requests
- All TAB API requests must be anonymous
- Logs must be sanitized (no IP addresses, session data, user identifiers)
- Only public racing data is stored

## Terminology
| Term | Means |
|------|-------|
| Elo | Rating system for multi-runner horse racing |
| RD | Rating Deviation — uncertainty quantification |
| K-factor | Controls rating update magnitude |
| Starter | Horse/driver/trainer combination in a race |
| Recompute | Batch recalculation of all ratings |
| Adjustment | Learned barrier/handicap effects |
