Backend Documentation¶
The backend is built with FastAPI and provides REST APIs for the frontend and WebSocket support for real-time updates.
Project Structure¶
app/
├── __init__.py
├── main.py # FastAPI application entry point
├── api/
│ ├── __init__.py
│ ├── telemetry.py # Telemetry, Animals, Paddocks endpoints
│ ├── websocket.py # WebSocket endpoints
│ ├── alerts.py # Alerts endpoints
│ └── broadcast.py # Internal broadcast endpoints
├── core/
│ ├── __init__.py
│ ├── config.py # Configuration settings
│ └── database.py # Database connections
├── models/
│ └── __init__.py # SQLAlchemy models
└── schemas/
└── __init__.py # Pydantic schemas
Running the Backend¶
Using Docker¶
# Build and run with docker-compose
docker-compose up -d api
Locally¶
# Install dependencies
pip install -r requirements.txt
# Run the server
python -m uvicorn app.main:app --reload --port 8000
API Endpoints¶
Health Check¶
GET /health
Returns the health status of the API.
Response:
{
"status": "healthy"
}
Paddocks¶
List Paddocks¶
GET /api/paddocks
Returns a list of all paddocks with their geometry.
Response:
[
{
"id": "paddock-1",
"name": "North Paddock",
"area_hectares": 50.5,
"geometry": "POLYGON((144.94 -36.59,144.95 -36.59,...))",
"created_at": "2026-04-21T18:27:20.951114Z"
}
]
Get Paddock¶
GET /api/paddocks/{paddock_id}
Returns a single paddock by ID.
Create Paddock¶
POST /api/paddocks
Creates a new paddock.
Request Body:
{
"id": "paddock-4",
"name": "West Paddock",
"geometry": "POLYGON((144.94 -36.59,144.95 -36.59,144.95 -36.6,144.94 -36.6,144.94 -36.59))",
"area_hectares": 45.0
}
Animals¶
List Animals¶
GET /api/animals
Returns a list of all animals.
Response:
[
{
"id": "animal-1",
"name": "Cow 001",
"species": "cattle",
"belt_id": "BELT-001",
"current_paddock_id": "paddock-1",
"created_at": "2026-04-21T18:27:20.951114Z"
}
]
Get Animal¶
GET /api/animals/{belt_id}
Returns a single animal by belt ID.
Create Animal¶
POST /api/animals
Creates a new animal.
Request Body:
{
"id": "animal-6",
"name": "Cow 006",
"species": "cattle",
"belt_id": "BELT-006"
}
Telemetry¶
Get Latest Telemetry¶
GET /api/telemetry/latest
Returns the latest telemetry reading for each belt.
Response:
[
{
"id": 147,
"belt_id": "BELT-001",
"latitude": -36.595,
"longitude": 144.945,
"temperature": 38.5,
"activity_level": 5.0,
"timestamp": "2026-04-22T00:09:45+00:00",
"created_at": "2026-04-21T18:39:45.055833Z"
}
]
Get Telemetry History¶
GET /api/telemetry/{belt_id}?hours=24&limit=100
Returns telemetry history for a specific belt.
Query Parameters:
- hours - Number of hours to look back (default: 24, max: 168)
- limit - Maximum number of records (default: 100, max: 1000)
Create Telemetry¶
POST /api/telemetry
Creates a new telemetry reading.
Request Body:
{
"belt_id": "BELT-001",
"latitude": -36.595,
"longitude": 144.945,
"temperature": 38.5,
"activity_level": 5.0,
"timestamp": 1713724800
}
Alerts¶
Get Alerts¶
GET /api/alerts
Returns all geofence breach alerts.
Response:
[
{
"id": "alert-1",
"belt_id": "BELT-001",
"latitude": -36.595,
"longitude": 144.945,
"paddock_id": "paddock-1",
"timestamp": 1713724800,
"type": "geofence_breach",
"expected_paddock_id": "paddock-1"
}
]
WebSocket Endpoints¶
Telemetry WebSocket¶
const ws = new WebSocket('ws://localhost:8000/ws/telemetry');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Telemetry:', data);
};
Message Format:
{
"type": "telemetry",
"belt_id": "BELT-001",
"latitude": -36.595,
"longitude": 144.945,
"temperature": 38.5,
"activity_level": 5.0,
"timestamp": 1713724800
}
Alerts WebSocket¶
const ws = new WebSocket('ws://localhost:8000/ws/alerts');
Internal Endpoints¶
These endpoints are used by worker services for broadcasting:
Broadcast Telemetry¶
POST /internal/broadcast-telemetry
{
"type": "telemetry",
"belt_id": "BELT-001",
"latitude": -36.595,
"longitude": 144.945,
"temperature": 38.5,
"activity_level": 5.0,
"timestamp": 1713724800
}
Broadcast Alert¶
POST /internal/broadcast-alert
{
"type": "alert",
"belt_id": "BELT-001",
"latitude": -36.595,
"longitude": 144.945,
"paddock_id": "paddock-1",
"timestamp": 1713724800
}
Configuration¶
Configuration is managed through environment variables in app/core/config.py:
class Settings(BaseSettings):
# Database
database_url: str = "postgresql://livestock:livestock123@localhost:5432/livestock_db"
timescale_url: str = "postgresql://livestock:livestock123@timescale:5432/timescale_db"
# Kafka
kafka_bootstrap_servers: str = "kafka:9092"
kafka_consumer_group: str = "livestock-consumer-group"
kafka_telemetry_topic: str = "telemetry_raw"
kafka_alerts_topic: str = "alerts"
# MQTT
mqtt_broker_host: str = "mosquitto"
mqtt_broker_port: int = 1883
mqtt_topic: str = "livestock/telemetry/#"
# App
app_name: str = "Livestock Tracking Platform"
debug: bool = True
Error Handling¶
All endpoints return appropriate HTTP status codes:
200- Success400- Bad Request404- Not Found500- Internal Server Error
Error responses include a detail message:
{
"detail": "Animal with this belt_id already exists"
}