# therminal-py

Python SDK for Therminal — Kalshi temperature prediction markets + NWS weather data + live METAR signaling.
Wraps the REST API at `https://api.mostlyright.xyz`. WeatherLive fetches directly from AWC (aviationweather.gov).

## Stack
- Python 3.11+, httpx (HTTP client), tomllib (config)
- Optional: pandas + pyarrow (DataFrame support), scikit-learn (ML), torch (PyTorch), typer + rich (CLI)

## Project Layout
```
src/therminal/
  __init__.py          # Public API re-exports (backward compat)
  client.py            # TherminalClient facade (composes WeatherHistory + MarketsClient)
  config.py            # TherminalConfig — TOML + env + kwargs layered defaults
  _http.py             # HttpSession composition class (shared HTTP logic)
  _convert.py          # Unit conversion with Go-matched rounding (WeatherLive only)
  _version.py          # Version string ("1.0.3")
  exceptions.py        # Error hierarchy (NotFoundError, RateLimitError, etc.)
  cli.py               # Optional CLI (typer)
  models/
    _base.py           # DictLikeMixin, _to_int, _to_float shared utilities
    observation.py     # Observation dataclass (29 fields, THE schema contract)
    candle.py          # Candle dataclass
    market.py          # Market, Series dataclasses
    climate.py         # Climate dataclass
    lob.py             # LOBSnapshot, Level dataclasses
  weather/
    client.py          # WeatherHistory (observations, climate, climate_gaps via API)
    live.py            # WeatherLive (real-time METAR from AWC, batch support)
    _awc.py            # AWC field mapping (ported from therminal-ingest/awc-client.ts)
  markets/
    client.py          # MarketsClient (series, markets, candles, lob via API)
  ml/
    features.py        # WeatherFeatures sklearn transformer
    lob.py             # LOBFeatures sklearn transformer
    cache.py           # Local disk cache
    torch.py           # TherminalDataset (PyTorch)
tests/
  test_client.py       # TherminalClient facade tests (30 tests)
  test_models.py       # Observation + other model tests (24 tests)
  test_awc.py          # AWC field mapping tests (25 tests)
  test_config.py       # Config loading + resolution tests (18 tests)
  test_convert.py      # Unit conversion tests (25 tests)
  test_live.py         # WeatherLive tests (11 tests)
  test_parity.py       # Schema parity test — AWC vs API (5 tests)
  test_backward_compat.py  # v0.5.0 import compat (10 tests)
  test_ml.py           # WeatherFeatures tests (18 tests)
  test_ml_lob.py       # LOBFeatures tests (18 tests)
  test_integration.py  # Parametric E2E tests against live API + AWC (45 tests)
```

## Commands
```bash
source .venv/bin/activate
pytest tests/ -v --ignore=tests/test_integration.py   # Unit tests (206 tests)
pytest tests/test_integration.py -v --tb=short         # Integration tests (45 tests, hits live API)
pytest tests/ --cov=therminal                          # Coverage (80%)
ruff check src/ tests/               # Lint
python -m build                      # Build package
python -m therminal.cli health       # CLI
```

## Architecture
- **WeatherHistory** — historical observations/climate via therminal-api (unit conversion server-side)
- **WeatherLive** — real-time METAR from AWC (unit conversion client-side via _convert.py)
- **MarketsClient** — series/markets/candles/lob via therminal-api
- **TherminalClient** — backward-compat facade composing Weather + Markets
- **HttpSession** — shared HTTP logic via composition (not inheritance)
- **TherminalConfig** — 4-layer resolution: built-in < ~/.therminal.toml < env vars < kwargs
- **DictLikeMixin** — all dataclasses support obs["field"], obs.get("field"), obs.field

## Coding Rules
- All methods return typed dataclasses by default, DataFrame with `as_dataframe=True`
- Dict-style access (obs["temp_f"]) preserved for backward compat
- Never mutate input parameters (frozen dataclasses, immutable config)
- All HTTP errors mapped to specific exceptions
- Date params accept both YYYY-MM-DD and RFC3339 (auto-normalized)
- Go-matched rounding (round-half-away-from-zero) in _convert.py and _awc.py
- sea_level_pressure_mb is NOT converted (already metric)
- observed_at uses strftime('%Y-%m-%dT%H:%M:%SZ'), not isoformat()
- Pandas is optional — ImportError with helpful message if missing

## Testing
```bash
pytest tests/ -v --ignore=tests/test_integration.py   # 206 unit tests
pytest tests/test_integration.py -v --tb=short         # 45 integration tests (live API)
pytest tests/test_integration.py -v --reruns 2 --reruns-delay 10 -m "not slow"  # CI mode
```
- **Unit tests (206):** mocked HTTP, fast, run on every push
- **Integration tests (45):** hit live API + AWC, gate PyPI publish on tag push
- Schema parity test: Observation.from_api_dict() and from_awc_json() produce identical output
- Real AWC + API fixtures in tests/fixtures_*.json
- pytest-httpx for mocking API calls, unittest.mock for WeatherLive AWC calls
- pytest-rerunfailures for retry on transient network blips
- Markers: `@pytest.mark.integration`, `@pytest.mark.awc`, `@pytest.mark.slow`

## Skill routing

When the user's request matches an available skill, ALWAYS invoke it using the Skill
tool as your FIRST action. Do NOT answer directly, do NOT use other tools first.
The skill has specialized workflows that produce better results than ad-hoc answers.

Key routing rules:
- Product ideas, "is this worth building", brainstorming → invoke office-hours
- Bugs, errors, "why is this broken", 500 errors → invoke investigate
- Ship, deploy, push, create PR → invoke ship
- QA, test the site, find bugs → invoke qa
- Code review, check my diff → invoke review
- Update docs after shipping → invoke document-release
- Weekly retro → invoke retro
- Design system, brand → invoke design-consultation
- Visual audit, design polish → invoke design-review
- Architecture review → invoke plan-eng-review
