This is a local backend system that helps factories track machine production. Machines send periodic telemetry events containing production counts and operational status. This API persists these events in a SQLite database and exposes simple REST endpoints for querying total production metrics and machine efficiency ratios.
-
Install dependencies:
pip install -r requirements.txt
-
Start the server:
uvicorn app.main:app --reload
-
Access API documentation: Open your browser to http://127.0.0.1:8000/docs
The database file (fabmetrics.db) is created automatically on first startup.
monzer_fabmetrics_task/
├── app/
│ ├── __init__.py # Package marker
│ ├── main.py # FastAPI application entry point with lifespan management
│ ├── database.py # SQLite connection and query functions
│ ├── models.py # Pydantic request/response schemas
│ └── routes.py # API endpoint definitions
├── fabmetrics.db # SQLite database (auto-created on first run)
├── requirements.txt # Python dependencies
└── README.md
CREATE TABLE IF NOT EXISTS production_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
machine_id TEXT NOT NULL,
timestamp TEXT NOT NULL,
produced_item_count INTEGER NOT NULL,
machine_status TEXT NOT NULL CHECK (machine_status IN ('running', 'stopped')),
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_machine_id ON production_events(machine_id);
CREATE INDEX IF NOT EXISTS idx_timestamp ON production_events(timestamp);| Column | Type | Description |
|---|---|---|
id |
INTEGER | Auto-incrementing primary key |
machine_id |
TEXT | Identifier for the machine (e.g., "M1", "M2") |
timestamp |
TEXT | Event time in ISO 8601 format (stored as-is from client) |
produced_item_count |
INTEGER | Number of items produced in this event |
machine_status |
TEXT | Machine state: 'running' or 'stopped' |
created_at |
TEXT | Server timestamp when record was inserted |
Create a new production event.
Request Body:
{
"machine_id": "M1",
"timestamp": "2024-01-15T10:30:00",
"produced_item_count": 100,
"machine_status": "running"
}Example Request:
curl -X POST "http://127.0.0.1:8000/events" \
-H "Content-Type: application/json" \
-d '{"machine_id": "M1", "timestamp": "2024-01-15T10:30:00", "produced_item_count": 100, "machine_status": "running"}'Example Response (201 Created):
{
"id": 1,
"machine_id": "M1",
"timestamp": "2024-01-15T10:30:00",
"produced_item_count": 100,
"machine_status": "running"
}Get total production count for all machines or a specific machine.
Query Parameters:
machine_id(optional): Filter to a specific machine
Behavior:
- Without
machine_id: Returns a list of totals for all machines - With
machine_id: Returns a single object for that machine (404 if not found)
Example Request (all machines):
curl "http://127.0.0.1:8000/production/total"Example Response (list):
[
{"machine_id": "M1", "total_produced": 250},
{"machine_id": "M2", "total_produced": 240}
]Example Request (specific machine):
curl "http://127.0.0.1:8000/production/total?machine_id=M1"Example Response (single object):
{"machine_id": "M1", "total_produced": 250}Example Response (machine not found - 404):
{"detail": "Machine 'UNKNOWN' not found"}Get total production per machine within a time range.
Query Parameters:
start(required): Start timestamp in ISO 8601 formatend(required): End timestamp in ISO 8601 formatmachine_id(optional): Filter to a specific machine
Example Request:
curl "http://127.0.0.1:8000/production/range?start=2024-01-15T00:00:00&end=2024-01-15T23:59:59"Example Response:
[
{"machine_id": "M1", "total_produced": 250},
{"machine_id": "M2", "total_produced": 240}
]Example Request (with machine filter):
curl "http://127.0.0.1:8000/production/range?start=2024-01-15T00:00:00&end=2024-01-15T23:59:59&machine_id=M1"Example Response:
[
{"machine_id": "M1", "total_produced": 250}
]Get efficiency metrics for a specific machine.
Path Parameters:
machine_id(required): The machine identifier
Example Request:
curl "http://127.0.0.1:8000/efficiency/M1"Example Response:
{
"machine_id": "M1",
"total_events": 3,
"running_events": 2,
"efficiency_ratio": 0.6666666666666666
}Example Response (machine not found - 404):
{"detail": "Machine 'UNKNOWN' not found"}The efficiency ratio is calculated as:
efficiency_ratio = running_events / total_events
Where:
- running_events = count of events where
machine_status = 'running' - total_events = count of all events for that machine
This measures the proportion of recorded events where the machine was running. It does not infer continuous runtime or estimate duration between events.
Example: If a machine has 3 events (2 running, 1 stopped), its efficiency ratio is 2/3 ≈ 0.67.
All endpoints tested and verified:
| Test | Endpoint | Expected | Actual | Status |
|---|---|---|---|---|
| 1 | GET /production/total | M1: 250, M2: 240 | M1: 250, M2: 240 | ✅ PASS |
| 2 | GET /production/total?machine_id=M1 | M1: 250 | M1: 250 | ✅ PASS |
| 3 | GET /efficiency/M1 | 3 events, 2 running, 0.667 | 3 events, 2 running, 0.667 | ✅ PASS |
| 4 | GET /efficiency/M2 | 2 events, 2 running, 1.0 | 2 events, 2 running, 1.0 | ✅ PASS |
Below are manual test cases to verify the system works correctly.
Insert 3 events for machine "M1" (2 running, 1 stopped):
curl -X POST "http://127.0.0.1:8000/events" \
-H "Content-Type: application/json" \
-d '{"machine_id": "M1", "timestamp": "2024-01-15T10:00:00", "produced_item_count": 100, "machine_status": "running"}'
curl -X POST "http://127.0.0.1:8000/events" \
-H "Content-Type: application/json" \
-d '{"machine_id": "M1", "timestamp": "2024-01-15T11:00:00", "produced_item_count": 80, "machine_status": "running"}'
curl -X POST "http://127.0.0.1:8000/events" \
-H "Content-Type: application/json" \
-d '{"machine_id": "M1", "timestamp": "2024-01-15T12:00:00", "produced_item_count": 70, "machine_status": "stopped"}'Insert 2 events for machine "M2" (both running):
curl -X POST "http://127.0.0.1:8000/events" \
-H "Content-Type: application/json" \
-d '{"machine_id": "M2", "timestamp": "2024-01-15T10:30:00", "produced_item_count": 90, "machine_status": "running"}'
curl -X POST "http://127.0.0.1:8000/events" \
-H "Content-Type: application/json" \
-d '{"machine_id": "M2", "timestamp": "2024-01-15T11:30:00", "produced_item_count": 150, "machine_status": "running"}'curl "http://127.0.0.1:8000/production/total"Expected Response:
[
{"machine_id": "M1", "total_produced": 250},
{"machine_id": "M2", "total_produced": 240}
]curl "http://127.0.0.1:8000/production/total?machine_id=M1"Expected Response:
{"machine_id": "M1", "total_produced": 250}curl "http://127.0.0.1:8000/production/range?start=2024-01-15T00:00:00&end=2024-01-15T23:59:59"Expected Response:
[
{"machine_id": "M1", "total_produced": 250},
{"machine_id": "M2", "total_produced": 240}
]curl "http://127.0.0.1:8000/production/range?start=2024-01-15T10:00:00&end=2024-01-15T11:30:00&machine_id=M1"Expected Response:
[
{"machine_id": "M1", "total_produced": 180}
](Only the first two M1 events fall within this range: 100 + 80 = 180)
curl "http://127.0.0.1:8000/efficiency/M1"Expected Response:
{
"machine_id": "M1",
"total_events": 3,
"running_events": 2,
"efficiency_ratio": 0.6666666666666666
}curl "http://127.0.0.1:8000/efficiency/M2"Expected Response:
{
"machine_id": "M2",
"total_events": 2,
"running_events": 2,
"efficiency_ratio": 1.0
}curl -X POST "http://127.0.0.1:8000/events" \
-H "Content-Type: application/json" \
-d '{"machine_id": "M1", "timestamp": "2024-01-15T10:00:00", "produced_item_count": 100}'Expected Response (422):
{
"detail": [
{
"type": "missing",
"loc": ["body", "machine_status"],
"msg": "Field required",
"input": {...}
}
]
}curl "http://127.0.0.1:8000/production/total?machine_id=UNKNOWN"Expected Response (404):
{"detail": "Machine 'UNKNOWN' not found"}curl "http://127.0.0.1:8000/efficiency/UNKNOWN"Expected Response (404):
{"detail": "Machine 'UNKNOWN' not found"}curl "http://127.0.0.1:8000/production/range?start=invalid-date&end=2024-01-15T23:59:59"Expected Response (422):
{"detail": "Invalid timestamp format. Use ISO 8601."}curl -X POST "http://127.0.0.1:8000/events" \
-H "Content-Type: application/json" \
-d '{"machine_id": "M1", "timestamp": "2024-01-15T10:00:00", "produced_item_count": 100, "machine_status": "idle"}'Expected Response (422):
{
"detail": [
{
"type": "value_error",
"loc": ["body", "machine_status"],
"msg": "Value error, machine_status must be 'running' or 'stopped'",
"input": "idle"
}
]
}-
Timestamps stored as-is: Timestamps are stored exactly as provided by the client without timezone conversion. ISO 8601 format is validated but not normalized.
-
Event-count based efficiency: The efficiency ratio measures the proportion of events where the machine was running. It does not infer continuous runtime or calculate duration between events.
-
No authentication: This API is intended for local development and does not implement authentication or authorization.
-
Single SQLite database: Uses a single SQLite file (
fabmetrics.db) suitable for local development only. Not designed for production deployment or concurrent heavy loads. -
Return type varies for /production/total:
- Without
machine_idparameter: returns a list of all machines - With
machine_idparameter: returns a single object for that machine (or 404)
- Without
-
Empty results: List endpoints (
/production/totalwithout filter,/production/range) return an empty list[]when no data matches, not a 404 error. -
No additional endpoints: Only the four specified endpoints are implemented. No health checks, no bulk operations, no delete operations.
-
Timestamp comparison: Time range queries compare timestamps as strings. This works correctly for ISO 8601 format since it sorts lexicographically in chronological order.