Device Data (v1)
GET /api/user/deviceData returns aggregated IoT sensor readings for a given date range and category. It is the primary endpoint for dashboard charts and device data tables.
GET /api/user/deviceData
GET /api/user/deviceData?date1=09/11/2024&category=sourceCategory&type=DAY
Authorization: Bearer <access_token>
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
date1 | string | Yes | — | DD/MM/YYYY — start date |
date2 | string | No | — | DD/MM/YYYY — end of range for comparison queries |
category | string | Yes | — | One of sourceCategory, stockCategory, qualityCategory, energyCategory |
subCategory | string | No | — | Sub-category ID within the selected category |
divisionFactor | int | No | 1000 | Divisor applied to raw readings before display. Sent as 1000 if omitted |
type | string | Yes | — | Aggregation pattern from PatternType enum: HOUR, DAY, MONTH |
includeToday | boolean | No | true | When true, fetches the latest live reading for the current period |
Validation
Validation runs before any data fetching. The first missing required field short-circuits the request.
| Condition | Error code | Message |
|---|---|---|
date1 not provided | 1403 | `date1` missing |
type not provided | 1403 | `type` missing |
category not provided | 1403 | `category` missing |
category value not in current_user['categories'] | 1401 | Invalid category, `{category}` doesn't exist |
Date Conversion
DeviceDataRouteHandler.get_date1_and_date2() converts the DD/MM/YYYY request string to a UTC timestamp pair before any Cosmos DB query runs. The conversion is shift-aware — every industry defines a shift start time and an optional daysOffset for industries whose shift spans midnight.
Steps:
days_offset = int(current_user['daysOffset'])shiftis read fromindustry_meta = current_user['industryData']['meta']date1is combined withshift.startAtand parsed:f'{date1}T{shift.startAt}' → '%d/%m/%YT%H:%M:%S' → date1_local_with_shiftDateTimeUtil().convert_date_from_local_to_utc(date1_local_with_shift)converts to UTCdate1_utc_with_shift = utc_result - timedelta(days=days_offset)- Steps 3–5 are repeated for
date2if provided
The method returns four values: date1_utc_with_shift, date2_utc_with_shift, date1_local_with_shift, date2_local_with_shift.
Example — Industry with timezone=Asia/Kolkata, startAt=06:00:00, daysOffset=0:
| Field | Value |
|---|---|
| Request | date1=09/11/2024 |
| Parsed local | 09/11/2024 06:00:00 IST |
| Converted to UTC | 09/11/2024 00:30:00 UTC |
| Cosmos DB query range | 09/11/2024 00:30:00 UTC → 10/11/2024 00:30:00 UTC |
Edge case — daysOffset=1: The industry's logical day starts before midnight in local time. A request for date1=09/11/2024 will shift the UTC start back by one day. This is transparently handled; callers always pass the local business date.
Concurrent Data Fetch
Once date conversion completes, the route spawns up to 4 concurrent futures via ThreadPoolExecutor:
| Future | Method | Condition | Result slot |
|---|---|---|---|
| 1 | get_latest_data() → DeviceDataService.get_latest_data(date1_utc_with_shift) | Only if includeToday=True | latest_data (slot c=1) |
| 2 | get_last_data() → DeviceDataService.get_last_updated_time() | Always | last_updated_time (slot c=2) |
| 3 | get_devices_data() | Always | data1, data2 (slot c=3) |
| 4 | get_last_seven_days_data() | Only if type=='HOUR' AND qualityCategory present | quality_graph1, quality_graph2 (slot c=4) |
Future 3 — date range selection:
The Cosmos DB query coordinates differ by aggregation type:
type | Query coordinates |
|---|---|
HOUR | DeviceDataService.get_data(date1_utc, date2_utc) — UTC timestamps |
DAY, MONTH | DeviceDataService.get_data(date1_local, date2_local) — local timestamps |
Future 4 — quality week data:
Runs get_device_data_for_quality_week(date1_utc) and, if date2 was provided, a second call with date2_utc. Only executes when both conditions are met: type=='HOUR' and qualityCategory is present in the request context.
Formatting
The collected results are passed to DeviceDataFormatter.format_data(). The date arguments passed to the formatter differ by type to match the query coordinates used in Future 3:
type | Formatter date arguments |
|---|---|
HOUR | format_data(data1, data2, latest_data, last_updated_time, date1_utc, date2_utc, date1_utc, date2_utc, quality_graph1, quality_graph2) |
DAY, MONTH | format_data(data1, data2, latest_data, last_updated_time, date1_local, date2_local, date1_utc, date2_utc, quality_graph1, quality_graph2) |
Edge Cases
| Scenario | Behaviour |
|---|---|
includeToday=False | Future 1 is skipped entirely; latest_data is None; formatter receives None for that slot |
qualityCategory absent or type != 'HOUR' | Future 4 is skipped; quality_graph1 and quality_graph2 are None |
date2 not provided | Only date1 range is queried; data2 and quality_graph2 are None; formatter renders single-date view |
divisionFactor not sent | Defaults to 1000 before any processing; raw Cosmos readings are in milli-units |
daysOffset non-integer in DB | int() cast raises ValueError — treat as data integrity issue in industryData |
Status Codes
| Code | Cause |
|---|---|
200 | Data returned successfully |
401 | Missing or invalid JWT, or category not in user's allowed categories |
1403 | date1, type, or category missing |
Offline Device Thresholds
Devices are classified as offline based on how long has elapsed since their last data transmission. The threshold varies by the view period being rendered:
| View period | Minutes without data before "offline" |
|---|---|
| Hour view | 15 minutes |
| Day view | 60 minutes |
| Month view | 1800 minutes |
These thresholds are defined in Constants and are applied in both the user device data formatters and the admin offline devices endpoint.
Status Code Reference
| Code | Meaning |
|---|---|
200 | Success |
401 | Missing or invalid JWT; category not in user's allowed categories |
1401 | Invalid category value — does not exist in user context |
1403 | Missing required parameter (date1, type, category, or unitId) |