{"openapi":"3.0.0","paths":{"/api/usage-history/hour/{deviceId}":{"get":{"operationId":"getAllUsageHistoryHourFromDevice","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"Return all usage hours for energy devices (PLUG/MCB).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UsageHistoryHour"}}}}}},"summary":"Get All Usage Hours with DeviceId (Energy devices)","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/hour/date/{deviceId}":{"get":{"operationId":"getUsageHistoryByDate","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"201":{"description":"Returns usage hours for a specific device and date.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UsageHistoryHour"}}}}}},"summary":"Get Usage Hours for a Specific Date and Device","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/day/{deviceId}":{"get":{"operationId":"getAllUsageHistoryDayFromDevice","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"Returns usage days for energy devices (PLUG/MCB).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UsageHistoryDay"}}}}}},"summary":"Get Usage Days for a Specific Device (Energy devices)","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/date-range/distribution-box/{distributionBoxId}":{"get":{"operationId":"getPastUsageDistributionBox","parameters":[{"name":"distributionBoxId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"201":{"description":"Returns a list of daily energy usage with cost for a distribution box.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UsageHistoryDayWithCostBreakdownDto"}}}}}},"summary":"Get Energy Usage for a distribution box within a date range","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB"]}},"/api/usage-history/date-range/device/{deviceId}":{"get":{"operationId":"getPastUsageDevice","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Daily usage breakdown with cost for the device.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UsageHistoryDayWithCostBreakdownDto"}}}}}},"summary":"Get daily energy usage for a device within a date range","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/date-range/device-id/{deviceId}":{"get":{"description":"Returns hourly usage breakdown and the current tariff rate for cost calculations.","operationId":"getUsageHistoryPlugHourByDateRange","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns hourly usage data and current tariff rate for a device.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageHistoryHourWithTariffResponseDto"}}}}},"summary":"Get hourly usage for a device (Plug/MCB) within a date range with current tariff rate","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/date-range/realTime/energy/device-id/{deviceId}":{"get":{"description":"Returns real-time energy usage data including power, current, voltage, and power factor for PLUG and MCB devices.","operationId":"getEnergyDeviceRealTimeByDateRange","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Real-time energy usage data for MCB or Plug devices within date range","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UsageHistoryEnergyRealtimeDto"}}}}}},"summary":"Get Energy Usage for energy devices (RealTime) using a Specific Date Range (MCB, Plug only)","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/device/{deviceId}/period-cost":{"get":{"description":"Integrates the device power series over an exact time selection (as dragged on a realtime chart) and prices it at the rate resolved from the device scope chain. Returns two forward projections: `continuous` (this rate of draw sustained around the clock) and `recurring` (this exact window repeating daily) — the latter is the \"savings if scheduled off\" figure. Selections longer than 2 days are answered from hour rollups instead of raw realtime, quantised to whole hours and without power figures; check `resolution`. `coverageRatio` reports how much of the selection was actually backed by data.","operationId":"getDevicePeriodCost","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","description":"Start of the selected period (ISO 8601, inclusive)","schema":{"example":"2026-08-20T22:00:00.000Z","type":"string"}},{"name":"till","required":true,"in":"query","description":"End of the selected period (ISO 8601, exclusive)","schema":{"example":"2026-08-21T06:00:00.000Z","type":"string"}}],"responses":{"200":{"description":"Period energy, cost, and monthly / yearly projections","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevicePeriodCostResponseDto"}}}}},"summary":"Get the energy cost of an arbitrary selected period, projected","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/date-range/realTime/room/{roomId}/mcb":{"get":{"description":"Returns the aggregated total power for all MCB devices in the specified room within the given date range.","operationId":"getMcbRealtimeByRoom","parameters":[{"name":"roomId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Summed realtime power for all MCBs in the room.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McbRealtimeResponseDto"}}}}},"summary":"Get summed realtime power for all MCBs in a room","tags":["UsageHistory","Usage History - Usage Retrieval (System/Tenant/Zone/Room)"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB"]}},"/api/usage-history/date-range/realTime/zone/{zoneId}/mcb":{"get":{"description":"Returns the aggregated total power for all MCB devices assigned directly to the specified zone within the given date range.","operationId":"getMcbRealtimeByZone","parameters":[{"name":"zoneId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Summed realtime power for all MCBs in the zone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McbRealtimeResponseDto"}}}}},"summary":"Get summed realtime power for all MCBs directly assigned to a zone","tags":["UsageHistory","Usage History - Usage Retrieval (System/Tenant/Zone/Room)"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB"]}},"/api/usage-history/date-range/realTime/distribution-box/{distributionBoxId}/mcb":{"get":{"description":"Same shape as the room and zone MCB realtime endpoints — one series per measurement type plus a COMBINED total — and additionally a `devices` breakdown so the selection can be charted split out per MCB.","operationId":"getMcbRealtimeByDistributionBox","parameters":[{"name":"distributionBoxId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Summed realtime power for all MCBs in the distribution box, plus the per-device split.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McbRealtimeResponseDto"}}}}},"summary":"Get summed realtime power for all MCBs in a distribution box","tags":["UsageHistory","Usage History - Usage Retrieval (System/Tenant/Zone/Room)"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB"]}},"/api/usage-history/date-range/realTime/devices":{"post":{"description":"Summed and per-device realtime power for up to 20 selected PLUG or MCB devices. POST rather than GET because `DeviceGuard` scopes a batch through `body.deviceIds`. Ids of other device types are dropped — they write no energy realtime rows.","operationId":"getRealtimeByDeviceSelection","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevicesRealtimeRequestDto"}}}},"responses":{"200":{"description":"Summed series across the selection plus the per-device split, ordered by peak power.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McbRealtimeResponseDto"}}}}},"summary":"Get realtime power for an explicit device selection (max 20)","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-read-only":true,"x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/date-range/realTime/idle-energy/device-id/{deviceId}":{"get":{"operationId":"getUsageHistoryPlugRealTimeIdleEnergy","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns idle energy (RT) for a device.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceEnergyDto"}}}}},"summary":"Get Idle Energy Usage for a device (RealTime) using a Specific Date Range","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/date-range/realTime/average-energy/device-id/{deviceId}":{"get":{"operationId":"getUsageHistoryPlugAverageEnergy","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns average energy (RT) for a device.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceEnergyDto"}}}}},"summary":"Get Average Energy Usage for a device (RealTime) using a Specific Date Range","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/date-range/distribution-box-id/hour/{distributionBoxId}":{"get":{"description":"Returns hourly usage breakdown and the current tariff rate for cost calculations.","operationId":"getUsageHistoryDistributionBoxHourByDateRange","parameters":[{"name":"distributionBoxId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns hourly usage data and current tariff rate for a distribution box.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageHistoryHourWithTariffResponseDto"}}}}},"summary":"Get hourly usage for a distribution box within a date range with current tariff rate","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB"]}},"/api/usage-history/to-date/device-id/{deviceId}":{"get":{"description":"Returns total energy usage and calculated cost based on current tariff rate for any device type. Cost will be null if no tariff rate is configured for the system. This endpoint works for Plugs, MCBs, and Distribution Boxes.","operationId":"getUsageFromDeviceToDate","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns usage, cost, and tariff rate for a device. Cost is calculated as totalEnergyUsage * tariffRate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageWithCostResponseDto"}}}}},"summary":"Get Usage and Energy Cost for a device (Plug/MCB/Distribution Box) from beginning to date","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/device/hourly-boxplot":{"get":{"description":"Generic endpoint that aggregates hourly readings across multiple devices of the same type (PLUG, MCB, DISTRIBUTION_BOX, ENV_SENSOR), grouped by hour. Returns box plot statistics (min, Q1, median, Q3, max) per hour. For DISTRIBUTION_BOX, entityIds are distribution box IDs and energy is summed per box from its MCBs.","operationId":"getDeviceHourlyBoxplot","parameters":[{"name":"entityIds","required":true,"in":"query","description":"Comma-separated entity IDs. For PLUG/MCB/ENV_SENSOR: device IDs. For DISTRIBUTION_BOX: distribution box IDs.","schema":{"example":"60f7c2b3e1b3c2a1d4e5f6a7,60f7c2b3e1b3c2a1d4e5f6a8","type":"string"}},{"name":"deviceType","required":true,"in":"query","description":"Device type to aggregate across","schema":{"$ref":"#/components/schemas/BoxplotDeviceType"}},{"name":"date","required":true,"in":"query","description":"Date in YYYY-MM-DD format","schema":{"example":"2026-03-20","type":"string"}},{"name":"metric","required":false,"in":"query","description":"Metric field to aggregate. Required for ENV_SENSOR (e.g. temperature, humidity). Ignored for energy device types.","schema":{"example":"temperature","type":"string"}}],"responses":{"200":{"description":"Hourly box plot entries for the requested entities","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceBoxplotResponseDto"}}}}},"summary":"Get hourly box plot aggregation across devices on a given date","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/device/hourly-drilldown":{"get":{"description":"Returns overall box plot statistics and a per-entity value breakdown for the given hour. For DISTRIBUTION_BOX, each entry represents one distribution box total.","operationId":"getDeviceHourlyDrilldown","parameters":[{"name":"entityIds","required":true,"in":"query","description":"Comma-separated entity IDs. For PLUG/MCB/ENV_SENSOR: device IDs. For DISTRIBUTION_BOX: distribution box IDs.","schema":{"example":"60f7c2b3e1b3c2a1d4e5f6a7,60f7c2b3e1b3c2a1d4e5f6a8","type":"string"}},{"name":"deviceType","required":true,"in":"query","description":"Device type to aggregate across","schema":{"$ref":"#/components/schemas/BoxplotDeviceType"}},{"name":"date","required":true,"in":"query","description":"Date in YYYY-MM-DD format","schema":{"example":"2026-03-20","type":"string"}},{"name":"metric","required":false,"in":"query","description":"Metric field to aggregate. Required for ENV_SENSOR (e.g. temperature, humidity). Ignored for energy device types.","schema":{"example":"temperature","type":"string"}},{"name":"hour","required":true,"in":"query","description":"Hour of the day (0–23)","schema":{"example":14,"type":"number"}}],"responses":{"200":{"description":"Overall box stats and per-entity breakdown for the hour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceDrilldownResponseDto"}}}}},"summary":"Get per-entity energy breakdown for a specific hour","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB","PLUG"]}},"/api/usage-history/environment-sensor/hourly/{deviceId}":{"get":{"description":"Uses EnvironmentSensorUsageHistoryService. Returns hourly readings for all environmental metrics for ENV_SENSOR devices.","operationId":"getEnvironmentSensorHourlyReadings","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"till","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Hourly environment readings (temperature, humidity, CO2, PM2.5, TVOC, sound, motion, etc.)","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TelemetryHourEnvironmentSensorResponseDto"}}}}}},"summary":"Get hourly environment readings for a sensor within a date range - New Architecture","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["ENVIRONMENT_SENSOR"]}},"/api/usage-history/environment-sensor/realtime/{deviceId}":{"get":{"description":"Returns real-time readings for an ENV_SENSOR, downsampled server-side into fixed time buckets (see bucketMinutes) so the payload stays bounded.","operationId":"getEnvironmentSensorRealtimeReadings","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"Start of the range (ISO 8601). Defaults to now when omitted.","schema":{"type":"string"}},{"name":"till","required":false,"in":"query","description":"End of the range (ISO 8601). Defaults to now when omitted.","schema":{"type":"string"}},{"name":"bucketMinutes","required":false,"in":"query","description":"Downsample bucket size in minutes. One averaged point is returned per bucket. Defaults to 1.","schema":{"type":"number","enum":[1,5,10]}}],"responses":{"200":{"description":"Real-time environment readings, downsampled into time buckets (one averaged point per bucket).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TelemetryEnvironmentSensorRealtimeResponseDto"}}}}}},"summary":"Get real-time environment readings for a sensor within a date range - New Architecture","tags":["UsageHistory","Usage History - Device-Level Retrieval & Analytics"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["ENVIRONMENT_SENSOR"]}},"/api/mcb/by-id/{deviceId}":{"get":{"description":"Retrieves a single MCB by its unique ID with full details including system, tenant, room, and distribution box information.","operationId":"findMcbById","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"MCB retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MCB"}}}},"404":{"description":"MCB not found"}},"summary":"Get MCB by ID","tags":["MCB","MCB - Management"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB"]}},"/api/mcb/system/{systemId}":{"get":{"description":"Retrieve all MCBs in a system with support for pagination, search, and sorting by multiple fields. Sortable fields: createdAt, updatedAt, deviceName, macId, mcbType, ratedCurrent, realTimeVoltage, realTimePower, realTimeCurrent, powerFactor, ssid, chipModel, versionNumber, batchNumber, deviceNumber, lastSeen, onStatus, totalEnergyUsage, rangeEnergyUsage (requires from/till), rowLine, slotNumber, tenantName, zoneName, roomName, systemName, distributionBoxName. Use comma-separated values for multiple sorts, e.g., \"ratedCurrent,createdAt\" with corresponding \"desc,asc\". Defaults to physical panel position (distributionBoxName, rowLine, slotNumber ascending). With expandPhases=true, sorting by deviceName orders on each row's displayed phase name, and the realtime/energy fields order on the row's own phase values.","operationId":"findAllMcbsBySystem","parameters":[{"name":"systemId","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"Page number for pagination","schema":{"minimum":1,"default":1,"example":1,"type":"number"}},{"name":"size","required":false,"in":"query","description":"Items per page","schema":{"minimum":1,"maximum":1000,"default":50,"example":50,"type":"number"}},{"name":"sort_by","required":false,"in":"query","description":"Fields to sort by (comma-separated for multiple fields)","schema":{"example":"createdAt,name","type":"string"}},{"name":"sort_order","required":false,"in":"query","description":"Sort order for each field (comma-separated, must match sort_by length)","schema":{"example":"desc,asc","type":"array","items":{"type":"string","enum":["asc","desc"]}}},{"name":"search","required":false,"in":"query","description":"Search keyword","schema":{"example":"overheat","type":"string"}},{"name":"from","required":false,"in":"query","description":"Start of the energy window (inclusive), ISO 8601. Populates `rangeEnergyUsage` on each row. Requires `till`.","schema":{"example":"2026-08-01T00:00:00.000Z","type":"string"}},{"name":"till","required":false,"in":"query","description":"End of the energy window (inclusive), ISO 8601. Populates `rangeEnergyUsage` on each row. Requires `from`.","schema":{"example":"2026-08-31T23:59:59.999Z","type":"string"}},{"name":"tenantId","required":false,"in":"query","description":"Filter by system Id","schema":{"example":"Engineering Department","type":"string"}},{"name":"zoneId","required":false,"in":"query","description":"Filter by Zone ID","schema":{"example":"Engineering Department","type":"string"}},{"name":"roomId","required":false,"in":"query","description":"Filter by Room Id","schema":{"example":"Living Room","type":"string"}},{"name":"distributionBoxId","required":false,"in":"query","description":"Filter by Distribution Box Id","schema":{"example":"Distribution Box 1","type":"string"}},{"name":"onStatus","required":false,"in":"query","description":"Filter by device status","schema":{"$ref":"#/components/schemas/DeviceStatus"}},{"name":"versionNumber","required":false,"in":"query","description":"Filter by firmware version","schema":{"example":"1.0.0","type":"string"}},{"name":"mcbType","required":false,"in":"query","description":"Filter by MCB type (e.g. COIL for single-phase clamps, COIL3P for 3-phase clamps). Omit to return all types.","schema":{"$ref":"#/components/schemas/MCBType"}},{"name":"isSpare","required":false,"in":"query","description":"Filter by spare status. When true, returns only spare devices. When false, returns only non-spare devices. Omit to return all.","schema":{"example":false,"type":"boolean"}},{"name":"expandPhases","required":false,"in":"query","description":"When true, each in-use phase of a COIL3P device in split mode (isSingle=false) becomes its own row (with `phase` set and realTimePower/realTimeCurrent/realTimeVoltage/totalEnergyUsage/powerFactor holding that phase values), interleaved with single-phase MCBs for sorting and pagination. Combined-mode (isSingle=true) COIL3P devices stay as one row. Pagination counts rows, not devices.","schema":{"example":false,"type":"boolean"}}],"responses":{"200":{"description":"List of MCBs with pagination metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetMCBsResponseDto"}}}},"400":{"description":"Bad request - invalid sort field"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden: No access to this system"},"404":{"description":"System not found"}},"summary":"Get all MCBs in a system (with filtering and sorting)","tags":["MCB","MCB - Management"],"x-api-token":true,"x-api-token-audience":"system","x-sandbox-groups":["MCB"]}},"/api/mcb/bulk/names":{"get":{"description":"Generates suggested device names for multiple MCBs based on their Distribution Box name, Room name, and Measurement type. Format: \"DistributionBoxName | RoomName | MeasurementType\" (e.g. \"DB-4-7U-5 | 4/7/E | Lights & Fans\"). Requires SUPERADMIN or ADMIN role.","operationId":"getBulkMCBNames","parameters":[{"name":"mcbIds","required":true,"in":"query","description":"Comma-separated list of MCB IDs to generate names for","schema":{"example":"64b7f1e6f9c9d4e5b8a9f7c3,64b7f1e6f9c9d4e5b8a9f7c4","type":"string"}}],"responses":{"200":{"description":"Suggested names generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBulkMCBNamesResponseDto"}}}},"404":{"description":"One or more MCBs not found"}},"summary":"Get bulk MCB suggested names","tags":["MCB","MCB - Management"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB"]}},"/api/environment-sensor/by-id/{deviceId}":{"get":{"description":"Retrieves a single Environment Sensor device by its unique ID with full details including system, tenant, user, and room information.","operationId":"findEnvironmentSensorById","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Environment Sensor device retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentSensor"}}}},"404":{"description":"Environment Sensor device not found"}},"summary":"Get Environment Sensor device by ID","tags":["Environment Sensor - Management"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["ENVIRONMENT_SENSOR"]}},"/api/environment-sensor/system/{systemId}":{"get":{"description":"Retrieve all environment sensors in a system with support for pagination, search, and sorting by multiple fields. Sortable fields: createdAt, updatedAt, deviceName, humidity, carbonDioxide, lux, temperatureMin, temperatureMax, pm1, pm2_5, pm10, totalVolatileOrganicCompounds, soundLevel, motionDetected, environmentSensorType, lastSeen, tenantName, userName, roomName, chipModel, versionNumber, batchNumber, systemName, onStatus. Use comma-separated values for multiple sorts, e.g., \"carbonDioxide,createdAt\" with corresponding \"desc,asc\".","operationId":"findAllEnvironmentSensorsBySystem","parameters":[{"name":"systemId","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"Page number for pagination","schema":{"minimum":1,"default":1,"example":1,"type":"number"}},{"name":"size","required":false,"in":"query","description":"Items per page","schema":{"minimum":1,"maximum":1000,"default":50,"example":50,"type":"number"}},{"name":"sort_by","required":false,"in":"query","description":"Fields to sort by (comma-separated for multiple fields)","schema":{"example":"createdAt,name","type":"string"}},{"name":"sort_order","required":false,"in":"query","description":"Sort order for each field (comma-separated, must match sort_by length)","schema":{"example":"desc,asc","type":"array","items":{"type":"string","enum":["asc","desc"]}}},{"name":"search","required":false,"in":"query","description":"Search keyword","schema":{"example":"overheat","type":"string"}},{"name":"tenantId","required":false,"in":"query","description":"Filter by Tenant ID","schema":{"example":"64b7f1e6f9c9d4e5b8a9f7c3","type":"string"}},{"name":"zoneId","required":false,"in":"query","description":"Filter by Zone ID","schema":{"example":"64b7f1e6f9c9d4e5b8a9f7c4","type":"string"}},{"name":"roomId","required":false,"in":"query","description":"Filter by Room ID","schema":{"example":"64b7f1e6f9c9d4e5b8a9f7c5","type":"string"}},{"name":"environmentSensorType","required":false,"in":"query","description":"Filter by environment sensor type","schema":{"$ref":"#/components/schemas/EnvironmentSensorType"}},{"name":"isSpare","required":false,"in":"query","description":"Filter by spare status. When true, returns only spare devices. When false, returns only non-spare devices. Omit to return all.","schema":{"example":false,"type":"boolean"}}],"responses":{"200":{"description":"List of environment sensors with pagination metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetEnvironmentSensorsDto"}}}},"400":{"description":"Bad request - invalid sort field"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden: No access to this system"},"404":{"description":"System not found"}},"summary":"Get all environment sensors in a system (with filtering and sorting)","tags":["Environment Sensor - Management"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["ENVIRONMENT_SENSOR"]}},"/api/environment-sensor/threshold-group/system/{systemId}":{"get":{"description":"Returns all environment sensor threshold groups for the specified system. Optionally filter by environment sensor type.","operationId":"findAllThresholdGroupsBySystem","parameters":[{"name":"systemId","required":true,"in":"path","schema":{"type":"string"}},{"name":"environmentSensorType","required":false,"in":"query","description":"Filter by environment sensor type","schema":{"$ref":"#/components/schemas/EnvironmentSensorType"}}],"responses":{"200":{"description":"List of threshold groups","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/EnvironmentSensorThresholdGroup"}}}}},"403":{"description":"Forbidden: No access to this system"}},"summary":"Get all threshold groups for a system","tags":["Environment Sensor - Management","Environment Sensor - Threshold Groups"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["ENVIRONMENT_SENSOR"]}},"/api/distribution-box/by-id/{id}":{"get":{"description":"Retrieves a single Distribution Box by its unique ID with full details including system, tenant, room, and associated MCBs.","operationId":"findDistributionBoxById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Distribution Box retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DistributionBox"}}}},"404":{"description":"Distribution Box not found"}},"summary":"Get Distribution Box by ID","tags":["DistributionBox","Distribution Box - Management"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB"]}},"/api/distribution-box/system/{systemId}":{"get":{"description":"Retrieves a paginated list of Distribution Boxes for a system. Supports filtering by tenant, zone, and room. Includes search and comprehensive sorting capabilities (by createdAt, updatedAt, name, boxNumber, location, numberOfRows, totalMCBs, totalEnergyUsage, tenantName, zoneName, systemName).","operationId":"findAllDistributionBoxesBySystem","parameters":[{"name":"systemId","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"Page number for pagination","schema":{"minimum":1,"default":1,"example":1,"type":"number"}},{"name":"size","required":false,"in":"query","description":"Items per page","schema":{"minimum":1,"maximum":1000,"default":50,"example":50,"type":"number"}},{"name":"sort_by","required":false,"in":"query","description":"Fields to sort by (comma-separated for multiple fields)","schema":{"example":"createdAt,name","type":"string"}},{"name":"sort_order","required":false,"in":"query","description":"Sort order for each field (comma-separated, must match sort_by length)","schema":{"example":"desc,asc","type":"array","items":{"type":"string","enum":["asc","desc"]}}},{"name":"search","required":false,"in":"query","description":"Search keyword","schema":{"example":"overheat","type":"string"}},{"name":"tenantId","required":false,"in":"query","description":"Filter by tenant ID","schema":{"example":"507f1f77bcf86cd799439011","type":"string"}},{"name":"zoneId","required":false,"in":"query","description":"Filter by zone ID","schema":{"example":"507f1f77bcf86cd799439012","type":"string"}},{"name":"roomId","required":false,"in":"query","description":"Filter by room ID","schema":{"example":"507f1f77bcf86cd799439013","type":"string"}}],"responses":{"200":{"description":"List of Distribution Boxes retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetDistributionBoxesDto"}}}}},"summary":"Get all Distribution Boxes with pagination and sorting","tags":["DistributionBox","Distribution Box - Management"],"x-api-token":true,"x-api-token-audience":"system","x-sandbox-groups":["MCB"]}},"/api/distribution-box/zone/{zoneId}":{"get":{"description":"Retrieves a paginated list of Distribution Boxes assigned to a specific zone. Supports search and sorting.","operationId":"findAllDistributionBoxesByZone","parameters":[{"name":"zoneId","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"Page number for pagination","schema":{"minimum":1,"default":1,"example":1,"type":"number"}},{"name":"size","required":false,"in":"query","description":"Items per page","schema":{"minimum":1,"maximum":1000,"default":50,"example":50,"type":"number"}},{"name":"sort_by","required":false,"in":"query","description":"Fields to sort by (comma-separated for multiple fields)","schema":{"example":"createdAt,name","type":"string"}},{"name":"sort_order","required":false,"in":"query","description":"Sort order for each field (comma-separated, must match sort_by length)","schema":{"example":"desc,asc","type":"array","items":{"type":"string","enum":["asc","desc"]}}},{"name":"search","required":false,"in":"query","description":"Search keyword","schema":{"example":"overheat","type":"string"}}],"responses":{"200":{"description":"List of Distribution Boxes retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetDistributionBoxesDto"}}}}},"summary":"Get all Distribution Boxes for a zone","tags":["DistributionBox","Distribution Box - Management"],"x-api-token":true,"x-api-token-audience":"system","x-sandbox-groups":["MCB"]}},"/api/distribution-box/{id}/mcbs":{"get":{"description":"Retrieves all MCBs contained in a Distribution Box with their full details including current status and energy usage.","operationId":"getDistributionBoxMcbs","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"MCBs retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GetDistributionBoxMcbDto"}}}}},"404":{"description":"Distribution Box not found"}},"summary":"Get all MCBs in Distribution Box","tags":["DistributionBox","Distribution Box - Management"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB"]}},"/api/distribution-box/{id}/energy-usage":{"get":{"description":"Retrieves current energy usage data for a specific distribution box. Returns the sum of all MCBs contained in this distribution box including voltage, current, power, and energy consumption.","operationId":"getDistributionBoxEnergyUsage","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Energy usage retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetDistributionBoxEnergyUsageDto"}}}},"404":{"description":"Distribution Box not found"}},"summary":"Get Distribution Box energy usage","tags":["DistributionBox","Distribution Box - Management"],"x-api-token":true,"x-api-token-audience":"consumer","x-sandbox-groups":["MCB"]}},"/api/distribution-box/system/{systemId}/energy-usage":{"get":{"description":"Retrieves aggregated energy usage for all distribution boxes in a system. Provides system-wide distribution box analytics including total energy consumption and individual box breakdowns. Requires CIRCUIT_BREAKER feature.","operationId":"getDistributionBoxSystemEnergyUsage","parameters":[{"name":"systemId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Aggregated energy usage retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAggregatedDistributionBoxEnergyUsageDto"}}}}},"summary":"Get system distribution box energy usage","tags":["DistributionBox","Distribution Box - Management"],"x-api-token":true,"x-api-token-audience":"system","x-sandbox-groups":["MCB"]}},"/api/distribution-box/tenant/{tenantId}/energy-usage":{"get":{"description":"Retrieves aggregated energy usage for all distribution boxes assigned to a specific tenant. Useful for tenant billing and energy monitoring.","operationId":"getDistributionBoxTenantEnergyUsage","parameters":[{"name":"tenantId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Aggregated energy usage retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAggregatedDistributionBoxEnergyUsageDto"}}}}},"summary":"Get tenant distribution box energy usage","tags":["DistributionBox","Distribution Box - Management"],"x-api-token":true,"x-api-token-audience":"tenant","x-sandbox-groups":["MCB"]}}},"info":{"title":"Ecovolt API - Sandbox (Read-Only) Endpoints","description":"Read-only endpoints available to sandbox API tokens.","version":"1.0","contact":{},"x-spec-hash":"5f9267ad3417194de1cde3c16ba1768516e508f6d8869f9c871c006de90549cc"},"tags":[],"servers":[{"url":"https://api.ecovolt.ai"}],"components":{"securitySchemes":{"bearerAuth":{"scheme":"bearer","bearerFormat":"JWT","type":"http"},"cookie":{"type":"apiKey","in":"cookie","name":"connect.sid"},"cookieAuth":{"type":"apiKey","in":"cookie","name":"jwtToken"}},"schemas":{"MediaType":{"type":"string","enum":["image","video"],"description":"Type of media asset (image or video)"},"ReleaseResponseBodyDto":{"type":"object","properties":{"releaseDescription":{"type":"string","description":"Release Description for current release page","example":"We are excited to announce the release of version 1.2.0..."},"assetKey":{"type":"string","description":"S3 object key for the release media asset","example":"releases/123e4567-e89b-12d3-a456-426614174000.mp4"},"assetType":{"description":"Type of media asset (image or video)","example":"video","allOf":[{"$ref":"#/components/schemas/MediaType"}]},"assetUrl":{"type":"string","description":"Url to access the media asset","example":"https://cdn.example.com/releases/123e4567-e89b-12d3-a456-426614174000.mp4"},"ctaLabel":{"type":"string","description":"Optional call-to-action button label","example":"Open Notification Settings"},"ctaUrl":{"type":"string","description":"Optional call-to-action URL. Use {systemId} as a placeholder for the active system.","example":"/system/{systemId}/system-settings/notification-settings"},"ctaIsExternal":{"type":"boolean","description":"Whether the CTA link should open in a new tab (external) or navigate within the app.","example":false}},"required":["releaseDescription","assetKey","assetType","assetUrl"]},"ReleaseResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier for the release","example":"507f1f77bcf86cd799439011"},"releaseTitle":{"type":"string","description":"Release Title for current global operation","example":"New Release v1.2.0"},"releaseBody":{"description":"Array of objects containing respective descriptions and media for the release.","type":"array","items":{"$ref":"#/components/schemas/ReleaseResponseBodyDto"}}},"required":["_id","releaseTitle","releaseBody"]},"ReleaseBodyDto":{"type":"object","properties":{"releaseDescription":{"type":"string","description":"Release description for current release page","example":"We are excited to announce the release of version 1.2.0..."},"file":{"type":"string","description":"Media file (image or video) for the current release page","format":"binary"},"existingAssetKey":{"type":"string","description":"Existing S3 object key for the release media asset"},"ctaLabel":{"type":"string","description":"Optional call-to-action button label","example":"Open Notification Settings"},"ctaUrl":{"type":"string","description":"Optional call-to-action URL. Use {systemId} as a placeholder for the active system.","example":"/system/{systemId}/system-settings/notification-settings"},"ctaIsExternal":{"type":"boolean","description":"Whether the CTA link should open in a new tab (external) or navigate within the app.","example":false}},"required":["releaseDescription"]},"ReleaseDto":{"type":"object","properties":{"releaseTitle":{"type":"string","description":"Release Title for current global operation","example":"New Release v1.0.0"},"releaseBody":{"description":"Array of objects containing respective descriptions and media for the release.","type":"array","items":{"$ref":"#/components/schemas/ReleaseBodyDto"}}},"required":["releaseTitle","releaseBody"]},"CreateManufacturerApiKeyDto":{"type":"object","properties":{"label":{"type":"string","description":"Human-readable label for this key","example":"Factory Line A"}},"required":["label"]},"ManufacturerApiKeyCreatedResponseDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"label":{"type":"string","example":"Factory Line A"},"keyPrefix":{"type":"string","description":"First 8 chars of the key (for identification only)","example":"mfr_a1b2"},"enabled":{"type":"boolean","example":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"key":{"type":"string","description":"Full API key — shown once only, store it securely","example":"mfr_a1b2c3d4-e5f6-7890-abcd-ef1234567890"}},"required":["_id","label","keyPrefix","enabled","createdAt","updatedAt","key"]},"ManufacturerApiKeyResponseDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"label":{"type":"string","example":"Factory Line A"},"keyPrefix":{"type":"string","description":"First 8 chars of the key (for identification only)","example":"mfr_a1b2"},"enabled":{"type":"boolean","example":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","label","keyPrefix","enabled","createdAt","updatedAt"]},"DeviceType":{"type":"string","enum":["PLUG","MCB","ENV_SENSOR","SMART_SWITCH","AIRCON_CONTROL","WATER_METER","RIDGE","EXTERNAL"],"description":"Type of device (PLUG, MCB, etc.)"},"UsageHistoryHour":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the UsageHistoryHour","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"hour":{"type":"number","description":"No. What Hour (E.g. 7 means 0700 - 0800)","example":"7"},"onStatus":{"type":"string","description":"Status of the Plug (ON / OFF)","enum":["ON","OFF","INACTIVE","ERROR","ACTIVE"],"example":"ON"},"deviceType":{"description":"Type of device (PLUG, MCB, etc.)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"actualDate":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"macId":{"type":"string","description":"MAC ID of the device","example":"1092384701928347"},"wifiStrength":{"type":"number","description":"WiFi signal strength at the time of the hourly record","example":-65},"device":{"type":"string","description":"Reference to the device","example":"64b7f1e6f9c9d4e5b8a9f7c3"}},"required":["_id","hour","onStatus","deviceType","createdAt","updatedAt","actualDate","macId","device"]},"UsageHistoryDay":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the UsageHistoryDay","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"usageHistoryHourly":{"description":"Usage History of the device (Hourly)","type":"array","items":{"$ref":"#/components/schemas/UsageHistoryHour"}},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"deviceType":{"description":"Type of device (PLUG, MCB, etc.)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"macId":{"type":"string","description":"MAC ID of the device","example":"1092384701928347"},"device":{"type":"string","description":"Reference to the device","example":"64b7f1e6f9c9d4e5b8a9f7c3"}},"required":["_id","usageHistoryHourly","createdAt","updatedAt","deviceType","macId","device"]},"UsageHistoryDayWithCostBreakdownDto":{"type":"object","properties":{"date":{"type":"string","description":"No. Hour of the Usage History","example":"2023-03-15"},"totalEnergyUsage":{"type":"number","description":"Total Energy Usage in a Day (kWh)","example":12.48},"totalEnergyUsageA":{"type":"number","description":"Energy Usage on phase A in a Day (kWh)","example":4.1},"totalEnergyUsageB":{"type":"number","description":"Energy Usage on phase B in a Day (kWh)","example":4.6},"totalEnergyUsageC":{"type":"number","description":"Energy Usage on phase C in a Day (kWh)","example":3.78},"activeEnergy":{"type":"number","description":"Active Energy consumed in a Day (Wh)","example":12480},"reactiveEnergy":{"type":"number","description":"Reactive Energy consumed in a Day (VARh)","example":4320},"apparentEnergy":{"type":"number","description":"Apparent Energy consumed in a Day (VAh)","example":13200},"cost":{"type":"number","description":"Energy cost for this day in dollars. Zero (0) if no tariff rate is configured for this day.","example":23.1},"tariffRate":{"type":"number","description":"Tariff rate applied for this day in cents per kWh/L. Null if no tariff rate is configured.","example":10,"nullable":true}},"required":["date","totalEnergyUsage","cost"]},"UsageHistoryDayBreakdownDto":{"type":"object","properties":{"date":{"type":"string","description":"No. Hour of the Usage History","example":"2023-03-15"},"totalEnergyUsage":{"type":"number","description":"Total Energy Usage in a Day (kWh)","example":12.48},"totalEnergyUsageA":{"type":"number","description":"Energy Usage on phase A in a Day (kWh)","example":4.1},"totalEnergyUsageB":{"type":"number","description":"Energy Usage on phase B in a Day (kWh)","example":4.6},"totalEnergyUsageC":{"type":"number","description":"Energy Usage on phase C in a Day (kWh)","example":3.78},"activeEnergy":{"type":"number","description":"Active Energy consumed in a Day (Wh)","example":12480},"reactiveEnergy":{"type":"number","description":"Reactive Energy consumed in a Day (VARh)","example":4320},"apparentEnergy":{"type":"number","description":"Apparent Energy consumed in a Day (VAh)","example":13200}},"required":["date","totalEnergyUsage"]},"UsageHistoryHourBreakdownDto":{"type":"object","properties":{"hour":{"type":"number","description":"No. Hour of the Usage History","example":"7"},"totalEnergyUsage":{"type":"number","description":"Total Energy Usage in a Hour (kWh)","example":"231"},"totalEnergyUsageA":{"type":"number","description":"Energy Usage on phase A in a Hour (kWh)","example":0.18},"totalEnergyUsageB":{"type":"number","description":"Energy Usage on phase B in a Hour (kWh)","example":0.2},"totalEnergyUsageC":{"type":"number","description":"Energy Usage on phase C in a Hour (kWh)","example":0.14},"activeEnergy":{"type":"number","description":"Active Energy consumed in a Hour (Wh)","example":520},"reactiveEnergy":{"type":"number","description":"Reactive Energy consumed in a Hour (VARh)","example":180},"apparentEnergy":{"type":"number","description":"Apparent Energy consumed in a Hour (VAh)","example":550}},"required":["hour","totalEnergyUsage"]},"UsageHistoryHourWithTariffResponseDto":{"type":"object","properties":{"data":{"description":"Array of hourly usage data","example":[{"hour":0,"totalEnergyUsage":1.2},{"hour":1,"totalEnergyUsage":1.5}],"type":"array","items":{"$ref":"#/components/schemas/UsageHistoryHourBreakdownDto"}},"currentTariffRate":{"type":"number","description":"Current tariff rate in cost per kWh. Null if no tariff rate is configured. Useful for frontend calculations.","example":0.25,"nullable":true}},"required":["data"]},"DeviceStatus":{"type":"string","enum":["ON","OFF","INACTIVE","ERROR","ACTIVE"],"description":"Status of the device (ON / OFF)"},"UsageHistoryEnergyRealtimeDto":{"type":"object","properties":{"onStatus":{"description":"Status of the device (ON / OFF)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01T00:00:00.000Z"},"current":{"type":"number","description":"Current in Amperes (A)","example":2.5},"voltage":{"type":"number","description":"Voltage in Volts (V)","example":230},"powerFactor":{"type":"number","description":"Power factor (0-1)","example":0.9},"power":{"type":"number","description":"Power in Watts (W)","example":517.5},"voltageA":{"type":"number","description":"Phase A voltage (V)"},"voltageB":{"type":"number","description":"Phase B voltage (V)"},"voltageC":{"type":"number","description":"Phase C voltage (V)"},"currentA":{"type":"number","description":"Phase A current (A)"},"currentB":{"type":"number","description":"Phase B current (A)"},"currentC":{"type":"number","description":"Phase C current (A)"},"activePowerA":{"type":"number","description":"Phase A active power (W)"},"activePowerB":{"type":"number","description":"Phase B active power (W)"},"activePowerC":{"type":"number","description":"Phase C active power (W)"},"reactivePowerA":{"type":"number","description":"Phase A reactive power (VAR)"},"reactivePowerB":{"type":"number","description":"Phase B reactive power (VAR)"},"reactivePowerC":{"type":"number","description":"Phase C reactive power (VAR)"},"apparentPowerA":{"type":"number","description":"Phase A apparent power (VA)"},"apparentPowerB":{"type":"number","description":"Phase B apparent power (VA)"},"apparentPowerC":{"type":"number","description":"Phase C apparent power (VA)"},"powerFactorA":{"type":"number","description":"Phase A power factor (0-1)"},"powerFactorB":{"type":"number","description":"Phase B power factor (0-1)"},"powerFactorC":{"type":"number","description":"Phase C power factor (0-1)"},"frequency":{"type":"number","description":"Line frequency (Hz)"},"rssi":{"type":"number","description":"WiFi RSSI value at the time of the reading (dBm)","example":-65}},"required":["onStatus","createdAt","current","voltage","powerFactor","power"]},"RateSource":{"type":"string","enum":["SYSTEM","TENANT","ZONE","ROOM","NATIONAL_DEFAULT"],"description":"Scope level that supplied the rate. Null when no level of the scope chain has a rate and the national default was used."},"PeriodCostResolution":{"type":"string","enum":["REALTIME","HOUR"],"description":"Record tier the figure was integrated from"},"PeriodCostProjectionDto":{"type":"object","properties":{"dailyKWh":{"type":"number","example":1.84},"dailyCost":{"type":"number","example":0.55},"monthlyKWh":{"type":"number","example":56.02},"monthlyCost":{"type":"number","example":16.81},"yearlyKWh":{"type":"number","example":671.6},"yearlyCost":{"type":"number","example":201.48}},"required":["dailyKWh","dailyCost","monthlyKWh","monthlyCost","yearlyKWh","yearlyCost"]},"DevicePeriodCostResponseDto":{"type":"object","properties":{"kWh":{"type":"number","description":"Energy consumed by the device over the selected period","example":1.84},"cost":{"type":"number","description":"Cost of the selected period, in dollars","example":0.55},"costPerKWh":{"type":"number","description":"Tariff rate applied, in cents per kWh","example":29.88},"rateSource":{"description":"Scope level that supplied the rate. Null when no level of the scope chain has a rate and the national default was used.","example":"ZONE","allOf":[{"$ref":"#/components/schemas/RateSource"}]},"resolution":{"description":"Record tier the figure was integrated from","example":"REALTIME","allOf":[{"$ref":"#/components/schemas/PeriodCostResolution"}]},"windowSeconds":{"type":"number","description":"Length of the selected period in seconds","example":28800},"coveredSeconds":{"type":"number","description":"Seconds of the period actually backed by data. Falls short of windowSeconds when the device was offline or reporting sparsely.","example":25200},"coverageRatio":{"type":"number","description":"coveredSeconds / windowSeconds, 0–1","example":0.875},"sampleCount":{"type":"number","description":"Number of source records in the period","example":476},"avgPowerW":{"type":"number","description":"Mean power across the covered part of the period, in watts. Realtime resolution only.","example":263.1},"peakPowerW":{"type":"number","description":"Highest instantaneous power in the period, in watts. Realtime resolution only.","example":1420.5},"continuous":{"description":"Projection assuming this rate of draw continues around the clock.","allOf":[{"$ref":"#/components/schemas/PeriodCostProjectionDto"}]},"recurring":{"description":"Projection assuming this exact period repeats once per day — the figure to read as \"savings if scheduled off\".","allOf":[{"$ref":"#/components/schemas/PeriodCostProjectionDto"}]}},"required":["kWh","cost","costPerKWh","resolution","windowSeconds","coveredSeconds","coverageRatio","sampleCount","continuous","recurring"]},"McbRealtimeDataPointDto":{"type":"object","properties":{"createdAt":{"format":"date-time","type":"string"},"power":{"type":"number","description":"Summed power at this timestamp in Watts (W)","example":200}},"required":["createdAt","power"]},"McbRealtimeSeriesDto":{"type":"object","properties":{"measurementType":{"type":"string","description":"MCB measurement type for this series, or \"COMBINED\" for the total across all types","example":"SSO"},"data":{"type":"array","items":{"$ref":"#/components/schemas/McbRealtimeDataPointDto"}},"max":{"type":"number","description":"Maximum power value across all data points in this series (W). Only present for the COMBINED series.","example":500},"average":{"type":"number","description":"Average power value across all data points in this series (W). Only present for the COMBINED series.","example":250}},"required":["measurementType","data"]},"McbMeasurementType":{"type":"string","enum":["SSO","LIGHTS_FANS","LIGHTS","FANS","AIRCON","POWER_OUTLETS","GENERAL","COMBINED","WATER_HEATER","BACKUP","HVAC"],"description":"Measurement type, for MCBs only"},"DeviceRealtimeSeriesDto":{"type":"object","properties":{"deviceId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceName":{"type":"string","example":"Pantry Water Cooler"},"deviceType":{"allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"measurementType":{"description":"Measurement type, for MCBs only","allOf":[{"$ref":"#/components/schemas/McbMeasurementType"}]},"data":{"type":"array","items":{"$ref":"#/components/schemas/McbRealtimeDataPointDto"}},"max":{"type":"number","description":"Peak power in the range (W)"},"average":{"type":"number","description":"Mean power across the range (W)"}},"required":["deviceId","deviceName","deviceType","data","max","average"]},"McbRealtimeResponseDto":{"type":"object","properties":{"series":{"description":"One time-series per MCB measurement type found in the room or zone, plus a COMBINED series that sums all types","type":"array","items":{"$ref":"#/components/schemas/McbRealtimeSeriesDto"}},"devices":{"description":"Per-device breakdown of the same range, for charting the selection split out. Present only on endpoints that provide it. A phase child rolls up into its parent clamp, since the rows belong to the clamp.","type":"array","items":{"$ref":"#/components/schemas/DeviceRealtimeSeriesDto"}}},"required":["series"]},"DevicesRealtimeRequestDto":{"type":"object","properties":{"deviceIds":{"description":"Device ids to read. PLUG and MCB only — other types write no energy realtime rows. Max 20: each id widens a high-frequency time-series scan.","maxItems":20,"example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"from":{"type":"string","description":"Range start","example":"2026-09-01"},"till":{"type":"string","description":"Range end","example":"2026-09-02"}},"required":["deviceIds","from","till"]},"RoomMcbAiInsightPossibleDeviceDto":{"type":"object","properties":{"device":{"type":"string","example":"Portable AC"},"reason":{"type":"string","example":"600–900W continuously from 10pm–8am on SSO circuit"}},"required":["device","reason"]},"RoomMcbAiInsightResponseDto":{"type":"object","properties":{"summary":{"type":"string","description":"One-liner summary of room energy behaviour","example":"This room shows signs of an overnight portable AC and a mini fridge drawing constant baseline power."},"possibleDevices":{"description":"List of predicted devices and why","type":"array","items":{"$ref":"#/components/schemas/RoomMcbAiInsightPossibleDeviceDto"}}},"required":["summary","possibleDevices"]},"DeviceEnergyDto":{"type":"object","properties":{"energy":{"type":"number","description":"Total energy consumed by the device in watt-hours.","example":231}},"required":["energy"]},"UsageWithCostResponseDto":{"type":"object","properties":{"totalEnergyUsage":{"type":"number","description":"Total energy usage in kWh","example":125.5},"totalCost":{"type":"number","description":"Total cost based on current tariff rate. Null if no tariff rate is configured.","example":31.375,"nullable":true},"tariffRate":{"type":"number","description":"Current tariff rate in cost per kWh. Null if no tariff rate is configured.","example":0.25,"nullable":true}},"required":["totalEnergyUsage"]},"TelemetryHourSmartSwitchResponseDto":{"type":"object","properties":{"hour":{"type":"number","description":"Hour of the day (0–23)","example":7},"onStatus":{"description":"Device on/off status for this hour","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"downTime":{"type":"number","description":"Total downtime in milliseconds for this hour (3600000 = full hour down)","example":1800000},"gangSwitchStatus":{"description":"Gang switch states reported by the device (array index = gang index, 1 = ON, 0 = OFF)","example":[1,0,1],"type":"array","items":{"type":"number"}},"actualDate":{"format":"date-time","type":"string","description":"Actual date this hourly record is for","example":"2024-01-15T07:00:00.000Z"},"createdAt":{"format":"date-time","type":"string","description":"Record creation timestamp","example":"2024-01-15T07:10:00.000Z"}},"required":["hour","onStatus","createdAt"]},"TelemetryAirconControlRealtimeResponseDto":{"type":"object","properties":{"onStatus":{"description":"Device on/off status","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"ambientTemperature":{"type":"number","description":"Ambient temperature in Celsius","example":24.5},"ambientHumidity":{"type":"number","description":"Ambient humidity percentage","example":65},"motionDetected":{"type":"boolean","description":"Whether motion was detected (MAXWELL_ULTRA only)","example":true},"rssi":{"type":"number","description":"WiFi RSSI value at the time of the reading (dBm)","example":-65},"createdAt":{"format":"date-time","type":"string","description":"Record creation timestamp"},"occupancy":{"type":"number","description":"Occupancy estimate. Absent when the device carries no people counter","example":7}},"required":["onStatus","createdAt"]},"TelemetryHourAirconControlResponseDto":{"type":"object","properties":{"hour":{"type":"number","description":"Hour of the day (0–23)","example":7},"onStatus":{"description":"Device on/off status for this hour","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"downTime":{"type":"number","description":"Total downtime in milliseconds for this hour (3600000 = full hour down)","example":1800000},"ambientTemperature":{"type":"number","description":"Ambient temperature in Celsius","example":24.5},"ambientHumidity":{"type":"number","description":"Ambient humidity percentage","example":65},"motionDetected":{"type":"boolean","description":"Whether motion was detected during this hour (MAXWELL_ULTRA only)","example":true},"actualDate":{"format":"date-time","type":"string","description":"Actual date this hourly record is for","example":"2024-01-15T07:00:00.000Z"},"createdAt":{"format":"date-time","type":"string","description":"Record creation timestamp","example":"2024-01-15T07:10:00.000Z"},"occupancyMedian":{"type":"number","description":"Median occupancy over the hour. Prefer over the average — robust to walk-by bursts","example":6},"occupancyAverage":{"type":"number","description":"Mean occupancy over the hour, rounded","example":6},"occupancyMin":{"type":"number","description":"Minimum occupancy over the hour","example":0},"occupancyMax":{"type":"number","description":"Maximum occupancy over the hour","example":14},"occupancySamples":{"type":"number","description":"Settled occupancy figures that fed the hourly aggregate; a low count is a low-confidence hour","example":300}},"required":["hour","onStatus","createdAt"]},"WifiHeatmapCellDto":{"type":"object","properties":{"hour":{"type":"number","description":"Hour of day, 0-23","example":14},"avgRssi":{"type":"number","description":"Mean signal in this hour slot across the window, in dBm","example":-67.4},"minRssi":{"type":"number","description":"Worst reading in this slot, in dBm — where a dropout actually starts","example":-88},"sampleHours":{"type":"number","description":"Hourly snapshots that contributed. Low counts are weak evidence.","example":29}},"required":["hour","avgRssi","minRssi","sampleHours"]},"WifiHeatmapDeviceDto":{"type":"object","properties":{"deviceId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","example":"Pantry Water Cooler"},"deviceType":{"type":"string","example":"PLUG"},"avgRssi":{"type":"number","description":"Mean signal across the window, dBm","example":-71.2},"minRssi":{"type":"number","example":-91},"maxRssi":{"type":"number","example":-54},"sampleHours":{"type":"number","description":"Hourly snapshots carrying a signal reading","example":690},"missingHours":{"type":"number","description":"Hours in the window with no hourly record at all. A device that stops reporting has no weak reading to show — the silence is the signal.","example":30},"cells":{"description":"Hour-of-day slots that carried data. Absent hours are omitted, not zeroed.","type":"array","items":{"$ref":"#/components/schemas/WifiHeatmapCellDto"}}},"required":["deviceId","name","deviceType","avgRssi","minRssi","maxRssi","sampleHours","missingHours","cells"]},"WifiHeatmapResponseDto":{"type":"object","properties":{"periodStart":{"format":"date-time","type":"string","example":"2026-07-25T00:00:00.000Z"},"periodEnd":{"format":"date-time","type":"string","example":"2026-08-24T00:00:00.000Z"},"windowHours":{"type":"number","description":"Hours spanned by the window, the denominator for missingHours","example":720},"deviceCount":{"type":"number","description":"Devices in the system that reported any signal in the window","example":167},"truncated":{"type":"boolean","description":"Present and true when devices were dropped by `limit`. Never silently truncated — raise `limit` or narrow the window.","example":true},"devices":{"description":"Weakest average signal first, so dead spots sort to the top","type":"array","items":{"$ref":"#/components/schemas/WifiHeatmapDeviceDto"}}},"required":["periodStart","periodEnd","windowHours","deviceCount","devices"]},"BoxplotDeviceType":{"type":"string","enum":["PLUG","MCB","DISTRIBUTION_BOX","ENV_SENSOR"]},"DeviceBoxplotEntryDto":{"type":"object","properties":{"min":{"type":"number","example":0.12},"q1":{"type":"number","example":0.25},"median":{"type":"number","example":0.42},"q3":{"type":"number","example":0.61},"max":{"type":"number","example":1.05},"hour":{"type":"number","description":"Hour of the day (0–23)","example":14},"deviceCount":{"type":"number","description":"Number of distinct entities with data in this hour","example":5}},"required":["min","q1","median","q3","max","hour","deviceCount"]},"DeviceBoxplotResponseDto":{"type":"object","properties":{"data":{"description":"Hourly box plot entries (only hours with data)","type":"array","items":{"$ref":"#/components/schemas/DeviceBoxplotEntryDto"}}},"required":["data"]},"DeviceBoxStatsDto":{"type":"object","properties":{"min":{"type":"number","example":0.12},"q1":{"type":"number","example":0.25},"median":{"type":"number","example":0.42},"q3":{"type":"number","example":0.61},"max":{"type":"number","example":1.05}},"required":["min","q1","median","q3","max"]},"DeviceBreakdownEntryDto":{"type":"object","properties":{"deviceId":{"type":"string"},"deviceName":{"type":"string","example":"Living Room Plug"},"value":{"type":"number","description":"Aggregated value for this entity in the hour","example":0.42}},"required":["deviceId","deviceName","value"]},"DeviceDrilldownResponseDto":{"type":"object","properties":{"overallBoxStats":{"description":"Overall box plot statistics across all entities in the hour","allOf":[{"$ref":"#/components/schemas/DeviceBoxStatsDto"}]},"deviceBreakdown":{"description":"Per-entity breakdown","type":"array","items":{"$ref":"#/components/schemas/DeviceBreakdownEntryDto"}},"totalDevices":{"type":"number","example":5}},"required":["overallBoxStats","deviceBreakdown","totalDevices"]},"TelemetryHourEnvironmentSensorResponseDto":{"type":"object","properties":{"hour":{"type":"number","description":"Hour of the day this record covers (0–23, e.g. 7 = 0700–0800)","example":7},"actualDate":{"format":"date-time","type":"string","description":"Date the hourly bucket belongs to","example":"2024-01-15T07:00:00.000Z"},"createdAt":{"format":"date-time","type":"string","description":"Record creation timestamp","example":"2024-01-15T08:00:00.000Z"},"wifiStrength":{"type":"number","description":"WiFi signal strength at the time of the hourly record, in dBm","example":-65},"temperature":{"type":"number","description":"Average temperature for the hour, in degrees Celsius","example":24},"humidity":{"type":"number","description":"Average humidity percentage for the hour","example":65},"carbonDioxide":{"type":"number","description":"Average CO2 for the hour, in ppm","example":400},"lux":{"type":"number","description":"Average light level for the hour, in lux (lx)","example":500},"formaldehyde":{"type":"number","description":"Average formaldehyde (HCHO) for the hour, in µg/m³","example":0.03},"pm1":{"type":"number","description":"Average PM1 for the hour, in µg/m³","example":1},"pm2_5":{"type":"number","description":"Average PM2.5 for the hour, in µg/m³","example":1},"pm10":{"type":"number","description":"Average PM10 for the hour, in µg/m³","example":1},"totalVolatileOrganicCompounds":{"type":"number","description":"Average total volatile organic compounds for the hour, in ppb","example":1},"soundLevel":{"type":"number","description":"Average sound level for the hour, in dB","example":1},"motionDetected":{"type":"boolean","description":"Whether motion was detected during the hour","example":true}},"required":["hour"]},"TelemetryEnvironmentSensorRealtimeResponseDto":{"type":"object","properties":{"createdAt":{"format":"date-time","type":"string","description":"Start timestamp of the downsampled bucket","example":"2024-01-15T07:00:00.000Z"},"temperature":{"type":"number","description":"Average temperature for the bucket, in degrees Celsius","example":24},"humidity":{"type":"number","description":"Average humidity percentage for the bucket","example":65},"carbonDioxide":{"type":"number","description":"Average CO2 for the bucket, in ppm","example":400},"lux":{"type":"number","description":"Average light level for the bucket, in lux (lx)","example":500},"formaldehyde":{"type":"number","description":"Average formaldehyde (HCHO) for the bucket, in µg/m³","example":0.03},"pm1":{"type":"number","description":"Average PM1 for the bucket, in µg/m³","example":1},"pm2_5":{"type":"number","description":"Average PM2.5 for the bucket, in µg/m³","example":1},"pm10":{"type":"number","description":"Average PM10 for the bucket, in µg/m³","example":1},"totalVolatileOrganicCompounds":{"type":"number","description":"Average total volatile organic compounds for the bucket, in ppb","example":1},"soundLevel":{"type":"number","description":"Average sound level for the bucket, in dB","example":1},"motionDetected":{"type":"boolean","description":"Whether any sample in the bucket detected motion","example":true},"rssi":{"type":"number","description":"Average WiFi RSSI for the bucket (dBm)","example":-65}},"required":["createdAt"]},"TelemetryHourWaterMeterResponseDto":{"type":"object","properties":{"hour":{"type":"number","description":"Hour of the day this record covers (0–23, e.g. 7 = 0700–0800)","example":7},"onStatus":{"description":"Device on/off status during this hour","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"netVolume":{"type":"number","description":"Net water volume for this hour (L)","example":340},"flowRate":{"type":"number","description":"Average flow rate for this hour (m³/h)","example":5.7},"velocity":{"type":"number","description":"Average flow velocity for this hour (m/s)","example":1.12},"soundSpeed":{"type":"number","description":"Average speed of sound for this hour (m/s)","example":1489},"signalQuality":{"type":"number","description":"Average signal quality for this hour (no units)","example":82},"energyFlowRate":{"type":"number","description":"Average thermal energy flow rate for this hour (GJ/h)","example":0.05},"energy":{"type":"number","description":"Total thermal energy for this hour (GJ)","example":0.05},"inletTemperature":{"type":"number","description":"Average inlet water temperature for this hour (°C)","example":25},"outletTemperature":{"type":"number","description":"Average outlet water temperature for this hour (°C)","example":23.5},"actualDate":{"format":"date-time","type":"string","description":"Date the hourly bucket belongs to","example":"2024-01-15T07:00:00.000Z"},"createdAt":{"format":"date-time","type":"string","description":"Record creation timestamp","example":"2024-01-15T08:00:00.000Z"}},"required":["hour","onStatus","netVolume","flowRate","velocity","soundSpeed","signalQuality"]},"TelemetryWaterMeterRealtimeResponseDto":{"type":"object","properties":{"onStatus":{"description":"Device on/off status at the time of the reading","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"meterConnected":{"type":"boolean","description":"Whether the meter is physically connected","example":true},"flowRate":{"type":"number","description":"Flow rate at the time of the reading (m³/h)","example":12.4},"velocity":{"type":"number","description":"Flow velocity at the time of the reading (m/s)","example":1.25},"soundSpeed":{"type":"number","description":"Speed of sound in fluid at the time of the reading (m/s)","example":1490.5},"signalQuality":{"type":"number","description":"Ultrasonic signal quality (no units)","example":85},"inletTemperature":{"type":"number","description":"Inlet water temperature (°C)","example":25},"outletTemperature":{"type":"number","description":"Outlet water temperature (°C)","example":23.5},"energyFlowRate":{"type":"number","description":"Thermal energy flow rate (GJ/h)","example":0.05},"rssi":{"type":"number","description":"WiFi RSSI value at the time of the reading (dBm)","example":-65},"createdAt":{"format":"date-time","type":"string","description":"Record creation timestamp","example":"2024-01-15T07:05:00.000Z"}},"required":["onStatus","flowRate","velocity","soundSpeed","signalQuality","createdAt"]},"TelemetryDayWaterMeterResponseDto":{"type":"object","properties":{"netVolume":{"type":"number","description":"Total net water volume for the day (L)","example":8150},"flowRate":{"type":"number","description":"Average flow rate for the day (m³/h)","example":5.7},"velocity":{"type":"number","description":"Average flow velocity for the day (m/s)","example":1.12},"soundSpeed":{"type":"number","description":"Average speed of sound for the day (m/s)","example":1489},"signalQuality":{"type":"number","description":"Average signal quality for the day (no units)","example":82},"energy":{"type":"number","description":"Total thermal energy for the day (GJ)","example":1.2},"energyFlowRate":{"type":"number","description":"Average thermal energy flow rate for the day (GJ/h)","example":0.05},"inletTemperature":{"type":"number","description":"Average inlet water temperature for the day (°C)","example":25},"outletTemperature":{"type":"number","description":"Average outlet water temperature for the day (°C)","example":23.5},"usageHistoryHourly":{"description":"Hourly rows this daily aggregate was rolled up from","type":"array","items":{"$ref":"#/components/schemas/TelemetryHourWaterMeterResponseDto"}},"createdAt":{"format":"date-time","type":"string","description":"Start of the day this aggregate covers","example":"2024-01-15T00:00:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp of the last update to this aggregate","example":"2024-01-16T00:05:00.000Z"}},"required":["netVolume","flowRate","velocity","soundSpeed","signalQuality","usageHistoryHourly"]},"Role":{"type":"string","enum":["SUPERADMIN","ADMIN","USER","TENANT","ROOM","DEVELOPER"],"description":"Administrator role defining access permissions (SUPERADMIN or ADMIN)"},"FrontendApp":{"type":"string","enum":["everest","base","docs","trek","emulator","beacon","bi","vista","sandbox"],"description":"Frontend app context for app-specific signup flows"},"SignUpDto":{"type":"object","properties":{"name":{"type":"string","description":"Full name of the administrator","example":"John Smith"},"email":{"type":"string","description":"Email address for the administrator account (must be unique)","example":"john.smith@ecovolt.com"},"password":{"type":"string","description":"Account password (minimum 6 characters, optional for invited admins)","example":"SecurePass123"},"role":{"description":"Administrator role defining access permissions (SUPERADMIN or ADMIN)","example":"ADMIN","allOf":[{"$ref":"#/components/schemas/Role"}]},"app":{"description":"Frontend app context for app-specific signup flows","example":"base","allOf":[{"$ref":"#/components/schemas/FrontendApp"}]},"unitName":{"type":"string","description":"Unit name for B2C signup — used as the zone name for the user's space","example":"Block 123 #04-12"},"acceptedTerms":{"type":"boolean","description":"Whether the user accepted the Terms & Conditions. Required (must be true) for base (B2C) self-signup.","example":true},"approved":{"type":"boolean","description":"Whether the admin account is approved and can access the system","example":true},"associatedZone":{"type":"string","description":"Optional zone ID to associate this admin with a specific zone account","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"associatedTenant":{"type":"string","description":"Optional tenant ID to associate this admin with a specific tenant","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"associatedRoom":{"type":"string","description":"Optional room ID to associate this admin with a specific room","example":"64b7f1e6f9c9d4e5b8a9f7c2"}},"required":["name","email"]},"LoginResponseDto":{"type":"object","properties":{"isSuccessful":{"type":"boolean","description":"Indicates if the login was successful","example":true},"id":{"type":"string","description":"User ID of the authenticated user","example":"60f2e1f3f7f7f0002f00c7e9"},"token":{"type":"string","description":"JWT access token. Returned to clients that carry their own credentials — native apps identifying as `beacon`, and any caller that sends no `app`. A web app that names itself via `app` receives this as an httpOnly cookie instead and gets no copy here, since a copy readable by JavaScript is what httpOnly exists to prevent. The exception is the pre-2FA step, whose short-lived 10-minute token is returned to every caller because it is not yet a session.","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},"mainSystemId":{"type":"string","description":"Main system ID associated with the user","example":"60f2e1f3f7f7f0002f00c7e9"},"role":{"description":"Role of the authenticated user","example":"ADMIN","allOf":[{"$ref":"#/components/schemas/Role"}]},"email":{"type":"string","description":"Email of the authenticated user","example":"admin@ecovolt.ai"},"associatedTenantId":{"type":"string","description":"Associated tenant ID if user has TENANT role","example":"60f2e1f3f7f7f0002f00c7e9"},"associatedUserId":{"type":"string","description":"Associated user ID if user has USER role","example":"60f2e1f3f7f7f0002f00c7e9"},"associatedRoomId":{"type":"string","description":"Associated room ID if user has ROOM_ADMIN role","example":"60f2e1f3f7f7f0002f00c7e9"},"requires2FA":{"type":"boolean","description":"Indicates if the user has 2FA enabled and needs to complete 2FA verification","example":false},"refreshToken":{"type":"string","description":"Refresh token used to obtain a new access token. Absent when 2FA verification is still pending, and absent for web callers, which receive it as an httpOnly cookie scoped to /api/auth instead.","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},"expiresIn":{"type":"number","description":"How many seconds the access token remains valid, counted from when this response was issued. Returned to every caller, including web ones that receive no token at all — a cookie client cannot read the token to find its `exp`, so this is how it knows when to refresh ahead of a 401. Relative rather than absolute, matching OAuth 2.0 `expires_in`, so a client whose clock disagrees with the server still refreshes at the right moment. A lifetime is not a credential, so exposing it costs nothing. Absent when the response carries no session (pending approval, or 2FA still to complete).","example":900}},"required":["isSuccessful","id","mainSystemId","requires2FA"]},"LoginDto":{"type":"object","properties":{"email":{"type":"string","description":"Administrator email address used for authentication","example":"admin@ecovolt.com"},"password":{"type":"string","description":"Administrator password (minimum 6 characters)","example":"SecurePass123"},"app":{"description":"Frontend app identifier for role-based access control","example":"everest","allOf":[{"$ref":"#/components/schemas/FrontendApp"}]}},"required":["email","password"]},"SignoutDto":{"type":"object","properties":{"refreshToken":{"type":"string","description":"The refresh token to revoke alongside the access token. Optional so existing cookie/header callers keep working; when present it is blacklisted so it can no longer be exchanged.","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6..."}}},"RefreshTokenDto":{"type":"object","properties":{"refreshToken":{"type":"string","description":"The 30-day refresh token issued at login. Exchanged for a fresh access + refresh token pair. The presented token is revoked (single use). Optional: browser clients omit it and the token is read from the httpOnly refresh cookie instead.","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6..."}}},"InviteDto":{"type":"object","properties":{"inviterId":{"type":"string","description":"ID of the administrator sending the invitation","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"systemId":{"type":"string","description":"System ID where the new admin will have access","example":"64b7f1e6f9c9d4e5b8a9f7c2"},"name":{"type":"string","description":"Full name of the person being invited","example":"Jane Doe"},"email":{"type":"string","description":"Email address where the invitation will be sent","example":"jane.doe@ecovolt.com"},"role":{"description":"Role to be assigned to the new administrator (SUPERADMIN or ADMIN)","example":"ADMIN","allOf":[{"$ref":"#/components/schemas/Role"}]},"associatedZone":{"type":"string","description":"Optional zone ID to link this admin account to an existing zone","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"associatedTenant":{"type":"string","description":"Optional tenant ID to associate this admin with a specific tenant for billing","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"associatedRoom":{"type":"string","description":"Optional room ID to associate this admin with a specific room","example":"64b7f1e6f9c9d4e5b8a9f7c2"}},"required":["inviterId","systemId","name","email","role"]},"ReInviteDto":{"type":"object","properties":{"inviterId":{"type":"string","description":"ID of the Admin that is inviting","example":"1234567890abcdef12345678"},"systemId":{"type":"string","description":"ID of the System","example":"1234567890abcdef12345678"},"email":{"type":"string","description":"Email of the Admin","example":"glenn@gmail.com"}},"required":["inviterId","systemId","email"]},"SignUpViaInviteDto":{"type":"object","properties":{"token":{"type":"string","description":"Reset password token for the Admin","example":"reset-token-123"},"password":{"type":"string","description":"Password of the Admin","example":"password"},"acceptedTerms":{"type":"boolean","description":"Whether the user accepted the Terms & Conditions. Sent by the base (B2C) create-account page.","example":true}},"required":["token"]},"RequestResetPasswordDto":{"type":"object","properties":{"email":{"type":"string","description":"Email of the Admin","example":"glenn@gmail.com"}},"required":["email"]},"ResetPasswordDto":{"type":"object","properties":{"token":{"type":"string","description":"Reset password token for the Admin","example":"reset-token-123"},"newPassword":{"type":"string","description":"New password for the Admin","example":"NewPassword123!"}},"required":["token","newPassword"]},"ValidateTokenResponseDto":{"type":"object","properties":{"message":{"type":"string","example":"validated successfully"},"adminId":{"type":"string","example":"adminId"}},"required":["message","adminId"]},"GoogleTokenDto":{"type":"object","properties":{"credential":{"type":"string","description":"Google Identity Services ID token (JWT credential) obtained client-side via the GIS button. Verified server-side against the configured Google client ID.","example":"eyJhbGciOiJSUzI1NiIsImtpZCI6..."},"app":{"description":"Frontend app identifier for role-based access control","example":"base","allOf":[{"$ref":"#/components/schemas/FrontendApp"}]}},"required":["credential"]},"MicrosoftTokenDto":{"type":"object","properties":{"credential":{"type":"string","description":"Microsoft Identity Platform id_token (JWT credential) obtained client-side via the native OAuth flow. Verified server-side against Microsoft’s JWKS for the pinned tenant.","example":"eyJhbGciOiJSUzI1NiIsImtpZCI6..."},"app":{"description":"Frontend app identifier for role-based access control","example":"beacon","allOf":[{"$ref":"#/components/schemas/FrontendApp"}]}},"required":["credential"]},"Auth2FAResponseDto":{"type":"object","properties":{"issuer":{"type":"string","description":"Issuer for the 2FA secret","example":"Ecovolt"},"label":{"type":"string","description":"Label for the 2FA secret","example":"Ecovolt"},"secret":{"type":"string","description":"2FA secret","example":"ABC123"}},"required":["issuer","label","secret"]},"Disable2FAResponseDto":{"type":"object","properties":{"message":{"type":"string","description":"Success message","example":"2FA disabled for 5 users in the system"}},"required":["message"]},"ValidateTokenDto":{"type":"object","properties":{"token":{"type":"string","example":"your-jwt-token-here"}},"required":["token"]},"Disable2FASystemResponseDto":{"type":"object","properties":{"message":{"type":"string","description":"Success message","example":"2FA disabled for 5 users in the system"},"affectedUsers":{"type":"number","description":"Number of users whose 2FA was disabled","example":5}},"required":["message","affectedUsers"]},"AuditAction":{"type":"string","enum":["DEVICE_TOGGLE","DEVICE_RESET","SCHEDULE_SET","SCHEDULE_EDIT","SCHEDULE_DELETE","ACCOUNT_APPROVED","ACCOUNT_UNAPPROVED","NOTIFICATION_PREFERENCES_OVERRIDDEN","LOGIN","LOGOUT","LOGIN_2FA","SIGNUP_LINK_CLAIMED"]},"AuditOutcome":{"type":"string","enum":["SUCCESS","FAILURE"]},"AuditTargetType":{"type":"string","enum":["DEVICE","EXTERNAL_DEVICE","SCHEDULE","ACCOUNT"]},"StatusTriggerSource":{"type":"string","enum":["MANUAL","CLOUD","SCHEDULED","ECOGUARD","WATCHDOG","LWT","UNKNOWN","BOOTUP","RECONNECT","RECONCILE","SMART_MODE","SMARTHUB","API","DEVICE_SCHEDULE"],"description":"Trigger source for device actions"},"GetAuditLogDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the audit log entry","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"action":{"description":"The action that was performed","allOf":[{"$ref":"#/components/schemas/AuditAction"}]},"outcome":{"description":"Whether the action succeeded or failed","allOf":[{"$ref":"#/components/schemas/AuditOutcome"}]},"actorId":{"type":"string","description":"Account that performed the action","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"actorName":{"type":"string","description":"Display name of the acting account (resolved from actorId)","example":"Glenn Quah"},"actorRole":{"description":"Role of the actor at the time of the action","allOf":[{"$ref":"#/components/schemas/Role"}]},"systemId":{"type":"string","description":"System the action was scoped to","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system the action was scoped to","example":"TEST SYSTEM"},"targetType":{"description":"Type of resource the action targeted","allOf":[{"$ref":"#/components/schemas/AuditTargetType"}]},"targetId":{"type":"string","description":"ID of the targeted resource","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"triggerSource":{"description":"Trigger source for device actions","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"metadata":{"type":"object","description":"Free-form contextual data; shape varies by action","example":{"statusBefore":"OFF","statusAfter":"ON"}},"createdAt":{"format":"date-time","type":"string","description":"When the action occurred","example":"2026-06-07T12:00:00Z"}},"required":["_id","action","outcome","createdAt"]},"GetAuditLogsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of audit log entries matching the query parameters","type":"array","items":{"$ref":"#/components/schemas/GetAuditLogDto"}}},"required":["total","page","size","totalPages","data"]},"ChatRole":{"type":"string","enum":["system","user","assistant"],"description":"Role of the message sender"},"ChatMessageDto":{"type":"object","properties":{"role":{"description":"Role of the message sender","example":"user","allOf":[{"$ref":"#/components/schemas/ChatRole"}]},"content":{"type":"string","description":"Content of the message","example":"What is Ecovolt?"}},"required":["role","content"]},"ChatRequestDto":{"type":"object","properties":{"message":{"type":"string","description":"The user's current message","example":"How do I authenticate with the API?"},"conversationHistory":{"description":"Array of previous messages in the conversation (max 30 messages)","example":[{"role":"user","content":"What is Ecovolt?"},{"role":"assistant","content":"Ecovolt is an energy monitoring platform..."}],"type":"array","items":{"$ref":"#/components/schemas/ChatMessageDto"}}},"required":["message","conversationHistory"]},"PlugCategoryResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Category ID"},"name":{"type":"string","description":"Unique category key","example":"CHARGER"},"displayName":{"type":"string","description":"Human-readable display name","example":"Charger"},"isBuiltIn":{"type":"boolean","description":"Whether this is a built-in category"},"createdBy":{"type":"string","description":"Admin who created this category"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"}},"required":["_id","name","displayName","isBuiltIn","createdAt"]},"PlugCategoriesListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PlugCategoryResponseDto"}},"total":{"type":"number","example":35}},"required":["data","total"]},"CreatePlugCategoryDto":{"type":"object","properties":{"name":{"type":"string","description":"Unique category key — must be UPPER_SNAKE_CASE","example":"STANDING_DESK"},"displayName":{"type":"string","description":"Human-readable display name","example":"Standing Desk"}},"required":["name","displayName"]},"UpdatePlugCategoryDto":{"type":"object","properties":{"displayName":{"type":"string","description":"Human-readable display name","example":"Standing Desk"}}},"SurgeConfig":{"type":"object","properties":{"calculatedThresholdKwh":{"type":"number","description":"Auto-calculated threshold (median) in kWh","example":15.5},"manualThresholdKwh":{"type":"number","description":"Manual threshold override in kWh (if provided)","example":18},"defaultViolationCount":{"type":"number","description":"Default number of violations to trigger flagging","example":3},"defaultViolationWindow":{"type":"number","description":"Default rolling window in days for violation counting","example":7},"digestTime":{"type":"string","description":"Local time of day (HH:mm, in the system timezone) the digest fires at. Anchors the every-N-days digest so it no longer inherits the moment the config was saved.","example":"08:00"},"digestStartDate":{"type":"string","description":"Local date (YYYY-MM-DD, system timezone) of the first digest run. Picks which day of the N-day cycle the digest lands on; defaults to the next occurrence of digestTime.","example":"2026-09-15"},"sentinelPowerRangeRules":{"description":"Sentinel power range rules for suspected device classification","type":"array","items":{"type":"array"}},"byTypeKey":{"type":"object","description":"Per-type-key overrides, keyed by the entity type key (e.g. TYPE_A). An entity whose type key has no entry here — including entities with no type key at all — uses the baseline above.","additionalProperties":{"$ref":"#/components/schemas/SurgeConfigTypeOverride"},"example":{"TYPE_A":{"manualThresholdKwh":4},"TYPE_B":{"manualThresholdKwh":8},"TYPE_C":{"manualThresholdKwh":12}}}},"required":["calculatedThresholdKwh","defaultViolationCount","defaultViolationWindow"]},"LocationObject":{"type":"object","properties":{"locationInput":{"type":"string","description":"Location of the Tenant","example":"Punggol East Flyover, Punggol, Singapore 828649"},"type":{"type":"string","description":"Type of the Tenant","example":"Point"},"coordinates":{"description":"Geolocation of the Tenant","example":[103.7759,1.2966],"type":"array","items":{"type":"number"}}},"required":["locationInput","type","coordinates"]},"FeatureType":{"type":"string","enum":["ZONE_MANAGEMENT","ROOMS_MANAGEMENT","RELATIONSHIP_MAP","ADMIN_PANEL","TENANT_MANAGEMENT","SMART_PLUGS","AI_FEATURE","HEATMAP","CIRCUIT_BREAKER","AIRCON_CONTROL","SMART_SWITCH","WATER_METERING","CALENDAR","CHATBOT","APIKEY","ENVIRONMENT_SENSOR","USAGE_SURGE_MONITORING","EXTERNAL_DASHBOARD","TWO_FACTOR_AUTHENTICATION","PREDICTIVE_MAINTENANCE","RIDGE","TREK","SMART_HUB","OCCUPANCY"],"description":"Type of the feature"},"Feature":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the feature","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"Name of the feature"},"featureType":{"description":"Type of the feature","allOf":[{"$ref":"#/components/schemas/FeatureType"}]},"description":{"type":"string","description":"Description of the feature","example":"Tenant Management of the system"},"createdAt":{"format":"date-time","type":"string","description":"Indicates the creation time of the feature","example":"2026-09-25T12:46:43.033Z"},"updatedAt":{"format":"date-time","type":"string","description":"Indicates the last update time of the feature","example":"2026-09-25T12:46:43.033Z"}},"required":["_id","name","featureType","description","createdAt","updatedAt"]},"SubscriptionTier":{"type":"object","properties":{"_id":{"type":"string","description":"The unique identifier of the subscription tier","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"The name of the subscription tier","example":"Premium"},"description":{"type":"string","description":"The description of the subscription tier","example":"Access to all premium features"},"features":{"description":"The features included in the subscription tier","type":"array","items":{"$ref":"#/components/schemas/Feature"}},"createdAt":{"format":"date-time","type":"string","description":"The date when the subscription tier was created","example":"2023-08-01T12:00:00Z"},"updatedAt":{"format":"date-time","type":"string","description":"The date when the subscription tier was last updated","example":"2023-08-01T12:00:00Z"},"isDeleted":{"type":"boolean","description":"Indicates whether the subscription tier is deleted","example":false,"default":false}},"required":["_id","name","description","features","createdAt","updatedAt","isDeleted"]},"SystemPermission":{"type":"object","properties":{"subscriptionTier":{"description":"Subscription tier that defines available features for the system","example":"64b7f1e6f9c9d4e5b8a9f7c3","allOf":[{"$ref":"#/components/schemas/SubscriptionTier"}]},"featuresEnabled":{"description":"Array of features that are currently enabled for this system","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"$ref":"#/components/schemas/Feature"}},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the permission settings were last updated","example":"2026-09-25T12:46:43.036Z"},"expiryDate":{"format":"date-time","type":"string","description":"Optional expiry date for the current permission settings","example":"2027-09-25T12:46:43.036Z"}},"required":["featuresEnabled"]},"SandboxApiGroup":{"type":"string","enum":["MCB","PLUG","ENVIRONMENT_SENSOR","WATER_METER","AIRCON_CONTROL","SMART_SWITCH","SPACE_ENERGY","WIFI"],"description":"API groups that sandbox (DEVELOPER) API tokens may call on this System. Empty means none."},"ScheduleExclusionType":{"type":"string","enum":["HOLIDAY","CUSTOM"],"description":"Type of exclusion"},"CountryCode":{"type":"string","enum":["SG","PH","MY","ID"],"description":"ISO country code (for holidays from API)"},"ExclusionMode":{"type":"string","enum":["FULL","TIMED"],"description":"FULL: skip all schedules for the day. TIMED: skip recurring schedules but run devices within timeStart–timeEnd."},"ScheduleExclusion":{"type":"object","properties":{"date":{"type":"string","description":"ISO date format (YYYY-MM-DD)","example":"2025-01-01"},"name":{"type":"string","description":"Name of the exclusion day","example":"New Year's Day"},"type":{"description":"Type of exclusion","example":"HOLIDAY","allOf":[{"$ref":"#/components/schemas/ScheduleExclusionType"}]},"countryCode":{"description":"ISO country code (for holidays from API)","example":"SG","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"mode":{"description":"FULL: skip all schedules for the day. TIMED: skip recurring schedules but run devices within timeStart–timeEnd.","example":"FULL","allOf":[{"$ref":"#/components/schemas/ExclusionMode"}]},"timeStart":{"type":"string","description":"Time (HH:mm) when devices turn ON. Only set when mode is TIMED.","example":"13:00"},"timeEnd":{"type":"string","description":"Time (HH:mm) when devices turn OFF. Only set when mode is TIMED.","example":"18:00"}},"required":["date","name","type","mode"]},"SystemTimezone":{"type":"string","enum":["Asia/Singapore","Asia/Manila","Asia/Kuala_Lumpur","Asia/Jakarta"],"description":"IANA timezone identifier for the system location. Used for scheduling and public holiday calculations."},"EntityLabelPairDto":{"type":"object","properties":{"singular":{"type":"string","description":"Singular form of the label","example":"Floor"},"plural":{"type":"string","description":"Plural form of the label","example":"Floors"}},"required":["singular","plural"]},"SystemEntityLabelsDto":{"type":"object","properties":{"zone":{"description":"Custom label for the zone entity","allOf":[{"$ref":"#/components/schemas/EntityLabelPairDto"}]},"tenant":{"description":"Custom label for the tenant entity","allOf":[{"$ref":"#/components/schemas/EntityLabelPairDto"}]},"room":{"description":"Custom label for the room entity","allOf":[{"$ref":"#/components/schemas/EntityLabelPairDto"}]}}},"System":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system","example":"National University of Singapore"},"watchDogTimerRuleName":{"type":"string","description":"Name of the Watch Dog Timer Rule for the system","example":"watchDogTimerRuleName"},"inActiveDevicesCronJobRuleName":{"type":"string","description":"Name of the Inactive Devices Cron Job Rule for the system","example":"inActiveDevicesCronJobRuleName"},"remarks":{"type":"string","description":"Remarks for the system","example":"Pilot program for energy monitoring"},"startPilotDate":{"format":"date-time","type":"string","description":"Start date of the pilot program","example":"2024-01-01T00:00:00.000Z"},"endPilotDate":{"format":"date-time","type":"string","description":"End date of the pilot program","example":"2024-12-31T23:59:59.999Z"},"realTimeToggleOn":{"type":"boolean","description":"Real Time Toggle Status for the system"},"heartBeatInterval":{"type":"number","description":"Heart Beat Interval for the system"},"location":{"description":"Locations of the Tenant","allOf":[{"$ref":"#/components/schemas/LocationObject"}]},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"createdAdmin":{"type":"string","description":"Admin which created the system (ObjectId reference)","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"admins":{"description":"System Admins of the system (ObjectId references)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"tenants":{"description":"Tenants of the system (ObjectId references)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"devices":{"description":"All IoT devices in the System (ObjectId references — Plugs, MCBs, Environment sensors, etc.)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"distributionBoxes":{"description":"All distribution boxes in the System (ObjectId references)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"systemPermission":{"description":"System permission settings including subscription tier, enabled features, and metadata","allOf":[{"$ref":"#/components/schemas/SystemPermission"}]},"sandboxApiGroups":{"type":"array","description":"API groups that sandbox (DEVELOPER) API tokens may call on this System. Empty means none.","items":{"$ref":"#/components/schemas/SandboxApiGroup"}},"scheduleExclusions":{"description":"Dates when recurring schedules will NOT execute","type":"array","items":{"$ref":"#/components/schemas/ScheduleExclusion"}},"countryCode":{"description":"ISO 3166-1 alpha-2 country code for the system. Determines which public holidays are available for scheduling.","example":"SG","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"timezone":{"description":"IANA timezone identifier for the system location. Used for scheduling and public holiday calculations.","example":"Asia/Singapore","allOf":[{"$ref":"#/components/schemas/SystemTimezone"}]},"surgeMonitoringScheduleName":{"type":"string","description":"Name of the surge monitoring daily check scheduled task rule for this system. If present, surge monitoring is enabled. If absent/undefined, monitoring is disabled.","example":"surge_monitoring_64b7f1e6f9c9d4e5b8a9f7c3_prod"},"surgeMonitoringDailyCheckTime":{"type":"string","description":"Local time of day (HH:mm, in this system timezone) the daily surge check runs at. Defaults to 01:00 when unset.","example":"01:00"},"surgeMonitoringDigestScheduleName":{"type":"string","description":"Name of the surge monitoring digest scheduled task rule for this system","example":"surge_digest_64b7f1e6f9c9d4e5b8a9f7c3_prod"},"surgeMonitoringConfig":{"type":"object","description":"Surge monitoring configuration by entity type (ROOM, TENANT, USER). Map keys are MonitoringEntityType enum values.","additionalProperties":{"$ref":"#/components/schemas/SurgeConfig"},"example":{"ROOM":{"calculatedThresholdKwh":15.5,"manualThresholdKwh":18,"defaultViolationCount":3,"defaultViolationWindow":7}}},"scheduleAdjustmentConfig":{"type":"object","description":"Schedule adjustment configuration for this system. Controls automatic schedule adjustments based on user manual override patterns.","example":{"enabled":true,"autoApplyEnabled":false,"occurrenceThreshold":5,"timeWindowMinutes":30,"minDeltaMinutes":1}},"entityLabels":{"description":"Custom display labels for entity types (zone, tenant, room). Each entry has a singular and plural form. When absent, the frontend uses default names.","allOf":[{"$ref":"#/components/schemas/SystemEntityLabelsDto"}]},"locked":{"type":"boolean","description":"Whether the system is locked. A locked system is flagged by a SUPERADMIN (e.g. for non-payment or suspension). Toggle via PATCH /system/:systemId/lock.","default":false,"example":false}},"required":["_id","systemName","watchDogTimerRuleName","inActiveDevicesCronJobRuleName","remarks","startPilotDate","endPilotDate","realTimeToggleOn","heartBeatInterval","location","createdAt","updatedAt","createdAdmin","admins","tenants","devices","distributionBoxes","systemPermission","countryCode","timezone","locked"]},"Tenant":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Tenant","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"tenantName":{"type":"string","description":"Name of the Tenant","example":"Faculty of Engineering"},"tenantEmail":{"type":"string","description":"Email of the Admin","example":"glenn@gmail.com"},"remarks":{"type":"string","description":"Remarks for the Plug"},"tenantType":{"type":"string","description":"Tenant type key — must match a valid tenant type for this system","example":"RETAIL"},"location":{"description":"Locations of the Tenant","allOf":[{"$ref":"#/components/schemas/LocationObject"}]},"level":{"type":"number","description":"Level of the Tenant","example":"7"},"energyUsage":{"type":"number","description":"Energy Usage of the Tenant (Separated from the Tenants)","example":"1000"},"system":{"description":"System of the Tenant","allOf":[{"$ref":"#/components/schemas/System"}]},"devices":{"description":"All IoT devices under the Tenant (ObjectId references — Plugs, MCBs, Environment sensors, etc.)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"distributionBoxes":{"description":"All distribution boxes under the Tenant (ObjectId references)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"zones":{"description":"Zones under the Tenant (ObjectId references)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"rooms":{"description":"List of Rooms assigned to the Tenant (ObjectId references)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}}},"required":["_id","tenantName","remarks","location","level","energyUsage","system","devices","distributionBoxes","zones","rooms"]},"Zone":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Zone","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"Name of the Zone","example":"Glenn Quah Han Rong"},"email":{"type":"string","description":"Email of the Zone","example":"glenn@gmail.com"},"remarks":{"type":"string","description":"Remarks for the Zone"},"zoneType":{"type":"string","description":"Zone type key — must match a valid zone type for this system","example":"LABORATORY"},"tenant":{"description":"Tenant tagged to zone","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"rooms":{"description":"List of Rooms assigned to the zone (ObjectId references)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"devices":{"description":"All IoT devices under the Zone (ObjectId references — Plugs, MCBs, Environment sensors, etc.)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"distributionBoxes":{"description":"All distribution boxes under the Zone (ObjectId references)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"floorPlanKey":{"type":"string","description":"S3 key for the 2D floor plan image","example":"floor-plans/uuid.png"},"floorPlanUrl":{"type":"string","description":"CDN URL for the 2D floor plan image","example":"https://cdn.ecovolt.ai/floor-plans/uuid.png"},"floorPlan":{"type":"string","description":"3D floor plan design linked to this zone"},"floorPlanV2":{"type":"string","description":"V2 floor plan canvas linked to this zone"}},"required":["_id","name","email","remarks","tenant","rooms","devices","distributionBoxes"]},"Room":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Room","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"Name of the Room","example":"Living Room"},"remarks":{"type":"string","description":"Remarks for the Room"},"system":{"description":"System that the room belongs to (optional - derived from tenant if not provided)","allOf":[{"$ref":"#/components/schemas/System"}]},"zone":{"description":"Zone tagged to room","allOf":[{"$ref":"#/components/schemas/Zone"}]},"tenant":{"description":"Tenant associated with the room (optional for single-tenant systems)","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"devices":{"description":"All IoT devices in the Room (ObjectId references — Plugs, MCBs, Environment sensors, etc.)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"distributionBoxes":{"description":"All distribution boxes in the Room (ObjectId references)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"roomType":{"type":"string","description":"Room type key — must match a valid room type for this system","example":"MEETING_ROOM"},"squareMeter":{"type":"number","description":"Floor area of the room in square meters","example":25.5},"floorPlanKey":{"type":"string","description":"S3 key for the floor plan image","example":"floor-plans/abc123-def456.png"},"floorPlanUrl":{"type":"string","description":"CDN URL for the floor plan image","example":"https://cdn.example.com/floor-plans/abc123-def456.png"}},"required":["_id","name","remarks","devices","distributionBoxes"]},"QuietHours":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether Do Not Disturb is active for this account. On its own (no start/end) this suppresses every channel but in-app at all times; paired with start/end it scopes suppression to that window instead.","example":true},"start":{"type":"string","description":"Quiet hours start time in HH:mm format (local to timezone). Omit alongside end for an always-on Do Not Disturb toggle instead of a scheduled window.","example":"22:00"},"end":{"type":"string","description":"Quiet hours end time in HH:mm format (local to timezone). Omit alongside start for an always-on Do Not Disturb toggle instead of a scheduled window.","example":"07:00"},"timezone":{"description":"Timezone the start/end times are evaluated in, from the supported set","example":"Asia/Singapore","allOf":[{"$ref":"#/components/schemas/SystemTimezone"}]}},"required":["enabled"]},"NotificationPreferences":{"type":"object","properties":{"channels":{"type":"object","description":"Which channels this account takes, across every notification type. A channel missing here falls back to NOTIFICATION_CHANNEL_DEFAULTS."},"quietHours":{"description":"Account-level Do Not Disturb. Suppresses every channel but in-app while active; in-app is never gated.","allOf":[{"$ref":"#/components/schemas/QuietHours"}]}}},"Admin":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Admin","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"Name of the Admin","example":"Glenn Quah Han Rong"},"email":{"type":"string","description":"Email of the Admin (stored and matched in lowercase)","example":"glenn@gmail.com"},"password":{"type":"string","description":"Password of the Admin","example":"password"},"role":{"type":"string","description":"Role of the Admin (ENUM)","example":"SUPERADMIN"},"images":{"description":"Profile Image of the Admin","type":"array","items":{"type":"object"}},"systems":{"description":"Systems available to use for the Admin (ObjectId references)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"approved":{"type":"boolean","description":"Whether the Admin is approved","example":true},"pending":{"type":"boolean","description":"Indicates whether the Admin's invitation is pending","example":true},"associatedRoom":{"description":"Reference to the associated Room","allOf":[{"$ref":"#/components/schemas/Room"}]},"associatedZone":{"description":"Reference to the associated Zone","allOf":[{"$ref":"#/components/schemas/Zone"}]},"associatedTenant":{"description":"Reference to the associated Tenant","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"twoFASecret":{"type":"string","description":"The live 2FA secret. Its presence is what marks an account as enrolled — distinct from `enable2FA`, which only says 2FA is required.","example":"secret"},"pendingTwoFASecret":{"type":"string","description":"A 2FA secret that has been handed to the client but not yet proven by a valid code. Promoted to `twoFASecret` on first successful verification, so an abandoned setup leaves the account exactly as it was."},"lastTwoFATimeStep":{"type":"number","description":"RFC 6238 time step of the last 2FA code accepted for this account. Codes at or below it are refused, so a code observed in transit cannot be replayed for the remainder of its validity window."},"passwordResetJti":{"type":"string","description":"One-time id of the currently-valid password-reset token. Cleared once a reset succeeds so the token cannot be replayed."},"sessionsValidFrom":{"format":"date-time","type":"string","description":"Cutoff for session validity. Any access or refresh token issued before this instant is rejected, which is how a password change evicts every existing session — including one held by an attacker. Unset means no cutoff has ever been applied."},"enable2FA":{"type":"boolean","description":"Indicates whether 2FA is enabled for the Admin","example":true},"acceptedTermsAt":{"format":"date-time","type":"string","description":"Timestamp when the Admin accepted the Terms & Conditions (set on base signup)","example":"2023-10-01T12:00:00Z"},"createdAt":{"format":"date-time","type":"string","description":"Date when the Admin was created","example":"2023-10-01T12:00:00Z"},"updatedAt":{"format":"date-time","type":"string","description":"Date when the Admin was last updated","example":"2023-10-01T12:00:00Z"},"lockCount":{"type":"number","description":"Number of failed login attempts for the Admin","example":3},"notificationPreferences":{"description":"Everything this account chose about how it is notified — per-group channel settings and the Do Not Disturb window.","allOf":[{"$ref":"#/components/schemas/NotificationPreferences"}]},"telegramChatId":{"type":"string","description":"Telegram chat ID for this account's personal group. Paired by adding the bot to a group and sending \"/connect <code>\". When set, notifications are sent there.","example":"-1001234567890"},"telegramChatTitle":{"type":"string","description":"Telegram group name/title for this account, as reported by Telegram at pairing time."},"writingStylePromptedAt":{"format":"date-time","type":"string","description":"When this account was last asked to set up a writing style. Unset means they have never been asked."}},"required":["_id","name","email","role","images","systems","approved","twoFASecret","enable2FA","createdAt","updatedAt","lockCount"]},"CreateSystemDto":{"type":"object","properties":{"systemName":{"type":"string","description":"Name of the system. Typically represents an organization, building, or facility using the Ecovolt platform.","example":"National University of Singapore"},"realTimeToggleOn":{"type":"boolean","description":"Controls whether plugs in this system should send real-time energy data. When enabled, increases data transmission frequency.","example":true},"heartBeatInterval":{"type":"number","description":"Interval in minutes between heartbeat signals from plugs. Determines how frequently plugs report their online status.","example":3},"admin":{"description":"Admin account assigned to manage this system. This field is auto-assigned and should not be provided manually.","allOf":[{"$ref":"#/components/schemas/Admin"}]},"location":{"description":"Geographic location details of the system. Includes address, city, country, and coordinates for mapping purposes.","allOf":[{"$ref":"#/components/schemas/LocationObject"}]},"subscriptionTier":{"type":"string","description":"Unique identifier of the subscription tier to assign to this system. Determines available features and usage limits.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"costPerKWh":{"type":"number","description":"Optional initial cost per kilowatt-hour (kWh) in cents. If provided, seeds the first tariff rate period for the system. If omitted, the system uses the national default (SP Group tariff) until a rate is configured. Example: 30.65 cents = $0.3065 per kWh.","example":30.65},"remarks":{"type":"string","description":"Additional notes or comments about the system. Useful for documenting special configurations or pilot program details.","example":"Pilot program for energy monitoring in campus buildings"},"startPilotDate":{"format":"date-time","type":"string","description":"Start date of the pilot program for this system. Used for tracking trial periods and generating time-based reports.","example":"2024-01-01T00:00:00.000Z"},"endPilotDate":{"format":"date-time","type":"string","description":"End date of the pilot program for this system. After this date, the system may transition to a permanent subscription.","example":"2025-12-31T23:59:59.999Z"},"countryCode":{"description":"ISO 3166-1 alpha-2 country code for the system. Determines which public holidays are available for scheduling.","example":"SG","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"timezone":{"description":"System timezone used for scheduling and public holiday calculations. Should match the country code.","example":"Asia/Singapore","allOf":[{"$ref":"#/components/schemas/SystemTimezone"}]}},"required":["systemName","realTimeToggleOn","heartBeatInterval","location","countryCode","timezone"]},"GetSystemDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system","example":"National University of Singapore"},"realTimeToggleOn":{"type":"boolean","description":"Real-time data toggle status for the system","example":true},"heartBeatInterval":{"type":"number","description":"Heartbeat interval in minutes","example":5},"numberOfAdmins":{"type":"number","description":"Number of admins linked to the system","example":3},"numberOfDevices":{"type":"number","description":"Number of devices in the system","example":150},"deviceOnCount":{"type":"number","description":"Number of devices currently in ON status for this system.","example":80},"deviceOffCount":{"type":"number","description":"Number of devices currently in OFF status for this system.","example":50},"deviceInactiveCount":{"type":"number","description":"Number of devices currently in INACTIVE status for this system.","example":20},"deviceSpareCount":{"type":"number","description":"Number of spare devices for this system.","example":3},"createdAdmin":{"description":"Admin who created the system","allOf":[{"$ref":"#/components/schemas/Admin"}]},"location":{"description":"Location details of the system","allOf":[{"$ref":"#/components/schemas/LocationObject"}]},"createdAt":{"format":"date-time","type":"string","description":"When the system was created"},"updatedAt":{"format":"date-time","type":"string","description":"When the system was last updated"},"subscriptionTierName":{"type":"string","description":"Name of the subscription tier assigned to this system","example":"Premium"},"subscriptionExpiryDate":{"format":"date-time","type":"string","description":"Expiry date of the system subscription"}},"required":["_id","systemName","realTimeToggleOn","heartBeatInterval","numberOfAdmins","numberOfDevices","deviceOnCount","deviceOffCount","deviceInactiveCount","location","createdAt","updatedAt"]},"GetSystemsDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"List of systems","type":"array","items":{"$ref":"#/components/schemas/GetSystemDto"}}},"required":["total","page","size","totalPages","data"]},"ScheduleAdjustmentConfigDto":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether schedule adjustment suggestions are enabled. If false, no suggestions will be generated.","example":true},"autoApplyEnabled":{"type":"boolean","description":"Whether auto-apply is enabled after reaching threshold. If false, always require manual approval. When enabled, patterns are applied on the NEXT manual toggle after threshold.","example":false},"occurrenceThreshold":{"type":"number","description":"Number of occurrences required before auto-apply. Examples: 3 = quick response, 5 = balanced, 10 = conservative","example":5,"minimum":1,"maximum":20},"timeWindowMinutes":{"type":"number","description":"Time window in minutes for grouping similar override times. Overrides within this window are considered the same pattern. Examples: 15 = strict, 30 = moderate, 45 = lenient","example":30,"minimum":5,"maximum":60},"minDeltaMinutes":{"type":"number","description":"Minimum schedule change in minutes for a suggestion to be surfaced. Suggestions that change the schedule by this many minutes or fewer are treated as no-ops and never created (e.g. turning OFF at the exact scheduled OFF time). Examples: 0 = only hide exact no-ops, 1 = default, 5 = stricter","example":1,"minimum":0,"maximum":60}},"required":["enabled","autoApplyEnabled","occurrenceThreshold","timeWindowMinutes","minDeltaMinutes"]},"GetSystemDetailDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system","example":"National University of Singapore"},"realTimeToggleOn":{"type":"boolean","description":"Real-time data toggle status for the system","example":true},"heartBeatInterval":{"type":"number","description":"Heartbeat interval in minutes","example":5},"countryCode":{"description":"ISO 3166-1 alpha-2 country code for the system. Determines which public holidays are available for scheduling.","example":"SG","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"timezone":{"description":"IANA timezone identifier for the system location. Used for scheduling and public holiday calculations.","example":"Asia/Singapore","allOf":[{"$ref":"#/components/schemas/SystemTimezone"}]},"locked":{"type":"boolean","description":"Whether the system is locked. A locked system is flagged by a SUPERADMIN (e.g. for non-payment or suspension).","example":false},"entityLabels":{"description":"Custom display labels for entity types (zone, tenant, room). When absent, the frontend uses default names.","allOf":[{"$ref":"#/components/schemas/SystemEntityLabelsDto"}]},"systemPermission":{"description":"System permission settings. For non-admin callers `subscriptionTier` is omitted — only `featuresEnabled` is returned, so the client can still gate UI on enabled features.","allOf":[{"$ref":"#/components/schemas/SystemPermission"}]},"createdAt":{"format":"date-time","type":"string","description":"When the system was created"},"updatedAt":{"format":"date-time","type":"string","description":"When the system was last updated"},"location":{"description":"Location details of the system. Admin-only — omitted for non-admin callers.","allOf":[{"$ref":"#/components/schemas/LocationObject"}]},"createdAdmin":{"description":"Admin who created the system. Admin-only — omitted for non-admin callers.","allOf":[{"$ref":"#/components/schemas/Admin"}]},"admins":{"description":"System admins of the system (ObjectId references). Admin-only — omitted for non-admin callers.","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"tenants":{"description":"Tenants of the system (ObjectId references). Admin-only — omitted for non-admin callers.","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"devices":{"description":"All IoT devices in the system (ObjectId references). Admin-only — omitted for non-admin callers.","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"distributionBoxes":{"description":"All distribution boxes in the system (ObjectId references). Admin-only — omitted for non-admin callers.","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"systemEnergyReport":{"type":"string","description":"System-level energy report reference. Admin-only — omitted for non-admin callers.","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"remarks":{"type":"string","description":"Remarks for the system. Admin-only.","example":"Pilot program for energy monitoring"},"startPilotDate":{"format":"date-time","type":"string","description":"Start date of the pilot program. Admin-only."},"endPilotDate":{"format":"date-time","type":"string","description":"End date of the pilot program. Admin-only."},"scheduleExclusions":{"description":"Dates when recurring schedules will NOT execute. Admin-only.","type":"array","items":{"$ref":"#/components/schemas/ScheduleExclusion"}},"watchDogTimerRuleName":{"type":"string","description":"Name of the Watch Dog Timer Rule for the system. Admin-only (internal infrastructure identifier)."},"inActiveDevicesCronJobRuleName":{"type":"string","description":"Name of the Inactive Devices Cron Job Rule for the system. Admin-only (internal infrastructure identifier)."},"surgeMonitoringScheduleName":{"type":"string","description":"Name of the surge monitoring daily check scheduled task rule. Admin-only (internal infrastructure identifier)."},"surgeMonitoringDigestScheduleName":{"type":"string","description":"Name of the surge monitoring digest scheduled task rule. Admin-only (internal infrastructure identifier)."},"surgeMonitoringConfig":{"type":"object","description":"Surge monitoring configuration by entity type (ROOM, TENANT, USER). Admin-only.","additionalProperties":{"$ref":"#/components/schemas/SurgeConfig"}},"scheduleAdjustmentConfig":{"description":"Schedule adjustment configuration for this system. Admin-only.","allOf":[{"$ref":"#/components/schemas/ScheduleAdjustmentConfigDto"}]}},"required":["_id","systemName","realTimeToggleOn","heartBeatInterval","countryCode","timezone","locked","systemPermission","createdAt","updatedAt"]},"TenantEnergyUsageDto":{"type":"object","properties":{"tenantId":{"type":"string","description":"Unique identifier of the tenant","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"tenantName":{"type":"string","description":"Name of the tenant","example":"Faculty of Engineering"},"energyUsage":{"type":"number","description":"Total energy usage in kilowatt-hours (kWh) for the specified period","example":125.45}},"required":["tenantId","tenantName","energyUsage"]},"GetTenantEnergyUsageResponseDto":{"type":"object","properties":{"systemId":{"type":"string","description":"System ID for which tenant energy usage was calculated","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"days":{"type":"number","description":"Number of days the energy usage was calculated over","example":7},"startDate":{"type":"string","description":"Start date of the energy usage period (ISO 8601 format)","example":"2024-01-15T00:00:00.000Z"},"endDate":{"type":"string","description":"End date of the energy usage period (ISO 8601 format)","example":"2024-01-22T00:00:00.000Z"},"tenants":{"description":"List of tenants with their energy usage data","type":"array","items":{"$ref":"#/components/schemas/TenantEnergyUsageDto"}},"totalTenants":{"type":"number","description":"Total number of tenants found in this system","example":5},"totalEnergyUsage":{"type":"number","description":"Total energy usage across all tenants in kWh","example":547.32}},"required":["systemId","days","startDate","endDate","tenants","totalTenants","totalEnergyUsage"]},"CreateTariffRateDto":{"type":"object","properties":{"costPerKWh":{"type":"number","description":"Cost in cents per kWh","example":30.65,"minimum":0},"effectiveFrom":{"type":"string","description":"Date when this tariff rate becomes effective (ISO 8601 format)","example":"2024-01-01T00:00:00.000Z"},"notes":{"type":"string","description":"Notes or reason for this tariff rate change","example":"Annual rate adjustment for 2024"}},"required":["costPerKWh","effectiveFrom"]},"TariffScope":{"type":"string","enum":["SYSTEM","TENANT","ZONE","ROOM"],"description":"Scope level this rate applies to"},"GetTariffRatePeriodDto":{"type":"object","properties":{"_id":{"type":"string","description":"Tariff rate period ID","example":"507f1f77bcf86cd799439011"},"system":{"type":"string","description":"System ID that this tariff rate applies to","example":"507f1f77bcf86cd799439012"},"scopeType":{"description":"Scope level this rate applies to","example":"SYSTEM","allOf":[{"$ref":"#/components/schemas/TariffScope"}]},"scopeId":{"type":"string","description":"ID of the scope entity (system/tenant/zone/room). Equals system when scopeType is SYSTEM.","example":"507f1f77bcf86cd799439012"},"costPerKWh":{"type":"number","description":"Cost in cents per kWh","example":30.65},"effectiveFrom":{"format":"date-time","type":"string","description":"Date when this tariff rate becomes effective","example":"2024-01-01T00:00:00.000Z"},"effectiveTo":{"format":"date-time","type":"string","description":"Date when this tariff rate expires (null means currently active)","example":"2024-12-31T23:59:59.999Z"},"notes":{"type":"string","description":"Notes or reason for this tariff rate change","example":"Annual rate adjustment for 2024"},"createdBy":{"type":"string","description":"Admin who created this tariff rate","example":"507f1f77bcf86cd799439013"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when this record was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when this record was last updated"}},"required":["_id","system","scopeType","scopeId","costPerKWh","effectiveFrom","createdAt","updatedAt"]},"GetTariffRateHistoryDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of tariff rate periods","type":"array","items":{"$ref":"#/components/schemas/GetTariffRatePeriodDto"}}},"required":["total","page","size","totalPages","data"]},"UpdateTariffRateDto":{"type":"object","properties":{"costPerKWh":{"type":"number","description":"Updated tariff rate in cents per kWh","example":32.5},"effectiveFrom":{"type":"string","description":"Updated start date for this tariff rate period","example":"2024-01-01T00:00:00.000Z"},"effectiveTo":{"type":"string","description":"Updated end date for this tariff rate period (null means currently active)","example":"2024-12-31T23:59:59.999Z","nullable":true},"notes":{"type":"string","description":"Notes or reason for this tariff rate","example":"Updated: Annual rate adjustment for 2024 (revised)"}}},"GetEffectiveTariffRateDto":{"type":"object","properties":{"costPerKWh":{"type":"number","description":"Effective rate in cents per kWh, or null if no rate is configured anywhere in the scope chain","nullable":true,"example":40},"rateSource":{"description":"Where the effective rate came from: the most specific scope level with an active rate, or NATIONAL_DEFAULT when nothing is configured and the SP Group fallback applies. Null only if even the fallback is unavailable.","nullable":true,"example":"ZONE","allOf":[{"$ref":"#/components/schemas/RateSource"}]},"scopeId":{"type":"string","description":"ID of the entity that owns the winning rate — the queried entity when it has its own rate, otherwise a broader ancestor. Null when no rate applies.","nullable":true,"example":"507f1f77bcf86cd799439013"}},"required":["costPerKWh","rateSource","scopeId"]},"PublicHolidayResponseDto":{"type":"object","properties":{"date":{"type":"string","example":"2025-01-01"},"name":{"type":"string","example":"New Year's Day"},"countryCode":{"example":"SG","allOf":[{"$ref":"#/components/schemas/CountryCode"}]}},"required":["date","name","countryCode"]},"CustomExclusionEntryDto":{"type":"object","properties":{"date":{"type":"string","description":"Single date in YYYY-MM-DD format. Cannot be used with startDate/endDate.","example":"2026-12-25"},"startDate":{"type":"string","description":"Range start in YYYY-MM-DD format. Must be used with endDate.","example":"2026-12-27"},"endDate":{"type":"string","description":"Range end in YYYY-MM-DD format. Must be used with startDate.","example":"2026-12-31"},"name":{"type":"string","description":"Human-readable name for this exclusion","example":"Christmas Day"},"type":{"description":"Exclusion type. Defaults to CUSTOM if omitted.","example":"CUSTOM","allOf":[{"$ref":"#/components/schemas/ScheduleExclusionType"}]},"countryCode":{"type":"string","description":"Country code for HOLIDAY entries (e.g. \"SG\"). Optional.","example":"SG"},"mode":{"description":"FULL: skip all schedules for the day. TIMED: skip recurring schedules but run devices within timeStart–timeEnd.","example":"FULL","allOf":[{"$ref":"#/components/schemas/ExclusionMode"}]},"timeStart":{"type":"string","description":"Required when mode is TIMED. Time (HH:mm, system timezone) when devices turn ON.","example":"13:00"},"timeEnd":{"type":"string","description":"Required when mode is TIMED. Time (HH:mm, system timezone) when devices turn OFF.","example":"18:00"}},"required":["name","mode"]},"BulkAddCustomExclusionsDto":{"type":"object","properties":{"exclusions":{"description":"List of exclusion entries. Each can be a full-day exclusion or a timed override.","type":"array","items":{"$ref":"#/components/schemas/CustomExclusionEntryDto"}},"replaceExistingOfType":{"description":"If set, all existing exclusions of this type are removed before the new entries are inserted. Use HOLIDAY to sync public holidays without accumulating stale entries.","example":"HOLIDAY","allOf":[{"$ref":"#/components/schemas/ScheduleExclusionType"}]}},"required":["exclusions"]},"BulkAddCustomExclusionsResponseDto":{"type":"object","properties":{"exclusionsReplaced":{"type":"number","description":"Number of existing exclusions removed (when replaceExistingOfType is set)","example":15},"exclusionsAdded":{"type":"number","description":"Number of new exclusion dates added","example":7},"duplicatesSkipped":{"type":"number","description":"Number of dates skipped because an exclusion already existed","example":1},"timedOverridesApplied":{"type":"number","description":"Number of TIMED entries processed","example":2},"oneTimeTasksCreated":{"type":"number","description":"Total one-time ON+OFF tasks created across all TIMED entries and devices","example":48},"devicesProcessed":{"type":"number","description":"Number of devices that received one-time tasks (relevant for TIMED entries)","example":12}},"required":["exclusionsReplaced","exclusionsAdded","duplicatesSkipped","timedOverridesApplied","oneTimeTasksCreated","devicesProcessed"]},"RemoveExclusionResponseDto":{"type":"object","properties":{"message":{"type":"string","example":"Exclusion removed successfully"},"removedDate":{"type":"string","example":"2025-01-01"}},"required":["message","removedDate"]},"UpdateSystemDto":{"type":"object","properties":{"systemName":{"type":"string","description":"Name of the system. Typically represents an organization, building, or facility using the Ecovolt platform.","example":"National University of Singapore"},"location":{"description":"Geographic location details of the system. Includes address, city, country, and coordinates for mapping purposes.","allOf":[{"$ref":"#/components/schemas/LocationObject"}]},"subscriptionTier":{"type":"string","description":"Unique identifier of the subscription tier to assign to this system. Determines available features and usage limits.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"remarks":{"type":"string","description":"Additional notes or comments about the system. Useful for documenting special configurations or pilot program details.","example":"Pilot program for energy monitoring in campus buildings"},"startPilotDate":{"type":"string","description":"Start date of the pilot program for this system. Used for tracking trial periods and generating time-based reports.","example":"2024-01-01T00:00:00.000Z"},"endPilotDate":{"type":"string","description":"End date of the pilot program for this system. After this date, the system may transition to a permanent subscription.","example":"2025-12-31T23:59:59.999Z"},"countryCode":{"description":"ISO 3166-1 alpha-2 country code for the system. Determines which public holidays are available for scheduling.","example":"SG","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"timezone":{"description":"System timezone used for scheduling and public holiday calculations. Should match the country code if both are provided.","example":"Asia/Singapore","allOf":[{"$ref":"#/components/schemas/SystemTimezone"}]},"entityLabels":{"description":"Custom display labels for entity types (zone, tenant, room). Each entry has a singular and plural form. When absent, the frontend falls back to default names.","allOf":[{"$ref":"#/components/schemas/SystemEntityLabelsDto"}]}},"required":["systemName","location"]},"HeartBeatConfigurationDto":{"type":"object","properties":{"toggleOn":{"type":"boolean","description":"Controls whether plugs in this system should send real-time energy data. When enabled, increases data transmission frequency for live monitoring.","example":true},"heartBeatInterval":{"type":"number","description":"Interval in minutes between heartbeat signals from plugs. Determines how frequently plugs report their online status to the system.","example":5}},"required":["toggleOn","heartBeatInterval"]},"LinkAdminToSystemDto":{"type":"object","properties":{"adminId":{"type":"string","description":"Unique identifier of the admin account to be linked to the system. This establishes a management relationship between the admin and the system.","example":"64b7f1e6f9c9d4e5b8a9f7c1"}},"required":["adminId"]},"LinkAdminResponseDto":{"type":"object","properties":{"system":{"description":"The system entity that was successfully linked to the admin account. Contains full system details including configuration and metadata.","allOf":[{"$ref":"#/components/schemas/System"}]},"admin":{"description":"The admin entity that was successfully linked to the system. Contains full admin details including role and permissions.","allOf":[{"$ref":"#/components/schemas/Admin"}]}},"required":["system","admin"]},"UnlinkAdminResponseDto":{"type":"object","properties":{"system":{"description":"The system entity that was successfully unlinked from the admin account. Contains full system details including configuration and metadata.","allOf":[{"$ref":"#/components/schemas/System"}]},"admin":{"description":"The admin entity that was successfully unlinked from the system. Contains full admin details including role and permissions.","allOf":[{"$ref":"#/components/schemas/Admin"}]}},"required":["system","admin"]},"UpdateSystemPermissionDto":{"type":"object","properties":{"subscriptionTier":{"type":"string","description":"Unique identifier of the subscription tier to assign to this system. Changes the base feature set and limitations.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"featuresEnabled":{"description":"Array of feature identifiers to enable for this system. Overrides or extends the default features from the subscription tier.","example":["64b7f1e6f9c9d4e5b8a9f7c2","64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"expiryDate":{"type":"string","description":"Expiration date for the system permission. After this date, the system may lose access to certain features or services.","example":"2025-12-31T23:59:59.999Z"}}},"SandboxApiGroupsResponseDto":{"type":"object","properties":{"groups":{"type":"array","description":"API groups sandbox (DEVELOPER) API tokens may call on this System. Empty means none.","example":["MCB","ENVIRONMENT_SENSOR"],"items":{"$ref":"#/components/schemas/SandboxApiGroup"}}},"required":["groups"]},"UpdateSandboxApiGroupsDto":{"type":"object","properties":{"groups":{"type":"array","description":"API groups sandbox (DEVELOPER) API tokens may call on this System. Replaces the whole list; an empty list disables sandbox API access.","example":["MCB","ENVIRONMENT_SENSOR"],"items":{"$ref":"#/components/schemas/SandboxApiGroup"}}},"required":["groups"]},"UpdateScheduleAdjustmentConfigDto":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether schedule adjustment suggestions are enabled","example":true},"autoApplyEnabled":{"type":"boolean","description":"Whether auto-apply is enabled (applies on next manual toggle after threshold)","example":false},"occurrenceThreshold":{"type":"number","description":"Number of occurrences required before auto-apply (1-20)","example":5,"minimum":1,"maximum":20},"timeWindowMinutes":{"type":"number","description":"Time window in minutes for grouping similar override times (5-60)","example":30,"minimum":5,"maximum":60},"minDeltaMinutes":{"type":"number","description":"Minimum schedule change in minutes for a suggestion to be surfaced (0-60). Suggestions changing the schedule by this many minutes or fewer are skipped.","example":1,"minimum":0,"maximum":60}}},"ToggleSystemLockDto":{"type":"object","properties":{"locked":{"type":"boolean","description":"Set to true to lock the system, false to unlock it. Only accessible to SUPERADMIN users.","example":true}},"required":["locked"]},"ToggleSystemLockResponseDto":{"type":"object","properties":{"systemId":{"type":"string","description":"System ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"locked":{"type":"boolean","description":"Whether the system is now locked.","example":true},"message":{"type":"string","description":"Confirmation message","example":"System Acme HQ locked."}},"required":["systemId","locked","message"]},"CronJobType":{"type":"string","enum":["inactive-devices-snapshot","invoice-generation","hourly-update-watchdog","heartbeat-watchdog","calendar-ics-sync","calendar-webhook-renewal","forecast-nightly","forecast-retrain","forecast-precompute","weather-trigger-check","ascent-recommendation"],"description":"Type of cron job to schedule"},"ScheduleCronJobRequestDto":{"type":"object","properties":{"jobType":{"description":"Type of cron job to schedule","example":"inactive-devices-snapshot","allOf":[{"$ref":"#/components/schemas/CronJobType"}]}},"required":["jobType"]},"ScheduleCronJobResponseDto":{"type":"object","properties":{"jobType":{"description":"Type of cron job","example":"inactive-devices-snapshot","allOf":[{"$ref":"#/components/schemas/CronJobType"}]},"scheduleName":{"type":"string","description":"AWS EventBridge schedule name","example":"inactive_devices_snapshot_daily_production"},"message":{"type":"string","description":"Success message","example":"Cron job scheduled successfully"}},"required":["jobType","scheduleName","message"]},"DeleteCronJobRequestDto":{"type":"object","properties":{"jobType":{"description":"Type of cron job to delete","example":"inactive-devices-snapshot","allOf":[{"$ref":"#/components/schemas/CronJobType"}]}},"required":["jobType"]},"CronJobState":{"type":"string","enum":["ENABLED","DISABLED"],"description":"Current state of the schedule (if exists)"},"CronJobStatusResponseDto":{"type":"object","properties":{"jobType":{"description":"Type of cron job","example":"inactive-devices-snapshot","allOf":[{"$ref":"#/components/schemas/CronJobType"}]},"isEnabled":{"type":"boolean","description":"Whether the cron job is currently scheduled and enabled","example":true},"scheduleName":{"type":"string","description":"AWS EventBridge schedule name (if exists)","example":"inactive_devices_snapshot_daily_production"},"message":{"type":"string","description":"Status message","example":"Cron job is enabled"},"scheduleExpression":{"type":"string","description":"Schedule expression (if exists)","example":"cron(0 16 * * ? *)"},"state":{"description":"Current state of the schedule (if exists)","example":"ENABLED","allOf":[{"$ref":"#/components/schemas/CronJobState"}]}},"required":["jobType","isEnabled","message"]},"PopulatedTenantDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c2"},"tenantName":{"type":"string","example":"Main Office"}},"required":["_id","tenantName"]},"PopulatedUserDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"name":{"type":"string","example":"John Doe"},"email":{"type":"string","example":"john@example.com"}},"required":["_id","name","email"]},"PopulatedRoomDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"name":{"type":"string","example":"Conference Room"}},"required":["_id","name"]},"PopulatedZoneDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"name":{"type":"string","example":"Zone A"}},"required":["_id","name"]},"DeviceChipModel":{"type":"string","enum":["ESP8266","ESP32C2","ESP32C5","ESP32","ESP32C3","ESP32S3"],"description":"Hardware chip model identifier used in the device"},"PlugType":{"type":"string","enum":["SOCKET","SOAPBAR","ADAPTER"],"description":"Hardware type classification of the plug device (PLUG only)"},"MCBType":{"type":"string","enum":["UNREGISTERED","SWITCH","BUILT_IN","COIL","COIL3P"],"description":"MCB type (MCB only)"},"McbPhase":{"type":"string","enum":["A","B","C"],"description":"Set only when the list is requested with expandPhases=true and this row represents a single phase of a split-mode COIL3P MCB. In that mode realTimePower, realTimeCurrent, realTimeVoltage, totalEnergyUsage and powerFactor hold the values of this phase."},"AirconControlMode":{"type":"string","enum":["COOL","HEAT","DRY","FAN","AUTO"],"description":"Operating mode (AIRCON_CONTROL only)"},"AirconControlFan":{"type":"string","enum":["LOW","MEDIUM","HIGH","AUTO"],"description":"Fan speed setting (AIRCON_CONTROL only)"},"AirconControlSwing":{"type":"string","enum":["ON","OFF","AUTO"],"description":"Swing setting (AIRCON_CONTROL only)"},"AirconControlBrand":{"type":"string","enum":["DAIKIN","MITSUBISHI","PANASONIC","LG","SAMSUNG","FUJITSU","TOSHIBA","SHARP","HITACHI","CARRIER","MIDEA","HAIER","GREE","AKB74955603","LG2","AKB75215403","AKB73757604","LG6711A20083V","GE6711AR2853M","DAIKIN176","DAIKIN280","DAIKIN2","DAIKIN312","DAIKIN216","DAIKIN160","DAIKIN128","DAIKIN152","DAIKIN64","MITSUBISHI_AC","MITSUBISHI136","MITSUBISHI112","MITSUBISHI_HEAVY","MITSUBISHI_HEAVY_ZMS","MITSUBISHI_HEAVY_152","MITSUBISHI_HEAVY_ZJS","MITSUBISHI_HEAVY_88","PANASONIC32","ARRAH2E","ARDB1","ARREB1E","ARJW2","ARRY4","ARREW4E","MIDEA_AC","COOLIX","COOLIX48","BOSCH","BOSCH144","KDK_M56SR","M56QR","M11SU","KDK_U48FP","KDK_U60FW","KDK_T60AW","UNREGISTERED"],"description":"Air conditioner brand (AIRCON_CONTROL only)"},"GangSwitchState":{"type":"object","properties":{"gangNumber":{"type":"number","description":"Gang number (1-4)","example":1},"onStatus":{"description":"ON/OFF status of this gang switch","example":"OFF","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"label":{"type":"string","description":"Optional label/name for this gang switch","example":"Living Room Main Light"}},"required":["gangNumber","onStatus"]},"SwitchType":{"type":"string","enum":["UNREGISTERED","LIGHT","FAN","AIRCON"],"description":"Type of appliance the switch controls (SMART_SWITCH only)"},"ScheduledActionType":{"type":"string","enum":["ON","OFF","SET_TEMPERATURE","SET_BRIGHTNESS","SET_MODE","TOGGLE_SMART_SWITCH"],"description":"Action type to execute at scheduled time"},"OneTimeScheduleEntry":{"type":"object","properties":{"dateTime":{"type":"string","example":"2023-10-31T06:00:00Z","description":"Scheduled date and time (ISO 8601 format)","format":"date-time"},"action":{"example":"OFF","description":"Action type to execute at scheduled time","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"actionValue":{"type":"number","description":"Optional value for actions that require it (e.g., temperature for SET_TEMPERATURE, brightness for SET_BRIGHTNESS)","example":24},"ruleName":{"type":"string","description":"EventBridge rule name for tracking and management"}},"required":["dateTime","action"]},"DailyScheduleEntry":{"type":"object","properties":{"time":{"type":"string","example":"06:00","description":"Scheduled time in HH:mm format"},"action":{"example":"OFF","description":"Action type to execute at scheduled time","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"actionValue":{"type":"number","description":"Optional value for actions that require it (e.g., temperature for SET_TEMPERATURE, brightness for SET_BRIGHTNESS)","example":24},"ruleName":{"type":"string","description":"EventBridge rule name for tracking and management"}}},"ScheduleExecutionMode":{"type":"string","enum":["CLOUD","ON_DEVICE"],"description":"Where the weekly schedule executes. CLOUD = AWS EventBridge rules fired by the backend. ON_DEVICE = compiled into an MQTT timer payload stored in firmware, so it survives the device going offline. One-time schedules are always CLOUD."},"ScheduleConfirmationState":{"type":"string","enum":["PENDING","CONFIRMED"],"description":"Whether the device has acked the on-device schedule over MQTT. PENDING until it confirms. Only meaningful when executionMode is ON_DEVICE."},"DeviceSchedule":{"type":"object","properties":{"oneTimeSchedules":{"description":"One-time scheduled tasks that execute once and auto-delete after execution","type":"array","items":{"$ref":"#/components/schemas/OneTimeScheduleEntry"}},"monday":{"description":"Recurring schedule entries for Monday","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"tuesday":{"description":"Recurring schedule entries for Tuesday","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"wednesday":{"description":"Recurring schedule entries for Wednesday","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"thursday":{"description":"Recurring schedule entries for Thursday","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"friday":{"description":"Recurring schedule entries for Friday","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"saturday":{"description":"Recurring schedule entries for Saturday","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"sunday":{"description":"Recurring schedule entries for Sunday","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"executionMode":{"description":"Where the weekly schedule executes. CLOUD = AWS EventBridge rules fired by the backend. ON_DEVICE = compiled into an MQTT timer payload stored in firmware, so it survives the device going offline. One-time schedules are always CLOUD.","allOf":[{"$ref":"#/components/schemas/ScheduleExecutionMode"}]},"confirmationState":{"description":"Whether the device has acked the on-device schedule over MQTT. PENDING until it confirms. Only meaningful when executionMode is ON_DEVICE.","allOf":[{"$ref":"#/components/schemas/ScheduleConfirmationState"}]}}},"DeviceThresholdsDto":{"type":"object","properties":{"pending":{"type":"object","description":"Limits sent to this device but not yet confirmed by its firmware. Keys are the threshold field names below. Present only while a save is outstanding: the device is offline, or its firmware does not echo confirmations. A value here is NOT being enforced by the hardware, and the corresponding field below still holds the last confirmed value (or is absent if there never was one).","additionalProperties":{"type":"number"}},"thresholdEnabled":{"type":"boolean","description":"Whether threshold alerting is switched on for this device. When false the values below are retained but no notification fires.","example":true},"notificationThresholdHour":{"type":"number","description":"Sustained hours a reading must stay out of range before a notification is sent.","example":2},"notificationCooldown":{"type":"number","description":"Minimum time in MILLISECONDS between repeat notifications for the same device — the hardware works in ms. Schema default is 300000 (5 minutes).","example":300000},"voltageMax":{"type":"number","description":"Maximum voltage before an alert, in volts.","example":250},"currentMax":{"type":"number","description":"Maximum current before an alert, in amperes.","example":13},"powerMax":{"type":"number","description":"Maximum power before an alert, in watts.","example":3000},"voltageAMin":{"type":"number","description":"Phase A minimum voltage."},"voltageBMin":{"type":"number","description":"Phase B minimum voltage."},"voltageCMin":{"type":"number","description":"Phase C minimum voltage."},"voltageAMax":{"type":"number","description":"Phase A maximum voltage."},"voltageBMax":{"type":"number","description":"Phase B maximum voltage."},"voltageCMax":{"type":"number","description":"Phase C maximum voltage."},"currentAMax":{"type":"number","description":"Phase A maximum current."},"currentBMax":{"type":"number","description":"Phase B maximum current."},"currentCMax":{"type":"number","description":"Phase C maximum current."},"powerAMax":{"type":"number","description":"Phase A maximum power."},"powerBMax":{"type":"number","description":"Phase B maximum power."},"powerCMax":{"type":"number","description":"Phase C maximum power."},"temperatureMin":{"type":"number","description":"Minimum ambient temperature before an alert, in °C.","example":18},"temperatureMax":{"type":"number","description":"Maximum ambient temperature before an alert, in °C.","example":30},"humidityMin":{"type":"number","description":"Minimum relative humidity before an alert, in %.","example":40},"humidityMax":{"type":"number","description":"Maximum relative humidity before an alert, in %.","example":70},"thresholdGroupName":{"type":"string","description":"Name of the shared threshold group this environment sensor follows, when one is assigned. The group holds the full per-metric limits (CO₂, PM, VOC, lux, sound); this DTO carries only the sensor-level values.","example":"Level 3 Offices"},"flowRateMin":{"type":"number","description":"Minimum flow rate before an alert."},"flowRateMax":{"type":"number","description":"Maximum flow rate before an alert."},"inletTemperatureMin":{"type":"number","description":"Minimum inlet temperature before an alert, in °C."},"inletTemperatureMax":{"type":"number","description":"Maximum inlet temperature before an alert, in °C."},"outletTemperatureMin":{"type":"number","description":"Minimum outlet temperature before an alert, in °C."},"outletTemperatureMax":{"type":"number","description":"Maximum outlet temperature before an alert, in °C."},"leakThresholdFlowRate":{"type":"number","description":"Flow rate that, if sustained, is treated as a leak and raises a leak alert."}}},"DeviceDetailsDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique device identifier","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceName":{"type":"string","description":"Device name","example":"Office Plug 1"},"macId":{"type":"string","description":"Device MAC address","example":"1C:69:20:18:0A:BC"},"deviceType":{"description":"Device type","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"deviceStatus":{"type":"string","description":"Device status","example":"ACTIVE"},"onStatus":{"description":"Current ON/OFF status","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"system":{"type":"string","description":"System ID","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"systemName":{"type":"string","description":"System name (only populated in SUPERADMIN /devices endpoint)","example":"National University of Singapore"},"tenant":{"description":"Tenant details (populated)","allOf":[{"$ref":"#/components/schemas/PopulatedTenantDto"}]},"user":{"description":"User details (populated)","allOf":[{"$ref":"#/components/schemas/PopulatedUserDto"}]},"room":{"description":"Room details (populated)","allOf":[{"$ref":"#/components/schemas/PopulatedRoomDto"}]},"zone":{"description":"Zone details (populated)","allOf":[{"$ref":"#/components/schemas/PopulatedZoneDto"}]},"createdAt":{"format":"date-time","type":"string","description":"Device creation timestamp","example":"2024-01-15T10:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp","example":"2024-01-15T14:30:00.000Z"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups","example":1},"remarks":{"type":"string","description":"Additional remarks or notes about the device","example":"Located near the window"},"location":{"type":"string","description":"Physical location or address where the device is installed","example":"Room 101, Building A"},"chipModel":{"description":"Hardware chip model identifier used in the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is connected to","example":"IoT-Network-5G"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join","example":"IoT-Network-5G"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join","example":"IoT-Network-5G-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join","example":"IoT-Network-5G-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them","example":["IoT-Network-5G","IoT-Network-5G-2","IoT-Network-5G-3"],"type":"array","items":{"type":"string"}},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2024-01-15T14:30:00.000Z"},"isLocked":{"type":"boolean","description":"Locked status of the device","example":false},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","example":false},"deviceModel":{"type":"string","description":"Model name or identifier of the device connected to this plug (PLUG only)","example":"Dell XPS 15"},"deviceBrand":{"type":"string","description":"Brand or manufacturer of the device connected to this plug (PLUG only)","example":"Dell"},"plugCategory":{"type":"string","description":"Category classification for the connected device type (PLUG only). See GET /plug-category for valid values.","example":"CHARGER"},"plugType":{"description":"Hardware type classification of the plug device (PLUG only)","example":"ADAPTER","allOf":[{"$ref":"#/components/schemas/PlugType"}]},"ecoguardPlug":{"type":"string","description":"Ecoguard power-saving configuration settings reference ID (PLUG only)","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"ecoguardEnabled":{"type":"boolean","description":"Whether EcoGuard is currently enabled for this plug (PLUG only)","example":true},"realTimePower":{"type":"number","description":"Real-time power consumption in watts (PLUG only)","example":500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes (PLUG only)","example":2.5},"realTimeVoltage":{"type":"number","description":"Real-time voltage (PLUG only)","example":230},"powerFactor":{"type":"number","description":"Power factor (PLUG only)","example":0.95},"totalEnergyUsage":{"type":"number","description":"Total energy usage in watt-hours (PLUG only)","example":125000},"ecoguardWatts":{"type":"number","description":"Ecoguard power threshold in watts (PLUG only)","example":100},"ecoguardMinutes":{"type":"number","description":"Ecoguard time threshold in minutes (PLUG only)","example":30},"ratedCurrent":{"type":"number","description":"Rated current in amperes (MCB only)","example":32},"mcbType":{"description":"MCB type (MCB only)","example":"BUILT_IN","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"tripStatus":{"type":"boolean","description":"Trip status - whether the MCB has tripped (MCB only)","example":false},"distributionBox":{"type":"string","description":"Distribution box ID (MCB only)","example":"64b7f1e6f9c9d4e5b8a9f7d2"},"position":{"type":"number","description":"Position in distribution box (MCB only)","example":1},"isSingle":{"type":"boolean","description":"Whether a COIL3P MCB is treated as one combined 3-phase load (true/undefined) or split into 3 independent single-phase circuits (false) (MCB only)","example":true},"phase":{"description":"Set only when the list is requested with expandPhases=true and this row represents a single phase of a split-mode COIL3P MCB. In that mode realTimePower, realTimeCurrent, realTimeVoltage, totalEnergyUsage and powerFactor hold the values of this phase.","example":"A","allOf":[{"$ref":"#/components/schemas/McbPhase"}]},"phaseName":{"type":"string","description":"User-defined name of this row's phase (expandPhases=true rows only)","example":"Kitchen"},"externalId":{"type":"string","description":"Downstream address of a RIDGE-fronted unit on its gateway bus (EXTERNAL only). Such a row shares its gateway's deviceNumber, so this is what tells the units of one gateway apart — the pair reads as \"1660:3\", the way a phase leg reads \"11/L1\".","example":"3"},"unitNumber":{"type":"number","description":"Per-gateway number of a RIDGE-fronted unit (EXTERNAL only). Reads as \"1660-3\" with the gateway's deviceNumber; preferred over externalId for display.","example":3},"isRidgeChild":{"type":"boolean","description":"True when this row is a unit behind a RIDGE gateway rather than directly-connected hardware.","example":false},"realTimePowerA":{"type":"number","description":"Real-time active power for phase A in watts (COIL3P MCB only)","example":120},"realTimePowerB":{"type":"number","description":"Real-time active power for phase B in watts (COIL3P MCB only)","example":130},"realTimePowerC":{"type":"number","description":"Real-time active power for phase C in watts (COIL3P MCB only)","example":110},"realTimeCurrentA":{"type":"number","description":"Real-time current for phase A in amperes (COIL3P MCB only)","example":1.2},"realTimeCurrentB":{"type":"number","description":"Real-time current for phase B in amperes (COIL3P MCB only)","example":1.3},"realTimeCurrentC":{"type":"number","description":"Real-time current for phase C in amperes (COIL3P MCB only)","example":1.1},"realTimeVoltageA":{"type":"number","description":"Real-time voltage for phase A in volts (COIL3P MCB only)","example":230},"realTimeVoltageB":{"type":"number","description":"Real-time voltage for phase B in volts (COIL3P MCB only)","example":231},"realTimeVoltageC":{"type":"number","description":"Real-time voltage for phase C in volts (COIL3P MCB only)","example":229},"totalEnergyUsageA":{"type":"number","description":"Total energy usage for phase A in kWh (COIL3P MCB only)","example":45.2},"totalEnergyUsageB":{"type":"number","description":"Total energy usage for phase B in kWh (COIL3P MCB only)","example":47.8},"totalEnergyUsageC":{"type":"number","description":"Total energy usage for phase C in kWh (COIL3P MCB only)","example":44.1},"temperature":{"type":"number","description":"Set temperature in Celsius (AIRCON_CONTROL only)","example":24},"mode":{"description":"Operating mode (AIRCON_CONTROL only)","example":"COOL","allOf":[{"$ref":"#/components/schemas/AirconControlMode"}]},"fan":{"description":"Fan speed setting (AIRCON_CONTROL only)","example":"AUTO","allOf":[{"$ref":"#/components/schemas/AirconControlFan"}]},"swing":{"description":"Swing setting (AIRCON_CONTROL only)","example":"OFF","allOf":[{"$ref":"#/components/schemas/AirconControlSwing"}]},"brand":{"description":"Air conditioner brand (AIRCON_CONTROL only)","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"isIRRemote":{"type":"boolean","description":"Whether this device uses IR remote control (AIRCON_CONTROL only)","example":true},"numberOfGangs":{"type":"number","description":"Number of gangs/buttons on the switch (SMART_SWITCH only)","example":2,"minimum":1,"maximum":4},"gangSwitchStates":{"description":"Individual gang switch states with ON/OFF status (SMART_SWITCH only)","type":"array","items":{"$ref":"#/components/schemas/GangSwitchState"}},"switchType":{"description":"Type of appliance the switch controls (SMART_SWITCH only)","example":"LIGHT","allOf":[{"$ref":"#/components/schemas/SwitchType"}]},"deviceSchedule":{"description":"Weekly and one-time schedule configuration for this device","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the named Schedule this device currently follows. deviceSchedule is compiled from it. Absent or null when the device follows nothing.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c3"},"sensorData":{"type":"object","description":"Environmental sensor data (ENV_SENSOR only)"},"thresholds":{"description":"Alert thresholds configured for this device, gathered into one object. Which fields are populated depends on deviceType — see DeviceThresholdsDto. Omitted entirely when the device has no thresholds set.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]}},"required":["_id","deviceName","macId","deviceType","onStatus","system","createdAt","updatedAt","lastSeen"]},"GetDevicesResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of devices with their full details. Each device includes base fields plus type-specific fields based on deviceType.","type":"array","items":{"$ref":"#/components/schemas/DeviceDetailsDto"}}},"required":["total","page","size","totalPages","data"]},"DeviceHistoryItem":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier for the history entry","example":"507f1f77bcf86cd799439011"},"onStatus":{"type":"string","description":"Status of the device (ON/OFF)","enum":["ON","OFF","INACTIVE","ERROR","ACTIVE"],"example":"ON"},"triggerSource":{"type":"string","description":"Source that triggered the status change","enum":["MANUAL","CLOUD","SCHEDULED","ECOGUARD","WATCHDOG","LWT","UNKNOWN","BOOTUP","RECONNECT","RECONCILE","SMART_MODE","SMARTHUB","API","DEVICE_SCHEDULE"],"example":"MANUAL"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the status change occurred","example":"2024-01-15T10:30:00.000Z"},"temperature":{"type":"number","description":"Temperature setting (Aircon Control only) - present when device is an aircon control","example":24},"mode":{"description":"Operation mode (Aircon Control only) - present when device is an aircon control","example":"COOL","allOf":[{"$ref":"#/components/schemas/AirconControlMode"}]},"fan":{"description":"Fan speed (Aircon Control only) - present when device is an aircon control","example":"AUTO","allOf":[{"$ref":"#/components/schemas/AirconControlFan"}]},"swing":{"description":"Swing setting (Aircon Control only) - present when device is an aircon control","example":"OFF","allOf":[{"$ref":"#/components/schemas/AirconControlSwing"}]},"brand":{"description":"Brand of AC (Aircon Control only) - present when device is an aircon control","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"gangSwitchStates":{"description":"Gang switch states (Smart Switch only) - present when device is a smart switch, shows the state of each gang at the time of this history entry","example":[{"gangNumber":1,"onStatus":"ON","label":"Main Light"},{"gangNumber":2,"onStatus":"OFF","label":"Accent Light"}],"type":"array","items":{"$ref":"#/components/schemas/GangSwitchState"}}},"required":["_id","onStatus","triggerSource","createdAt"]},"GetDeviceHistoryDto":{"type":"object","properties":{"deviceHistory":{"description":"Array of device history entries","type":"array","items":{"$ref":"#/components/schemas/DeviceHistoryItem"}},"total":{"type":"number","description":"Total number of history entries","example":100}},"required":["deviceHistory","total"]},"MultipleDevicesConfigurationDto":{"type":"object","properties":{"deviceType":{"description":"Type of devices to configure","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"deviceIds":{"description":"Array of unique device identifiers from IoT devices to configure in batch","example":["1092384701928347","1092384701928348"],"type":"array","items":{"type":"string"}},"ssid":{"type":"string","description":"Primary network SSID (Wi-Fi name) to configure for all selected devices","example":"EcovoltHotspot"},"password":{"type":"string","description":"Primary Wi-Fi network password to configure for all selected devices","example":"123abc456"},"ssid2":{"type":"string","description":"Secondary network SSID (Wi-Fi name) to configure for all selected devices","example":"EcovoltHotspot2"},"password2":{"type":"string","description":"Secondary Wi-Fi network password to configure for all selected devices","example":"123abc456"},"ssid3":{"type":"string","description":"Tertiary network SSID (Wi-Fi name) to configure for all selected devices","example":"EcovoltHotspot3"},"password3":{"type":"string","description":"Tertiary Wi-Fi network password to configure for all selected devices","example":"123abc456"},"ssidPriority":{"description":"Ordered list of SSIDs by connection priority. The device will attempt to connect to these networks in the given order.","example":["EcovoltHotspot","EcovoltHotspot2","EcovoltHotspot3"],"type":"array","items":{"type":"string"}},"systemName":{"type":"string","description":"System name to assign to all selected devices for organizational grouping","example":"NUS"},"useShadow":{"type":"boolean","description":"Deliver via the AWS IoT device shadow when the firmware supports it (default). Set false to force the legacy changeWifi publish for every device in this request, e.g. to roll back if shadow delivery misbehaves.","default":true}},"required":["deviceType","deviceIds"]},"DeviceConfigurationDto":{"type":"object","properties":{"deviceType":{"description":"Type of device to configure (PLUG, MCB, ENV_SENSOR). Determines which device will receive the configuration.","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"ssid":{"type":"string","description":"Primary network SSID (Wi-Fi name) that the device should connect to","example":"EcovoltHotspot"},"password":{"type":"string","description":"Primary Wi-Fi network password","example":"123abc456"},"ssid2":{"type":"string","description":"Secondary network SSID (Wi-Fi name)","example":"EcovoltHotspot2"},"password2":{"type":"string","description":"Secondary Wi-Fi network password","example":"123abc456"},"ssid3":{"type":"string","description":"Tertiary network SSID (Wi-Fi name)","example":"EcovoltHotspot3"},"password3":{"type":"string","description":"Tertiary Wi-Fi network password","example":"123abc456"},"ssidPriority":{"description":"Ordered list of SSIDs by connection priority. The device will attempt to connect to these networks in the given order.","example":["EcovoltHotspot","EcovoltHotspot2","EcovoltHotspot3"],"type":"array","items":{"type":"string"}},"systemName":{"type":"string","description":"Name of the system that the device belongs to for organizational grouping","example":"NUS"},"useShadow":{"type":"boolean","description":"Deliver via the AWS IoT device shadow when the firmware supports it (default). Set false to force the legacy changeWifi publish for every device in this request, e.g. to roll back if shadow delivery misbehaves.","default":true}},"required":["deviceType"]},"DeviceErrorType":{"type":"string","enum":["Info","Warning","Error"]},"ErrorLog":{"type":"object","properties":{"_id":{"type":"string","description":"Error log document ID"},"errorType":{"description":"Type or code of the error","example":"Error","allOf":[{"$ref":"#/components/schemas/DeviceErrorType"}]},"errorTitle":{"type":"string","description":"Type or code of the error"},"errorMessage":{"type":"string","description":"Detailed error message"},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"macId":{"type":"string","description":"Device MAC Address","example":"AA:BB:CC:DD:EE:FF"},"deviceType":{"description":"Type of device","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"deviceId":{"type":"string","description":"Device ID","example":"60f7c0b5b4d1c826d8f0e6b7"},"externalDeviceId":{"type":"string","description":"ExternalDevice this error belongs to, when the reporting device is a RIDGE gateway relaying a fault from one of the units it fronts. `deviceId` stays the gateway, so a per-gateway query still returns it.","example":"64b7f1e6f9c9d4e5b8a9f7c9"},"externalId":{"type":"string","description":"Downstream address of the faulting unit as the gateway reported it (iTM pointId / Daikin ac id). Retained even when no ExternalDevice row matched.","example":"134"},"systemId":{"type":"string","description":"Tagged System"}},"required":["_id","errorType","errorTitle","errorMessage","createdAt","macId","deviceType","deviceId","systemId"]},"GetErrorHistoryDto":{"type":"object","properties":{"errorLogs":{"description":"Array of error log records for devices matching the query","type":"array","items":{"$ref":"#/components/schemas/ErrorLog"}},"total":{"type":"number","description":"Total count of error logs available for pagination purposes","example":25}},"required":["errorLogs","total"]},"CreateWhiteListedDevicesDto":{"type":"object","properties":{"macIds":{"description":"Array of unique MAC address identifiers from IoT devices to add to the whitelist","example":["AA:BB:CC:DD:EE:FF","AA:BB:CC:DD:EE:FE"],"type":"array","items":{"type":"string"}},"deviceType":{"description":"Type of device","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking device production groups and quality control","example":1},"chipModel":{"description":"Hardware chip model identifier used in the devices (e.g., ESP32C2, ESP8266)","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number installed on the devices in semantic versioning format","example":"1.0.0"}},"required":["macIds","deviceType","batchNumber","chipModel","versionNumber"]},"WhiteList":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the MacId","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"Mac ID of the device (from IoT device)","example":"AA:BB:CC:DD:EE:FF"},"deviceType":{"description":"Type of device","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"batchNumber":{"type":"number","description":"Batch Number of the device","example":2},"chipModel":{"description":"Chip Model of the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Version number of the device","example":"1.0.0"},"deviceNumber":{"type":"number","description":"Auto-incremented Device Number for whitelisted devices","example":1},"adminCreated":{"type":"string","description":"Name of the admin who created the whitelist entry","example":"Glenn"},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"}},"required":["_id","macId","deviceType","batchNumber","chipModel","versionNumber","deviceNumber","adminCreated","createdAt"]},"GetWhitelistedDevicesDto":{"type":"object","properties":{"whiteListedDevices":{"description":"Array of devices that are approved and registered in the whitelist","type":"array","items":{"$ref":"#/components/schemas/WhiteList"}},"total":{"type":"number","description":"Total count of whitelisted devices for pagination and tracking","example":200}},"required":["whiteListedDevices","total"]},"GetIsDeviceWhiteListedDto":{"type":"object","properties":{"isWhiteListed":{"type":"boolean","description":"Boolean flag indicating whether the device is approved and present in the whitelist","example":true}},"required":["isWhiteListed"]},"BulkRemoveWhitelistDto":{"type":"object","properties":{"ids":{"description":"Array of whitelist entry IDs to remove","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"ManufacturerRegisterDeviceDto":{"type":"object","properties":{"macId":{"type":"string","description":"MAC address of the device","example":"AA:BB:CC:DD:EE:FF"},"deviceType":{"description":"Type of device being registered","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"batchNumber":{"type":"number","description":"Manufacturing batch number","example":1},"chipModel":{"description":"Hardware chip model of the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number installed on the device","example":"1.0.0"},"adminCreated":{"type":"string","description":"Name of the manufacturer or person registering the device. Defaults to \"Manufacturer\" if not provided.","example":"Ah Kow Manufacturing"}},"required":["macId","deviceType","batchNumber","chipModel","versionNumber"]},"ManufacturerDeviceResponseDto":{"type":"object","properties":{"macId":{"type":"string","description":"MAC address of the device","example":"AA:BB:CC:DD:EE:FF"},"deviceNumber":{"type":"number","description":"Auto-assigned device number for this device","example":42},"alreadyExisted":{"type":"boolean","description":"True if the MAC was already registered and this was a lookup, false if newly registered","example":false}},"required":["macId","deviceNumber","alreadyExisted"]},"ManufacturerPlugStatusDto":{"type":"object","properties":{"_id":{"type":"string","description":"Device ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"MAC address of the device","example":"AA:BB:CC:DD:EE:FF"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":42},"onStatus":{"description":"Current on/off status of the device","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}},"required":["_id","macId","deviceNumber","onStatus"]},"ManufacturerSystemPlugsResponseDto":{"type":"object","properties":{"data":{"description":"List of plug devices in the system","type":"array","items":{"$ref":"#/components/schemas/ManufacturerPlugStatusDto"}},"total":{"type":"number","description":"Total number of plug devices","example":10}},"required":["data","total"]},"ManufacturerDeviceStatusDto":{"type":"object","properties":{"_id":{"type":"string","description":"Device ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"MAC address of the device","example":"AA:BB:CC:DD:EE:FF"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":42},"onStatus":{"description":"Current on/off status of the device","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}},"required":["_id","macId","deviceNumber","onStatus"]},"ManufacturerSystemDevicesResponseDto":{"type":"object","properties":{"data":{"description":"List of devices of the requested type in the system","type":"array","items":{"$ref":"#/components/schemas/ManufacturerDeviceStatusDto"}},"total":{"type":"number","description":"Total number of devices returned","example":10}},"required":["data","total"]},"MultipleDevicesChangeSystemDto":{"type":"object","properties":{"deviceIds":{"description":"Array of unique device identifiers (plugs or MCBs) to reassign to a different system","example":["1092384701928347","1092384701928348"],"type":"array","items":{"type":"string"}},"newSystemId":{"type":"string","description":"Unique identifier of the target system to reassign all selected devices to","example":"64b7f1e6f9c9d4e5b8a9f7c3"}},"required":["deviceIds","newSystemId"]},"UpdateMultipleDevicesRelationshipsDto":{"type":"object","properties":{"deviceIds":{"description":"Array of unique device identifiers to update relationships for","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"tenantId":{"type":"string","description":"Unique identifier of the tenant to assign the devices to. Pass null to remove the tenant relationship.","example":"64b7f1e6f9c9d4e5b8a9f7c5","nullable":true},"zoneId":{"type":"string","description":"Unique identifier of the zone to assign ownership of the devices to. Pass null to remove the zone relationship.","example":"64b7f1e6f9c9d4e5b8a9f7c3","nullable":true},"roomId":{"type":"string","description":"Unique identifier of the room to assign the devices to. Pass null to remove the room relationship.","example":"64b7f1e6f9c9d4e5b8a9f7c4","nullable":true}},"required":["deviceIds"]},"UpdateDeviceBaseDto":{"type":"object","properties":{"deviceName":{"type":"string","description":"Name of the device","example":"Living Room Light"},"location":{"type":"string","description":"Physical location of the device","example":"Block 123, Unit 01-23"},"remarks":{"type":"string","description":"Additional remarks or notes about the device","example":"Handle with care - critical equipment"},"tenantId":{"type":"string","description":"Tenant ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zoneId":{"type":"string","description":"Zone ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"roomId":{"type":"string","description":"Room ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device cannot be toggled ON/OFF.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false}}},"DeviceUpdateItem":{"type":"object","properties":{"deviceId":{"type":"string","description":"MongoDB ID of the device to update","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"data":{"description":"Fields to update on the device","allOf":[{"$ref":"#/components/schemas/UpdateDeviceBaseDto"}]}},"required":["deviceId","data"]},"BulkUpdateDevicesDto":{"type":"object","properties":{"updates":{"description":"Array of device update operations to perform in a single batch request","example":[{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c3","data":{"deviceName":"Main Switch","location":"Lobby","remarks":"Critical"}}],"type":"array","items":{"$ref":"#/components/schemas/DeviceUpdateItem"}}},"required":["updates"]},"UpdateDevicesLockStatusDto":{"type":"object","properties":{"deviceIds":{"description":"Array of device IDs to lock or unlock","example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"isLocked":{"type":"boolean","description":"Lock status to set. True to lock devices, false to unlock.","example":true}},"required":["deviceIds","isLocked"]},"FailedDeviceLockUpdateDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Device ID that failed to update","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"reason":{"type":"string","description":"Reason for failure","example":"Device not found"}},"required":["deviceId","reason"]},"UpdateDevicesLockStatusResponseDto":{"type":"object","properties":{"successCount":{"type":"number","description":"Number of devices successfully updated","example":5},"failedCount":{"type":"number","description":"Number of devices that failed to update","example":1},"successfulDeviceIds":{"description":"List of device IDs that were successfully updated","example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"failedDevices":{"description":"List of devices that failed to update with reasons","type":"array","items":{"$ref":"#/components/schemas/FailedDeviceLockUpdateDto"}}},"required":["successCount","failedCount","successfulDeviceIds","failedDevices"]},"UpdateDevicesSpareStatusDto":{"type":"object","properties":{"deviceIds":{"description":"Array of device IDs to mark as spare or non-spare","example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"isSpare":{"type":"boolean","description":"Spare status to set. True to mark as spare, false to unmark.","example":true}},"required":["deviceIds","isSpare"]},"UpdateDevicesRelationshipTenantDto":{"type":"object","properties":{"deviceIds":{"description":"Array of unique device identifiers to update relationships for","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"zoneId":{"type":"string","description":"Unique identifier of the zone to assign ownership of the devices to within the tenant","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"roomId":{"type":"string","description":"Unique identifier of the room to assign the devices to for location tracking","example":"64b7f1e6f9c9d4e5b8a9f7c4"}},"required":["deviceIds"]},"UpdateDevicesRelationshipZoneDto":{"type":"object","properties":{"deviceIds":{"description":"Array of unique device identifiers to update room assignment for","example":["64b7f1e6f9c9d4e5b8a9f7c2","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"roomId":{"type":"string","description":"Unique identifier of the room to assign the devices to for location tracking","example":"64b7f1e6f9c9d4e5b8a9f7c4"}},"required":["deviceIds"]},"MultipleDevicesCalibrationDto":{"type":"object","properties":{"deviceIds":{"description":"Array of unique device identifiers to apply calibration settings to","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"systemName":{"type":"string","description":"Name of the system containing the devices to calibrate for batch processing","example":"NUS"},"deviceType":{"description":"Type of device to calibrate (PLUG, MCB, ENV_SENSOR). Determines which devices will receive calibration data.","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"voltageMultiplier":{"type":"number","description":"Calibration multiplier to adjust voltage sensor readings for accuracy correction","example":1.5},"voltageOffset":{"type":"number","description":"Calibration offset value in volts to adjust voltage sensor baseline readings","example":0.5},"currentMultiplier":{"type":"number","description":"Calibration multiplier to adjust current sensor readings for accuracy correction","example":1.2},"currentOffset":{"type":"number","description":"Calibration offset value in amperes to adjust current sensor baseline readings","example":0.2},"powerMultiplier":{"type":"number","description":"Calibration multiplier to adjust power measurement calculations for accuracy","example":1.1},"powerOffset":{"type":"number","description":"Calibration offset value in watts to adjust power measurement baseline","example":0.1}},"required":["deviceIds","systemName","deviceType","voltageMultiplier","voltageOffset","currentMultiplier","currentOffset","powerMultiplier","powerOffset"]},"InactiveDeviceDto":{"type":"object","properties":{"_id":{"type":"string","description":"Device document ID","example":"507f1f77bcf86cd799439011"},"macId":{"type":"string","description":"Device MAC ID (unique identifier)","example":"28:56:2F:0E:58:C8"},"deviceNumber":{"type":"number","description":"Device number identifier","example":1},"deviceName":{"type":"string","description":"Device display name","example":"Conference Room AC"},"deviceType":{"description":"Type of device","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"tenantId":{"type":"string","description":"Tenant ID that owns this device","example":"507f1f77bcf86cd799439011"},"tenantName":{"type":"string","description":"Tenant name that owns this device","example":"Building A - Floor 3"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device sent a heartbeat (last seen timestamp)","example":"2024-10-15T08:30:00.000Z"},"inactiveDuration":{"type":"number","description":"Duration the device has been inactive in milliseconds","example":172800000},"inactiveDurationFormatted":{"type":"string","description":"Duration the device has been inactive in human-readable format","example":"2 days, 3 hours"}},"required":["_id","macId","deviceNumber","deviceName","deviceType","lastSeen","inactiveDuration","inactiveDurationFormatted"]},"GetInactiveDevicesDto":{"type":"object","properties":{"inactiveDevices":{"description":"Array of inactive devices with their details","type":"array","items":{"$ref":"#/components/schemas/InactiveDeviceDto"}},"total":{"type":"number","description":"Total count of inactive devices found","example":15},"retrievedAt":{"format":"date-time","type":"string","description":"Timestamp when this data was retrieved","example":"2024-10-17T14:45:00.000Z"}},"required":["inactiveDevices","total","retrievedAt"]},"SaveInactiveSnapshotResponseDto":{"type":"object","properties":{"snapshotId":{"type":"string","description":"Snapshot document ID","example":"507f1f77bcf86cd799439011"},"systemId":{"type":"string","description":"System ID this snapshot belongs to","example":"507f1f77bcf86cd799439011"},"totalInactiveDevices":{"type":"number","description":"Number of inactive devices captured in this snapshot","example":5},"snapshotDate":{"format":"date-time","type":"string","description":"Timestamp when the snapshot was taken","example":"2024-10-22T00:00:00.000Z"},"message":{"type":"string","description":"Message confirming the snapshot was saved","example":"Inactive device snapshot saved successfully"}},"required":["snapshotId","systemId","totalInactiveDevices","snapshotDate","message"]},"InactiveDeviceSnapshot":{"type":"object","properties":{"_id":{"type":"string","description":"Snapshot document ID"},"system":{"description":"System this snapshot belongs to","allOf":[{"$ref":"#/components/schemas/System"}]},"inactiveDevices":{"description":"Array of inactive devices with their details","type":"array","items":{"$ref":"#/components/schemas/InactiveDeviceDto"}},"total":{"type":"number","description":"Total count of inactive devices"},"retrievedAt":{"format":"date-time","type":"string","description":"Timestamp when this data was retrieved"},"referenceDate":{"format":"date-time","type":"string","description":"Optional reference date used for duration calculation"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when this snapshot was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when this snapshot was last updated"}},"required":["_id","system","inactiveDevices","total","retrievedAt","createdAt","updatedAt"]},"GetInactiveSnapshotsDto":{"type":"object","properties":{"snapshots":{"description":"Array of inactive device snapshots","type":"array","items":{"$ref":"#/components/schemas/InactiveDeviceSnapshot"}},"total":{"type":"number","description":"Total count of snapshots found","example":10}},"required":["snapshots","total"]},"HierarchyType":{"type":"string","enum":["SYSTEM","TENANT","USER","ROOM"]},"DeviceCountDto":{"type":"object","properties":{"deviceCount":{"type":"number","description":"Total number of devices registered in the system across all statuses","example":4},"deviceOnCount":{"type":"number","description":"Number of devices currently in ON status and actively consuming power","example":2},"deviceOffCount":{"type":"number","description":"Number of devices currently in OFF status and not consuming power","example":1},"deviceInactiveCount":{"type":"number","description":"Number of devices marked as inactive or not communicating with the system","example":1},"deviceSpareCount":{"type":"number","description":"Number of spare devices (excluded from total, on, off, and inactive counts)","example":1}},"required":["deviceCount","deviceOnCount","deviceOffCount","deviceInactiveCount","deviceSpareCount"]},"DeviceTypeBreakdownDto":{"type":"object","properties":{"PLUG":{"type":"number","description":"Number of PLUG devices in the system","example":15},"MCB":{"type":"number","description":"Number of MCB (Miniature Circuit Breaker) devices in the system","example":8},"ENV_SENSOR":{"type":"number","description":"Number of ENV_SENSOR (Environmental Sensor) devices in the system","example":3},"SMART_SWITCH":{"type":"number","description":"Number of SMART_SWITCH devices in the system","example":5},"AIRCON_CONTROL":{"type":"number","description":"Number of AIRCON_CONTROL (Air Conditioning Controller) devices in the system","example":4},"WATER_METER":{"type":"number","description":"Number of WATER_METER devices in the system","example":2},"RIDGE":{"type":"number","description":"Number of RIDGE gateways plus every external device they front","example":6},"total":{"type":"number","description":"Total number of devices in the system","example":37}},"required":["PLUG","MCB","ENV_SENSOR","SMART_SWITCH","AIRCON_CONTROL","WATER_METER","RIDGE","total"]},"ChangeDeviceSystemDto":{"type":"object","properties":{"newSystemId":{"type":"string","description":"Unique identifier of the system to reassign the device to for organizational management","example":"64b7f1e6f9c9d4e5b8a9f7c3"}},"required":["newSystemId"]},"ToggleDeviceStatusDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Unique identifier of the device to be toggled","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"onStatus":{"description":"Desired power state. Use ON to turn the device on (all gangs for SmartSwitch), or OFF to turn it off (all gangs for SmartSwitch).","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}},"required":["deviceId"]},"DeviceConfig":{"type":"object","properties":{}},"Device":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceType":{"description":"Type of device (discriminator field)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"macId":{"type":"string","description":"MAC ID of the Device (from IoT device)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Name of the Device","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"onStatus":{"description":"Status of the Device (ON / OFF / INACTIVE / ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend (cloud / scheduled). Set when a toggle is issued; cleared when the device reports a matching state or a physical (MANUAL) press overrides it. Always ON or OFF when present.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when targetState was last written. Used to expire stale SCHEDULED intents (24hr TTL). CLOUD intents do not expire.","example":"2026-05-25T08:00:00.000Z"},"targetSource":{"description":"Origin of the pending targetState. Drives TTL behavior on reconcile. Only CLOUD / SCHEDULED are ever written here.","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"location":{"type":"string","description":"Physical location or address where the device is installed","example":"Room 101, Building A"},"remarks":{"type":"string","description":"Remarks for the Device"},"deviceModel":{"type":"string","description":"Model name or identifier of the device","example":"TP-Link HS110"},"deviceBrand":{"type":"string","description":"Brand or manufacturer of the device","example":"TP-Link"},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups","example":1},"chipModel":{"description":"Hardware chip model identifier used in the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is connected to","example":"IoT-Network-5G"},"wifiError":{"type":"string","description":"Last WiFi shadow delta failure reported by the device (e.g. join_failed); cleared once it reports a successful join","example":"join_failed"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join. Set at send time on both the shadow and legacy changeWifi paths; convergence is `ssid` matching any desired SSID.","example":"Corp-IoT"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them.","example":["Corp-IoT","Corp-IoT-2","Corp-IoT-3"],"type":"array","items":{"type":"string"}},"config":{"description":"Settings as last reported by the device into its `config` shadow, one block per feature","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"pendingConfig":{"description":"Settings written to the `config` shadow but not yet reported back. A key the device reports at a different value was rejected","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"configRequestedAt":{"format":"date-time","type":"string","description":"When settings were last written to the `config` shadow"},"configReportedAt":{"format":"date-time","type":"string","description":"When the device last reported into its `config` shadow"},"rssi":{"type":"number","description":"Latest WiFi signal strength reported by the device (dBm). Updated on each realtime MQTT report.","example":-65},"tenant":{"description":"Tagged Tenant","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"zone":{"description":"Tagged Zone","allOf":[{"$ref":"#/components/schemas/Zone"}]},"room":{"description":"Tagged Room","allOf":[{"$ref":"#/components/schemas/Room"}]},"system":{"description":"Tagged System","allOf":[{"$ref":"#/components/schemas/System"}]},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a physical unit. Such rows have no real MAC, never report telemetry, and are excluded from firmware, OTA, provisioning and panel-slot views.","example":false},"isRidgeChild":{"type":"boolean","description":"True when this row is a unit behind a RIDGE gateway rather than a directly-connected unit. Such rows have no real MAC, are never fleet-provisioned, and are excluded from firmware, OTA and liveness watchdogs — their gateway carries those.","example":false},"hasPhaseChildren":{"type":"boolean","description":"True when this clamp has been split into per-phase children. Such a clamp is org-detached and excluded from energy and organisational views — its children carry those — while remaining the sole target for firmware, OTA and diagnostics.","example":false},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2021-09-01"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device on/off status cannot be toggled.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"deviceSchedule":{"description":"Schedule configuration for the device. Contains recurring weekly schedules and one-time scheduled tasks.","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the single Schedule (in the schedules collection) this device currently follows. deviceSchedule is compiled from that schedule. When null, the device follows nothing.","example":"507f1f77bcf86cd799439011","nullable":true},"realTimePower":{"type":"number","description":"Real-time power consumption in watts","example":1500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes","example":6.5},"totalEnergyUsage":{"type":"number","description":"Total cumulative energy usage in kWh","example":0},"totalEnergySaved":{"type":"number","description":"Total energy saved through optimization in kWh","example":0},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update OTA attempts sent to this device","example":0},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether device is blacklisted from auto-updates (max retries exceeded)","example":false},"manualUpdateBlacklist":{"type":"boolean","description":"Whether device is manually blacklisted from auto-updates by admin","example":false},"lastAutoUpdateAttempt":{"format":"date-time","type":"string","description":"Timestamp of last auto-update OTA attempt"},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","default":false},"inactivityNotificationSentAt":{"format":"date-time","type":"string","description":"Timestamp when the last inactivity notification was sent for this device"},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"pendingThresholds":{"type":"object","description":"Threshold limits sent to the device but not yet confirmed by its firmware. Keys match DeviceThresholdsDto. Present only while a save is outstanding — a key that persists here is not being enforced by the hardware.","additionalProperties":{"type":"number"}},"thresholds":{"description":"This device's alert thresholds gathered into one object. Which fields are populated depends on deviceType — see DeviceThresholdsDto. Absent when no thresholds are configured. The same values remain available as flat top-level fields.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]}},"required":["_id","deviceType","macId","deviceName","deviceNumber","onStatus","location","remarks","batchNumber","chipModel","versionNumber","ssid","tenant","zone","room","system","createdAt","updatedAt","lastSeen","isLocked","totalEnergyUsage","totalEnergySaved","autoUpdateAttempts","autoUpdateBlacklisted","manualUpdateBlacklist","inactivityNotificationEnabled"]},"BulkToggleDeviceDto":{"type":"object","properties":{"deviceIds":{"description":"Array of device IDs to toggle","example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"onStatus":{"description":"Desired power state for all devices. Use ON to turn all devices on (all gangs for SmartSwitch), or OFF to turn all devices off (all gangs for SmartSwitch).","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}},"required":["deviceIds"]},"DeviceResetScope":{"type":"string","enum":["USAGE_HISTORY","EVENT_HISTORY","ERROR_LOGS","RELATIONSHIPS","IDENTITY","SCHEDULES","ENERGY_COUNTERS","THRESHOLDS","ECOGUARD","NOTIFICATION_STATE","LOCK","AUTO_UPDATE_STATE"],"description":"Slices of device state to wipe, applied identically to every listed device."},"BulkResetDevicesDto":{"type":"object","properties":{"deviceIds":{"description":"Devices to reset. Ids outside the system are reported back as notFound.","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}},"scopes":{"type":"array","description":"Slices of device state to wipe, applied identically to every listed device.","example":["USAGE_HISTORY","ERROR_LOGS"],"items":{"$ref":"#/components/schemas/DeviceResetScope"}}},"required":["deviceIds","scopes"]},"ResetDevicesResponseDto":{"type":"object","properties":{"requested":{"type":"number","description":"Number of device IDs requested"},"reset":{"type":"number","description":"Number of devices found in the system and reset"},"notFound":{"description":"Requested device IDs that were not found in the system","type":"array","items":{"type":"string"}},"scopes":{"type":"array","description":"Scopes that were applied","items":{"$ref":"#/components/schemas/DeviceResetScope"}}},"required":["requested","reset","notFound","scopes"]},"ResetDeviceDto":{"type":"object","properties":{"scopes":{"type":"array","description":"Slices of device state to wipe. Only the listed scopes are touched; everything else is left alone.","example":["USAGE_HISTORY","ERROR_LOGS"],"items":{"$ref":"#/components/schemas/DeviceResetScope"}}},"required":["scopes"]},"BulkDeleteDeviceDto":{"type":"object","properties":{"deviceIds":{"description":"Array of device IDs to delete","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}}},"required":["deviceIds"]},"BulkManualBlacklistDto":{"type":"object","properties":{"deviceIds":{"description":"Array of device IDs to blacklist/unblacklist","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}},"blacklist":{"type":"boolean","description":"Whether to blacklist (true) or remove blacklist (false)","example":true}},"required":["deviceIds","blacklist"]},"BulkUnblacklistDto":{"type":"object","properties":{"deviceIds":{"description":"Array of device IDs to clear from all auto-update blacklists (auto + manual)","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}}},"required":["deviceIds"]},"EnvironmentType":{"type":"string","enum":["STAGING","PRODUCTION"],"description":"Environment type to set for the devices. STAGING for staging environment, PRODUCTION for production environment."},"ChangeEnvironmentDto":{"type":"object","properties":{"deviceIds":{"description":"Array of device IDs to change environment for","example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"setEnvironment":{"description":"Environment type to set for the devices. STAGING for staging environment, PRODUCTION for production environment.","example":"STAGING","allOf":[{"$ref":"#/components/schemas/EnvironmentType"}]}},"required":["deviceIds","setEnvironment"]},"BulkUpdateEnergyThresholdsDto":{"type":"object","properties":{"voltageMax":{"type":"number","description":"Max Threshold voltage reading in volts","example":240},"currentMax":{"type":"number","description":"Max Threshold current reading in amps","example":10},"powerMax":{"type":"number","description":"Max Threshold power reading in watts","example":100},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold — minimum hours between repeat notifications of the same type","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device. MCB only.","example":true},"voltageAMin":{"type":"number","description":"Min threshold phase A voltage in volts (COIL3P only)","example":190},"voltageBMin":{"type":"number","description":"Min threshold phase B voltage in volts (COIL3P only)","example":190},"voltageCMin":{"type":"number","description":"Min threshold phase C voltage in volts (COIL3P only)","example":190},"voltageAMax":{"type":"number","description":"Max threshold phase A voltage in volts (COIL3P only)","example":240},"voltageBMax":{"type":"number","description":"Max threshold phase B voltage in volts (COIL3P only)","example":240},"voltageCMax":{"type":"number","description":"Max threshold phase C voltage in volts (COIL3P only)","example":240},"currentAMax":{"type":"number","description":"Max threshold phase A current in amps (COIL3P only)","example":16},"currentBMax":{"type":"number","description":"Max threshold phase B current in amps (COIL3P only)","example":16},"currentCMax":{"type":"number","description":"Max threshold phase C current in amps (COIL3P only)","example":16},"powerAMax":{"type":"number","description":"Max threshold phase A power in watts (COIL3P only)","example":3000},"powerBMax":{"type":"number","description":"Max threshold phase B power in watts (COIL3P only)","example":3000},"powerCMax":{"type":"number","description":"Max threshold phase C power in watts (COIL3P only)","example":3000},"deviceIds":{"description":"IDs of energy devices (Plugs or MCBs) to update","type":"array","items":{"type":"string"}}},"required":["deviceIds"]},"DeviceBulkEnvironmentThresholdsDto":{"type":"object","properties":{"temperatureMin":{"type":"number","description":"Min Threshold temperature reading in degrees Celsius","example":24},"temperatureMax":{"type":"number","description":"Max Threshold temperature reading in degrees Celsius","example":24},"carbonDioxide":{"type":"number","description":"Threshold CO2 reading in ppm","example":400},"lux":{"type":"number","description":"Threshold light level in lux (lx)","example":500},"formaldehyde":{"type":"number","description":"Threshold formaldehyde (HCHO) reading in µg/m³","example":0.03},"humidity":{"type":"number","description":"Threshold humidity percentage","example":24},"pm1":{"type":"number","description":"Threshold PM1 in µg/m³","example":1},"pm2_5":{"type":"number","description":"Threshold PM2.5 in µg/m³","example":1},"pm10":{"type":"number","description":"Threshold PM10 in µg/m³","example":1},"totalVolatileOrganicCompounds":{"type":"number","description":"Threshold Total Volatile Organic Compounds in ppb","example":1},"soundLevel":{"type":"number","description":"Threshold Sound Level in dB","example":1},"motionDetected":{"type":"boolean","description":"Motion detection status","example":true},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold for the Environment Sensor","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device. MCB only.","example":true},"deviceIds":{"description":"IDs of environment sensors to update","type":"array","items":{"type":"string"}}},"required":["deviceIds"]},"DeviceBulkWaterMeterThresholdsDto":{"type":"object","properties":{"flowRateMax":{"type":"number","description":"Max threshold flow rate (m³/h)","example":10},"flowRateMin":{"type":"number","description":"Min threshold flow rate (m³/h)","example":0},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold — minimum hours between repeat notifications of the same type","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device","example":true},"inletTemperatureMax":{"type":"number","description":"Max threshold inlet temperature (°C)","example":60},"inletTemperatureMin":{"type":"number","description":"Min threshold inlet temperature (°C)","example":0},"outletTemperatureMax":{"type":"number","description":"Max threshold outlet temperature (°C)","example":60},"outletTemperatureMin":{"type":"number","description":"Min threshold outlet temperature (°C)","example":0},"deviceIds":{"description":"IDs of water meters to update","type":"array","items":{"type":"string"}}},"required":["deviceIds"]},"UpdateEnergyDeviceThresholdDto":{"type":"object","properties":{"voltageMax":{"type":"number","description":"Max Threshold voltage reading in volts","example":240},"currentMax":{"type":"number","description":"Max Threshold current reading in amps","example":10},"powerMax":{"type":"number","description":"Max Threshold power reading in watts","example":100},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold — minimum hours between repeat notifications of the same type","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device. MCB only.","example":true},"voltageAMin":{"type":"number","description":"Min threshold phase A voltage in volts (COIL3P only)","example":190},"voltageBMin":{"type":"number","description":"Min threshold phase B voltage in volts (COIL3P only)","example":190},"voltageCMin":{"type":"number","description":"Min threshold phase C voltage in volts (COIL3P only)","example":190},"voltageAMax":{"type":"number","description":"Max threshold phase A voltage in volts (COIL3P only)","example":240},"voltageBMax":{"type":"number","description":"Max threshold phase B voltage in volts (COIL3P only)","example":240},"voltageCMax":{"type":"number","description":"Max threshold phase C voltage in volts (COIL3P only)","example":240},"currentAMax":{"type":"number","description":"Max threshold phase A current in amps (COIL3P only)","example":16},"currentBMax":{"type":"number","description":"Max threshold phase B current in amps (COIL3P only)","example":16},"currentCMax":{"type":"number","description":"Max threshold phase C current in amps (COIL3P only)","example":16},"powerAMax":{"type":"number","description":"Max threshold phase A power in watts (COIL3P only)","example":3000},"powerBMax":{"type":"number","description":"Max threshold phase B power in watts (COIL3P only)","example":3000},"powerCMax":{"type":"number","description":"Max threshold phase C power in watts (COIL3P only)","example":3000}}},"BulkUniqueEnergyThresholdItemDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Device ID (Plug or MCB)"},"thresholds":{"$ref":"#/components/schemas/UpdateEnergyDeviceThresholdDto"}},"required":["deviceId","thresholds"]},"BulkUniqueUpdateEnergyThresholdsDto":{"type":"object","properties":{"items":{"description":"Array of devices with their individual threshold values","type":"array","items":{"$ref":"#/components/schemas/BulkUniqueEnergyThresholdItemDto"}}},"required":["items"]},"UpdateEnvironmentSensorThresholdDto":{"type":"object","properties":{"temperatureMin":{"type":"number","description":"Min Threshold temperature reading in degrees Celsius","example":24},"temperatureMax":{"type":"number","description":"Max Threshold temperature reading in degrees Celsius","example":24},"carbonDioxide":{"type":"number","description":"Threshold CO2 reading in ppm","example":400},"lux":{"type":"number","description":"Threshold light level in lux (lx)","example":500},"formaldehyde":{"type":"number","description":"Threshold formaldehyde (HCHO) reading in µg/m³","example":0.03},"humidity":{"type":"number","description":"Threshold humidity percentage","example":24},"pm1":{"type":"number","description":"Threshold PM1 in µg/m³","example":1},"pm2_5":{"type":"number","description":"Threshold PM2.5 in µg/m³","example":1},"pm10":{"type":"number","description":"Threshold PM10 in µg/m³","example":1},"totalVolatileOrganicCompounds":{"type":"number","description":"Threshold Total Volatile Organic Compounds in ppb","example":1},"soundLevel":{"type":"number","description":"Threshold Sound Level in dB","example":1},"motionDetected":{"type":"boolean","description":"Motion detection status","example":true},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold for the Environment Sensor","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device. MCB only.","example":true}}},"BulkUniqueEnvSensorThresholdItemDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Environment sensor device ID"},"thresholds":{"description":"Threshold values for this device. Fields not supported by the sensor type are silently skipped.","allOf":[{"$ref":"#/components/schemas/UpdateEnvironmentSensorThresholdDto"}]}},"required":["deviceId","thresholds"]},"BulkUniqueUpdateEnvSensorThresholdsDto":{"type":"object","properties":{"items":{"description":"Array of devices with their individual threshold values","type":"array","items":{"$ref":"#/components/schemas/BulkUniqueEnvSensorThresholdItemDto"}}},"required":["items"]},"UpdateWaterMeterThresholdDto":{"type":"object","properties":{"flowRateMax":{"type":"number","description":"Max threshold flow rate (m³/h)","example":10},"flowRateMin":{"type":"number","description":"Min threshold flow rate (m³/h)","example":0},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold — minimum hours between repeat notifications of the same type","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device","example":true},"inletTemperatureMax":{"type":"number","description":"Max threshold inlet temperature (°C)","example":60},"inletTemperatureMin":{"type":"number","description":"Min threshold inlet temperature (°C)","example":0},"outletTemperatureMax":{"type":"number","description":"Max threshold outlet temperature (°C)","example":60},"outletTemperatureMin":{"type":"number","description":"Min threshold outlet temperature (°C)","example":0}}},"BulkUniqueWaterMeterThresholdItemDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Water meter device ID"},"thresholds":{"$ref":"#/components/schemas/UpdateWaterMeterThresholdDto"}},"required":["deviceId","thresholds"]},"BulkUniqueUpdateWaterMeterThresholdsDto":{"type":"object","properties":{"items":{"description":"Array of devices with their individual threshold values","type":"array","items":{"$ref":"#/components/schemas/BulkUniqueWaterMeterThresholdItemDto"}}},"required":["items"]},"EnergyThresholdBaselineDto":{"type":"object","properties":{"voltageMin":{"type":"number","description":"Minimum voltage reading (V)"},"voltageMax":{"type":"number","description":"Maximum voltage reading (V)"},"currentMin":{"type":"number","description":"Minimum current reading (A)"},"currentMax":{"type":"number","description":"Maximum current reading (A)"},"powerMin":{"type":"number","description":"Minimum power reading (W)"},"powerMax":{"type":"number","description":"Maximum power reading (W)"},"currentAMin":{"type":"number","description":"Minimum phase-A current reading (A) — MCB only"},"currentAMax":{"type":"number","description":"Maximum phase-A current reading (A) — MCB only"},"currentBMin":{"type":"number","description":"Minimum phase-B current reading (A) — MCB only"},"currentBMax":{"type":"number","description":"Maximum phase-B current reading (A) — MCB only"},"currentCMin":{"type":"number","description":"Minimum phase-C current reading (A) — MCB only"},"currentCMax":{"type":"number","description":"Maximum phase-C current reading (A) — MCB only"}},"required":["voltageMin","voltageMax","currentMin","currentMax","powerMin","powerMax"]},"EnvironmentSensorThresholdBaselineDto":{"type":"object","properties":{"temperatureMin":{"type":"number","description":"Minimum temperature reading (°C)"},"temperatureMax":{"type":"number","description":"Maximum temperature reading (°C)"},"carbonDioxideMin":{"type":"number","description":"Minimum CO2 reading (ppm)"},"carbonDioxideMax":{"type":"number","description":"Maximum CO2 reading (ppm)"},"luxMin":{"type":"number","description":"Minimum light level reading (lx)"},"luxMax":{"type":"number","description":"Maximum light level reading (lx)"},"formaldehydeMin":{"type":"number","description":"Minimum formaldehyde reading (µg/m³)"},"formaldehydeMax":{"type":"number","description":"Maximum formaldehyde reading (µg/m³)"},"humidityMin":{"type":"number","description":"Minimum humidity (%)"},"humidityMax":{"type":"number","description":"Maximum humidity (%)"},"pm1Min":{"type":"number","description":"Minimum PM1 reading (µg/m³)"},"pm1Max":{"type":"number","description":"Maximum PM1 reading (µg/m³)"},"pm2_5Min":{"type":"number","description":"Minimum PM2.5 reading (µg/m³)"},"pm2_5Max":{"type":"number","description":"Maximum PM2.5 reading (µg/m³)"},"pm10Min":{"type":"number","description":"Minimum PM10 reading (µg/m³)"},"pm10Max":{"type":"number","description":"Maximum PM10 reading (µg/m³)"},"totalVolatileOrganicCompoundsMin":{"type":"number","description":"Minimum TVOC reading (ppb)"},"totalVolatileOrganicCompoundsMax":{"type":"number","description":"Maximum TVOC reading (ppb)"},"soundLevelMin":{"type":"number","description":"Minimum sound level (dB)"},"soundLevelMax":{"type":"number","description":"Maximum sound level (dB)"}}},"WaterMeterThresholdBaselineDto":{"type":"object","properties":{"flowRateMin":{"type":"number","description":"Minimum flow rate (m³/h)"},"flowRateMax":{"type":"number","description":"Maximum flow rate (m³/h)"}}},"ThresholdBaselineResponseDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Device ID"},"deviceType":{"type":"string","description":"Device type"},"periodStart":{"format":"date-time","type":"string","description":"Start date of the baseline period"},"periodEnd":{"format":"date-time","type":"string","description":"End date of the baseline period"},"dataPointCount":{"type":"number","description":"Number of data points used to calculate the baseline"},"message":{"type":"string","description":"Warning message when insufficient data (< 14 days) was available for the baseline calculation","example":"Only 3 days of data available. Baseline calculated from limited data."},"energy":{"description":"Energy device baseline (Plug/MCB)","allOf":[{"$ref":"#/components/schemas/EnergyThresholdBaselineDto"}]},"environmentSensor":{"description":"Environment sensor baseline","allOf":[{"$ref":"#/components/schemas/EnvironmentSensorThresholdBaselineDto"}]},"waterMeter":{"description":"Water meter baseline","allOf":[{"$ref":"#/components/schemas/WaterMeterThresholdBaselineDto"}]}},"required":["deviceId","deviceType","periodStart","periodEnd","dataPointCount"]},"BulkThresholdBaselineRequestDto":{"type":"object","properties":{"deviceIds":{"description":"IDs of devices to calculate threshold baselines for","example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}}},"required":["deviceIds"]},"FailedThresholdBaselineDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Device ID that failed to generate a baseline","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"reason":{"type":"string","description":"Reason for failure","example":"Device not found in system"}},"required":["deviceId","reason"]},"BulkThresholdBaselineResponseDto":{"type":"object","properties":{"successCount":{"type":"number","description":"Number of devices for which baselines were successfully generated","example":5},"failedCount":{"type":"number","description":"Number of devices for which baseline generation failed","example":1},"successful":{"description":"Successfully generated baselines","type":"array","items":{"$ref":"#/components/schemas/ThresholdBaselineResponseDto"}},"failed":{"description":"Devices that failed baseline generation with reasons","type":"array","items":{"$ref":"#/components/schemas/FailedThresholdBaselineDto"}}},"required":["successCount","failedCount","successful","failed"]},"UpdateInactivityNotificationDto":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enable or disable inactivity notifications for this device","example":true}},"required":["enabled"]},"ClaimTokenResponseDto":{"type":"object","properties":{"claimToken":{"type":"string","description":"Encrypted claim token for device","example":"a1b2c3d4e5f6..."},"claimUrl":{"type":"string","description":"Full URL for claiming the device","example":"https://base.ecovolt.ai/claim/a1b2c3d4e5f6..."}},"required":["claimToken","claimUrl"]},"PrintQrRequestDto":{"type":"object","properties":{"macId":{"type":"string","description":"MAC address of the device. Provide either macId or deviceNumber.","example":"AA:BB:CC:DD:EE:FF"},"deviceNumber":{"type":"number","description":"Whitelist device number of the device. Provide either macId or deviceNumber.","example":42},"deviceType":{"description":"Type of device (unused for print lookup)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"batchNumber":{"type":"number","description":"Manufacturing batch number (unused for print lookup)","example":1},"chipModel":{"description":"Hardware chip model (unused for print lookup)","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number (unused for print lookup)","example":"1.0.0"},"adminCreated":{"type":"string","description":"Name of the requester (unused for print lookup)","example":"Manufacturer"}}},"PrintQrResponseDto":{"type":"object","properties":{"deviceNumber":{"type":"number","description":"Auto-assigned device number for this device","example":42},"macId":{"type":"string","description":"MAC address of the device","example":"AA:BB:CC:DD:EE:FF"},"claimUrl":{"type":"string","description":"Full URL for claiming the device — encode this as QR","example":"https://base.ecovolt.ai/claim/a1b2c3d4e5f6..."}},"required":["deviceNumber","macId","claimUrl"]},"ClaimDeviceResponseDto":{"type":"object","properties":{"message":{"type":"string","description":"Success message","example":"Device claimed successfully"},"deviceId":{"type":"string","description":"Device ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceName":{"type":"string","description":"Device name","example":"Smart Plug 1"},"deviceNumber":{"type":"number","description":"Device number","example":42},"deviceType":{"description":"Device type","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"systemId":{"type":"string","description":"System ID the device belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"tenantId":{"type":"string","description":"Tenant ID the device was assigned to (if any)","example":"64b7f1e6f9c9d4e5b8a9f7c2"},"tenantName":{"type":"string","description":"Tenant name the device was assigned to (if any)","example":"Acme Corp"},"zoneId":{"type":"string","description":"Zone ID the device was assigned to","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"zoneName":{"type":"string","description":"Zone name the device was assigned to","example":"Unit 01-01"}},"required":["message","deviceId","deviceName","deviceNumber","deviceType","systemId","zoneId","zoneName"]},"CreateNotificationDto":{"type":"object","properties":{"title":{"type":"string","description":"Title of the notification","example":"New Notification"},"message":{"type":"string","description":"Message of the notification","example":"Power outage in your area"},"system":{"type":"string","description":"Notification for a specific System"},"device":{"type":"string","description":"Notification for a specific Device (optional - if not provided, finds system-level admin)"},"type":{"type":"string","enum":["DEVICE_INACTIVITY_ALERT","POWER_SURGE","TEMP_SURGE","TEMP_DROP","LEAK_DETECTION","USAGE_SURGE","DEVICE_HEALTH_ANOMALY","CO2_SURGE","LUX_SURGE","FORMALDEHYDE_SURGE","HUMIDITY_SURGE","PM1_SURGE","PM25_SURGE","PM10_SURGE","TVOC_SURGE","SOUND_SURGE","MOTION_DETECTED","FLOW_RATE_SURGE","VOLTAGE_SURGE","CURRENT_SURGE","AIRCON_TEMP_SURGE","AIRCON_TEMP_DROP","AIRCON_HUMIDITY_SURGE","AIRCON_HUMIDITY_DROP","EXTERNAL_DEVICE_MALFUNCTION"],"description":"Type of the notification","example":"DEVICE_INACTIVITY_ALERT"},"recieveType":{"type":"string","description":"Receive type of the notification","example":"EMAIL","enum":["SYSTEM","EMAIL","SMS","PUSH"]}},"required":["title","message","system","type","recieveType"]},"Notification":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Notification","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"admin":{"type":"string","description":"The account this copy of the notification belongs to. One document is written per recipient, so this is the audience, not an ownership hint."},"title":{"type":"string","description":"Title of the Notification","example":"System Maintenance"},"message":{"type":"string","description":"Message of the Notification","example":"The system will be down for maintenance from 2 AM to 3 AM."},"system":{"description":"Notification for a specific System","allOf":[{"$ref":"#/components/schemas/System"}]},"device":{"type":"string","description":"Notification for a specific Device"},"type":{"type":"string","description":"Type of the Notification (ENUM)","example":"DEVICE_INACTIVITY_ALERT","enum":["DEVICE_INACTIVITY_ALERT","POWER_SURGE","TEMP_SURGE","TEMP_DROP","LEAK_DETECTION","USAGE_SURGE","DEVICE_HEALTH_ANOMALY","CO2_SURGE","LUX_SURGE","FORMALDEHYDE_SURGE","HUMIDITY_SURGE","PM1_SURGE","PM25_SURGE","PM10_SURGE","TVOC_SURGE","SOUND_SURGE","MOTION_DETECTED","FLOW_RATE_SURGE","VOLTAGE_SURGE","CURRENT_SURGE","AIRCON_TEMP_SURGE","AIRCON_TEMP_DROP","AIRCON_HUMIDITY_SURGE","AIRCON_HUMIDITY_DROP","EXTERNAL_DEVICE_MALFUNCTION"]},"recieveType":{"type":"string","description":"Type of the Notification (ENUM)","example":"EMAIL","enum":["SYSTEM","EMAIL","SMS","PUSH"]},"isRead":{"type":"boolean","description":"Whether the recipient has read this notification. Reading resolves it — there is no separate resolve step.","example":true},"eventKey":{"type":"string","description":"Identity of the event this notification reports, identical across every recipient copy. Suppression asks whether the event fired, not whether some document exists.","example":"64b7f1e6f9c9d4e5b8a9f7c3:POWER_SURGE"},"occurrenceId":{"type":"string","description":"Identity of the fan-out batch this copy belongs to, identical across every recipient copy of the same occurrence. Unlike `eventKey` (stable across re-fires), this distinguishes one occurrence from the next so read-side dedup collapses recipient copies without collapsing history.","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"createdAt":{"format":"date-time","type":"string","description":"Creation date of the Notification","example":"2023-08-01T12:00:00Z"},"updatedAt":{"format":"date-time","type":"string","description":"Last update date of the Notification","example":"2023-08-01T12:00:00Z"}},"required":["_id","admin","title","message","system","device","type","recieveType","isRead","eventKey","occurrenceId","createdAt","updatedAt"]},"NotificationGroup":{"type":"string","enum":["PLUG_MCB_SURGE","ENVIRONMENT_SENSOR","WATER_METER","INACTIVE_DEVICES","SENTINEL_REPORT","DEVICE_HEALTH","AIRCON_CONTROL","RIDGE"]},"SearchQueryResultDto":{"type":"object","properties":{"results":{"description":"Search results","type":"array","items":{"type":"array"}},"total":{"type":"number","description":"Total number of results","example":100}},"required":["results","total"]},"NotificationGroupsResponseDto":{"type":"object","properties":{"groups":{"type":"array","description":"Groups that at least one of the caller visible notifications belongs to. Derived from the notification types actually stored, so a group with nothing in it never appears and the filter cannot offer a dead option.","items":{"$ref":"#/components/schemas/NotificationGroup"}}},"required":["groups"]},"ReadAllNotificationsResponseDto":{"type":"object","properties":{"readCount":{"type":"number","description":"Number of notifications marked as read","example":12}},"required":["readCount"]},"BasicEmailNotificationDto":{"type":"object","properties":{"receiverEmail":{"type":"string","description":"Email address of the receiver","example":"user@example.com"},"subject":{"type":"string","description":"Subject of the email notification","example":"Password Reset Request"},"content":{"type":"string","description":"Content of the email notification","example":"Please click the link to reset your password."}},"required":["receiverEmail","subject","content"]},"BusinessOrderDevice":{"type":"string","enum":["office-printer","office-meeting-pod","office-water-dispenser","office-coffee","office-air-purifier","office-desk","office-tv","office-av"],"description":"Device type identifier"},"BusinessSelectedDeviceDto":{"type":"object","properties":{"deviceType":{"description":"Device type identifier","example":"office-printer","allOf":[{"$ref":"#/components/schemas/BusinessOrderDevice"}]},"qty":{"type":"number","description":"Number of units of this device","example":2,"minimum":1}},"required":["deviceType","qty"]},"PublicWebEmailNotificationDto":{"type":"object","properties":{"firstName":{"type":"string","description":"first name of the sender","example":"John"},"lastName":{"type":"string","description":"last name of the sender","example":"Doe"},"phoneNumber":{"type":"string","description":"phone number of the sender","example":"+1234567890"},"company":{"type":"string","description":"company name of the sender","example":"Acme Corp"},"email":{"type":"string","description":"Email address of the sender","example":"user@example.com"},"message":{"type":"string","description":"Content of the email notification","example":"We are interested in energy solutions for our office."},"selectedDevices":{"description":"Business devices selected with quantities","type":"array","items":{"$ref":"#/components/schemas/BusinessSelectedDeviceDto"}},"selectedCategories":{"description":"Business categories selected","type":"array","items":{"type":"string"}}},"required":["firstName","lastName","phoneNumber","company","email","message"]},"NewsletterSubscriberSource":{"type":"string","enum":["landing-page","other"],"description":"Subscriber source"},"NewsletterSubscriberDto":{"type":"object","properties":{"email":{"type":"string","description":"Subscriber email address","example":"user@example.com"},"name":{"type":"string","description":"Subscriber name","example":"John Doe"},"subscribedFromSource":{"description":"Subscriber source","example":"landing-page","allOf":[{"$ref":"#/components/schemas/NewsletterSubscriberSource"}]}},"required":["email"]},"NewsletterSubscriberStatus":{"type":"string","enum":["SUBSCRIBED","UNSUBSCRIBED"],"description":"Status of the newsletter subscriber"},"NewsletterSubscriberResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the newsletter subscriber","example":"64a7b2f5c9e77b6f4d8e4a2b"},"email":{"type":"string","description":"Email of the newsletter subscriber","example":"ecovolt@example.com"},"status":{"description":"Status of the newsletter subscriber","example":"SUBSCRIBED","allOf":[{"$ref":"#/components/schemas/NewsletterSubscriberStatus"}]},"name":{"type":"string","description":"Name of the newsletter subscriber","example":"John Doe"},"subscribedFromIp":{"type":"string","description":"IP address from which the user subscribed","example":"192.168.1.1"},"subscribedFromSource":{"description":"Source from which the user subscribed","example":"landing-page","allOf":[{"$ref":"#/components/schemas/NewsletterSubscriberSource"}]},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the subscriber was created","example":"2024-07-01T10:00:00Z"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the subscriber was last updated","example":"2024-07-01T10:00:00Z"}},"required":["_id","email","status"]},"EmailRecipientsType":{"type":"string","enum":["ALL_SUBSCRIBERS","ADMINS","MANUAL_SELECTION"],"description":"Type of recipient list for the email campaign"},"ManualSelectionRecipientType":{"type":"string","enum":["Admin","NewsletterSubscriber"],"description":"Type of recipient"},"ManualSelectionRecipientDto":{"type":"object","properties":{"recipientType":{"description":"Type of recipient","example":"NewsletterSubscriber","allOf":[{"$ref":"#/components/schemas/ManualSelectionRecipientType"}]},"recipientId":{"type":"string","description":"ID of the recipient","example":"64b8c3d2e1f4a2b3c4d5e6f7"}},"required":["recipientType","recipientId"]},"CreateEmailCampaignDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the email campaign","example":"July 2024 Newsletter Campaign"},"templateId":{"type":"string","description":"ID of the newsletter template to use for the email campaign","example":"64a7b2f5c9e77b6f4d8e4a2b"},"subject":{"type":"string","description":"Subject of the campaign email","example":"Exciting Updates in Our July Newsletter!"},"recipientType":{"description":"Type of recipient list for the email campaign","example":"ALL_SUBSCRIBERS","allOf":[{"$ref":"#/components/schemas/EmailRecipientsType"}]},"manualSelectionRecipients":{"description":"List of manually selected recipients with their types (required if recipientType is MANUAL_SELECTION)","type":"array","items":{"$ref":"#/components/schemas/ManualSelectionRecipientDto"}},"scheduledAt":{"type":"string","description":"Scheduled send time for the email campaign in ISO 8601 format","example":"2024-07-01T10:00:00Z"}},"required":["name","templateId","recipientType"]},"EmailCampaignStatus":{"type":"string","enum":["DRAFT","SCHEDULED","SENDING","SENT","FAILED","CANCELLED"],"description":"Current status of the email campaign"},"EmailCampaign":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the email campaign","example":"64a7b2f5c9e77b6f4d8e4a2b"},"name":{"type":"string","description":"Name of the email campaign","example":"July Newsletter Campaign"},"sourceTemplate":{"type":"string","description":"Reference to the original template used for this email campaign","example":"64a7b2f5c9e77b6f4d8e4a2b"},"subject":{"type":"string","description":"Subject of the campaign email","example":"Exciting Updates in Our July Newsletter!"},"designJson":{"type":"object","description":"Copied JSON from template for campaign-specific email customization","example":{"body":{"rows":[],"values":{"backgroundColor":"#ffffff","contentWidth":"600px"}}}},"renderedHtml":{"type":"string","description":"Rendered HTML content for this email campaign","example":"<html><body><h1>Welcome to our Newsletter</h1></body></html>"},"plainText":{"type":"string","description":"Plain text version of the email campaign","example":"Welcome to our Newsletter"},"previewText":{"type":"string","description":"Short text that appears in email preview before opening","example":"This is the preview text for the newsletter email."},"recipientType":{"description":"Type of recipients for the email campaign","example":"ALL_SUBSCRIBERS","allOf":[{"$ref":"#/components/schemas/EmailRecipientsType"}]},"manualSelection":{"description":"List of manually selected recipients with their types (if recipientType is MANUAL_SELECTION)","type":"array","items":{"$ref":"#/components/schemas/ManualSelectionRecipientDto"}},"status":{"description":"Current status of the email campaign","example":"DRAFT","allOf":[{"$ref":"#/components/schemas/EmailCampaignStatus"}]},"scheduledAt":{"format":"date-time","type":"string","description":"Scheduled send time for the email campaign","example":"2024-08-01T10:00:00Z"},"scheduleName":{"type":"string","description":"AWS EventBridge Scheduler name for the scheduled campaign send","example":"oneoff_SEND_EMAIL_CAMP_64a7b2f5c9e77b6f4d8e4a2b_1234567890_deve"},"sentAt":{"format":"date-time","type":"string","description":"Time when the email campaign was sent","example":"2024-08-01T10:00:00Z"},"totalRecipients":{"type":"number","description":"Total number of recipients for the email campaign","example":1500},"errorMessage":{"type":"string","description":"Error message if campaign sending failed","example":"Campaign failed due to SMTP server error."},"createdBy":{"type":"string","description":"Admin who created the campaign","example":"64a7b2f5c9e77b6f4d8e4a2b"},"updatedBy":{"type":"string","description":"Admin who last updated the campaign","example":"64a7b2f5c9e77b6f4d8e4a2b"}},"required":["_id","name","sourceTemplate","subject","designJson","renderedHtml","plainText","previewText","recipientType","manualSelection","status","scheduledAt","scheduleName","sentAt","totalRecipients","errorMessage","createdBy","updatedBy"]},"SendEmailCampaignDto":{"type":"object","properties":{"campaignId":{"type":"string","description":"ID of the email campaign to be sent","example":"60f7c0b5b4d1c826d8f0e6b7"},"status":{"description":"Current status of the email campaign","example":"SCHEDULED","allOf":[{"$ref":"#/components/schemas/EmailCampaignStatus"}]},"message":{"type":"string","description":"Result message after attempting to send the email campaign","example":"Campaign scheduled successfully"},"totalRecipients":{"type":"number","description":"Total number of recipients for the email campaign","example":1500}},"required":["campaignId","status","message","totalRecipients"]},"SendTestEmailRequestDto":{"type":"object","properties":{"testEmail":{"type":"string","description":"Email address to send the test email to","example":"test@example.com"},"testName":{"type":"string","description":"Name of the test recipient for personalization","example":"John Doe"}},"required":["testEmail"]},"GetEmailCampaignsDto":{"type":"object","properties":{"campaigns":{"description":"List of email campaigns","type":"array","items":{"$ref":"#/components/schemas/EmailCampaign"}},"total":{"type":"number","description":"Total number of email campaigns","example":10}},"required":["campaigns","total"]},"UpdateEmailCampaignDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the email campaign","example":"July 2024 Newsletter Campaign - Updated"},"subject":{"type":"string","description":"Subject of the campaign email","example":"Exciting Updates in Our July Newsletter!"},"recipientType":{"description":"Type of recipient list for the email campaign","example":"ALL_SUBSCRIBERS","allOf":[{"$ref":"#/components/schemas/EmailRecipientsType"}]},"manualSelectionRecipients":{"description":"List of manually selected recipients with their types (used when recipientType is MANUAL_SELECTION)","type":"array","items":{"$ref":"#/components/schemas/ManualSelectionRecipientDto"}},"designJson":{"type":"object","description":"Unlayer design JSON for campaign-specific email customization","example":{"body":{"rows":[],"values":{"backgroundColor":"#ffffff","contentWidth":"600px"}}}},"renderedHtml":{"type":"string","description":"Rendered HTML content of the email campaign","example":"<html><body><h1>Welcome</h1></body></html>"},"plainText":{"type":"string","description":"Plain text version of the email campaign","example":"Welcome to our newsletter"},"previewText":{"type":"string","description":"Short text that appears in email preview before opening","example":"Check out the latest news and updates!"},"scheduledAt":{"type":"string","description":"Scheduled send time for the email campaign in ISO 8601 format. Set to null to convert SCHEDULED to DRAFT.","example":"2024-07-01T10:00:00Z","nullable":true}}},"EmailTemplateDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the email template","example":"Monthly Newsletter Template"},"description":{"type":"string","description":"Description of the email template","example":"A template for our monthly newsletters"},"subject":{"type":"string","description":"Subject of the email template","example":"Your Ecovolt Monthly Update!"},"designJson":{"type":"object","description":"JSON structure of the email template","example":{"body":{"rows":[],"values":{"backgroundColor":"#ffffff","contentWidth":"600px"}}}},"previewText":{"type":"string","description":"Preview text for the email template","example":"Check out the latest news and updates!"},"renderedHtml":{"type":"string","description":"Rendered HTML content of the email template","example":"<html><body><h1>Welcome</h1></body></html>"},"plainText":{"type":"string","description":"Plain text version of the email template","example":"Welcome to our newsletter"}},"required":["name","subject","designJson"]},"EmailTemplateStatus":{"type":"string","enum":["DRAFT","PUBLISHED","ARCHIVED"],"description":"Indicates status of the email template"},"EmailTemplate":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the email template","example":"64a7b2f5c9e77b6f4d8e4a2b"},"name":{"type":"string","description":"Name of the email template","example":"Weekly Update"},"description":{"type":"string","description":"Description of the email template","example":"Template for the weekly Email updates"},"subject":{"type":"string","description":"Subject of the email template","example":"Your Weekly News is Here!"},"designJson":{"type":"object","description":"JSON from Unlayer editor","example":{"body":{"rows":[],"values":{"backgroundColor":"#ffffff","contentWidth":"600px"}}}},"renderedHtml":{"type":"string","description":"Compiled HTML content of the email template","example":"<html><body><h1>Welcome to our Email</h1></body></html>"},"plainText":{"type":"string","description":"Plain text version of the email template","example":"Welcome to our Email"},"previewText":{"type":"string","description":"Short text that appears in email preview before opening","example":"This is the preview text for the Email email."},"status":{"description":"Indicates status of the email template","example":"DRAFT","allOf":[{"$ref":"#/components/schemas/EmailTemplateStatus"}]},"thumbnailUrl":{"type":"string","description":"Thumbnail image of the template for superadmin dashboard","example":"https://example.com/thumbnail-image.png"},"createdBy":{"type":"string","description":"Admin who created the template","example":"64a7b2f5c9e77b6f4d8e4a2b"},"updatedBy":{"type":"string","description":"Admin who last updated the template","example":"64a7b2f5c9e77b6f4d8e4a2b"}},"required":["_id","name","description","subject","designJson","renderedHtml","plainText","previewText","status","thumbnailUrl","createdBy","updatedBy"]},"GetEmailTemplatesDto":{"type":"object","properties":{"templates":{"description":"List of email templates","type":"array","items":{"$ref":"#/components/schemas/EmailTemplate"}},"total":{"type":"number","description":"Total number of email templates","example":25}},"required":["templates","total"]},"PreviewEmailTemplateDto":{"type":"object","properties":{"renderedHtml":{"type":"string","description":"Rendered HTML preview of the email template","example":"<html><body><h1>Welcome to our Newsletter</h1></body></html>"},"plainText":{"type":"string","description":"Plain text version of the email template preview","example":"Welcome to our Newsletter"}},"required":["renderedHtml"]},"UpdateEmailTemplateDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the email template","example":"Monthly Newsletter Template"},"description":{"type":"string","description":"Description of the email template","example":"A template for our monthly newsletters"},"subject":{"type":"string","description":"Subject of the email template","example":"Your Ecovolt Monthly Update!"},"designJson":{"type":"object","description":"JSON structure of the email template","example":{"body":{"rows":[],"values":{"backgroundColor":"#ffffff","contentWidth":"600px"}}}},"previewText":{"type":"string","description":"Preview text for the email template","example":"Check out the latest news and updates!"},"renderedHtml":{"type":"string","description":"Rendered HTML content of the email template","example":"<html><body><h1>Welcome</h1></body></html>"},"plainText":{"type":"string","description":"Plain text version of the email template","example":"Welcome to our newsletter"},"status":{"description":"Status of the email template","example":"DRAFT","allOf":[{"$ref":"#/components/schemas/EmailTemplateStatus"}]}}},"CreateFirmwareDto":{"type":"object","properties":{"versionNumber":{"type":"string","description":"Semantic version number for the firmware release. Must follow the format major.minor.patch (e.g., 1.0.0).","example":"2.1.5"},"chipModel":{"description":"Target chip model for this firmware build. Determines which plug devices can receive this firmware.","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"deviceType":{"description":"Type of device this firmware is intended for (e.g., PLUG, MCB, ENV_SENSOR).","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"mcbType":{"description":"MCB subtype the firmware targets. Required when deviceType === MCB; ignored otherwise.","example":"COIL","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"remarks":{"type":"string","description":"Optional release notes or comments about this firmware version. Used to document changes and fixes.","example":"Bug fixes for power monitoring accuracy and stability improvements"},"file":{"type":"string","description":"Firmware binary file to upload. Must be a valid firmware binary for the specified chip model. Maximum file size: 10MB.","format":"binary"}},"required":["versionNumber","chipModel","deviceType","remarks","file"]},"ReuploadFirmwareDto":{"type":"object","properties":{"remarks":{"type":"string","description":"Optional updated release notes or comments about this firmware version. Used to document changes and fixes.","example":"Re-uploaded with critical security patch for authentication vulnerability"},"file":{"type":"string","description":"Updated firmware binary file to upload. Must be a valid firmware binary for the existing chip model. Maximum file size: 10MB.","format":"binary"}},"required":["file"]},"GetFirmwareResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Firmware","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"adminCreated":{"type":"string","description":"Name of the admin who created the firmware entry","example":"Glenn"},"adminLastModified":{"type":"string","description":"Name of the admin who last modified/re-uploaded the firmware","example":"Glenn"},"versionNumber":{"type":"string","description":"Firmware version number","example":"1.0.0"},"chipModel":{"description":"Chip Model of the Device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"deviceType":{"description":"Type of device this firmware is intended for","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"mcbType":{"description":"MCB subtype this firmware targets. Set only when deviceType === MCB.","example":"COIL","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"remarks":{"type":"string","description":"Remarks or notes about the firmware","example":"Initial release with basic features"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the firmware was created","example":"2024-01-15T10:30:00Z"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the firmware was last updated","example":"2024-01-15T10:30:00Z"}},"required":["_id","adminCreated","versionNumber","chipModel","deviceType","createdAt","updatedAt"]},"DeleteFirmwareByPrefixResponseDto":{"type":"object","properties":{"deleted":{"type":"number","description":"Number of firmware records removed","example":12}},"required":["deleted"]},"SendOtaDto":{"type":"object","properties":{"deviceIds":{"description":"Array of unique plug identifiers to receive the OTA firmware update. Each plug will be notified via MQTT.","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"versionNumber":{"type":"string","description":"Firmware version number to push to the specified devices. Must exist in the firmware repository.","example":"2.1.5"},"chipModel":{"description":"Target chip model for firmware compatibility validation. Ensures plugs receive compatible firmware.","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"deviceType":{"description":"Type of device this firmware is intended for (e.g., PLUG, MCB, ENV_SENSOR).","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"mcbType":{"description":"MCB subtype to match against the firmware record. Required when deviceType === MCB.","example":"COIL","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"targetVersion":{"type":"string","description":"Optional filter to verify plugs are running this specific firmware version before updating. Used for validation.","example":"2.0.8"},"streaming":{"type":"boolean","description":"Deliver the firmware as an AWS IoT Job with MQTT stream file delivery instead of pushing a download URL. Streaming needs no outbound HTTPS from the device and yields a trackable job id, but the device firmware must implement the IoT Jobs + streams client.","example":false,"default":false}},"required":["deviceIds","versionNumber","chipModel","deviceType"]},"OtaDispatchMode":{"type":"string","enum":["STREAM","MQTT_URL"],"description":"How the firmware was delivered"},"OtaDispatchResponseDto":{"type":"object","properties":{"mode":{"description":"How the firmware was delivered","example":"STREAM","allOf":[{"$ref":"#/components/schemas/OtaDispatchMode"}]},"jobId":{"type":"string","description":"AWS IoT Job id. Streaming mode only — poll it for progress.","example":"ota-plug-all-2-1-5-1786958377275"},"streamId":{"type":"string","description":"AWS IoT stream id carrying the firmware. Streaming mode only.","example":"ota-plug-all-2-1-5-1786958377275-stream"},"targetCount":{"type":"number","description":"Number of devices targeted. Streaming mode only.","example":12}},"required":["mode"]},"MassSendOtaRollbackDto":{"type":"object","properties":{"chipModel":{"description":"Chip Model of the Plug","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"deviceType":{"description":"Type of device this firmware is intended for (e.g., PLUG, MCB, ENV_SENSOR).","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]}},"required":["chipModel","deviceType"]},"SendOtaRollbackDto":{"type":"object","properties":{"deviceIds":{"description":"List of unique Plug IDs","example":"1092384701928347","type":"array","items":{"type":"string"}},"chipModel":{"description":"Chip Model of the Plug","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"deviceType":{"description":"Type of device this firmware is intended for (e.g., PLUG, MCB, ENV_SENSOR).","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]}},"required":["deviceIds","chipModel","deviceType"]},"GetFirmwareListDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of firmware objects containing version details, chip models, and metadata. Sorted by creation date. File information is excluded for security.","type":"array","items":{"$ref":"#/components/schemas/GetFirmwareResponseDto"}}},"required":["total","page","size","totalPages","data"]},"GenerateDownloadUrlResponseDto":{"type":"object","properties":{"url":{"type":"string","description":"Clean OTA download URL without embedded token","example":"https://api.ecovolt.ai/firmware/download-ota-update?versionNumber=1.0.4&chipModel=ESP32C2&deviceType=PLUG"},"jwtToken":{"type":"string","description":"JWT token for Authorization header (devices should send as Bearer token)","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}},"required":["url","jwtToken"]},"VersionDistributionDto":{"type":"object","properties":{"versionNumber":{"type":"string","description":"Firmware version number","example":"1.0.0"},"deviceCount":{"type":"number","description":"Number of devices with this firmware version","example":25}},"required":["versionNumber","deviceCount"]},"ChipModelDistributionDto":{"type":"object","properties":{"chipModel":{"description":"Chip model","example":"ESP32","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"totalDevices":{"type":"number","description":"Total number of devices for this chip model","example":50},"versions":{"description":"Distribution of firmware versions for this chip model","type":"array","items":{"$ref":"#/components/schemas/VersionDistributionDto"}}},"required":["chipModel","totalDevices","versions"]},"DeviceTypeFirmwareDistributionDto":{"type":"object","properties":{"deviceType":{"description":"Device type","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"totalDevices":{"type":"number","description":"Total number of devices for this device type","example":100},"chipModels":{"description":"Distribution of firmware versions grouped by chip model","type":"array","items":{"$ref":"#/components/schemas/ChipModelDistributionDto"}}},"required":["deviceType","totalDevices","chipModels"]},"GetFirmwareVersionDistributionDto":{"type":"object","properties":{"data":{"description":"Firmware version distribution grouped by device type","type":"array","items":{"$ref":"#/components/schemas/DeviceTypeFirmwareDistributionDto"}},"total":{"type":"number","description":"Total number of device types","example":6}},"required":["data","total"]},"DeviceFirmwareInfoDto":{"type":"object","properties":{"_id":{"type":"string","description":"Device ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"Device MAC ID","example":"1092384701928347"},"deviceName":{"type":"string","description":"Device name","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Device number","example":1},"deviceType":{"description":"Device type","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"chipModel":{"description":"Chip model","example":"ESP32","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number","example":"1.0.0"},"onStatus":{"description":"Device status","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"systemName":{"type":"string","description":"System name","example":"Main Building System"},"systemId":{"type":"string","description":"System ID","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2024-01-15T10:30:00.000Z"},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update attempts for this device","example":2,"default":0},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether the device is automatically blacklisted from auto-updates (max retries exceeded)","example":false,"default":false},"manualUpdateBlacklist":{"type":"boolean","description":"Whether the device is manually blacklisted from auto-updates by admin","example":false,"default":false}},"required":["_id","macId","deviceType","onStatus","systemName","systemId"]},"GetDeviceFirmwareListDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"List of devices with firmware information","type":"array","items":{"$ref":"#/components/schemas/DeviceFirmwareInfoDto"}}},"required":["total","page","size","totalPages","data"]},"CreateAutoUpdatePolicyDto":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enable or disable the auto-update policy","example":false,"default":false},"deviceType":{"description":"Target device type for auto-updates","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"chipModel":{"description":"Target chip model for auto-updates","example":"ESP32","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"mcbType":{"description":"MCB subtype the policy targets. Required when deviceType === MCB; ignored otherwise.","example":"COIL","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"targetVersion":{"type":"string","description":"Target firmware version to auto-update devices to","example":"2.0.0"},"sourceVersions":{"description":"List of source firmware versions to update FROM. If empty or not provided, updates all versions. If specified, only devices on these versions will be auto-updated.","example":["1.1.0","1.2.0"],"default":[],"type":"array","items":{"type":"string"}},"streaming":{"type":"boolean","description":"Deliver firmware as an AWS IoT Job + MQTT stream instead of pushing a download URL. One job covers the whole cohort — an offline device picks up its queued execution when it reconnects. Requires firmware that implements the IoT Jobs + streams client.","example":false,"default":false},"maxRetries":{"type":"number","description":"Maximum number of OTA attempts per device before blacklisting","example":5,"default":5}},"required":["enabled","deviceType","chipModel","targetVersion"]},"AutoUpdatePolicyResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the policy","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"enabled":{"type":"boolean","description":"Enable or disable the auto-update policy","example":true},"deviceType":{"description":"Target device type for auto-updates","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"chipModel":{"description":"Target chip model for auto-updates","example":"ESP32","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"targetVersion":{"type":"string","description":"Target firmware version to auto-update devices to","example":"2.0.0"},"sourceVersions":{"description":"List of source firmware versions to update FROM. If empty, updates all versions.","example":["1.1.0","1.2.0"],"type":"array","items":{"type":"string"}},"streaming":{"type":"boolean","description":"Whether the policy delivers firmware as an AWS IoT Job + MQTT stream instead of pushing a download URL.","example":false},"maxRetries":{"type":"number","description":"Maximum number of OTA attempts per device before blacklisting","example":5},"createdBy":{"type":"string","description":"Admin email/ID who created this policy","example":"admin@example.com"},"lastModifiedBy":{"type":"string","description":"Admin email/ID who last modified this policy","example":"admin@example.com"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the policy was created","example":"2024-01-15T10:30:00Z"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the policy was last updated","example":"2024-01-15T10:30:00Z"}},"required":["_id","enabled","deviceType","chipModel","targetVersion","streaming","maxRetries","createdBy","lastModifiedBy","createdAt","updatedAt"]},"AutoUpdateAffectedDeviceDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceName":{"type":"string","description":"Device name","example":"Living Room Plug"},"macId":{"type":"string","description":"Device MAC address","example":"AA:BB:CC:DD:EE:01"},"deviceType":{"type":"string","description":"Device type","example":"PLUG"},"chipModel":{"type":"string","description":"Chip model","example":"ESP32"},"versionNumber":{"type":"string","description":"Current firmware version","example":"1.9.2"},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update OTA attempts sent to this device","example":2},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether device is blacklisted from further auto-update attempts","example":false},"lastAutoUpdateAttempt":{"format":"date-time","type":"string","description":"Timestamp of last auto-update OTA attempt","example":"2024-01-15T08:45:00Z"},"system":{"type":"object","description":"System information","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","example":"Home Energy System"}}}},"required":["_id","deviceName","macId","deviceType","chipModel","versionNumber","autoUpdateAttempts","autoUpdateBlacklisted","system"]},"AutoUpdateAffectedDevicesListDto":{"type":"object","properties":{"devices":{"description":"List of devices affected by the auto-update policy","type":"array","items":{"$ref":"#/components/schemas/AutoUpdateAffectedDeviceDto"}},"total":{"type":"number","description":"Total number of devices matching the policy criteria","example":25},"policyTargetVersion":{"type":"string","description":"Target firmware version from the policy","example":"2.0.0"},"needsUpdateCount":{"type":"number","description":"Number of devices that need updates (version mismatch, not blacklisted)","example":18}},"required":["devices","total","policyTargetVersion","needsUpdateCount"]},"CreateOtaJobDto":{"type":"object","properties":{"deviceType":{"example":"MCB","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"chipModel":{"example":"ESP32","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"mcbType":{"description":"MCB subtype. Required when deviceType === MCB.","example":"COIL3P","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"targetVersion":{"type":"string","description":"Firmware version to roll out to","example":"2.0.0"},"sourceVersions":{"description":"Only target devices currently on these versions. Empty/omitted = all older versions.","example":["1.1.0","1.2.0"],"default":[],"type":"array","items":{"type":"string"}},"deviceIds":{"description":"Restrict the rollout to these specific devices. Intersected with the type/chip/version filter — a listed device still needs the update to be targeted. Omitted = every matching device.","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"systemIds":{"description":"Restrict the rollout to devices in these systems. Omitted = every system. Intersected with the type/chip/version filter.","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"maxPerMinute":{"type":"number","description":"Rollout rate cap (devices per minute).","example":20,"default":20},"abortThresholdPercentage":{"type":"number","description":"Abort the rollout if this % of executions fail.","example":20,"default":20}},"required":["deviceType","chipModel","targetVersion"]},"OtaJobResponseDto":{"type":"object","properties":{"jobId":{"type":"string","description":"AWS IoT Job ID for the rollout","example":"ota-mcb-COIL3P-2.0.0-1737500000000"},"streamId":{"type":"string","description":"AWS IoT Stream ID the firmware is delivered through (MQTT)","example":"ota-stream-mcb-2.0.0-1737500000000"},"targetCount":{"type":"number","description":"Number of devices targeted by the rollout","example":42},"fileSizeBytes":{"type":"number","description":"Firmware size in bytes (streamed in blocks)","example":842192}},"required":["jobId","streamId","targetCount","fileSizeBytes"]},"OtaJobStatus":{"type":"string","enum":["SCHEDULED","IN_PROGRESS","COMPLETED","CANCELED","DELETION_IN_PROGRESS"]},"OtaJobListItemDto":{"type":"object","properties":{"jobId":{"type":"string","description":"AWS IoT Job ID"},"deviceType":{"allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"chipModel":{"allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"mcbType":{"allOf":[{"$ref":"#/components/schemas/MCBType"}]},"targetVersion":{"type":"string","example":"2.1.0"},"targetCount":{"type":"number","description":"Devices targeted at creation","example":42},"status":{"allOf":[{"$ref":"#/components/schemas/OtaJobStatus"}]},"succeeded":{"type":"number","description":"Executions succeeded (last reconcile)","example":30},"failed":{"type":"number","description":"Executions failed/timed out (last reconcile)","example":2},"createdAt":{"format":"date-time","type":"string","description":"When the rollout was created"}},"required":["jobId","deviceType","chipModel","targetVersion","targetCount","status","succeeded","failed","createdAt"]},"GetOtaJobsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"type":"array","items":{"$ref":"#/components/schemas/OtaJobListItemDto"}}},"required":["total","page","size","totalPages","data"]},"OtaJobExecutionDto":{"type":"object","properties":{"thingName":{"type":"string","description":"Target Thing name","example":"ecovolt_mcb_F0:F5:BD:89:D5:00"},"status":{"type":"string","description":"Execution status","example":"SUCCEEDED","enum":["QUEUED","IN_PROGRESS","SUCCEEDED","FAILED","TIMED_OUT","REJECTED","REMOVED","CANCELED"]},"detail":{"type":"string","description":"Raw device-reported progress from statusDetails, when the firmware publishes it. Only populated while the execution is IN_PROGRESS.","example":"99.63%"},"progressPercent":{"type":"number","description":"Download progress parsed from `detail`. Absent when the device does not report progress — render an indeterminate bar in that case.","example":99.63}},"required":["thingName","status"]},"OtaJobStatusDto":{"type":"object","properties":{"jobId":{"type":"string","example":"ota-mcb-COIL3P-2.0.0-1737500000000"},"status":{"type":"string","description":"Overall job status","example":"IN_PROGRESS","enum":["IN_PROGRESS","CANCELED","COMPLETED","DELETION_IN_PROGRESS","SCHEDULED"]},"executions":{"type":"array","items":{"$ref":"#/components/schemas/OtaJobExecutionDto"}},"total":{"type":"number","description":"Total target devices","example":42},"succeeded":{"type":"number","description":"Executions that succeeded","example":30},"failed":{"type":"number","description":"Executions that failed / timed out / were rejected","example":2},"queued":{"type":"number","description":"Executions still queued — device has not picked the job up","example":8},"inProgress":{"type":"number","description":"Executions the device is currently running","example":2},"percentComplete":{"type":"number","description":"Percent of targets in a terminal state ((succeeded + failed) / total)","example":76}},"required":["jobId","status","executions","total","succeeded","failed","queued","inProgress","percentComplete"]},"CreateTenantDto":{"type":"object","properties":{"tenantName":{"type":"string","description":"Name of the tenant. Represents a department, unit, or organization within the system.","example":"Faculty of Engineering"},"tenantEmail":{"type":"string","description":"Primary email address for tenant contact. Used for notifications and billing communications.","example":"engineering@university.edu"},"remarks":{"type":"string","description":"Additional notes or comments about the tenant. Useful for documenting special arrangements or contact details.","example":"Main office located on 7th floor, contact person: John Doe"},"level":{"type":"number","description":"Floor number where the tenant is primarily located. Used for organizing tenants within a building.","example":7},"energyUsage":{"type":"number","description":"Initial energy usage budget allocated to the tenant in kilowatt-hours. Used for tracking consumption against quotas.","example":1000},"location":{"description":"Geographic location details of the tenant. Includes address, city, country, and coordinates for mapping purposes.","allOf":[{"$ref":"#/components/schemas/LocationObject"}]},"adminCreation":{"type":"boolean","description":"Flag indicating whether an tenant admin should be created, if so send invite."},"tenantType":{"type":"string","description":"Tenant type key. Must be a valid type defined for this system via /entity-type/TENANT.","example":"RETAIL"}},"required":["tenantName","adminCreation"]},"GetTenantDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the tenant in the database.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"tenantName":{"type":"string","description":"Name of the tenant. Represents a department, unit, or organization within the system.","example":"Faculty of Engineering"},"tenantEmail":{"type":"string","description":"Primary email address for tenant contact. Used for notifications and billing communications.","example":"engineering@university.edu"},"location":{"description":"Geographic location details of the tenant. Includes address, city, country, and coordinates.","allOf":[{"$ref":"#/components/schemas/LocationObject"}]},"level":{"type":"number","description":"Floor number where the tenant is primarily located. Used for organizing tenants within a building.","example":7},"deviceCount":{"type":"number","description":"Total count of devices assigned to this tenant. Used for resource allocation tracking.","example":15},"deviceOnCount":{"type":"number","description":"Number of devices currently in ON status for this tenant.","example":8},"deviceOffCount":{"type":"number","description":"Number of devices currently in OFF status for this tenant.","example":5},"deviceInactiveCount":{"type":"number","description":"Number of devices currently in INACTIVE status for this tenant.","example":2},"deviceSpareCount":{"type":"number","description":"Number of spare devices for this tenant.","example":3},"totalEnergyUsage":{"type":"number","description":"Energy usage budget allocated to the tenant in kilowatt-hours. Tracks consumption against quotas.","example":1000},"rangeEnergyUsage":{"type":"number","description":"Energy consumption over the `from`..`till` window in kWh, aggregated from daily usage history. Present only when both query params are supplied — unlike the cumulative `totalEnergyUsage`, this is windowed.","example":42.75},"totalFlaggedCount":{"type":"number","description":"Total number of rolling windows this tenant has been flagged. Represents how many different time periods the tenant exceeded thresholds.","example":3},"totalViolationCount":{"type":"number","description":"Total number of individual violations across all time. Sum of all daily violations ever recorded for this tenant.","example":15},"isMonitoringDisabled":{"type":"boolean","description":"Whether surge monitoring is disabled for this tenant. If true, the tenant will not be checked for energy threshold violations.","example":false},"suspectedDeviceTypes":{"description":"Device types suspected of causing surge violations for this tenant.","example":["PLUG","MCB"],"type":"array","items":{"type":"string"}},"confirmedDeviceTypes":{"description":"Device types an operator has confirmed as the cause of surge violations for this tenant.","example":["Water Heater"],"type":"array","items":{"type":"string"}},"tenantType":{"type":"string","description":"Tenant type key","example":"RETAIL"}},"required":["_id","tenantName","tenantEmail","deviceCount","deviceOnCount","deviceOffCount","deviceInactiveCount","totalEnergyUsage"]},"GetTenantsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of tenant objects matching the query criteria","type":"array","items":{"$ref":"#/components/schemas/GetTenantDto"}}},"required":["total","page","size","totalPages","data"]},"GetRoomDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the room in MongoDB ObjectId format.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"name":{"type":"string","description":"Display name of the room as registered in the system.","example":"Living Room"},"tenantId":{"type":"string","description":"Unique identifier of the tenant organization that owns this room.","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantName":{"type":"string","description":"Name of the tenant organization that owns this room. Included when tenant data is populated.","example":"Faculty of Engineering"},"zoneId":{"type":"string","description":"Unique identifier of the zone that has access to this room.","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"zoneName":{"type":"string","description":"Name of the zone that has access to this room. Included when zone data is populated.","example":"Zone A"},"remarks":{"type":"string","description":"Optional administrative notes or comments about the room. Used for describing room purpose or equipment.","example":"Conference room with presentation equipment"},"deviceCount":{"type":"number","description":"Total count of devices currently assigned to this room.","example":8},"deviceOnCount":{"type":"number","description":"Number of devices currently in ON status for this room.","example":4},"deviceOffCount":{"type":"number","description":"Number of devices currently in OFF status for this room.","example":3},"deviceInactiveCount":{"type":"number","description":"Number of devices currently in INACTIVE status for this room.","example":1},"deviceSpareCount":{"type":"number","description":"Number of spare devices for this room.","example":3},"totalEnergyUsage":{"type":"number","description":"Cumulative energy consumption from all devices in this room, measured in kilowatt-hours (kWh).","example":124.67},"rangeEnergyUsage":{"type":"number","description":"Energy consumption over the `from`..`till` window in kWh, aggregated from daily usage history. Present only when both query params are supplied — unlike the cumulative `totalEnergyUsage`, this is windowed.","example":42.75},"totalFlaggedCount":{"type":"number","description":"Total number of rolling windows this room has been flagged. Represents how many different time periods the room exceeded thresholds.","example":5},"totalViolationCount":{"type":"number","description":"Total number of individual violations across all time. Sum of all daily violations ever recorded for this room.","example":22},"isMonitoringDisabled":{"type":"boolean","description":"Whether surge monitoring is disabled for this room. If true, the room will not be checked for energy threshold violations.","example":false},"squareMeter":{"type":"number","description":"Floor area of the room in square meters.","example":25.5},"energyIntensity":{"type":"number","description":"Energy intensity over the past 30 days in kWh/m²/month. Null if the room has no squareMeter set.","example":10.8},"suspectedDeviceTypes":{"description":"Device types suspected of causing surge violations for this room.","example":["PLUG","MCB"],"type":"array","items":{"type":"string"}},"confirmedDeviceTypes":{"description":"Device types an operator has confirmed as the cause of surge violations for this room.","example":["Water Heater"],"type":"array","items":{"type":"string"}},"roomType":{"type":"string","description":"Room type key","example":"MEETING_ROOM"}},"required":["_id","name","deviceCount","deviceOnCount","deviceOffCount","deviceInactiveCount","totalEnergyUsage"]},"GetZoneDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the zone in MongoDB ObjectId format.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"name":{"type":"string","description":"Full name of the zone as registered in the system.","example":"Zone A - Engineering Wing"},"email":{"type":"string","description":"Email address of the zone used for authentication and notifications.","example":"zone-a@ecovolt.com"},"remarks":{"type":"string","description":"Optional administrative notes or comments about the zone. Used for internal tracking and context.","example":"Department head - Engineering"},"tenantName":{"type":"string","description":"Name of the tenant organization that the zone belongs to. Included when tenant data is populated.","example":"Faculty of Engineering"},"tenantId":{"type":"string","description":"Unique identifier of the tenant organization that the zone is associated with.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"deviceCount":{"type":"number","description":"Total count of devices currently assigned to this zone across all their rooms.","example":12},"deviceOnCount":{"type":"number","description":"Number of devices currently in ON status for this zone.","example":6},"deviceOffCount":{"type":"number","description":"Number of devices currently in OFF status for this zone.","example":4},"deviceInactiveCount":{"type":"number","description":"Number of devices currently in INACTIVE status for this zone.","example":2},"deviceSpareCount":{"type":"number","description":"Number of spare devices for this zone.","example":3},"totalEnergyUsage":{"type":"number","description":"Cumulative energy consumption from all devices assigned to the zone, measured in kilowatt-hours (kWh).","example":124.67},"rangeEnergyUsage":{"type":"number","description":"Energy consumption over the `from`..`till` window in kWh, aggregated from daily usage history. Present only when both query params are supplied — unlike the cumulative `totalEnergyUsage`, this is windowed.","example":42.75},"rooms":{"description":"Array of room objects that the zone has access to. Included when room details are populated.","type":"array","items":{"$ref":"#/components/schemas/GetRoomDto"}},"totalFlaggedCount":{"type":"number","description":"Total number of rolling windows this zone has been flagged. Represents how many different time periods the zone exceeded thresholds.","example":2},"totalViolationCount":{"type":"number","description":"Total number of individual violations across all time. Sum of all daily violations ever recorded for this zone.","example":8},"isMonitoringDisabled":{"type":"boolean","description":"Whether surge monitoring is disabled for this zone. If true, the zone will not be checked for energy threshold violations.","example":false},"suspectedDeviceTypes":{"description":"Device types suspected of causing surge violations for this zone.","example":["PLUG","MCB"],"type":"array","items":{"type":"string"}},"confirmedDeviceTypes":{"description":"Device types an operator has confirmed as the cause of surge violations for this zone.","example":["Water Heater"],"type":"array","items":{"type":"string"}},"zoneType":{"type":"string","description":"Zone type key","example":"LABORATORY"}},"required":["_id","name","email","deviceCount","deviceOnCount","deviceOffCount","deviceInactiveCount","totalEnergyUsage"]},"TenantRelationshipDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the tenant in the database.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"tenantName":{"type":"string","description":"Name of the tenant. Represents a department, unit, or organization within the system.","example":"Faculty of Engineering"},"energyUsage":{"type":"number","description":"Energy usage budget allocated to the tenant in kilowatt-hours. Tracks consumption against quotas.","example":1000},"devices":{"description":"Array of IoT devices assigned to this tenant. Includes all device types: smart plugs, MCBs, distribution boxes, etc.","type":"array","items":{"$ref":"#/components/schemas/DeviceDetailsDto"}},"rooms":{"description":"Array of rooms belonging to this tenant. Provides room hierarchy and organization.","type":"array","items":{"$ref":"#/components/schemas/GetRoomDto"}},"zones":{"description":"Array of zones assigned to this tenant. Includes zone accounts with access to tenant resources.","type":"array","items":{"$ref":"#/components/schemas/GetZoneDto"}},"numberOfZones":{"type":"number","description":"Total count of zones assigned to this tenant. Used for resource planning and statistics.","example":24},"numberOfRooms":{"type":"number","description":"Total count of rooms belonging to this tenant. Used for resource planning and statistics.","example":18},"numberOfDevices":{"type":"number","description":"Total count of IoT devices assigned to this tenant. Used for resource planning and statistics. Includes all device types: smart plugs, MCBs, distribution boxes, etc.","example":65}},"required":["_id","tenantName","energyUsage","devices","rooms","zones","numberOfZones","numberOfRooms","numberOfDevices"]},"GetTenantsRelationshipDto":{"type":"object","properties":{"tenants":{"description":"Array of tenants with their complete relationship hierarchy including zones, rooms, and devices.","type":"array","items":{"$ref":"#/components/schemas/TenantRelationshipDto"}},"numberOfTenants":{"type":"number","description":"Total count of tenants in the system. Useful for pagination and statistics.","example":12},"numberOfZones":{"type":"number","description":"Total count of zones across all tenants in the system. Useful for system-wide statistics.","example":145},"numberOfRooms":{"type":"number","description":"Total count of rooms across all tenants in the system. Useful for system-wide statistics.","example":87},"numberOfDevices":{"type":"number","description":"Total count of IoT devices across all tenants in the system. Useful for system-wide statistics. Includes all device types: smart plugs, MCBs, distribution boxes, etc.","example":342}},"required":["tenants","numberOfTenants","numberOfZones","numberOfRooms","numberOfDevices"]},"RoomEnergyUsageDto":{"type":"object","properties":{"roomId":{"type":"string","description":"Unique identifier of the room","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"roomName":{"type":"string","description":"Name of the room","example":"Conference Room A"},"energyUsage":{"type":"number","description":"Total energy usage in kilowatt-hours (kWh) for the specified period","example":125.45},"isFlagged":{"type":"boolean","description":"Whether the room is currently flagged by surge monitoring","example":false},"totalFlaggedCount":{"type":"number","description":"Rolling windows in which the room exceeded its threshold","example":5},"totalViolationCount":{"type":"number","description":"Individual violations recorded for the room, all time","example":22},"isMonitoringDisabled":{"type":"boolean","description":"Whether surge monitoring is disabled for this room","example":false},"suspectedDeviceTypes":{"description":"Device types suspected of causing surge violations","example":["PLUG","MCB"],"type":"array","items":{"type":"string"}},"confirmedDeviceTypes":{"description":"Device types an operator has confirmed as the cause","example":["Water Heater"],"type":"array","items":{"type":"string"}}},"required":["roomId","roomName","energyUsage","isFlagged","totalFlaggedCount","totalViolationCount","isMonitoringDisabled"]},"GetRoomEnergyUsageResponseDto":{"type":"object","properties":{"tenantId":{"type":"string","description":"Tenant ID for which energy usage was calculated","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"days":{"type":"number","description":"Number of days the energy usage was calculated over","example":7},"startDate":{"type":"string","description":"Start date of the energy usage period (ISO 8601 format)","example":"2024-01-15T00:00:00.000Z"},"endDate":{"type":"string","description":"End date of the energy usage period (ISO 8601 format)","example":"2024-01-22T00:00:00.000Z"},"rooms":{"description":"List of rooms with their energy usage data","type":"array","items":{"$ref":"#/components/schemas/RoomEnergyUsageDto"}},"totalRooms":{"type":"number","description":"Total number of rooms found for this tenant","example":5},"totalEnergyUsage":{"type":"number","description":"Total energy usage across all rooms in kWh","example":547.32}},"required":["tenantId","days","startDate","endDate","rooms","totalRooms","totalEnergyUsage"]},"UpdateTenantDto":{"type":"object","properties":{"tenantName":{"type":"string","description":"Name of the tenant. Represents a department, unit, or organization within the system.","example":"Faculty of Engineering"},"tenantEmail":{"type":"string","description":"Primary email address for tenant contact. Used for notifications and billing communications.","example":"engineering@university.edu"},"remarks":{"type":"string","description":"Additional notes or comments about the tenant. Useful for documenting special arrangements or contact details.","example":"Main office located on 7th floor, contact person: John Doe"},"level":{"type":"number","description":"Floor number where the tenant is primarily located. Used for organizing tenants within a building.","example":7},"location":{"description":"Geographic location details of the tenant. Includes address, city, country, and coordinates for mapping purposes.","allOf":[{"$ref":"#/components/schemas/LocationObject"}]},"tenantType":{"type":"string","description":"Tenant type key. Must be a valid type defined for this system via /entity-type/TENANT.","example":"RETAIL"}},"required":["tenantName","tenantEmail","location"]},"SurgeConfigTypeOverride":{"type":"object","properties":{"calculatedThresholdKwh":{"type":"number","description":"Auto-calculated threshold (kWh) for this type key","example":8},"manualThresholdKwh":{"type":"number","description":"Manual threshold override (kWh) for this type key — wins over calculatedThresholdKwh","example":4},"sentinelPowerRangeRules":{"description":"Sentinel power range rules for this type key. Replaces the baseline rules entirely when set.","type":"array","items":{"type":"array"}}}},"MonitoringEntityType":{"type":"string","enum":["ROOM","TENANT","ZONE"],"description":"Type of entity to start usage surge analysis for"},"StartAnalysisDto":{"type":"object","properties":{"entityType":{"description":"Type of entity to start usage surge analysis for","example":"ROOM","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"startDate":{"type":"string","description":"Start date for historical analysis (YYYY-MM-DD format). Must be at least 7 days before end date.","example":"2026-01-01"},"endDate":{"type":"string","description":"End date for historical analysis (YYYY-MM-DD format). Must be at least 7 days after start date.","example":"2026-01-14"},"initialFlagThreshold":{"type":"number","description":"Minimum number of violations required to flag an entity as initially problematic during analysis","example":10}},"required":["entityType","startDate","endDate","initialFlagThreshold"]},"McbMeasurementTypeBreakdown":{"type":"object","properties":{"measurementType":{"description":"MCB measurement type classification","example":"AIRCON","allOf":[{"$ref":"#/components/schemas/McbMeasurementType"}]},"energyKwh":{"type":"number","description":"Total energy consumption in kWh for this measurement type","example":123.45},"costAmount":{"type":"number","description":"Total cost amount for this measurement type","example":35.18}},"required":["measurementType","energyKwh","costAmount"]},"EntityEnergyBreakdown":{"type":"object","properties":{"breakdownByMcbType":{"description":"Energy breakdown by MCB measurement type","type":"array","items":{"$ref":"#/components/schemas/McbMeasurementTypeBreakdown"}},"totalEnergyKwh":{"type":"number","description":"Total energy consumption across all measurement types (kWh)","example":456.78},"totalCost":{"type":"number","description":"Total cost across all measurement types ($)","example":130.18},"periodDays":{"type":"number","description":"Number of days in the analysis period","example":7},"dailyAverageKwh":{"type":"number","description":"Daily average energy consumption (kWh)","example":65.25},"predictedMonthlyKwh":{"type":"number","description":"Predicted monthly energy consumption (kWh)","example":1957.5},"predictedMonthlyCost":{"type":"number","description":"Predicted monthly cost ($)","example":558.39}},"required":["breakdownByMcbType","totalEnergyKwh","totalCost","periodDays","dailyAverageKwh","predictedMonthlyKwh","predictedMonthlyCost"]},"EntityAnalysisDetailDto":{"type":"object","properties":{"entityId":{"type":"string","description":"Entity ID","example":"65a1b2c3d4e5f6a7b8c9d0e1"},"typeKey":{"type":"string","description":"The entity's own type key (Room.roomType / Tenant.tenantType / Zone.zoneType). Omitted when the entity has none.","example":"MEETING_ROOM"},"entityName":{"type":"string","description":"Entity name","example":"Conference Room A"},"averageDailyUsageKwh":{"type":"number","description":"Average daily usage during analysis period (kWh)","example":12.5},"totalUsageKwh":{"type":"number","description":"Total usage during analysis period (kWh)","example":175},"validDaysCount":{"type":"number","description":"Number of days with valid data in analysis period","example":14},"exceededThreshold":{"type":"boolean","description":"Whether this entity exceeded the threshold during analysis","example":false},"exceedanceCount":{"type":"number","description":"Number of times threshold was exceeded during analysis period","example":0},"predictedMonthlyKwh":{"type":"number","description":"Predicted monthly usage (kWh) based on average daily usage","example":375},"predictedYearlyKwh":{"type":"number","description":"Predicted yearly usage (kWh) based on average daily usage","example":4562.5},"predictedMonthlyCost":{"type":"number","description":"Predicted monthly cost based on tariff","example":75},"predictedYearlyCost":{"type":"number","description":"Predicted yearly cost based on tariff","example":912.5},"mcbMeasurementBreakdown":{"description":"Energy breakdown by MCB measurement type","allOf":[{"$ref":"#/components/schemas/EntityEnergyBreakdown"}]}},"required":["entityId","entityName","averageDailyUsageKwh","totalUsageKwh","validDaysCount","exceededThreshold","exceedanceCount","predictedMonthlyKwh","predictedYearlyKwh","predictedMonthlyCost","predictedYearlyCost","mcbMeasurementBreakdown"]},"ThresholdSource":{"type":"string","enum":["TYPE","BASELINE"],"description":"TYPE when the threshold was calculated from this type key alone; BASELINE when the group had too few entities with valid data and the system-wide threshold was used instead."},"TypeKeyAnalysisDto":{"type":"object","properties":{"typeKey":{"type":"string","description":"Entity type key this slice covers (e.g. a Room.roomType). Omitted for entities with no type key configured.","example":"MEETING_ROOM"},"calculatedThresholdKwh":{"type":"number","description":"Threshold in kWh for this type key. Feed this into POST /config/:entityType as a per-type override.","example":8.4},"thresholdSource":{"description":"TYPE when the threshold was calculated from this type key alone; BASELINE when the group had too few entities with valid data and the system-wide threshold was used instead.","example":"TYPE","allOf":[{"$ref":"#/components/schemas/ThresholdSource"}]},"entitiesAnalyzed":{"type":"number","description":"Number of entities of this type key with valid usage data","example":12},"initialFlaggedCount":{"type":"number","description":"Number of entities of this type key flagged against this type key threshold","example":2},"entityDetails":{"description":"Detailed analysis for each entity of this type key","type":"array","items":{"$ref":"#/components/schemas/EntityAnalysisDetailDto"}},"energyBreakdown":{"description":"Energy breakdown aggregated across this type key","allOf":[{"$ref":"#/components/schemas/EntityEnergyBreakdown"}]}},"required":["calculatedThresholdKwh","thresholdSource","entitiesAnalyzed","initialFlaggedCount","entityDetails"]},"AnalysisStartResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Operation success status"},"message":{"type":"string","description":"Confirmation message","example":"Analysis completed using historical data from 2026-01-01 to 2026-01-14"},"calculatedThresholdKwh":{"type":"number","description":"Calculated threshold in kWh across all entities of this entity type (the baseline). Per-type thresholds are in byTypeKey.","example":15.5},"entitiesAnalyzed":{"type":"number","description":"Number of entities analyzed","example":42},"initialFlaggedCount":{"type":"number","description":"Number of initially flagged entities","example":3},"entityDetails":{"description":"Detailed analysis for every entity analysed, across all type keys","type":"array","items":{"$ref":"#/components/schemas/EntityAnalysisDetailDto"}},"byTypeKey":{"description":"Analysis split by the entities' own type keys. Entities with no type key are grouped in a single slice with typeKey omitted.","type":"array","items":{"$ref":"#/components/schemas/TypeKeyAnalysisDto"}},"systemEnergyBreakdown":{"description":"System-wide energy breakdown by MCB measurement type","allOf":[{"$ref":"#/components/schemas/EntityEnergyBreakdown"}]}},"required":["success","message","calculatedThresholdKwh","entitiesAnalyzed","initialFlaggedCount","entityDetails","byTypeKey"]},"SentinelPowerRangeRuleDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique rule identifier (UUID)","example":"a1b2c3d4-..."},"label":{"type":"string","description":"Human-readable label for the suspected device type","example":"Suspected Fridge"},"minWatts":{"type":"number","description":"Minimum power threshold in Watts (inclusive)","example":50},"maxWatts":{"type":"number","description":"Maximum power threshold in Watts (inclusive)","example":125},"minDurationMinutes":{"type":"number","description":"Minimum cumulative minutes device must sustain power in range on a violation day to be classified","example":120}},"required":["id","label","minWatts","maxWatts","minDurationMinutes"]},"TypeThresholdOverrideDto":{"type":"object","properties":{"typeKey":{"type":"string","description":"Entity type key this override applies to — must be a configured type for this system and entity kind","example":"TYPE_A"},"calculatedThresholdKwh":{"type":"number","description":"Auto-calculated threshold (kWh) for this type key","example":8},"manualThresholdKwh":{"type":"number","description":"Manual threshold override (kWh) for this type key — wins over calculatedThresholdKwh","example":4},"sentinelPowerRangeRules":{"description":"Sentinel power range rules for this type key. Replaces the baseline rules entirely when set.","type":"array","items":{"$ref":"#/components/schemas/SentinelPowerRangeRuleDto"}}},"required":["typeKey"]},"CreateSurgeConfigDto":{"type":"object","properties":{"calculatedThresholdKwh":{"type":"number","description":"Calculated threshold (median) in kWh","example":15.5},"manualThresholdKwh":{"type":"number","description":"Manual threshold override (kWh) - takes precedence over calculated threshold","example":18},"defaultViolationCount":{"type":"number","description":"Default violation count to trigger flag","example":3},"defaultViolationWindow":{"type":"number","description":"Default rolling window in days","example":7},"digestTime":{"type":"string","description":"Local time of day (HH:mm, system timezone) the digest fires at.","example":"08:00"},"digestStartDate":{"type":"string","description":"Local date (YYYY-MM-DD, system timezone) the digest cycle starts from. Fixes which day of the N-day cycle the digest lands on, so editing the config later does not re-phase it.","example":"2026-09-15"},"sentinelPowerRangeRules":{"description":"Sentinel power range rules for suspected device classification","type":"array","items":{"$ref":"#/components/schemas/SentinelPowerRangeRuleDto"}},"typeOverrides":{"description":"Per-type-key threshold overrides. Each entry gives one entity type key (e.g. a room type) its own threshold, and optionally its own sentinel rules. Feed the `byTypeKey` slices returned by the analysis endpoint straight in here so each type is monitored against its own threshold. Entities whose type key has no entry — including entities with no type key set — use the entity-type baseline threshold.","type":"array","items":{"$ref":"#/components/schemas/TypeThresholdOverrideDto"}}},"required":["calculatedThresholdKwh","digestTime","digestStartDate"]},"SurgeConfigResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Operation success status"},"entityType":{"description":"Entity type","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"message":{"type":"string","description":"Success or informational message"},"config":{"description":"Updated configuration","allOf":[{"$ref":"#/components/schemas/SurgeConfig"}]},"digestScheduleWarning":{"type":"string","description":"Present when the configuration was saved but its cloud digest schedule could not be rebuilt. The entity type will send no digest until surge monitoring is re-enabled — surface this to the operator."}},"required":["success","entityType","message","config"]},"SurgeConfigLookupResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Operation success status"},"entityType":{"description":"Entity type","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"configured":{"type":"boolean","description":"Whether a configuration exists for this entity type. When false, `config` is null and surge monitoring is not set up for this entity type."},"message":{"type":"string","description":"Success or informational message"},"config":{"description":"Current configuration, or null when never configured","nullable":true,"type":"object","allOf":[{"$ref":"#/components/schemas/SurgeConfig"}]}},"required":["success","entityType","configured","message","config"]},"UpdateSurgeScheduleDto":{"type":"object","properties":{"dailyCheckTime":{"type":"string","description":"Local time of day (HH:mm, system timezone) the daily surge check runs at. Defaults to 01:00.","example":"01:00"}}},"SurgeScheduleResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Operation success status"},"dailyCheckTime":{"type":"string","description":"Local time of day the daily surge check now runs at","example":"01:00"},"timezone":{"type":"string","description":"IANA timezone the schedule times are interpreted in","example":"Asia/Singapore"},"scheduleName":{"type":"string","description":"Name of the recreated daily check schedule. Absent when surge monitoring is not enabled yet — the time is stored and applied when monitoring is enabled.","example":"surge_monitoring_64b7f1e6f9c9d4e5b8a9f7c3_prod"}},"required":["success","dailyCheckTime","timezone"]},"UpdateSystemSurgeConfigDto":{"type":"object","properties":{"calculatedThresholdKwh":{"type":"number","description":"Auto-calculated threshold (median) in kWh - can be manually adjusted","example":15.5},"manualThresholdKwh":{"type":"number","description":"Manual threshold override (kWh) - takes precedence over calculated threshold","example":18},"defaultViolationCount":{"type":"number","description":"Default violation count to trigger flag","example":3},"defaultViolationWindow":{"type":"number","description":"Default rolling window in days","example":7},"digestTime":{"type":"string","description":"Local time of day (HH:mm, system timezone) the digest fires at. Must end up set together with digestStartDate — setting only one is rejected.","example":"08:00"},"digestStartDate":{"type":"string","description":"Local date (YYYY-MM-DD, system timezone) of the first digest run. Fixes which day of the N-day cycle the digest lands on. Must end up set together with digestTime — setting only one is rejected.","example":"2026-09-15"},"sentinelPowerRangeRules":{"description":"Sentinel power range rules for suspected device classification. Full replacement when present — omit to leave the existing rules untouched, send an empty array to clear them.","type":"array","items":{"$ref":"#/components/schemas/SentinelPowerRangeRuleDto"}},"typeOverrides":{"description":"Per-type-key threshold overrides. Each entry gives one entity type key (e.g. a room type) its own threshold, and optionally its own sentinel rules. Entities whose type key has no entry — including entities with no type key set — keep using the entity-type baseline threshold. Full replacement when present — omit to leave the existing overrides untouched, send an empty array to clear them.","type":"array","items":{"$ref":"#/components/schemas/TypeThresholdOverrideDto"}}}},"EntityMetadata":{"type":"object","properties":{"tenantId":{"type":"string","description":"Tenant ID","example":"65a1b2c3d4e5f6a7b8c9d0e1"},"tenantName":{"type":"string","description":"Tenant name","example":"Acme Corporation"},"zoneId":{"type":"string","description":"Zone ID","example":"65a1b2c3d4e5f6a7b8c9d0e2"},"zoneName":{"type":"string","description":"Zone name","example":"Zone A"}}},"ViolationResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Violation ID"},"entityType":{"description":"Entity type","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"entityId":{"type":"string","description":"Entity ID"},"entityName":{"type":"string","description":"Entity name"},"entityTypeKey":{"type":"string","description":"Entity's own type key (Room.roomType / Tenant.tenantType / Zone.zoneType) — undefined when it has none","example":"MEETING_ROOM"},"date":{"format":"date-time","type":"string","description":"Violation date"},"dailyUsageKwh":{"type":"number","description":"Actual daily usage (kWh)"},"thresholdKwh":{"type":"number","description":"Threshold that was exceeded (kWh) — the entity's effective threshold at the time of the violation, so a type-key override is already reflected"},"exceedanceKwh":{"type":"number","description":"Amount exceeded (kWh)"},"exceedancePercent":{"type":"number","description":"Percentage exceeded (%)"},"isFlagged":{"type":"boolean","description":"Whether violation triggered alert"},"supersededAt":{"format":"date-time","type":"string","description":"Set when a counters reset soft-deleted this violation. The row is still listed so the history stays inspectable, but it no longer contributes to any count, flag state or digest total — render it as struck through / greyed out."},"metadata":{"description":"Additional contextual metadata","allOf":[{"$ref":"#/components/schemas/EntityMetadata"}]}},"required":["_id","entityType","entityId","entityName","date","dailyUsageKwh","thresholdKwh","exceedanceKwh","exceedancePercent","isFlagged"]},"ViolationsListResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"List of violations","type":"array","items":{"$ref":"#/components/schemas/ViolationResponseDto"}}},"required":["total","page","size","totalPages","data"]},"LatestViolationDto":{"type":"object","properties":{"date":{"format":"date-time","type":"string","description":"Date of latest violation"},"dailyUsageKwh":{"type":"number","description":"Daily usage in kWh"},"thresholdKwh":{"type":"number","description":"Threshold in kWh"},"exceedanceKwh":{"type":"number","description":"Exceedance in kWh"},"exceedancePercent":{"type":"number","description":"Exceedance percentage"}},"required":["date","dailyUsageKwh","thresholdKwh","exceedanceKwh","exceedancePercent"]},"ViolationMetadata":{"type":"object","properties":{"entityName":{"type":"string","description":"Entity name (room/tenant/zone)"},"tenantId":{"type":"string","description":"Tenant ID if applicable"},"tenantName":{"type":"string","description":"Tenant name if applicable"},"userId":{"type":"string","description":"Zone ID if applicable"},"zoneName":{"type":"string","description":"Zone name if applicable"}}},"FlaggedEntityDto":{"type":"object","properties":{"entityId":{"type":"string","description":"Entity ID"},"entityName":{"type":"string","description":"Entity name"},"entityType":{"description":"Entity type","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"entityTypeKey":{"type":"string","description":"Entity's own type key (e.g. room type) — undefined when it has none","example":"TYPE_A"},"violationCount":{"type":"number","description":"Total violation count"},"currentPeriodViolationCount":{"type":"number","description":"Current period violation count"},"totalFlaggedCount":{"type":"number","description":"Total times this entity has been flagged"},"thresholdKwh":{"type":"number","description":"The entity's current effective threshold in kWh — active entity adjustment, else its type-key override, else the entity-type baseline"},"firstViolationDate":{"format":"date-time","type":"string","description":"Date of first violation"},"lastViolationDate":{"format":"date-time","type":"string","description":"Date of last violation"},"latestViolation":{"description":"Latest violation details","allOf":[{"$ref":"#/components/schemas/LatestViolationDto"}]},"allViolationDates":{"description":"All violation dates","type":"array","items":{"type":"string"}},"metadata":{"description":"Additional violation metadata","allOf":[{"$ref":"#/components/schemas/ViolationMetadata"}]}},"required":["entityId","entityName","entityType","violationCount","currentPeriodViolationCount","totalFlaggedCount","thresholdKwh","firstViolationDate","lastViolationDate","latestViolation","allViolationDates"]},"FlaggedEntitiesResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"List of flagged entities","type":"array","items":{"$ref":"#/components/schemas/FlaggedEntityDto"}}},"required":["total","page","size","totalPages","data"]},"ResetAllViolationsDto":{"type":"object","properties":{"entityType":{"description":"Scope the reset to a single policy (entity type). Omit to reset ALL policies (rooms, tenants, and zones) for the system.","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]}}},"ResetAllViolationsResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Operation success status"},"entityType":{"description":"Policy the reset was scoped to. Absent when all policies were reset.","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"deletedViolations":{"type":"number","description":"Number of violation records deleted"},"trackersReset":{"type":"number","description":"Number of entity trackers reset (unflagged)"},"message":{"type":"string","description":"Confirmation message"}},"required":["success","deletedViolations","trackersReset","message"]},"DisabledEntityDto":{"type":"object","properties":{"entityId":{"type":"string","description":"Entity ID"},"entityName":{"type":"string","description":"Entity name"},"entityType":{"description":"Entity type","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"disabledAt":{"format":"date-time","type":"string","description":"When monitoring was disabled for this entity"},"totalFlaggedCount":{"type":"number","description":"Times the entity was flagged before monitoring was disabled"},"reason":{"type":"string","description":"Why monitoring was disabled"}},"required":["entityId","entityName","entityType","disabledAt","totalFlaggedCount"]},"DisabledEntitiesResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"success":{"type":"boolean","description":"Success status"},"entityType":{"description":"Entity type filter applied","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"data":{"description":"List of disabled entities","type":"array","items":{"$ref":"#/components/schemas/DisabledEntityDto"}}},"required":["total","page","size","totalPages","success","data"]},"ToggleMonitoringDto":{"type":"object","properties":{"isEnabled":{"type":"boolean","description":"Whether to enable monitoring (true) or disable it (false)","example":false},"reason":{"type":"string","description":"Reason for enabling/disabling monitoring for this entity","example":"Approved high-power usage for medical equipment"}},"required":["isEnabled"]},"MonitoringStatusResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Operation success status"},"entityType":{"description":"Entity type","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"entityId":{"type":"string","description":"Entity ID"},"isMonitoringDisabled":{"type":"boolean","description":"Whether entity monitoring is currently disabled"},"message":{"type":"string","description":"Confirmation message"}},"required":["success","entityType","entityId","isMonitoringDisabled","message"]},"UpdateConfirmedDevicesDto":{"type":"object","properties":{"confirmedDeviceTypes":{"description":"Full list of operator-confirmed device types for this entity. Replaces the existing list — send an empty array to clear it.","example":["Water Heater","Aircon"],"type":"array","items":{"type":"string"}}},"required":["confirmedDeviceTypes"]},"ConfirmedDevicesResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Operation success status"},"entityType":{"description":"Entity type","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"entityId":{"type":"string","description":"Entity ID"},"confirmedDeviceTypes":{"description":"Confirmed device types now stored for this entity","example":["Water Heater","Aircon"],"type":"array","items":{"type":"string"}},"message":{"type":"string","description":"Confirmation message"}},"required":["success","entityType","entityId","confirmedDeviceTypes","message"]},"ResetViolationsDto":{"type":"object","properties":{"deleteViolationRecords":{"type":"boolean","description":"Whether to delete all violation records from database (default: false). If false, only counters are reset but historical records are preserved.","default":false}}},"ResetCounters":{"type":"object","properties":{"totalFlaggedCount":{"type":"number","description":"Total flagged count before reset"},"currentPeriodViolationCount":{"type":"number","description":"Current period violation count before reset"}},"required":["totalFlaggedCount","currentPeriodViolationCount"]},"ResetViolationsResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Success status"},"entityType":{"description":"Entity type","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"entityId":{"type":"string","description":"Entity ID"},"resetCounters":{"description":"Counter values before reset","allOf":[{"$ref":"#/components/schemas/ResetCounters"}]},"deletedViolations":{"type":"number","description":"Number of violation records deleted"},"message":{"type":"string","description":"Success message"}},"required":["success","entityType","entityId","resetCounters","deletedViolations","message"]},"EntityEnergyBreakdownResponseDto":{"type":"object","properties":{"entityId":{"type":"string","description":"Entity ID","example":"65a1b2c3d4e5f6a7b8c9d0e1"},"entityName":{"type":"string","description":"Entity name","example":"Conference Room A"},"entityType":{"description":"Entity type","example":"ROOM","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"startDate":{"format":"date-time","type":"string","description":"Analysis period start date"},"endDate":{"format":"date-time","type":"string","description":"Analysis period end date"},"energyBreakdown":{"description":"MCB measurement type breakdown with cost analysis","allOf":[{"$ref":"#/components/schemas/EntityEnergyBreakdown"}]}},"required":["entityId","entityName","entityType","startDate","endDate","energyBreakdown"]},"EntityCheckResultDto":{"type":"object","properties":{"entityId":{"type":"string","description":"Entity ID"},"entityName":{"type":"string","description":"Entity name"},"entityType":{"description":"Type of entity","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"entityTypeKey":{"type":"string","description":"Entity's own type key (e.g. room type) — drives which threshold was applied","example":"TYPE_A"},"dailyUsageKwh":{"type":"number","description":"Daily usage in kWh"},"thresholdKwh":{"type":"number","description":"Threshold in kWh"},"violated":{"type":"boolean","description":"Whether this entity violated the threshold"},"currentPeriodViolationCount":{"type":"number","description":"Current period violation count after check"},"totalFlaggedCount":{"type":"number","description":"Total lifetime flagged count after check"}},"required":["entityId","entityName","entityType","dailyUsageKwh","thresholdKwh","violated","currentPeriodViolationCount","totalFlaggedCount"]},"DailySurgeCheckResultDto":{"type":"object","properties":{"systemId":{"type":"string","description":"System ID that was checked"},"checkDate":{"format":"date-time","type":"string","description":"Check date"},"totalEntitiesChecked":{"type":"number","description":"Total entities checked across all types"},"totalViolationsDetected":{"type":"number","description":"Total violations detected across all types"},"checkResults":{"description":"Detailed results for each entity checked","type":"array","items":{"$ref":"#/components/schemas/EntityCheckResultDto"}},"summaryByType":{"type":"object","description":"Summary by entity type","example":{"ROOM":{"checked":10,"violated":2},"TENANT":{"checked":5,"violated":1},"USER":{"checked":20,"violated":3}}}},"required":["systemId","checkDate","totalEntitiesChecked","totalViolationsDetected","checkResults","summaryByType"]},"FlaggedEntityDigestResultDto":{"type":"object","properties":{"entityId":{"type":"string","description":"Entity ID"},"entityName":{"type":"string","description":"Entity name"},"currentPeriodViolationCount":{"type":"number","description":"Current period violation count"},"totalFlaggedCount":{"type":"number","description":"Total lifetime flagged count"},"thresholdKwh":{"type":"number","description":"Threshold in kWh"},"lastViolationDate":{"format":"date-time","type":"string","description":"Last violation date"}},"required":["entityId","entityName","currentPeriodViolationCount","totalFlaggedCount","thresholdKwh","lastViolationDate"]},"SurgeDigestResultDto":{"type":"object","properties":{"systemId":{"type":"string","description":"System ID"},"entityType":{"description":"Entity type for this digest","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"startDate":{"format":"date-time","type":"string","description":"Rolling window period start date"},"endDate":{"format":"date-time","type":"string","description":"Rolling window period end date"},"rollingWindowDays":{"type":"number","description":"Rolling window in days"},"totalFlaggedEntities":{"type":"number","description":"Total flagged entities in this digest"},"totalViolations":{"type":"number","description":"Total violations across all flagged entities"},"flaggedEntities":{"description":"List of flagged entities included in digest","type":"array","items":{"$ref":"#/components/schemas/FlaggedEntityDigestResultDto"}},"notificationCount":{"type":"number","description":"How many recipients this digest was written to — one notification document each"}},"required":["systemId","entityType","startDate","endDate","rollingWindowDays","totalFlaggedEntities","totalViolations","flaggedEntities","notificationCount"]},"EntityTypeKind":{"type":"string","enum":["ROOM","TENANT","ZONE"]},"EntityTypeResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Entity type ID"},"name":{"type":"string","description":"Unique type key","example":"MEETING_ROOM"},"displayName":{"type":"string","description":"Human-readable display name","example":"Meeting Room"},"system":{"type":"string","description":"System ID this type belongs to"},"createdBy":{"type":"string","description":"Admin who created this type"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"}},"required":["_id","name","displayName","system","createdAt"]},"EntityTypeListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EntityTypeResponseDto"}},"total":{"type":"number","example":5}},"required":["data","total"]},"CreateEntityTypeDto":{"type":"object","properties":{"name":{"type":"string","description":"Unique type key — must be UPPER_SNAKE_CASE, unique within the system","example":"MEETING_ROOM"},"displayName":{"type":"string","description":"Human-readable display name","example":"Meeting Room"}},"required":["name","displayName"]},"UpdateEntityTypeDto":{"type":"object","properties":{"displayName":{"type":"string","description":"Human-readable display name","example":"Meeting Room"}},"required":["displayName"]},"SingleScheduleEntryDto":{"type":"object","properties":{"dateTime":{"type":"string","description":"Single scheduled date and time in ISO 8601 format when the one-time action should execute. Cannot be used with startDateTime/endDateTime. Must be a future timestamp.","example":"2024-12-31T06:00:00Z","format":"date-time"},"startDateTime":{"type":"string","description":"Start date and time of the schedule range in ISO 8601 format. Must be used with endDateTime. Cannot be used with dateTime. Creates daily timers between start and end dates (inclusive) at the specified time.","example":"2024-12-20T06:00:00Z","format":"date-time"},"endDateTime":{"type":"string","description":"End date and time of the schedule range in ISO 8601 format. Must be used with startDateTime. Cannot be used with dateTime. Creates daily timers between start and end dates (inclusive) at the specified time.","example":"2024-12-31T06:00:00Z","format":"date-time"},"action":{"description":"Action type to perform at the scheduled date-time(s). For simple ON/OFF or complex actions like SET_TEMPERATURE.","example":"OFF","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"actionValue":{"type":"number","description":"Optional value for actions that require it (e.g., temperature value for SET_TEMPERATURE, brightness for SET_BRIGHTNESS). Required for certain action types.","example":24},"ruleName":{"type":"string","description":"AWS EventBridge rule name associated with this schedule entry. Used for managing and updating the schedule.","example":"device-64b7f1e6-timer-abc123"}},"required":["action"]},"DailyScheduleEntryDto":{"type":"object","properties":{"time":{"type":"string","description":"Time when the scheduled action should occur in 24-hour format (HH:MM). Applied to recurring weekly schedules.","example":"06:00"},"action":{"description":"Action type to perform at the scheduled time. For simple ON/OFF or complex actions like SET_TEMPERATURE.","example":"OFF","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"actionValue":{"type":"number","description":"Optional value for actions that require it (e.g., temperature value for SET_TEMPERATURE, brightness for SET_BRIGHTNESS). Required for certain action types.","example":24}},"required":["time","action"]},"DeviceWeeklyScheduleDto":{"type":"object","properties":{"oneTimeSchedules":{"description":"Array of one-time scheduled actions that occur at specific date-times. These schedules execute once and are automatically removed.","type":"array","items":{"$ref":"#/components/schemas/SingleScheduleEntryDto"}},"monday":{"description":"Array of scheduled actions for Monday. These execute weekly at specified times.","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"tuesday":{"description":"Array of scheduled actions for Tuesday. These execute weekly at specified times.","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"wednesday":{"description":"Array of scheduled actions for Wednesday. These execute weekly at specified times.","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"thursday":{"description":"Array of scheduled actions for Thursday. These execute weekly at specified times.","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"friday":{"description":"Array of scheduled actions for Friday. These execute weekly at specified times.","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"saturday":{"description":"Array of scheduled actions for Saturday. These execute weekly at specified times.","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"sunday":{"description":"Array of scheduled actions for Sunday. These execute weekly at specified times.","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}}}},"FollowScheduleDto":{"type":"object","properties":{"scheduleId":{"type":"string","description":"Id of the Schedule the device should follow, or null to follow nothing.","example":"507f1f77bcf86cd799439011","nullable":true}}},"DayOfWeek":{"type":"string","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"description":"Day of the week this time range applies to"},"DayTimeRange":{"type":"object","properties":{"day":{"description":"Day of the week this time range applies to","example":"monday","allOf":[{"$ref":"#/components/schemas/DayOfWeek"}]},"startTime":{"type":"string","description":"Start time (device turns ON) in HH:mm format. Absent for an OFF-only entry.","example":"08:00"},"endTime":{"type":"string","description":"End time (device turns OFF) in HH:mm format. Absent for an ON-only entry.","example":"18:00"},"action":{"description":"Action to run at this entry's time, for actions beyond plain ON/OFF (e.g. SET_TEMPERATURE, SET_MODE, SET_BRIGHTNESS). Only valid on a single-point entry (startTime or endTime, not both); defaults to ON/OFF when absent.","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"actionValue":{"description":"Value for actions that require one (e.g. temperature for SET_TEMPERATURE, brightness for SET_BRIGHTNESS, mode for SET_MODE).","oneOf":[{"type":"number"},{"type":"string"}],"example":24}},"required":["day"]},"Schedule":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Schedule","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"system":{"type":"string","description":"System this schedule belongs to"},"name":{"type":"string","description":"User-provided name","example":"Office hours"},"dailyTimes":{"description":"Per-day ON/OFF time ranges. A day may have multiple ranges; days absent have no schedule.","type":"array","items":{"$ref":"#/components/schemas/DayTimeRange"}},"executionMode":{"description":"Where this schedule executes on every device that follows it. CLOUD = AWS EventBridge rules fired by the backend (device must be online). ON_DEVICE = compiled into an MQTT timer payload stored in firmware, so it keeps running while the device is offline. Followers inherit this, and changing it re-applies the schedule to all of them. A follower whose subtype cannot store firmware timers falls back to CLOUD.","allOf":[{"$ref":"#/components/schemas/ScheduleExecutionMode"}]},"tenantId":{"type":"string","description":"Tenant this schedule is scoped to (shared-schedule tag)."},"zoneId":{"type":"string","description":"Zone this schedule is scoped to (shared-schedule tag)."},"roomId":{"type":"string","description":"Room this schedule is scoped to (shared-schedule tag)."},"createdBy":{"type":"string","description":"Account that created this schedule. Null for schedules created via an API token or a system process."}},"required":["_id","system","name","dailyTimes"]},"DeviceSchedulingOptionsDto":{"type":"object","properties":{"followedScheduleId":{"type":"string","description":"Id of the Schedule this device currently follows, or null if it follows nothing.","example":"507f1f77bcf86cd799439011","nullable":true},"schedules":{"description":"All schedules visible to this device by scope.","type":"array","items":{"$ref":"#/components/schemas/Schedule"}},"executionMode":{"description":"Where this device’s weekly schedule executes. CLOUD when the backend fires it via EventBridge, ON_DEVICE when firmware runs it from a stored MQTT timer payload. Taken from the followed schedule while the device follows one (the schedule owns the mode), otherwise from the device’s own stored schedule; CLOUD for a device with no schedule yet. Always reflects the mode actually in effect — if the schedule asks for ON_DEVICE but this subtype cannot store timers, this reads CLOUD and fellBackToCloud is true.","allOf":[{"$ref":"#/components/schemas/ScheduleExecutionMode"}]},"supportsOnDeviceSchedule":{"type":"boolean","description":"Whether this device’s firmware can run an on-device schedule at all. When false the frontend must hide the on-device option — POST .../offline-timer rejects with 400.","example":true},"confirmationState":{"description":"Ack state of the on-device schedule: PENDING until the device confirms receipt over MQTT. Null when executionMode is CLOUD.","nullable":true,"allOf":[{"$ref":"#/components/schemas/ScheduleConfirmationState"}]},"fellBackToCloud":{"type":"boolean","description":"True when the followed schedule asks for ON_DEVICE but this device’s subtype cannot store firmware timers, so it is being run from the cloud instead. The frontend should show the schedule as online-only for this device.","example":false}},"required":["followedScheduleId","schedules","executionMode","supportsOnDeviceSchedule","confirmationState","fellBackToCloud"]},"DayTimeRangeDto":{"type":"object","properties":{"day":{"description":"Day of the week this time range applies to","example":"monday","allOf":[{"$ref":"#/components/schemas/DayOfWeek"}]},"startTime":{"type":"string","description":"Start time (device turns ON) in HH:mm format. Omit for an OFF-only entry.","example":"08:00"},"endTime":{"type":"string","description":"End time (device turns OFF) in HH:mm format. Omit for an ON-only entry.","example":"18:00"},"action":{"description":"Action to run at this entry's time, for actions beyond plain ON/OFF (e.g. SET_TEMPERATURE, SET_MODE, SET_BRIGHTNESS). Only valid on a single-point entry (startTime or endTime, not both); defaults to ON/OFF when absent.","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"actionValue":{"description":"Value for actions that require one (e.g. temperature for SET_TEMPERATURE, brightness for SET_BRIGHTNESS, mode for SET_MODE). Required when action is one of those types.","oneOf":[{"type":"number"},{"type":"string"}],"example":24}},"required":["day"]},"ScheduleInputDto":{"type":"object","properties":{"name":{"type":"string","description":"User-provided name","example":"Office hours"},"dailyTimes":{"description":"Per-day ON/OFF time ranges. A day may have multiple ranges; days absent have no schedule.","type":"array","items":{"$ref":"#/components/schemas/DayTimeRangeDto"}},"executionMode":{"description":"Where this schedule executes on every device that follows it. CLOUD (default) = EventBridge rules fired by the backend. ON_DEVICE = stored in firmware over MQTT so it survives the device going offline. Followers whose subtype cannot store firmware timers fall back to CLOUD.","default":"CLOUD","allOf":[{"$ref":"#/components/schemas/ScheduleExecutionMode"}]},"tenantId":{"type":"string","description":"Tenant this schedule is scoped to","example":"507f1f77bcf86cd799439011"},"zoneId":{"type":"string","description":"Zone this schedule is scoped to","example":"507f1f77bcf86cd799439012"},"roomId":{"type":"string","description":"Room this schedule is scoped to","example":"507f1f77bcf86cd799439013"}},"required":["name","dailyTimes"]},"DeviceSingleScheduleDto":{"type":"object","properties":{"oneTimeSchedules":{"description":"Array of one-time scheduled actions with specific date-times. These schedules execute once and are automatically removed after execution.","type":"array","items":{"$ref":"#/components/schemas/SingleScheduleEntryDto"}}}},"EditDeviceSingleTimerDto":{"type":"object","properties":{"ruleName":{"type":"string","description":"AWS EventBridge rule name of the one-time timer to edit. Used to identify which schedule to update.","example":"device-64b7f1e6-timer-abc123"},"dateTime":{"format":"date-time","type":"string","description":"Updated scheduled date and time for the timer in ISO 8601 format. Must be a future timestamp.","example":"2024-12-31T18:00:00Z"},"action":{"description":"Updated action type to perform at the scheduled time. Replaces the existing action in the timer.","example":"ON","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"actionValue":{"type":"number","description":"Optional value for actions that require it (e.g., temperature value for SET_TEMPERATURE, brightness for SET_BRIGHTNESS). Required for certain action types.","example":24}},"required":["ruleName","dateTime","action"]},"DeleteDeviceSingleTimerDto":{"type":"object","properties":{"ruleName":{"type":"string","description":"AWS EventBridge rule name of the one-time timer to delete. Used to identify which schedule to remove.","example":"device-64b7f1e6-timer-abc123"}},"required":["ruleName"]},"MergeMode":{"type":"string","enum":["replace","merge","append"],"description":"Defines how schedules should be merged. REPLACE: Clear all existing schedules and set new ones (default). MERGE: Add new schedules to existing ones, updating duplicates. APPEND: Add new schedules without modifying existing ones."},"MassDevicesScheduleDto":{"type":"object","properties":{"deviceSchedule":{"description":"Schedule configuration containing weekly recurring and one-time schedules to apply to multiple devices.","allOf":[{"$ref":"#/components/schemas/DeviceWeeklyScheduleDto"}]},"deviceIds":{"description":"Array of unique device identifiers to apply the schedule configuration to. Each device will receive the same schedule.","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"mergeMode":{"default":"replace","description":"Defines how schedules should be merged. REPLACE: Clear all existing schedules and set new ones (default). MERGE: Add new schedules to existing ones, updating duplicates. APPEND: Add new schedules without modifying existing ones.","example":"merge","allOf":[{"$ref":"#/components/schemas/MergeMode"}]},"executionMode":{"default":"CLOUD","description":"Where the weekly schedule executes on each device. CLOUD (default): EventBridge rules fired by the backend. ON_DEVICE: compiled into an MQTT timer stored in firmware so it survives the device going offline — only ON/OFF actions are allowed, one-time schedules stay cloud-only, mergeMode must be REPLACE, and every target device type must support on-device schedules.","example":"ON_DEVICE","allOf":[{"$ref":"#/components/schemas/ScheduleExecutionMode"}]}},"required":["deviceIds"]},"MassDevicesSingleScheduleDto":{"type":"object","properties":{"oneTimeSchedules":{"description":"Array of one-time scheduled actions with specific date-times to apply to multiple devices.","type":"array","items":{"$ref":"#/components/schemas/SingleScheduleEntryDto"}},"deviceIds":{"description":"Array of unique device identifiers to apply the one-time schedules to. Each device will receive the same schedule.","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}}},"required":["deviceIds"]},"DeviceScheduleInfo":{"type":"object","properties":{"ruleName":{"type":"string","description":"AWS EventBridge rule name associated with this device schedule. Used for identifying and managing the schedule.","example":"device-64b7f1e6-timer-abc123"},"deviceId":{"type":"string","description":"Unique identifier of the device in MongoDB ObjectId format.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"deviceName":{"type":"string","description":"Display name of the device for identification purposes in the UI.","example":"Living Room Smart Plug"}},"required":["ruleName","deviceId","deviceName"]},"MassEditDeviceSingleTimerDto":{"type":"object","properties":{"devices":{"description":"Array of device details with EventBridge rule names for the one-time timers to batch edit. All specified timers will be updated with the same new schedule.","example":[{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c1","deviceName":"Living Room TV Plug","ruleName":"device-64b7f1e6-timer-abc123"},{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c2","deviceName":"Kitchen Appliance Plug","ruleName":"device-64b7f1e6-timer-def456"}],"type":"array","items":{"$ref":"#/components/schemas/DeviceScheduleInfo"}},"newDateTime":{"format":"date-time","type":"string","description":"New scheduled date and time to apply to all specified timers. Must be a future timestamp in ISO 8601 format.","example":"2024-12-31T20:00:00Z"},"newAction":{"description":"New action type to apply to all specified timers at the scheduled time. Replaces existing actions.","example":"OFF","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"newActionValue":{"type":"number","description":"Optional value for the new action (e.g., temperature for SET_TEMPERATURE, brightness for SET_BRIGHTNESS). Required for certain action types.","example":24}},"required":["devices","newDateTime","newAction"]},"MassEditDeviceWeeklyTimerDto":{"type":"object","properties":{"devices":{"description":"Array of device details with EventBridge rule names for the recurring weekly timers to batch edit. All specified timers will be updated.","example":[{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c1","deviceName":"Living Room TV Plug","ruleName":"device-64b7f1e6-weekly-abc123"},{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c2","deviceName":"Kitchen Appliance Plug","ruleName":"device-64b7f1e6-weekly-def456"}],"type":"array","items":{"$ref":"#/components/schemas/DeviceScheduleInfo"}},"newTime":{"type":"string","description":"New time in 24-hour format (HH:MM) to apply to all specified weekly timers. Must be valid time between 00:00 and 23:59.","example":"20:00"},"newAction":{"description":"New action type to apply to all specified weekly timers at the scheduled time. Replaces existing actions.","example":"OFF","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"currentTime":{"type":"string","description":"Current time (HH:MM) of the timers being edited. Required when any target device runs its schedule in firmware (executionMode ON_DEVICE): those entries have no EventBridge ruleName, so they are matched by their existing time instead. Ignored for cloud timers, which are matched by ruleName.","example":"19:00"},"newActionValue":{"type":"number","description":"Optional value for the new action (e.g., temperature for SET_TEMPERATURE, brightness for SET_BRIGHTNESS). Required for certain action types.","example":24},"daysOfWeek":{"type":"array","description":"Optional filter to only edit timers on specific days of the week. If provided, only rules matching these days will be updated. If omitted, all provided rules will be updated (backward compatible).","example":["monday","wednesday","friday"],"items":{"type":"string","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]}}},"required":["devices","newTime","newAction"]},"MassDeleteDeviceTimerDto":{"type":"object","properties":{"devices":{"description":"Array of device details with EventBridge rule names for the timers to batch delete. Supports both one-time and recurring weekly schedules.","example":[{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c1","deviceName":"Living Room TV Plug","ruleName":"device-64b7f1e6-timer-abc123"},{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c2","deviceName":"Kitchen Appliance MCB","ruleName":"device-64b7f1e6-weekly-def456"}],"type":"array","items":{"$ref":"#/components/schemas/DeviceScheduleInfo"}}},"required":["devices"]},"GroupedRecurringSchedule":{"type":"object","properties":{"time":{"type":"string","description":"Scheduled time in 24-hour format (HH:MM) when the recurring action executes. Shared by all devices in this group.","example":"06:00"},"action":{"description":"Action type to perform at the scheduled time. All devices in this group will execute the same action.","example":"ON","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"actionValue":{"type":"number","description":"Optional value for actions that require it (e.g., temperature, brightness). All devices in this group will use the same value.","example":24},"daysOfWeek":{"description":"Array of weekday names when this recurring schedule is active. Schedule repeats weekly on these days.","example":["monday","tuesday","wednesday"],"type":"array","items":{"type":"string"}},"deviceCount":{"type":"number","description":"Total number of devices that share this identical recurring schedule pattern.","example":50},"devices":{"description":"Array of device details with their EventBridge rule names. Each device has this same recurring schedule configured.","example":[{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c1","deviceName":"Living Room TV Plug","ruleName":"device-64b7f1e6-schedule-abc123"},{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c2","deviceName":"Kitchen MCB","ruleName":"device-64b7f1e6-schedule-def456"}],"type":"array","items":{"$ref":"#/components/schemas/DeviceScheduleInfo"}}},"required":["time","action","daysOfWeek","deviceCount","devices"]},"GroupedOneTimeSchedule":{"type":"object","properties":{"dateTime":{"format":"date-time","type":"string","description":"Scheduled date and time when this one-time action will execute. Shared by all devices in this group.","example":"2024-12-31T18:00:00Z"},"action":{"description":"Action type to perform at the scheduled date-time. All devices in this group will execute the same action.","example":"OFF","allOf":[{"$ref":"#/components/schemas/ScheduledActionType"}]},"actionValue":{"type":"number","description":"Optional value for actions that require it (e.g., temperature, brightness). All devices in this group will use the same value.","example":24},"deviceCount":{"type":"number","description":"Total number of devices that share this identical one-time schedule configuration.","example":10},"devices":{"description":"Array of device details with their EventBridge rule names. Each device has this same one-time schedule configured.","example":[{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c1","deviceName":"Living Room TV Plug","ruleName":"device-64b7f1e6-timer-abc123"},{"deviceId":"64b7f1e6f9c9d4e5b8a9f7c2","deviceName":"Kitchen Appliance MCB","ruleName":"device-64b7f1e6-timer-def456"}],"type":"array","items":{"$ref":"#/components/schemas/DeviceScheduleInfo"}}},"required":["dateTime","action","deviceCount","devices"]},"GetSystemGroupedSchedulesDto":{"type":"object","properties":{"recurring":{"description":"Array of recurring weekly schedules grouped by time and action. Shows which devices share the same recurring schedule pattern.","type":"array","items":{"$ref":"#/components/schemas/GroupedRecurringSchedule"}},"oneTime":{"description":"Array of one-time schedules grouped by date-time and action. Shows which devices share the same one-time schedule.","type":"array","items":{"$ref":"#/components/schemas/GroupedOneTimeSchedule"}},"totalDevices":{"type":"number","description":"Total count of all schedulable devices in the system (includes devices with and without schedules). Only counts device types that support scheduling.","example":100},"devicesWithSchedules":{"type":"number","description":"Count of devices that have at least one active schedule configured. Used to calculate scheduling adoption rate.","example":75}},"required":["recurring","oneTime","totalDevices","devicesWithSchedules"]},"SortOrder":{"type":"string","enum":["asc","desc"]},"SuggestionFilter":{"type":"string","enum":["ready","all"]},"SuggestionSource":{"type":"string","enum":["manual_override","ascent"]},"ScheduleAdjustmentStatus":{"type":"string","enum":["monitoring","ready_for_review","ready_for_auto_apply","pending","accepted","rejected","auto_applied","auto_apply_failed","superseded"]},"AdjustmentType":{"type":"string","enum":["extend_start","extend_end","fill_gap","add_block","shrink_block","split_block","full_schedule"],"description":"Type of adjustment suggested"},"OverrideAction":{"type":"string","enum":["ON","OFF"],"description":"Override-only. What the user did. Unset on ASCENT rows."},"ScheduleMapDto":{"type":"object","properties":{"monday":{"description":"Monday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"tuesday":{"description":"Tuesday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"wednesday":{"description":"Wednesday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"thursday":{"description":"Thursday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"friday":{"description":"Friday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"saturday":{"description":"Saturday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"sunday":{"description":"Sunday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}}},"required":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]},"RecommendationTier":{"type":"string","enum":["rule-based","clustering","llm"],"description":"ASCENT-only. Which analysis tier produced the schedule. Only RULE_BASED rows are eligible for auto-apply, so this explains why an otherwise-confident suggestion still needs review."},"ScheduleAdjustmentSuggestionDto":{"type":"object","properties":{"_id":{"type":"string","description":"Suggestion ID","example":"65a1b2c3d4e5f6a7b8c9d0e1"},"device":{"type":"string","description":"Device ID","example":"65a1b2c3d4e5f6a7b8c9d0e2"},"deviceName":{"type":"string","description":"Device name","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Device number","example":12},"roomName":{"type":"string","description":"Room name the device belongs to","example":"Living Room"},"deviceType":{"description":"Device type","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"source":{"description":"What produced this suggestion. MANUAL_OVERRIDE rows carry the override fields below and describe a boundary change; ASCENT rows come from the weekly telemetry pass, use adjustmentType FULL_SCHEDULE, and leave every override field unset.","example":"manual_override","allOf":[{"$ref":"#/components/schemas/SuggestionSource"}]},"system":{"type":"string","description":"System ID","example":"65a1b2c3d4e5f6a7b8c9d0e3"},"adjustmentType":{"description":"Type of adjustment suggested","example":"extend_start","allOf":[{"$ref":"#/components/schemas/AdjustmentType"}]},"affectedDays":{"type":"array","description":"Which day(s) this adjustment applies to. For ASCENT rows this lists only the weekdays whose entries actually changed, not all seven.","example":["monday","tuesday","wednesday","thursday","friday"],"items":{"$ref":"#/components/schemas/DayOfWeek"}},"overrideTime":{"type":"string","description":"Override-only. Time when the user triggered this override. Unset on ASCENT rows.","example":"08:15"},"overrideAction":{"description":"Override-only. What the user did. Unset on ASCENT rows.","example":"ON","allOf":[{"$ref":"#/components/schemas/OverrideAction"}]},"currentSchedule":{"description":"Current schedule before adjustment","allOf":[{"$ref":"#/components/schemas/ScheduleMapDto"}]},"suggestedSchedule":{"description":"Suggested adjusted schedule","allOf":[{"$ref":"#/components/schemas/ScheduleMapDto"}]},"status":{"description":"Status of this suggestion","example":"monitoring","allOf":[{"$ref":"#/components/schemas/ScheduleAdjustmentStatus"}]},"reviewedAt":{"format":"date-time","type":"string","description":"When user responded to this","example":"2024-02-13T10:30:00.000Z"},"reviewedByUser":{"type":"string","description":"User who reviewed this","example":"65a1b2c3d4e5f6a7b8c9d0e4"},"occurrenceCount":{"type":"number","description":"Override-only. Number of times this pattern was detected. Unset on ASCENT rows.","example":5},"firstOccurrence":{"format":"date-time","type":"string","description":"First time this pattern was detected. On ASCENT rows this is the run timestamp.","example":"2024-02-08T08:12:00.000Z"},"lastOccurrence":{"format":"date-time","type":"string","description":"Most recent time this pattern was detected. On ASCENT rows this is the run timestamp.","example":"2024-02-13T08:18:00.000Z"},"occurrenceTimes":{"description":"Override-only. All override times that contributed to this pattern. Empty on ASCENT rows.","example":["08:12","08:17","08:20","08:15","08:18"],"type":"array","items":{"type":"string"}},"earliestOverrideTime":{"type":"string","description":"Override-only. Earliest time from all occurrences. Unset on ASCENT rows.","example":"08:12"},"latestOverrideTime":{"type":"string","description":"Override-only. Latest time from all occurrences. Unset on ASCENT rows.","example":"08:20"},"timeRangeMinutes":{"type":"number","description":"Override-only. Time range in minutes (latest - earliest). Unset on ASCENT rows.","example":8},"confidenceScore":{"type":"number","description":"Confidence score 0-1. MANUAL_OVERRIDE derives it from occurrence count and time range; ASCENT uses the pattern confidence it scored the device at.","example":0.85},"deltaMinutes":{"type":"number","description":"Signed ON-minutes change if the suggestion is applied. Positive = suggested schedule has less ON time (e.g. turn off earlier); negative = more ON time (e.g. turn on earlier).","example":45},"tier":{"description":"ASCENT-only. Which analysis tier produced the schedule. Only RULE_BASED rows are eligible for auto-apply, so this explains why an otherwise-confident suggestion still needs review.","allOf":[{"$ref":"#/components/schemas/RecommendationTier"}]},"reasons":{"description":"Human-readable justification for the confidence score. On ASCENT rows: Ascent's confidence reasons plus its rule-based flags. Show these next to the score — a bare percentage is not actionable.","example":["28 days analysed, 15-min slots","Clear daily pattern","Weekends off","Confidence 82%"],"type":"array","items":{"type":"string"}},"createdAt":{"format":"date-time","type":"string","description":"When this suggestion was created","example":"2024-02-08T08:12:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"When this suggestion was last updated","example":"2024-02-13T08:18:00.000Z"}},"required":["_id","device","deviceType","source","system","adjustmentType","affectedDays","currentSchedule","suggestedSchedule","status","confidenceScore","createdAt","updatedAt"]},"SuggestionStatusCountsDto":{"type":"object","properties":{"monitoring":{"type":"number","example":12},"readyForReview":{"type":"number","example":4},"readyForAutoApply":{"type":"number","example":2},"accepted":{"type":"number","example":30},"rejected":{"type":"number","example":7},"autoApplied":{"type":"number","example":5},"autoApplyFailed":{"type":"number","example":1}},"required":["monitoring","readyForReview","readyForAutoApply","accepted","rejected","autoApplied","autoApplyFailed"]},"GetSystemSuggestionsResponseDto":{"type":"object","properties":{"data":{"description":"Every suggestion matching the filters, newest first unless sorted. Narrow with `status`, `filter`, `source`, `deviceId` or `search`.","type":"array","items":{"$ref":"#/components/schemas/ScheduleAdjustmentSuggestionDto"}},"total":{"type":"number","description":"Rows returned","example":42},"counts":{"description":"Totals per status across the whole filtered set","allOf":[{"$ref":"#/components/schemas/SuggestionStatusCountsDto"}]}},"required":["data","total","counts"]},"GetDeviceSuggestionsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total number of suggestions","example":3},"suggestions":{"description":"List of suggestions for the device","type":"array","items":{"$ref":"#/components/schemas/ScheduleAdjustmentSuggestionDto"}}},"required":["total","suggestions"]},"AcceptSuggestionRequestDto":{"type":"object","properties":{"days":{"type":"array","description":"Days to apply. Days listed are taken from the suggested schedule; days omitted keep their current schedule untouched and are never emptied. Omit to apply every affected day.","example":["monday","tuesday"],"items":{"type":"string","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]}}}},"AcceptSuggestionResponseDto":{"type":"object","properties":{"message":{"type":"string","description":"Success message","example":"Suggestion accepted and applied successfully"},"suggestion":{"description":"The accepted suggestion","allOf":[{"$ref":"#/components/schemas/ScheduleAdjustmentSuggestionDto"}]},"deviceId":{"type":"string","description":"Device ID"},"deviceName":{"type":"string","description":"Device name"}},"required":["message","suggestion","deviceId","deviceName"]},"RejectSuggestionResponseDto":{"type":"object","properties":{"message":{"type":"string","description":"Success message","example":"Suggestion rejected successfully"},"suggestion":{"description":"The rejected suggestion","allOf":[{"$ref":"#/components/schemas/ScheduleAdjustmentSuggestionDto"}]}},"required":["message","suggestion"]},"BulkSuggestionsRequestDto":{"type":"object","properties":{"suggestionIds":{"description":"Suggestion IDs to process","example":["65a1b2c3d4e5f6a7b8c9d0e1","65a1b2c3d4e5f6a7b8c9d0e2"],"type":"array","items":{"type":"string"}},"days":{"type":"object","description":"Days to apply, keyed by suggestion ID. Days listed are taken from the suggested schedule; days omitted keep their current schedule untouched and are never emptied. A suggestion missing from this map applies all of its affected days. Accept-only — ignored by bulk-reject.","example":{"65a1b2c3d4e5f6a7b8c9d0e1":["monday","tuesday"]},"additionalProperties":{"type":"array","items":{"type":"string","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]}}}},"required":["suggestionIds"]},"BulkSuggestionFailureDto":{"type":"object","properties":{"suggestionId":{"type":"string","description":"Suggestion ID that failed to process","example":"65a1b2c3d4e5f6a7b8c9d0e1"},"reason":{"type":"string","description":"Failure reason","example":"Suggestion 65a1b2c3d4e5f6a7b8c9d0e1 not found"}},"required":["suggestionId","reason"]},"BulkSuggestionsResponseDto":{"type":"object","properties":{"message":{"type":"string","description":"Summary message","example":"3 suggestion(s) accepted, 1 failed"},"processedCount":{"type":"number","description":"Number of suggestions successfully processed","example":3},"failed":{"description":"Suggestions that could not be processed","type":"array","items":{"$ref":"#/components/schemas/BulkSuggestionFailureDto"}}},"required":["message","processedCount","failed"]},"InvoiceItemDto":{"type":"object","properties":{"product":{"type":"string","description":"Reference to the product ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"quantity":{"type":"number","description":"Quantity purchased","example":3}},"required":["product","quantity"]},"CreateInvoiceDto":{"type":"object","properties":{"systemId":{"type":"string","description":"System ID associated with the invoice","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"tenantId":{"type":"string","description":"Tenant ID associated with the invoice","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"invoiceDate":{"type":"string","description":"Invoice date (defaults to current date)","example":"2025-12-05T12:00:00Z"},"dueDate":{"type":"string","description":"Due date for the invoice","example":"2026-01-05T12:00:00Z"},"items":{"description":"Array of invoice line items","type":"array","items":{"$ref":"#/components/schemas/InvoiceItemDto"}},"taxRate":{"type":"number","description":"Tax rate as a percentage (e.g., 10 for 10%)","example":10},"discountAmount":{"type":"number","description":"Discount amount applied to the invoice","example":10},"notes":{"type":"string","description":"Additional notes","example":"Thank you for your business"}},"required":["dueDate","items"]},"BillingType":{"type":"string","enum":["TENANT","ZONE","USER"]},"InvoiceStatus":{"type":"string","enum":["PENDING","PAID","OVERDUE","CANCELED"],"description":"Current payment status"},"Product":{"type":"object","properties":{"_id":{"type":"string","description":"The unique identifier of the subscription tier","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"price":{"type":"number","description":"Price of product","example":9.99},"name":{"type":"string","description":"Name of product","enum":["PLUG","MCB","ENV_SENSOR","SMART_SWITCH","AIRCON_CONTROL","WATER_METER","RIDGE","EXTERNAL","API_CALL","INSTALLATION_FEE","MAINTENANCE_FEE","CONSULTATION_FEE"]},"description":{"type":"string","description":"Description of the product","example":"A smart plug that can be controlled remotely"},"createdAt":{"format":"date-time","type":"string","description":"The date when the product was created","example":"2023-08-01T12:00:00Z"},"updatedAt":{"format":"date-time","type":"string","description":"The date when the product was last updated","example":"2023-08-01T12:00:00Z"}},"required":["_id","price","name","description","createdAt","updatedAt"]},"SystemInvoiceItem":{"type":"object","properties":{"product":{"description":"Reference to the product","example":"64b7f1e6f9c9d4e5b8a9f7c3","allOf":[{"$ref":"#/components/schemas/Product"}]},"quantity":{"type":"number","description":"Quantity purchased","example":3}},"required":["product","quantity"]},"SystemInvoice":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"billingType":{"example":"TENANT","allOf":[{"$ref":"#/components/schemas/BillingType"}]},"system":{"$ref":"#/components/schemas/System"},"tenant":{"$ref":"#/components/schemas/Tenant"},"zone":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"user":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"invoiceNumber":{"type":"string","description":"Unique invoice number for tracking and reference","example":"INV-2026-01-001"},"invoiceDate":{"format":"date-time","type":"string","description":"Invoice date","example":"2026-01-01T00:00:00Z"},"dueDate":{"format":"date-time","type":"string","description":"Due date for payment","example":"2026-01-31T23:59:59Z"},"subtotal":{"type":"number","description":"Subtotal before tax","example":100},"taxRate":{"type":"number","description":"Tax rate percentage","example":9},"tax":{"type":"number","description":"Tax amount (subtotal x taxRate / 100)","example":9},"total":{"type":"number","description":"Final total amount","example":109},"status":{"description":"Current payment status","example":"PENDING","allOf":[{"$ref":"#/components/schemas/InvoiceStatus"}]},"isDeleted":{"type":"boolean","description":"Soft delete flag","example":false},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"items":{"description":"Array of invoice line items","type":"array","items":{"$ref":"#/components/schemas/SystemInvoiceItem"}},"discount":{"type":"number","description":"Discount amount","example":10},"notes":{"type":"string","description":"Additional notes","example":"Thank you for your business"}},"required":["_id","billingType","system","invoiceNumber","invoiceDate","dueDate","subtotal","taxRate","tax","total","status","isDeleted","createdAt","updatedAt","items","discount","notes"]},"UpdateInvoiceDiscountDto":{"type":"object","properties":{"discountAmount":{"type":"number","description":"Discount amount to be applied to the invoice","example":50}},"required":["discountAmount"]},"UpdateInvoiceStatusDto":{"type":"object","properties":{"status":{"description":"New status of the invoice","example":"PAID","allOf":[{"$ref":"#/components/schemas/InvoiceStatus"}]}},"required":["status"]},"UpdateInvoiceNotesDto":{"type":"object","properties":{"notes":{"type":"string","description":"Notes for the invoice","example":"Payment received via bank transfer. Reference: TXN123456","maxLength":1000}},"required":["notes"]},"InvoiceItemInput":{"type":"object","properties":{"product":{"type":"string","description":"Reference to the product ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"quantity":{"type":"number","description":"Quantity purchased","example":5}},"required":["product","quantity"]},"UpdateInvoiceItemsDto":{"type":"object","properties":{"items":{"description":"Array of invoice items to replace the existing items list. Each item requires a product ID and quantity.","example":[{"product":"64b7f1e6f9c9d4e5b8a9f7c3","quantity":5},{"product":"64b7f1e6f9c9d4e5b8a9f7c4","quantity":10}],"type":"array","items":{"$ref":"#/components/schemas/InvoiceItemInput"}}},"required":["items"]},"TriggerInvoiceGenerationDto":{"type":"object","properties":{"systemId":{"type":"string","description":"System ID to generate invoice for","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"from":{"format":"date-time","type":"string","description":"Start of billing period","example":"2025-11-01T00:00:00Z"},"to":{"format":"date-time","type":"string","description":"End of billing period","example":"2025-11-30T23:59:59Z"},"taxRate":{"type":"number","description":"Tax rate percentage (0-100)","example":8,"default":0}},"required":["systemId","from","to"]},"GenerateInvoiceResultDto":{"type":"object","properties":{"systemId":{"type":"string","description":"System ID for which the invoice was generated","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"System name","example":"National University of Singapore"},"success":{"type":"boolean","description":"Whether invoice generation was successful","example":true},"invoice":{"description":"Generated invoice (if successful)","allOf":[{"$ref":"#/components/schemas/SystemInvoice"}]},"error":{"type":"string","description":"Error message (if failed)","example":"No billable items found for this system"},"plugCount":{"type":"number","description":"Number of plugs billed","example":10},"apiCallCount":{"type":"number","description":"Number of API calls billed","example":500}},"required":["systemId","systemName","success","plugCount","apiCallCount"]},"TriggerBulkInvoiceGenerationDto":{"type":"object","properties":{"taxRate":{"type":"number","description":"Tax rate percentage (0-100)","example":8,"default":0}}},"BulkInvoiceGenerationResultDto":{"type":"object","properties":{"totalSystems":{"type":"number","description":"Total number of systems processed","example":10},"results":{"description":"Results for each system","type":"array","items":{"$ref":"#/components/schemas/GenerateInvoiceResultDto"}},"billingPeriodStart":{"format":"date-time","type":"string","description":"Billing period start date","example":"2025-11-01T00:00:00Z"},"billingPeriodEnd":{"format":"date-time","type":"string","description":"Billing period end date","example":"2025-11-30T23:59:59Z"},"executedAt":{"format":"date-time","type":"string","description":"Timestamp when the cron job ran","example":"2025-12-01T00:00:00Z"}},"required":["totalSystems","results","billingPeriodStart","billingPeriodEnd","executedAt"]},"CreateBillingConfigDto":{"type":"object","properties":{"billingType":{"example":"TENANT","allOf":[{"$ref":"#/components/schemas/BillingType"}]},"tenantId":{"type":"string","description":"Tenant ID (required if billingType is TENANT)","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zoneId":{"type":"string","description":"Zone ID (required if billingType is ZONE)","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"userId":{"type":"string","description":"User ID (required if billingType is USER)","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"tariffRate":{"type":"number","description":"Price per kWh","example":0.25},"billingDay":{"type":"number","description":"Day of month to auto-generate invoice (1-28)","example":1},"billingEmail":{"type":"string","description":"Email address for invoice PDF delivery","example":"billing@acme.com"},"replyTo":{"type":"string","description":"Reply-to email for invoice emails. Replies go to this address.","example":"admin@ecovolt.ai"}},"required":["billingType","tariffRate","billingDay","billingEmail"]},"UpdateBillingConfigDto":{"type":"object","properties":{"tariffRate":{"type":"number","example":0.3},"billingDay":{"type":"number","example":15},"billingEmail":{"type":"string","example":"accounts@acme.com"},"replyTo":{"type":"string","description":"Reply-to email for invoice emails. Replies go to this address.","example":"admin@ecovolt.ai"},"isActive":{"type":"boolean","example":false}}},"BillingConfig":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"billingType":{"example":"TENANT","allOf":[{"$ref":"#/components/schemas/BillingType"}]},"system":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"tenant":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zone":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"user":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"tariffRate":{"type":"number","description":"Price per kWh","example":0.25},"billingDay":{"type":"number","description":"Day of month (1-28) to auto-generate invoice","example":1},"billingEmail":{"type":"string","description":"Email address to receive the invoice PDF","example":"billing@acme.com"},"replyTo":{"type":"string","description":"Reply-to email for invoice emails. Replies go to this address.","example":"admin@ecovolt.ai"},"scheduleName":{"type":"string","description":"AWS EventBridge schedule name for automated monthly billing","example":"monthly_billing_tenant_64b7f1e6_day_15_prod"},"isActive":{"type":"boolean","example":true},"isDeleted":{"type":"boolean","example":false},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","billingType","system","tariffRate","billingDay","billingEmail","isActive","isDeleted","createdAt","updatedAt"]},"EnergyInvoice":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"billingType":{"example":"TENANT","allOf":[{"$ref":"#/components/schemas/BillingType"}]},"system":{"$ref":"#/components/schemas/System"},"tenant":{"$ref":"#/components/schemas/Tenant"},"zone":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"user":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"invoiceNumber":{"type":"string","description":"Unique invoice number for tracking and reference","example":"INV-2026-01-001"},"invoiceDate":{"format":"date-time","type":"string","description":"Invoice date","example":"2026-01-01T00:00:00Z"},"dueDate":{"format":"date-time","type":"string","description":"Due date for payment","example":"2026-01-31T23:59:59Z"},"subtotal":{"type":"number","description":"Subtotal before tax","example":100},"taxRate":{"type":"number","description":"Tax rate percentage","example":9},"tax":{"type":"number","description":"Tax amount (subtotal x taxRate / 100)","example":9},"total":{"type":"number","description":"Final total amount","example":109},"status":{"description":"Current payment status","example":"PENDING","allOf":[{"$ref":"#/components/schemas/InvoiceStatus"}]},"isDeleted":{"type":"boolean","description":"Soft delete flag","example":false},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"billingPeriodFrom":{"format":"date-time","type":"string","description":"Billing period start date","example":"2026-01-01T00:00:00Z"},"billingPeriodTo":{"format":"date-time","type":"string","description":"Billing period end date","example":"2026-01-31T23:59:59Z"},"totalKwh":{"type":"number","description":"Total energy consumption in kWh during billing period","example":150.5},"tariffRate":{"type":"number","description":"Tariff rate applied at time of billing (price per kWh)","example":0.25},"currency":{"type":"string","description":"Currency code (automatically derived from system country code)","example":"SGD"},"emailSent":{"type":"boolean","description":"Whether invoice email with PDF attachment was successfully sent","example":false}},"required":["_id","billingType","system","invoiceNumber","invoiceDate","dueDate","subtotal","taxRate","tax","total","status","isDeleted","createdAt","updatedAt","billingPeriodFrom","billingPeriodTo","totalKwh","tariffRate","currency","emailSent"]},"TriggerBillingDto":{"type":"object","properties":{"entityId":{"type":"string","description":"ID of the entity to bill (tenant ID, zone ID, or user ID)","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"billingPeriodFrom":{"type":"string","description":"Start of billing period (ISO date)","example":"2026-01-01"},"billingPeriodTo":{"type":"string","description":"End of billing period (ISO date)","example":"2026-01-31"},"taxRate":{"type":"number","description":"Tax rate percentage (0 = no tax)","example":9,"default":0},"dueDate":{"type":"string","description":"Invoice due date (ISO date).","example":"2026-02-28"},"replyTo":{"type":"string","description":"Reply-to email address for the invoice. Replies go to this address.","example":"admin@ecovolt.ai"}},"required":["entityId","billingPeriodFrom","billingPeriodTo","dueDate"]},"TriggerBillingResponseDto":{"type":"object","properties":{"entityId":{"type":"string","description":"ID of the entity (tenant/zone/user) that was billed","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"entityName":{"type":"string","description":"Name of the entity that was billed","example":"Acme Corporation"},"success":{"type":"boolean","description":"Whether the billing operation was successful","example":true},"totalKwh":{"type":"number","description":"Total energy consumption in kWh for the billing period","example":150.5},"totalAmount":{"type":"number","description":"Total amount billed in the configured currency","example":41.01},"emailSent":{"type":"boolean","description":"Whether the invoice email was sent successfully","example":true},"error":{"type":"string","description":"Error message if the operation failed","example":"No usage data found for billing period"}},"required":["entityId","entityName","success"]},"CreateProductDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the product","example":"Smart Plug"},"description":{"type":"string","description":"Description of the product","example":"A smart plug that can be controlled remotely"},"price":{"type":"number","description":"Price of the product","example":9.99}},"required":["name","price"]},"UpdateProductDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the product","example":"Smart Plug Pro"},"description":{"type":"string","description":"Description of the product","example":"An upgraded smart plug with additional features"},"price":{"type":"number","description":"Price of the product","example":12.99}}},"ApiTokenAudience":{"type":"string","enum":["system","tenant","consumer"],"description":"Audience of the token. System reaches every API-token endpoint; Tenant is restricted to tenant-scoped endpoints and below; Consumer to consumer-scoped (zone and below) endpoints. Capped at the creating account's own level — ADMIN/SUPERADMIN may mint System, TENANT at most Tenant, USER at most Consumer; requesting higher is a 403. Omit to get that cap."},"CreateApiTokenDto":{"type":"object","properties":{"audience":{"description":"Audience of the token. System reaches every API-token endpoint; Tenant is restricted to tenant-scoped endpoints and below; Consumer to consumer-scoped (zone and below) endpoints. Capped at the creating account's own level — ADMIN/SUPERADMIN may mint System, TENANT at most Tenant, USER at most Consumer; requesting higher is a 403. Omit to get that cap.","allOf":[{"$ref":"#/components/schemas/ApiTokenAudience"}]},"regenerate":{"type":"boolean","description":"Replace the caller's existing token for this system instead of returning 409. The old token stops working immediately; usage history is kept.","default":false}}},"ApiTokenResponseDto":{"type":"object","properties":{"token":{"type":"string","description":"Api token string for accessing external APIs","example":"5cb691c114052d0746daade10116a04b0bf16853364843984ed5022ad08001a12d0e1aae72e285bb8a187e28a24dd7f326ef6d28204f7fbbe96d08dfbc4368b90097b5f3f315458e7f83c9ff0a5405565bcb04d19dfd9fc6d2c3d709f73ca43c"}},"required":["token"]},"GetSystemDetailsDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the system in the database.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"systemName":{"type":"string","description":"Name of the system. Typically represents an organization, building, or facility using the Ecovolt platform. Only returned to SUPERADMIN callers.","example":"National University of Singapore"},"numberOfDevices":{"type":"number","description":"Total number of devices in the system. Only returned to SUPERADMIN callers.","example":12},"deviceOnCount":{"type":"number","description":"Number of devices currently reporting ON. Only returned to SUPERADMIN callers.","example":7},"deviceOffCount":{"type":"number","description":"Number of devices currently reporting OFF. Only returned to SUPERADMIN callers.","example":3},"deviceInactiveCount":{"type":"number","description":"Number of devices that are currently inactive. Only returned to SUPERADMIN callers.","example":2}},"required":["_id"]},"GetAdminDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier of the admin account in the database.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"name":{"type":"string","description":"Full name of the admin user. Used for identification and display purposes.","example":"Glenn Quah Han Rong"},"email":{"type":"string","description":"Email address of the admin user. Used for authentication and notifications.","example":"glenn@ecovolt.com"},"role":{"description":"Administrative role determining the level of access and permissions within the system.","example":"SUPERADMIN","allOf":[{"$ref":"#/components/schemas/Role"}]},"systems":{"description":"Array of systems that are linked to and managed by this admin. Includes full system details for each linked system.","type":"array","items":{"$ref":"#/components/schemas/GetSystemDetailsDto"}},"approved":{"type":"boolean","description":"Indicates whether the admin account has been approved by a superadmin.","example":true},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the admin account was created in the system.","example":"2024-10-15T08:30:00Z"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the admin account was last modified.","example":"2024-10-15T14:45:00Z"},"pending":{"type":"boolean","description":"Indicates whether the admin account is awaiting approval. True if not yet approved.","example":false},"mainSystemId":{"type":"string","description":"Main system ID, typically the first system in the systems array. Used as the default system for the admin.","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"associatedUserId":{"type":"string","description":"Reference to the associated User ID if this admin account is linked to a specific user.","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"associatedTenantId":{"type":"string","description":"Reference to the associated Tenant ID if this admin account is linked to a specific tenant.","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"associatedRoomId":{"type":"string","description":"Reference to the associated Room ID if this admin account is linked to a specific room.","example":"64b7f1e6f9c9d4e5b8a9f7c7"},"enable2FA":{"type":"boolean","description":"Indicates whether two-factor authentication is enabled for this admin account.","example":false}},"required":["_id","name","email","role","systems","approved","createdAt","updatedAt","pending","mainSystemId"]},"ApiTokenGeneratedByResponseDto":{"type":"object","properties":{"admin":{"description":"Indicates API token has been generated by Admin","example":true,"allOf":[{"$ref":"#/components/schemas/GetAdminDto"}]}},"required":["admin"]},"ApiTokenUsageDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the ApiTokenUsage","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"url":{"type":"string","description":"URL accessed using the API token","example":"/api/v1/plug/system/60b7f1e6f9c9d4e5b8a9f7c3"},"method":{"type":"string","description":"HTTP method used with the API token","enum":["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"]},"createdAt":{"format":"date-time","type":"string","description":"Date when the usage was recorded","example":"2023-10-01T12:00:00Z"},"updatedAt":{"format":"date-time","type":"string","description":"Date when the usage was last updated","example":"2023-10-01T12:00:00Z"}},"required":["_id","url","method","createdAt","updatedAt"]},"ApiTokenUsageSearchResultDto":{"type":"object","properties":{"results":{"description":"Array of API token usage records","type":"array","items":{"$ref":"#/components/schemas/ApiTokenUsageDto"}},"total":{"type":"number","description":"Total number of matching records","example":150}},"required":["results","total"]},"GetApiTokenDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the API token","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemId":{"type":"string","description":"System ID associated with the API token","example":"64b7f1e6f9c9d4e5b8a9f7c4"}},"required":["_id","systemId"]},"GetApiTokenWithUsageDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the API token","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"admin":{"description":"Admin associated with the API token","allOf":[{"$ref":"#/components/schemas/GetAdminDto"}]},"systemId":{"type":"string","description":"System ID associated with the API token","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"systemName":{"type":"string","description":"System name associated with the API token","example":"Production System"},"audience":{"description":"Audience of the token — how far it reaches. Capped at the creating account's role, so it is not implied by admin.role alone (an ADMIN may deliberately mint below their ceiling).","example":"system","allOf":[{"$ref":"#/components/schemas/ApiTokenAudience"}]},"usageCount":{"type":"number","description":"Number of times this API token has been used","example":42},"createdAt":{"format":"date-time","type":"string","description":"Date when the API token was created","example":"2023-10-01T12:00:00Z"},"updatedAt":{"format":"date-time","type":"string","description":"Date when the API token was last updated","example":"2023-10-01T12:00:00Z"}},"required":["_id","admin","systemId","systemName","audience","usageCount","createdAt","updatedAt"]},"GetAdminsDto":{"type":"object","properties":{"admins":{"description":"Array of admin accounts retrieved from the system. Includes all admin details and metadata.","type":"array","items":{"$ref":"#/components/schemas/GetAdminDto"}},"total":{"type":"number","description":"Total count of admin accounts in the database. Used for pagination calculations.","example":25}},"required":["admins","total"]},"UpdateProfileDto":{"type":"object","properties":{"name":{"type":"string","description":"Full name of the account holder.","example":"Glenn Quah Han Rong"},"email":{"type":"string","description":"Email address of the account holder.","example":"glenn@ecovolt.com"},"currentPassword":{"type":"string","description":"Current password, required to confirm a password change. Must be provided together with newPassword.","example":"OldSecurePass123!"},"newPassword":{"type":"string","description":"New password for the account. Must be at least 6 characters long and will be hashed before storage. Must be provided together with currentPassword.","example":"NewSecurePass123!"}}},"NotificationChannelSettingsDto":{"type":"object","properties":{"email":{"type":"boolean","description":"Enable or disable email notifications for this group"},"sms":{"type":"boolean","description":"Enable or disable SMS notifications for this group"},"push":{"type":"boolean","description":"Enable or disable push notifications for this group"},"telegram":{"type":"boolean","description":"Enable or disable Telegram notifications for this group. Has no effect until the account has paired a personal Telegram chat."}}},"QuietHoursDto":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether Do Not Disturb is active for this account. On its own (no start/end) this suppresses every channel but in-app at all times; paired with start/end it scopes suppression to that window instead.","example":true},"start":{"type":"string","description":"Quiet hours start time in HH:mm format (local to timezone). Omit alongside end for an always-on Do Not Disturb toggle instead of a scheduled window.","example":"22:00"},"end":{"type":"string","description":"Quiet hours end time in HH:mm format (local to timezone). Omit alongside start for an always-on Do Not Disturb toggle instead of a scheduled window.","example":"07:00"},"timezone":{"description":"Timezone start/end are evaluated in, from the supported set. Only meaningful when both start and end are set.","example":"Asia/Singapore","allOf":[{"$ref":"#/components/schemas/SystemTimezone"}]}},"required":["enabled"]},"NotificationPreferencesResponseDto":{"type":"object","properties":{"channels":{"description":"Effective channel settings, always fully populated — a channel the account has never touched is returned at its default, so the client never has to reproduce the default rule.","allOf":[{"$ref":"#/components/schemas/NotificationChannelSettingsDto"}]},"quietHours":{"description":"Effective Do Not Disturb setting, disabled when the account has never set one.","allOf":[{"$ref":"#/components/schemas/QuietHoursDto"}]}},"required":["channels","quietHours"]},"UpdateNotificationPreferencesDto":{"type":"object","properties":{"channels":{"description":"Channels to enable or disable. Merged per channel, so omitting one leaves it as it was.","allOf":[{"$ref":"#/components/schemas/NotificationChannelSettingsDto"}]},"quietHours":{"description":"Do Not Disturb setting. Replaced wholesale rather than merged — disabling drops the window so re-enabling never resurrects a forgotten schedule.","allOf":[{"$ref":"#/components/schemas/QuietHoursDto"}]}}},"GetSessionDto":{"type":"object","properties":{"sid":{"type":"string","description":"Session id. Pass to DELETE /admins/me/sessions/:sid to sign this device out.","example":"3f6c1b8e-0f2a-4c31-9a7d-1e5b2c8d4f60"},"app":{"description":"Frontend the session was opened from. Absent for a client that did not name itself.","allOf":[{"$ref":"#/components/schemas/FrontendApp"}]},"userAgent":{"type":"string","description":"User-Agent the session was last seen from. Identifies browser and OS for web clients; native clients send a transport UA that names no device.","example":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"},"ipAddress":{"type":"string","description":"Client IP the session was last seen from. Stored raw; resolve to a location at display time.","example":"203.0.113.42"},"lastUsedAt":{"format":"date-time","type":"string","description":"When this session last rotated its tokens — its most recent activity, to roughly the access-token lifetime."},"createdAt":{"format":"date-time","type":"string","description":"When the session was opened"},"isCurrent":{"type":"boolean","description":"True for the session that made this request, so the UI can label it \"This device\" and warn before signing it out.","example":true}},"required":["sid","lastUsedAt","createdAt","isCurrent"]},"UpdateAdminRoleDto":{"type":"object","properties":{"role":{"type":"string","description":"The new administrative role to assign to the admin. Determines the level of access and permissions within the system.","example":"ADMIN"},"associatedZone":{"type":"string","description":"Optional zone ID to associate this admin with a specific zone account","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"associatedTenant":{"type":"string","description":"Optional tenant ID to associate this admin with a specific tenant","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"associatedRoom":{"type":"string","description":"Optional room ID to associate this admin with a specific room","example":"64b7f1e6f9c9d4e5b8a9f7c2"}},"required":["role"]},"UpdateAdminDto":{"type":"object","properties":{"name":{"type":"string","description":"Full name of the admin user. Used for identification and display purposes within the system.","example":"Glenn Quah Han Rong"},"email":{"type":"string","description":"Email address of the admin user. Used for authentication and system notifications.","example":"glenn@ecovolt.com"},"password":{"type":"string","description":"New password for the admin account. Must be at least 6 characters long and will be hashed before storage.","example":"SecurePass123!"},"role":{"type":"string","description":"Administrative role determining the level of access and permissions within the system.","example":"SUPERADMIN"}}},"LinkSystemToAdminDto":{"type":"object","properties":{"systemId":{"type":"string","description":"Unique identifier of the system to be linked to the admin account. This establishes a management relationship between the admin and the system.","example":"64b7f1e6f9c9d4e5b8a9f7c1"}},"required":["systemId"]},"UnlinkSystemToAdminDto":{"type":"object","properties":{"systemId":{"type":"string","description":"Unique identifier of the system to be unlinked from the admin account. This removes the management relationship between the admin and the system.","example":"64b7f1e6f9c9d4e5b8a9f7c1"}},"required":["systemId"]},"CreateZoneDto":{"type":"object","properties":{"name":{"type":"string","description":"Full name of the zone to be registered in the system. This will be displayed across the application.","example":"Zone A - Engineering Wing"},"email":{"type":"string","description":"Unique email address for the zone account. Required when adminCreation is true. Must not already exist in the system and will be used for authentication.","example":"zone-a@gmail.com"},"remarks":{"type":"string","description":"Optional administrative notes or comments about the zone. Used for internal documentation and context.","example":"Department head - Engineering"},"adminCreation":{"type":"boolean","description":"Flag indicating whether an tenant admin should be created, if so send invite."},"zoneType":{"type":"string","description":"Zone type key. Must be a valid type defined for this system via /entity-type/ZONE.","example":"LABORATORY"}},"required":["name","adminCreation"]},"LinkMultiplePlugsDto":{"type":"object","properties":{"plugIds":{"description":"Array of plug identifiers to link to the zone. Used when assigning multiple plugs to a zone at once.","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}}},"required":["plugIds"]},"GetZonesResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of zone objects matching the query criteria","type":"array","items":{"$ref":"#/components/schemas/GetZoneDto"}}},"required":["total","page","size","totalPages","data"]},"UpdateZoneDto":{"type":"object","properties":{"name":{"type":"string","description":"Updated full name for the zone. This will replace the existing name in the system.","example":"Zone B - Updated Wing"},"email":{"type":"string","description":"Updated email address for the zone. Must be unique and not already in use by another account.","example":"updated-zone@example.com"},"tenantId":{"type":"string","description":"Updated tenant identifier to reassign the zone to a different organization.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"remarks":{"type":"string","description":"Updated administrative notes or comments about the zone. Replaces existing remarks if provided.","example":"Promoted to senior engineer"},"zoneType":{"type":"string","description":"Zone type key. Must be a valid type defined for this system via /entity-type/ZONE.","example":"LABORATORY"}}},"MigratePlugDto":{"type":"object","properties":{"targetPlugId":{"type":"string","description":"The ID of the target plug"},"sourcePlugId":{"type":"string","description":"The ID of the source plug"},"hasPreviousSetup":{"type":"boolean","description":"Indicates if the source plug has previous setup"}},"required":["targetPlugId","sourcePlugId"]},"EcoguardPlug":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the EcoGuard","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"ecoguardEnabled":{"type":"boolean","description":"Ecoguard Timer","example":true},"ecoguardTimer":{"type":"number","description":"Idle Timeout Duration of the Ecoguard (Minutes)"},"ecoguardIdleThreshold":{"type":"number","description":"Ecoguard Idle Threshold","example":60000}},"required":["_id","ecoguardEnabled","ecoguardTimer","ecoguardIdleThreshold"]},"Plug":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceType":{"description":"Type of device (discriminator field)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"macId":{"type":"string","description":"MAC ID of the Device (from IoT device)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Name of the Device","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"onStatus":{"description":"Status of the Device (ON / OFF / INACTIVE / ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend (cloud / scheduled). Set when a toggle is issued; cleared when the device reports a matching state or a physical (MANUAL) press overrides it. Always ON or OFF when present.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when targetState was last written. Used to expire stale SCHEDULED intents (24hr TTL). CLOUD intents do not expire.","example":"2026-05-25T08:00:00.000Z"},"targetSource":{"description":"Origin of the pending targetState. Drives TTL behavior on reconcile. Only CLOUD / SCHEDULED are ever written here.","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"location":{"type":"string","description":"Physical location or address where the device is installed","example":"Room 101, Building A"},"remarks":{"type":"string","description":"Remarks for the Device"},"deviceModel":{"type":"string","description":"Model of the device plugged into this smart plug","example":"Dell XPS 15"},"deviceBrand":{"type":"string","description":"Brand of the device plugged into this smart plug","example":"Dell"},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups","example":1},"chipModel":{"description":"Hardware chip model identifier used in the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is connected to","example":"IoT-Network-5G"},"wifiError":{"type":"string","description":"Last WiFi shadow delta failure reported by the device (e.g. join_failed); cleared once it reports a successful join","example":"join_failed"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join. Set at send time on both the shadow and legacy changeWifi paths; convergence is `ssid` matching any desired SSID.","example":"Corp-IoT"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them.","example":["Corp-IoT","Corp-IoT-2","Corp-IoT-3"],"type":"array","items":{"type":"string"}},"config":{"description":"Settings as last reported by the device into its `config` shadow, one block per feature","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"pendingConfig":{"description":"Settings written to the `config` shadow but not yet reported back. A key the device reports at a different value was rejected","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"configRequestedAt":{"format":"date-time","type":"string","description":"When settings were last written to the `config` shadow"},"configReportedAt":{"format":"date-time","type":"string","description":"When the device last reported into its `config` shadow"},"rssi":{"type":"number","description":"Latest WiFi signal strength reported by the device (dBm). Updated on each realtime MQTT report.","example":-65},"tenant":{"description":"Tagged Tenant","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"zone":{"description":"Tagged Zone","allOf":[{"$ref":"#/components/schemas/Zone"}]},"room":{"description":"Tagged Room","allOf":[{"$ref":"#/components/schemas/Room"}]},"system":{"description":"Tagged System","allOf":[{"$ref":"#/components/schemas/System"}]},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a physical unit. Such rows have no real MAC, never report telemetry, and are excluded from firmware, OTA, provisioning and panel-slot views.","example":false},"isRidgeChild":{"type":"boolean","description":"True when this row is a unit behind a RIDGE gateway rather than a directly-connected unit. Such rows have no real MAC, are never fleet-provisioned, and are excluded from firmware, OTA and liveness watchdogs — their gateway carries those.","example":false},"hasPhaseChildren":{"type":"boolean","description":"True when this clamp has been split into per-phase children. Such a clamp is org-detached and excluded from energy and organisational views — its children carry those — while remaining the sole target for firmware, OTA and diagnostics.","example":false},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2021-09-01"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device on/off status cannot be toggled.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"deviceSchedule":{"description":"Schedule configuration for the device. Contains recurring weekly schedules and one-time scheduled tasks.","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the single Schedule (in the schedules collection) this device currently follows. deviceSchedule is compiled from that schedule. When null, the device follows nothing.","example":"507f1f77bcf86cd799439011","nullable":true},"realTimePower":{"type":"number","description":"Real-time power consumption in watts","example":1500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes","example":6.5},"totalEnergyUsage":{"type":"number","description":"Total cumulative energy usage in kWh","example":0},"totalEnergySaved":{"type":"number","description":"Total energy saved through optimization in kWh","example":0},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update OTA attempts sent to this device","example":0},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether device is blacklisted from auto-updates (max retries exceeded)","example":false},"manualUpdateBlacklist":{"type":"boolean","description":"Whether device is manually blacklisted from auto-updates by admin","example":false},"lastAutoUpdateAttempt":{"format":"date-time","type":"string","description":"Timestamp of last auto-update OTA attempt"},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","default":false},"inactivityNotificationSentAt":{"format":"date-time","type":"string","description":"Timestamp when the last inactivity notification was sent for this device"},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"pendingThresholds":{"type":"object","description":"Threshold limits sent to the device but not yet confirmed by its firmware. Keys match DeviceThresholdsDto. Present only while a save is outstanding — a key that persists here is not being enforced by the hardware.","additionalProperties":{"type":"number"}},"thresholds":{"description":"This device's alert thresholds gathered into one object. Which fields are populated depends on deviceType — see DeviceThresholdsDto. Absent when no thresholds are configured. The same values remain available as flat top-level fields.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"plugCategory":{"type":"string","description":"Plug category name. See GET /plug-category for all valid values.","example":"CHARGER","default":"UNREGISTERED"},"plugType":{"type":"string","description":"Type of Plug","enum":["SOCKET","SOAPBAR","ADAPTER"],"example":"ADAPTER"},"energyUsageRecommendationReport":{"type":"string","description":"Energy Usage Recommendation Report","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"ecoguardPlug":{"description":"Ecoguard of the system","example":"30","allOf":[{"$ref":"#/components/schemas/EcoguardPlug"}]},"migratedTo":{"type":"string","description":"Plug to which this plug has been migrated","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"voltageMax":{"type":"number","description":"Max Threshold voltage reading in volts","example":240},"currentMax":{"type":"number","description":"Max Threshold current reading in amps","example":10},"powerMax":{"type":"number","description":"Max Threshold power reading in watts","example":100},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold — minimum hours between repeat notifications of the same type","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000}},"required":["_id","deviceType","macId","deviceName","deviceNumber","onStatus","location","remarks","batchNumber","chipModel","versionNumber","ssid","tenant","zone","room","system","createdAt","updatedAt","lastSeen","isLocked","totalEnergyUsage","totalEnergySaved","autoUpdateAttempts","autoUpdateBlacklisted","manualUpdateBlacklist","inactivityNotificationEnabled","plugCategory","plugType","energyUsageRecommendationReport","ecoguardPlug","migratedTo"]},"PlugUpdateItem":{"type":"object","properties":{"plugId":{"type":"string","description":"MongoDB ID of the plug to update","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"plugCategory":{"type":"string","description":"Category of the device connected to this plug. See GET /plug-category for valid values.","example":"CHARGER"},"deviceBrand":{"type":"string","description":"Brand of the device connected to this plug","example":"Novita"},"deviceModel":{"type":"string","description":"Model of the device connected to this plug","example":"ND-195"},"remarks":{"type":"string","description":"Additional remarks or notes about the plug","example":"Water dispenser appliance"}},"required":["plugId"]},"BulkUpdatePlugsDto":{"type":"object","properties":{"updates":{"description":"Array of plug update operations to perform in a single batch","example":[{"plugId":"64b7f1e6f9c9d4e5b8a9f7c3","plugCategory":"CHARGER","deviceBrand":"Novita","deviceModel":"ND-195","remarks":"Water dispenser appliance"}],"type":"array","items":{"$ref":"#/components/schemas/PlugUpdateItem"}}},"required":["updates"]},"AiCategorizePlugsRequestDto":{"type":"object","properties":{"plugIds":{"description":"Array of plug IDs to categorize using AI","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"minItems":1,"maxItems":100,"type":"array","items":{"type":"string"}}},"required":["plugIds"]},"UpdatePlugDto":{"type":"object","properties":{"deviceName":{"type":"string","description":"Name of the device","example":"Living Room Light"},"location":{"type":"string","description":"Physical location of the device","example":"Block 123, Unit 01-23"},"remarks":{"type":"string","description":"Additional remarks or notes about the device","example":"Handle with care - critical equipment"},"tenantId":{"type":"string","description":"Tenant ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zoneId":{"type":"string","description":"Zone ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"roomId":{"type":"string","description":"Room ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device cannot be toggled ON/OFF.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"plugType":{"type":"string","description":"Type of Plug","example":"SOAPBAR"},"plugCategory":{"type":"string","description":"Type of Plug Category","example":"CHARGER"},"deviceModel":{"type":"string","description":"Model identifier of the Plug","example":"TP-Link HS110"},"deviceBrand":{"type":"string","description":"Brand of the Plug device","example":"TP-Link"},"batchNumber":{"type":"number","description":"Batch Number of the Plug","example":23}}},"FilteredPlugDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique database identifier of the device document in the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"Unique identifier assigned to the device by the IoT hardware (MAC ID)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Friendly name for the device for easy identification","example":"Living Room AC"},"onStatus":{"description":"Current operational status of the device (ON, OFF, INACTIVE, or ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend. Present when a toggle was issued but the device has not yet acknowledged (e.g. issued while INACTIVE). Frontend can derive a \"Pending\" indicator when this differs from onStatus.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when the pending targetState was last written.","example":"2026-05-25T08:00:00.000Z"},"totalEnergyUsage":{"type":"number","description":"Cumulative total energy consumption in kilowatt-hours (kWh) since device installation","example":500},"deviceNumber":{"type":"number","description":"Auto-incremented sequential number for tracking device registration order","example":3},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups and quality control","example":23},"chipModel":{"description":"Hardware chip model identifier used in the device (e.g., ESP32C2, ESP8266)","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"tenantId":{"type":"string","description":"Unique identifier of the tenant organization that owns this device","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantName":{"type":"string","description":"Name of the tenant organization that owns this device","example":"Faculty of Engineering"},"zoneId":{"type":"string","description":"Unique identifier of the zone that this device is assigned to","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"zoneName":{"type":"string","description":"Name of the zone that this device is assigned to","example":"Zone A"},"roomId":{"type":"string","description":"Unique identifier of the room where this device is physically located","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"roomName":{"type":"string","description":"Name of the room where this device is physically located","example":"Room 101"},"dbId":{"type":"string","description":"Unique identifier of the distribution box that this device is connected to (for MCBs)","example":"64b7f1e6f9c9d4e5b8a9f7c7"},"dbName":{"type":"string","description":"Name of the distribution box that this device is connected to (for MCBs)","example":"Main Distribution Box"},"systemId":{"type":"string","description":"Unique identifier of the system that this device belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system that this device belongs to","example":"NUS Engineering Building"},"realTimePower":{"type":"number","description":"Real-time power consumption in watts (W) measured by the device","example":115},"realTimeCurrent":{"type":"number","description":"Real-time current draw in amperes (A) measured by the device","example":0.5},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is currently connected to","example":"XH001"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join","example":"XH001"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join","example":"XH001-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join","example":"XH001-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them","example":["XH001","XH001-2","XH001-3"],"type":"array","items":{"type":"string"}},"deviceSchedule":{"description":"Weekly and one-time schedule configuration for automated device control","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the named Schedule this device currently follows. deviceSchedule is compiled from it. Absent or null when the device follows nothing.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c3"},"lastSeen":{"format":"date-time","type":"string","description":"Timestamp when the device last communicated with the system","example":"2024-10-15T12:30:00.000Z"},"isLocked":{"type":"boolean","description":"Lock status indicating if the device is locked","example":false},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device","example":true},"thresholds":{"description":"Alert thresholds configured for this device, gathered into one object. Which fields are populated depends on the device type — see DeviceThresholdsDto. Omitted entirely when nothing is set.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"rangeEnergyUsage":{"type":"number","description":"Energy usage in kWh over the `from`..`till` window, aggregated from daily usage history. Present only when both query params are supplied — unlike the cumulative `totalEnergyUsage`, this is windowed.","example":42.75},"plugType":{"description":"Hardware type classification of the plug device (e.g., ADAPTER, EXTENSION)","example":"ADAPTER","allOf":[{"$ref":"#/components/schemas/PlugType"}]},"plugCategory":{"type":"string","description":"Category classification for the connected device type. See GET /plug-category for valid values.","example":"CHARGER"},"ecoguardPlug":{"description":"Ecoguard power-saving feature configuration and settings for this plug","allOf":[{"$ref":"#/components/schemas/EcoguardPlug"}]}},"required":["_id","macId","deviceName","onStatus","totalEnergyUsage","tenantId","tenantName","zoneId","zoneName","roomId","roomName","dbId","dbName","systemId","systemName","realTimePower","realTimeCurrent","ssid","plugType","plugCategory"]},"GetPlugsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of plug device objects matching the query criteria","type":"array","items":{"$ref":"#/components/schemas/FilteredPlugDto"}}},"required":["total","page","size","totalPages","data"]},"PlugCategoryEnergyBreakdownDto":{"type":"object","properties":{"plugCategory":{"type":"string","description":"Category classification of the plug device. See GET /plug-category for valid values.","example":"CHARGER"},"totalEnergyUsage":{"type":"number","description":"Total energy usage (kWh) for all plugs in this category","example":342.5}},"required":["plugCategory","totalEnergyUsage"]},"PlugCategoriesBreakdownDto":{"type":"object","properties":{"plugCategory":{"type":"string","description":"Category classification of the plug device. See GET /plug-category for valid values.","example":"CHARGER"},"count":{"type":"number","description":"Total count of plugs belonging to this specific category in the system","example":23}},"required":["plugCategory","count"]},"CreateEcoguardPlugDto":{"type":"object","properties":{"ecoguardEnabled":{"type":"boolean","description":"Enable or disable the Ecoguard automatic power-off feature for idle detection. When enabled, the plug monitors power consumption.","example":true},"ecoguardTimer":{"type":"number","description":"Duration in minutes before the plug automatically turns off after detecting idle power usage. Applied when power stays below threshold.","example":30},"ecoguardIdleThreshold":{"type":"number","description":"Power consumption threshold in watts that defines idle state. When usage falls below this value, the idle timer starts counting.","example":5}},"required":["ecoguardEnabled","ecoguardTimer","ecoguardIdleThreshold"]},"BreakerCurve":{"type":"string","enum":["B","C","D"],"description":"Trip curve stamped on the breaker"},"CoilClampPositionDto":{"type":"object","properties":{"x":{"type":"number","description":"Absolute X coordinate of a CT clamp on the digital twin canvas","example":320.5},"y":{"type":"number","description":"Absolute Y coordinate of a CT clamp on the digital twin canvas","example":410.5}},"required":["x","y"]},"SandboxDevice":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the sandbox device","example":"sandbox-device-1"},"type":{"type":"string","description":"Type of sandbox device","enum":["DUMB_MCB","RCCB","RCBO","MCCB","ISOLATOR","SPD","PLANNED_SMART"],"example":"DUMB_MCB"},"name":{"type":"string","description":"User-defined name for the device","example":"Kitchen Lights"},"digitalTwinX":{"type":"number","description":"X position on the digital twin canvas","example":400},"rowLine":{"type":"number","description":"Row line position (1-6)","example":2},"poles":{"type":"number","description":"Number of poles. Sets the module width for everything except an MCCB.","example":2},"widthModules":{"type":"number","description":"How many DIN modules wide the device body is, at 18mm per module. Equals `poles` for every modular device; an MCCB is sized by frame instead, so it carries its own value. Drives the rendered width on the digital twin.","example":2},"curve":{"description":"Trip curve stamped on the breaker","example":"C","allOf":[{"$ref":"#/components/schemas/BreakerCurve"}]},"confidence":{"type":"number","description":"How sure the reading was when this device came from a digitised photo, 0 to 1. Absent on hand-placed devices.","example":0.82},"ratedCurrent":{"type":"number","description":"Rated current in amperes","example":16},"ratedVoltage":{"type":"number","description":"Rated voltage in volts","example":230},"mcbType":{"description":"MCB type (for DUMB_MCB devices)","example":"BUILT_IN","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"leakageCurrent":{"type":"number","description":"Leakage current in milliamperes (for RCCB devices)","example":30},"color":{"type":"string","description":"Custom color for visual identification","example":"#94a3b8"},"coilClampPositions":{"description":"Absolute canvas positions of the 3 CT clamps, for a PLANNED_SMART device standing in for a Wattson Pro. Mirrors MCB.coilClampPositions so a planned hub is wired up on the canvas the same way a real one is.","type":"array","items":{"$ref":"#/components/schemas/CoilClampPositionDto"}},"notes":{"type":"string","description":"Additional notes about the device","example":"Connected to kitchen circuit"}},"required":["id","type","name","digitalTwinX","rowLine"]},"WireConnection":{"type":"object","properties":{"deviceId":{"type":"string","description":"Device ID (MCB._id or SandboxDevice.id)","example":"sandbox-device-1"},"deviceType":{"type":"string","description":"Type of device being connected","enum":["SMART_MCB","DUMB_MCB","RCCB","RCBO","MCCB","ISOLATOR","SPD","PLANNED_SMART"],"example":"DUMB_MCB"},"side":{"type":"string","description":"Side of the device where the connection is made","enum":["LEFT","RIGHT","TOP","BOTTOM"],"example":"LEFT"}},"required":["deviceId","deviceType","side"]},"SandboxWire":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the wire connection","example":"wire-1"},"from":{"description":"Source connection point","allOf":[{"$ref":"#/components/schemas/WireConnection"}]},"to":{"description":"Target connection point","allOf":[{"$ref":"#/components/schemas/WireConnection"}]},"color":{"type":"string","description":"Wire color (hex code)","example":"#fbbf24","default":"#fbbf24"},"thickness":{"type":"number","description":"Wire thickness in pixels","example":3,"default":3},"label":{"type":"string","description":"Optional label for the wire","example":"Phase 1"}},"required":["id","from","to"]},"DistributionBox":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Distribution Box","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"Name/identifier of the Distribution Box","example":"Main DB Box - Floor 1"},"boxNumber":{"type":"number","description":"Unique box number for identification within a system","example":1},"numberOfRows":{"type":"number","description":"Number of rows in the distribution box","example":1},"mcbs":{"description":"Array of MCB IDs or partially populated MCB objects (_id, deviceName, macId, onStatus)","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"hasMainBreaker":{"type":"boolean","description":"Whether the distribution box has a main circuit breaker","example":true},"mainBreakerRating":{"type":"number","description":"Main breaker rating in amperes (if hasMainBreaker is true)","example":63},"location":{"type":"string","description":"Physical location where the distribution box is installed","example":"Ground Floor, Electrical Room"},"remarks":{"type":"string","description":"Additional remarks or notes about the distribution box","example":"Main distribution panel installed in 2024"},"system":{"type":"string","description":"System to which this distribution box belongs","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"tenant":{"type":"string","description":"Tenant to which this distribution box belongs","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"zone":{"type":"string","description":"Zone to which this distribution box belongs","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"room":{"type":"string","description":"Room where this distribution box is located","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"sandboxDevices":{"description":"Array of sandbox (non-smart) devices in the distribution box","type":"array","items":{"$ref":"#/components/schemas/SandboxDevice"}},"sandboxWires":{"description":"Array of wire connections between devices in the distribution box","type":"array","items":{"$ref":"#/components/schemas/SandboxWire"}},"digitalTwinWidth":{"type":"number","description":"Width of the digital twin canvas in pixels","example":1200},"digitalTwinHeight":{"type":"number","description":"Height of the digital twin canvas in pixels","example":800},"singleLineDiagramKey":{"type":"string","description":"S3 key for the single line diagram image","example":"single-line-diagrams/abc123-def456.png"},"singleLineDiagramUrl":{"type":"string","description":"CDN URL for the single line diagram image","example":"https://cdn.example.com/single-line-diagrams/abc123-def456.png"},"dbPhotoKey":{"type":"string","description":"S3 key for the distribution box photo","example":"distribution-box-photos/abc123-def456.png"},"dbPhotoUrl":{"type":"string","description":"CDN URL for the distribution box photo","example":"https://cdn.example.com/distribution-box-photos/abc123-def456.png"},"createdAt":{"format":"date-time","type":"string","description":"Date and time when the distribution box was created"},"updatedAt":{"format":"date-time","type":"string","description":"Date and time when the distribution box was last updated"}},"required":["_id","name","system","createdAt","updatedAt"]},"MCB":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceType":{"description":"Type of device (discriminator field)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"macId":{"type":"string","description":"MAC ID of the Device (from IoT device)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Name of the Device","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"onStatus":{"description":"Status of the Device (ON / OFF / INACTIVE / ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend (cloud / scheduled). Set when a toggle is issued; cleared when the device reports a matching state or a physical (MANUAL) press overrides it. Always ON or OFF when present.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when targetState was last written. Used to expire stale SCHEDULED intents (24hr TTL). CLOUD intents do not expire.","example":"2026-05-25T08:00:00.000Z"},"targetSource":{"description":"Origin of the pending targetState. Drives TTL behavior on reconcile. Only CLOUD / SCHEDULED are ever written here.","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"location":{"type":"string","description":"Physical location or address where the device is installed","example":"Room 101, Building A"},"remarks":{"type":"string","description":"Remarks for the Device"},"deviceModel":{"type":"string","description":"Model name or identifier of the device","example":"TP-Link HS110"},"deviceBrand":{"type":"string","description":"Brand or manufacturer of the device","example":"TP-Link"},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups","example":1},"chipModel":{"description":"Hardware chip model identifier used in the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is connected to","example":"IoT-Network-5G"},"wifiError":{"type":"string","description":"Last WiFi shadow delta failure reported by the device (e.g. join_failed); cleared once it reports a successful join","example":"join_failed"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join. Set at send time on both the shadow and legacy changeWifi paths; convergence is `ssid` matching any desired SSID.","example":"Corp-IoT"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them.","example":["Corp-IoT","Corp-IoT-2","Corp-IoT-3"],"type":"array","items":{"type":"string"}},"config":{"description":"Settings as last reported by the device into its `config` shadow, one block per feature","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"pendingConfig":{"description":"Settings written to the `config` shadow but not yet reported back. A key the device reports at a different value was rejected","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"configRequestedAt":{"format":"date-time","type":"string","description":"When settings were last written to the `config` shadow"},"configReportedAt":{"format":"date-time","type":"string","description":"When the device last reported into its `config` shadow"},"rssi":{"type":"number","description":"Latest WiFi signal strength reported by the device (dBm). Updated on each realtime MQTT report.","example":-65},"tenant":{"description":"Tagged Tenant","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"zone":{"description":"Tagged Zone","allOf":[{"$ref":"#/components/schemas/Zone"}]},"room":{"description":"Tagged Room","allOf":[{"$ref":"#/components/schemas/Room"}]},"system":{"description":"Tagged System","allOf":[{"$ref":"#/components/schemas/System"}]},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a physical unit. Such rows have no real MAC, never report telemetry, and are excluded from firmware, OTA, provisioning and panel-slot views.","example":false},"isRidgeChild":{"type":"boolean","description":"True when this row is a unit behind a RIDGE gateway rather than a directly-connected unit. Such rows have no real MAC, are never fleet-provisioned, and are excluded from firmware, OTA and liveness watchdogs — their gateway carries those.","example":false},"hasPhaseChildren":{"type":"boolean","description":"True when this clamp has been split into per-phase children. Such a clamp is org-detached and excluded from energy and organisational views — its children carry those — while remaining the sole target for firmware, OTA and diagnostics.","example":false},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2021-09-01"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device on/off status cannot be toggled.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"deviceSchedule":{"description":"Schedule configuration for the device. Contains recurring weekly schedules and one-time scheduled tasks.","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the single Schedule (in the schedules collection) this device currently follows. deviceSchedule is compiled from that schedule. When null, the device follows nothing.","example":"507f1f77bcf86cd799439011","nullable":true},"realTimePower":{"type":"number","description":"Real-time power consumption in watts","example":1500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes","example":6.5},"totalEnergyUsage":{"type":"number","description":"Total cumulative energy usage in kWh","example":0},"totalEnergySaved":{"type":"number","description":"Total energy saved through optimization in kWh","example":0},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update OTA attempts sent to this device","example":0},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether device is blacklisted from auto-updates (max retries exceeded)","example":false},"manualUpdateBlacklist":{"type":"boolean","description":"Whether device is manually blacklisted from auto-updates by admin","example":false},"lastAutoUpdateAttempt":{"format":"date-time","type":"string","description":"Timestamp of last auto-update OTA attempt"},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","default":false},"inactivityNotificationSentAt":{"format":"date-time","type":"string","description":"Timestamp when the last inactivity notification was sent for this device"},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"pendingThresholds":{"type":"object","description":"Threshold limits sent to the device but not yet confirmed by its firmware. Keys match DeviceThresholdsDto. Present only while a save is outstanding — a key that persists here is not being enforced by the hardware.","additionalProperties":{"type":"number"}},"thresholds":{"description":"This device's alert thresholds gathered into one object. Which fields are populated depends on deviceType — see DeviceThresholdsDto. Absent when no thresholds are configured. The same values remain available as flat top-level fields.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"mcbType":{"description":"Type of MCB","example":"COIL","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"measurementType":{"description":"Type of load/circuit being measured by this MCB","example":"SSO","allOf":[{"$ref":"#/components/schemas/McbMeasurementType"}]},"ratedCurrent":{"type":"number","description":"Rated current capacity in amperes","example":16},"ratedVoltage":{"type":"number","description":"Rated voltage in volts","example":230},"realTimeVoltage":{"type":"number","description":"Real-time voltage in volts","example":230},"powerFactor":{"type":"number","description":"Power factor (0-1)","example":0.95},"distributionBox":{"description":"Parent distribution box containing this MCB","allOf":[{"$ref":"#/components/schemas/DistributionBox"}]},"digitalTwinX":{"type":"number","description":"X coordinate for digital twin SVG positioning","example":150.5},"digitalTwinY":{"type":"number","description":"Y coordinate for digital twin SVG positioning (free-placed devices such as coils)","example":130.5},"coilClampPositions":{"description":"Absolute canvas positions of the 3 CT clamps (COIL3P devices only)","type":"array","items":{"$ref":"#/components/schemas/CoilClampPositionDto"}},"coilRotation":{"type":"number","description":"Rotation of the coil body in the digital twin, in degrees (e.g. 0, 90, 180, 270).","example":90},"rowLine":{"type":"number","description":"Row line number (1-6) the MCB is connected to","example":1},"slotNumber":{"type":"number","description":"Slot/position number on the busbar","example":3},"isReversePower":{"type":"boolean","description":"Whether power/current readings should be reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isAReversePower":{"type":"boolean","description":"Whether power/current readings should be reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isBReversePower":{"type":"boolean","description":"Whether power/current readings should be reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isCReversePower":{"type":"boolean","description":"Whether power/current readings should be reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"voltageMax":{"type":"number","description":"Max Threshold voltage reading in volts","example":240},"currentMax":{"type":"number","description":"Max Threshold current reading in amps","example":10},"powerMax":{"type":"number","description":"Max Threshold power reading in watts","example":100},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold — minimum hours between repeat notifications of the same type","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"isSingle":{"type":"boolean","description":"COIL3P only. How the clamp is wired: false (default) = 3 x 1P, three independent circuits shown as three rows with per-phase readings and eligible for a phase split; true = one combined circuit shown as a single row, with thresholds ganged across all three phases.","default":false},"voltageAMin":{"type":"number","description":"Min threshold phase A voltage in volts (COIL3P only)","example":190},"voltageBMin":{"type":"number","description":"Min threshold phase B voltage in volts (COIL3P only)","example":190},"voltageCMin":{"type":"number","description":"Min threshold phase C voltage in volts (COIL3P only)","example":190},"voltageAMax":{"type":"number","description":"Max threshold phase A voltage in volts (COIL3P only)","example":240},"voltageBMax":{"type":"number","description":"Max threshold phase B voltage in volts (COIL3P only)","example":240},"voltageCMax":{"type":"number","description":"Max threshold phase C voltage in volts (COIL3P only)","example":240},"currentAMax":{"type":"number","description":"Max threshold phase A current in amps (COIL3P only)","example":16},"currentBMax":{"type":"number","description":"Max threshold phase B current in amps (COIL3P only)","example":16},"currentCMax":{"type":"number","description":"Max threshold phase C current in amps (COIL3P only)","example":16},"powerAMax":{"type":"number","description":"Max threshold phase A power in watts (COIL3P only)","example":3000},"powerBMax":{"type":"number","description":"Max threshold phase B power in watts (COIL3P only)","example":3000},"powerCMax":{"type":"number","description":"Max threshold phase C power in watts (COIL3P only)","example":3000},"phaseLabels":{"description":"COIL3P only. Optional labels for phases A, B and C (in order). Max 3 entries.","example":["Lighting","Sockets","Aircon"],"type":"array","items":{"type":"string"}},"phaseParent":{"type":"string","description":"Phase children only. The COIL3P clamp this row represents one phase of. The clamp owns the MAC, the telemetry and the firmware; this row owns only the organisational assignment.","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"phase":{"description":"Phase children only. Which phase of the parent clamp this row draws its readings from.","example":"A","allOf":[{"$ref":"#/components/schemas/McbPhase"}]},"splitAt":{"format":"date-time","type":"string","description":"Phase children only. Audit record of when this leg's document was created. Nothing filters on it: a COIL3P writes its per-phase columns on every message whatever its wiring, so a leg owns its column for the whole of the parent's history, including rows predating the split.","example":"2026-08-05T00:00:00.000Z"},"realTimePowerA":{"type":"number","description":"Phase A real-time power (W)"},"realTimePowerB":{"type":"number","description":"Phase B real-time power (W)"},"realTimePowerC":{"type":"number","description":"Phase C real-time power (W)"},"realTimeVoltageA":{"type":"number","description":"Phase A real-time voltage (V)"},"realTimeVoltageB":{"type":"number","description":"Phase B real-time voltage (V)"},"realTimeVoltageC":{"type":"number","description":"Phase C real-time voltage (V)"},"realTimeCurrentA":{"type":"number","description":"Phase A real-time current (A)"},"realTimeCurrentB":{"type":"number","description":"Phase B real-time current (A)"},"realTimeCurrentC":{"type":"number","description":"Phase C real-time current (A)"},"totalEnergyUsageA":{"type":"number","description":"Phase A cumulative energy usage (kWh)"},"totalEnergyUsageB":{"type":"number","description":"Phase B cumulative energy usage (kWh)"},"totalEnergyUsageC":{"type":"number","description":"Phase C cumulative energy usage (kWh)"},"powerFactorA":{"type":"number","description":"Phase A power factor (0-1)"},"powerFactorB":{"type":"number","description":"Phase B power factor (0-1)"},"powerFactorC":{"type":"number","description":"Phase C power factor (0-1)"}},"required":["_id","deviceType","macId","deviceName","deviceNumber","onStatus","location","remarks","batchNumber","chipModel","versionNumber","ssid","tenant","zone","room","system","createdAt","updatedAt","lastSeen","isLocked","totalEnergyUsage","totalEnergySaved","autoUpdateAttempts","autoUpdateBlacklisted","manualUpdateBlacklist","inactivityNotificationEnabled","mcbType","ratedCurrent","ratedVoltage","realTimeVoltage","powerFactor","distributionBox","isReversePower","isAReversePower","isBReversePower","isCReversePower"]},"FilteredMcbDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique database identifier of the device document in the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"Unique identifier assigned to the device by the IoT hardware (MAC ID)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Friendly name for the device for easy identification","example":"Living Room AC"},"onStatus":{"description":"Current operational status of the device (ON, OFF, INACTIVE, or ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend. Present when a toggle was issued but the device has not yet acknowledged (e.g. issued while INACTIVE). Frontend can derive a \"Pending\" indicator when this differs from onStatus.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when the pending targetState was last written.","example":"2026-05-25T08:00:00.000Z"},"totalEnergyUsage":{"type":"number","description":"Cumulative total energy consumption in kilowatt-hours (kWh) since device installation","example":500},"deviceNumber":{"type":"number","description":"Auto-incremented sequential number for tracking device registration order","example":3},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups and quality control","example":23},"chipModel":{"description":"Hardware chip model identifier used in the device (e.g., ESP32C2, ESP8266)","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"tenantId":{"type":"string","description":"Unique identifier of the tenant organization that owns this device","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantName":{"type":"string","description":"Name of the tenant organization that owns this device","example":"Faculty of Engineering"},"zoneId":{"type":"string","description":"Unique identifier of the zone that this device is assigned to","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"zoneName":{"type":"string","description":"Name of the zone that this device is assigned to","example":"Zone A"},"roomId":{"type":"string","description":"Unique identifier of the room where this device is physically located","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"roomName":{"type":"string","description":"Name of the room where this device is physically located","example":"Room 101"},"dbId":{"type":"string","description":"Unique identifier of the distribution box that this device is connected to (for MCBs)","example":"64b7f1e6f9c9d4e5b8a9f7c7"},"dbName":{"type":"string","description":"Name of the distribution box that this device is connected to (for MCBs)","example":"Main Distribution Box"},"systemId":{"type":"string","description":"Unique identifier of the system that this device belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system that this device belongs to","example":"NUS Engineering Building"},"realTimePower":{"type":"number","description":"Real-time power consumption in watts (W) measured by the device","example":115},"realTimeCurrent":{"type":"number","description":"Real-time current draw in amperes (A) measured by the device","example":0.5},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is currently connected to","example":"XH001"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join","example":"XH001"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join","example":"XH001-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join","example":"XH001-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them","example":["XH001","XH001-2","XH001-3"],"type":"array","items":{"type":"string"}},"deviceSchedule":{"description":"Weekly and one-time schedule configuration for automated device control","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the named Schedule this device currently follows. deviceSchedule is compiled from it. Absent or null when the device follows nothing.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c3"},"lastSeen":{"format":"date-time","type":"string","description":"Timestamp when the device last communicated with the system","example":"2024-10-15T12:30:00.000Z"},"isLocked":{"type":"boolean","description":"Lock status indicating if the device is locked","example":false},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","example":true,"default":false},"thresholds":{"description":"Alert thresholds configured for this device, gathered into one object. Which fields are populated depends on the device type — see DeviceThresholdsDto. Omitted entirely when nothing is set.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"rangeEnergyUsage":{"type":"number","description":"Energy usage in kWh over the `from`..`till` window, aggregated from daily usage history. Present only when both query params are supplied — unlike the cumulative `totalEnergyUsage`, this is windowed.","example":42.75},"mcbType":{"description":"Type of MCB (trip curve type)","example":"BUILT_IN","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"measurementType":{"description":"Type of load/circuit being measured by this MCB","example":"SSO","allOf":[{"$ref":"#/components/schemas/McbMeasurementType"}]},"ratedCurrent":{"type":"number","description":"Rated current capacity in amperes","example":16},"ratedVoltage":{"type":"number","description":"Rated voltage in volts","example":230},"realTimeVoltage":{"type":"number","description":"Real-time voltage in volts","example":230},"powerFactor":{"type":"number","description":"Power factor (0-1)","example":0.95},"digitalTwinX":{"type":"number","description":"X coordinate for digital twin SVG positioning","example":150.5},"digitalTwinY":{"type":"number","description":"Y coordinate for digital twin SVG positioning (free-placed devices such as coils)","example":130.5},"coilClampPositions":{"description":"Absolute canvas positions of the 3 CT clamps (COIL3P devices only)","type":"array","items":{"$ref":"#/components/schemas/CoilClampPositionDto"}},"coilRotation":{"type":"number","description":"Rotation of the coil body in the digital twin, in degrees (e.g. 0, 90, 180, 270).","example":90},"rowLine":{"type":"number","description":"Row line number (1-6) the MCB is connected to","example":1},"slotNumber":{"type":"number","description":"Slot/position number on the busbar","example":3},"isReversePower":{"type":"boolean","description":"Whether power/current readings are reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isAReversePower":{"type":"boolean","description":"Whether power/current readings are reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isBReversePower":{"type":"boolean","description":"Whether power/current readings are reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isCReversePower":{"type":"boolean","description":"Whether power/current readings are reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isSingle":{"type":"boolean","description":"COIL3P only. How the clamp is wired. false = 3 x 1P, three independent circuits, each shown as its own row with per-phase readings; true = one combined three-phase circuit shown as a single row, with one threshold shared by all three phases.","example":false,"default":false},"phaseLabels":{"description":"COIL3P only. Optional labels for phases A, B and C (in order).","example":["Lighting","Sockets","Aircon"],"type":"array","items":{"type":"string"}},"realTimePowerA":{"type":"number","description":"Phase A real-time power (W)"},"realTimePowerB":{"type":"number","description":"Phase B real-time power (W)"},"realTimePowerC":{"type":"number","description":"Phase C real-time power (W)"},"realTimeVoltageA":{"type":"number","description":"Phase A real-time voltage (V)"},"realTimeVoltageB":{"type":"number","description":"Phase B real-time voltage (V)"},"realTimeVoltageC":{"type":"number","description":"Phase C real-time voltage (V)"},"realTimeCurrentA":{"type":"number","description":"Phase A real-time current (A)"},"realTimeCurrentB":{"type":"number","description":"Phase B real-time current (A)"},"realTimeCurrentC":{"type":"number","description":"Phase C real-time current (A)"},"totalEnergyUsageA":{"type":"number","description":"Phase A cumulative energy usage (kWh)"},"totalEnergyUsageB":{"type":"number","description":"Phase B cumulative energy usage (kWh)"},"totalEnergyUsageC":{"type":"number","description":"Phase C cumulative energy usage (kWh)"},"powerFactorA":{"type":"number","description":"Phase A power factor (0-1)"},"powerFactorB":{"type":"number","description":"Phase B power factor (0-1)"},"powerFactorC":{"type":"number","description":"Phase C power factor (0-1)"},"phase":{"description":"Set only when the list is requested with expandPhases=true and this row represents a single phase of a COIL3P device. In that mode realTimePower, realTimeCurrent, realTimeVoltage, totalEnergyUsage and powerFactor hold the values of this phase.","example":"A","allOf":[{"$ref":"#/components/schemas/McbPhase"}]},"phaseParent":{"type":"string","description":"Set when this row is one phase of a split COIL3P. The hardware settings — rated current, thresholds, reverse-power, firmware, panel position — belong to the clamp at this id, not to the leg; only the name and the Room / Zone / Tenant assignment are the leg's own.","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a whole device.","example":false},"phaseName":{"type":"string","description":"User-defined name of this row's phase (expandPhases=true rows only)","example":"Kitchen"}},"required":["_id","macId","deviceName","onStatus","totalEnergyUsage","tenantId","tenantName","zoneId","zoneName","roomId","roomName","dbId","dbName","systemId","systemName","realTimePower","realTimeCurrent","ssid","mcbType","ratedCurrent"]},"GetMCBsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"List of MCBs with organizational hierarchy details","type":"array","items":{"$ref":"#/components/schemas/FilteredMcbDto"}}},"required":["total","page","size","totalPages","data"]},"MCBNameSuggestionDto":{"type":"object","properties":{"mcbId":{"type":"string","description":"MCB ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"suggestedName":{"type":"string","description":"Suggested device name composed of Distribution Box name, Room name, and Measurement type","example":"DB-4-7U-5 | 4/7/E | Lights & Fans"},"distributionBoxName":{"type":"string","description":"Distribution Box name","example":"DB-4-7U-5"},"roomName":{"type":"string","description":"Room name","example":"4/7/E"},"zoneName":{"type":"string","description":"Zone name","example":"Zone A"},"tenantName":{"type":"string","description":"Tenant name","example":"Tenant A"},"deviceNumber":{"type":"number","description":"Device number","example":1}},"required":["mcbId","suggestedName"]},"GetBulkMCBNamesResponseDto":{"type":"object","properties":{"names":{"description":"Array of MCB IDs with their suggested names","type":"array","items":{"$ref":"#/components/schemas/MCBNameSuggestionDto"}}},"required":["names"]},"MCBNameUpdateDto":{"type":"object","properties":{"mcbId":{"type":"string","description":"MCB ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceName":{"type":"string","description":"New device name to save","example":"DB-4-7U-5 | 4/7/E | Lights & Fans"}},"required":["mcbId","deviceName"]},"SaveBulkMCBNamesDto":{"type":"object","properties":{"updates":{"description":"Array of MCB ID and name pairs to save","example":[{"mcbId":"64b7f1e6f9c9d4e5b8a9f7c3","deviceName":"DB-4-7U-5 | 4/7/E | Lights & Fans"},{"mcbId":"64b7f1e6f9c9d4e5b8a9f7c4","deviceName":"DB-4-7U-5 | 4/7/E | Aircon"}],"type":"array","items":{"$ref":"#/components/schemas/MCBNameUpdateDto"}}},"required":["updates"]},"MCBUpdateDto":{"type":"object","properties":{"mcbId":{"type":"string","description":"MCB ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"ratedCurrent":{"type":"number","description":"Rated current capacity in amperes (1-100)","example":16},"ratedVoltage":{"type":"number","description":"Rated voltage in volts","example":230},"mcbType":{"description":"Type of MCB","example":"COIL","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"measurementType":{"description":"Type of load/circuit being measured by this MCB","example":"SSO","allOf":[{"$ref":"#/components/schemas/McbMeasurementType"}]}},"required":["mcbId"]},"MassUpdateMCBDto":{"type":"object","properties":{"updates":{"description":"Array of MCB updates","example":[{"mcbId":"64b7f1e6f9c9d4e5b8a9f7c3","ratedCurrent":16,"ratedVoltage":230,"mcbType":"COIL","measurementType":"SSO"},{"mcbId":"64b7f1e6f9c9d4e5b8a9f7c4","ratedCurrent":32,"mcbType":"COIL"}],"type":"array","items":{"$ref":"#/components/schemas/MCBUpdateDto"}}},"required":["updates"]},"UpdateMCBDto":{"type":"object","properties":{"deviceName":{"type":"string","description":"Name of the device","example":"Living Room Light"},"location":{"type":"string","description":"Physical location of the device","example":"Block 123, Unit 01-23"},"remarks":{"type":"string","description":"Additional remarks or notes about the device","example":"Handle with care - critical equipment"},"tenantId":{"type":"string","description":"Tenant ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zoneId":{"type":"string","description":"Zone ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"roomId":{"type":"string","description":"Room ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device cannot be toggled ON/OFF.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"mcbType":{"description":"Type of MCB (trip curve type)","example":"BUILT_IN","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"measurementType":{"description":"Measurement type classification for energy usage categorization","example":"AIRCON","allOf":[{"$ref":"#/components/schemas/McbMeasurementType"}]},"onStatus":{"type":"string","description":"Status of the MCB","enum":["ON","OFF","INACTIVE","ERROR","ACTIVE"],"example":"ON"},"ratedCurrent":{"type":"number","description":"Rated current capacity in amperes","example":16},"ratedVoltage":{"type":"number","description":"Rated voltage in volts","example":230},"distributionBoxId":{"type":"string","description":"Distribution Box ID containing this MCB"},"digitalTwinX":{"type":"number","description":"X coordinate for digital twin SVG positioning","example":150.5},"coilRotation":{"type":"number","description":"Rotation of the coil body in the digital twin, in degrees (e.g. 0, 90, 180, 270).","example":90},"rowLine":{"type":"number","description":"Row line number (1-6) the MCB is connected to","example":1},"slotNumber":{"type":"number","description":"Slot/position number on the busbar","example":3},"isSingle":{"type":"boolean","description":"COIL3P only. How the clamp is wired. false = 3 x 1P, three independent circuits, each shown as its own row with per-phase readings and thresholds set independently; true = one combined three-phase circuit shown as a single row, with the same threshold applied to all three phases and every phase marked in use.","example":false},"phaseLabels":{"description":"COIL3P only. Optional labels for phases A, B and C (in order). Max 3 entries.","example":["Lighting","Sockets","Aircon"],"type":"array","items":{"type":"string"}}}},"MassMoveMCBsDistributionBoxDto":{"type":"object","properties":{"mcbIds":{"description":"Array of MCB IDs to move to the new distribution box","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"newDistributionBoxId":{"type":"string","description":"ID of the new distribution box to move the MCBs to. If null or undefined, MCBs will be unassigned from their current distribution box.","example":"64b7f1e6f9c9d4e5b8a9f7c3"}},"required":["mcbIds"]},"PhaseAssignmentDto":{"type":"object","properties":{"phase":{"description":"Which phase of the clamp this assignment covers","example":"A","allOf":[{"$ref":"#/components/schemas/McbPhase"}]},"roomId":{"type":"string","description":"Room this phase feeds. At least one of roomId / zoneId / tenantId must be supplied, otherwise the phase has no location and its energy is attributed nowhere.","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zoneId":{"type":"string","description":"Zone this phase feeds","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantId":{"type":"string","description":"Tenant this phase is billed to","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"deviceName":{"type":"string","description":"Display name for this phase. Defaults to the clamp's own per-phase name, or '<clamp> - <phase>'.","example":"Kitchen"},"isSpare":{"type":"boolean","description":"Mark this leg as spare — the clamp is installed but nothing is wired to this phase. A spare leg still exists as a device so it can be assigned later, but is excluded from device counts, energy views and analytics. Clear it with PATCH /mcb/:systemId/:legId { isSpare: false } once the circuit is connected.","default":false}},"required":["phase"]},"SplitMcbPhasesDto":{"type":"object","properties":{"assignments":{"description":"One entry per phase to break out. Supply only the phases that are physically wired — a clamp with two live phases yields two children, not three. Each phase may appear at most once.","type":"array","items":{"$ref":"#/components/schemas/PhaseAssignmentDto"}}},"required":["assignments"]},"PhaseSplitIntegrityDto":{"type":"object","properties":{"parentsStillAttached":{"description":"Split clamps that still carry an organisational ref. Each one is counted alongside its own children, so its energy appears twice. The most serious of the three — it reaches invoices silently.","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"orphanedChildren":{"description":"Active phase children whose parent clamp is missing or no longer marked as split. Their history cannot be projected, so they report zero.","example":[],"type":"array","items":{"type":"string"}},"childrenWithoutLocation":{"description":"Active phase children with no room, zone or tenant, so their energy is attributed to no location. Informational only — it does not affect `healthy`, because a clamp that was never assigned produces unassigned children too, which is a pre-existing data gap rather than a fault in the split.","example":[],"type":"array","items":{"type":"string"}},"healthy":{"type":"boolean","description":"True when no split clamp is still attached and no child is orphaned — the two states that corrupt reported energy. `childrenWithoutLocation` is excluded deliberately; see its description.","example":true}},"required":["parentsStillAttached","orphanedChildren","childrenWithoutLocation","healthy"]},"MCBPositionDto":{"type":"object","properties":{"mcbId":{"type":"string","description":"MCB ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"digitalTwinX":{"type":"number","description":"X coordinate for digital twin SVG positioning","example":150.5},"digitalTwinY":{"type":"number","description":"Y coordinate for digital twin SVG positioning (free-placed devices such as coils)","example":130.5},"rowLine":{"type":"number","description":"Row line number (1-6) the MCB is connected to","example":1},"slotNumber":{"type":"number","description":"Slot/position number on the busbar","example":3},"coilClampPositions":{"description":"Absolute canvas positions of the 3 CT clamps (COIL3P devices only)","type":"array","items":{"$ref":"#/components/schemas/CoilClampPositionDto"}},"coilRotation":{"type":"number","description":"Rotation of the coil body in the digital twin, in degrees (e.g. 0, 90, 180, 270).","example":90}},"required":["mcbId","rowLine"]},"BulkUpdateMCBPositionsDto":{"type":"object","properties":{"positions":{"description":"Array of MCB positions to update","example":[{"mcbId":"64b7f1e6f9c9d4e5b8a9f7c3","digitalTwinX":150.5,"rowLine":1,"slotNumber":3},{"mcbId":"64b7f1e6f9c9d4e5b8a9f7c4","digitalTwinX":250.5,"rowLine":2,"slotNumber":4}],"type":"array","items":{"$ref":"#/components/schemas/MCBPositionDto"}}},"required":["positions"]},"SetReversePowerDto":{"type":"object","properties":{"isReversePower":{"type":"boolean","description":"Whether to enable reverse power mode. When true, negative power/current values are converted to positive (absolute values).","example":true},"isAReversePower":{"type":"boolean","description":"Whether to enable reverse power mode. When true, negative power/current values are converted to positive (absolute values).","example":true},"isBReversePower":{"type":"boolean","description":"Whether to enable reverse power mode. When true, negative power/current values are converted to positive (absolute values).","example":true},"isCReversePower":{"type":"boolean","description":"Whether to enable reverse power mode. When true, negative power/current values are converted to positive (absolute values).","example":true}},"required":["isReversePower","isAReversePower","isBReversePower","isCReversePower"]},"SetReversePowerResponseDto":{"type":"object","properties":{"mcbId":{"type":"string","description":"The MCB ID that was updated","example":"507f1f77bcf86cd799439011"},"isReversePower":{"type":"boolean","description":"The new reverse power status","example":true},"isAReversePower":{"type":"boolean","description":"The new reverse power status","example":true},"isBReversePower":{"type":"boolean","description":"The new reverse power status","example":true},"isCReversePower":{"type":"boolean","description":"The new reverse power status","example":true},"hourlyRecordsConverted":{"type":"number","description":"Number of hourly usage history records converted to absolute values","example":24},"dailyRecordsConverted":{"type":"number","description":"Number of daily usage history records converted to absolute values","example":7}},"required":["mcbId","isReversePower","isAReversePower","isBReversePower","isCReversePower","hourlyRecordsConverted","dailyRecordsConverted"]},"PeopleCounterConfig":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the BLE people counter is scanning. Enabling takes effect only on the next device boot, so a pending enable reports false until then, as does a unit whose BLE radio failed to start","example":true},"rssiGate":{"type":"number","description":"Room boundary in dBm (-100..-20); weaker devices are treated as past the wall. Must be calibrated per site","example":-50},"connectDuration":{"type":"number","description":"Seconds a BLE device must stay inside the gate before it is counted as connected (10..3600)","example":30},"disconnectDuration":{"type":"number","description":"Seconds a BLE device must be unseen before it is counted as disconnected (5..3600)","example":20},"debugModeEnabled":{"type":"boolean","description":"Whether the device publishes the BLE devices it sees on the generic debug topic (feature: peopleCounter)","example":false}}},"AirconControlConfig":{"type":"object","properties":{"peopleCounter":{"$ref":"#/components/schemas/PeopleCounterConfig"}}},"AirconControlIrType":{"type":"string","enum":["AIRCON","FAN"],"description":"Type of IR device being controlled (AIRCON or FAN)"},"AirconControlFanBrand":{"type":"string","enum":["KDK_U48FP","KDK_U60FW","U60FWS","KDK_T60AW","T60AWS"],"description":"Brand of the fan unit (only applicable when irType is FAN)"},"AirconControlSeriesType":{"type":"string","enum":["MAXWELL","MAXWELL_PRO","MAXWELL_ULTRA"],"description":"Series type — MAXWELL (control only), MAXWELL_PRO (control + ambient temp/humidity sensing), or MAXWELL_ULTRA (control + ambient temp/humidity + motion sensing)"},"AirconControl":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceType":{"description":"Type of device (discriminator field)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"macId":{"type":"string","description":"MAC ID of the Device (from IoT device)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Name of the Device","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"onStatus":{"description":"Status of the Device (ON / OFF / INACTIVE / ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend (cloud / scheduled). Set when a toggle is issued; cleared when the device reports a matching state or a physical (MANUAL) press overrides it. Always ON or OFF when present.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when targetState was last written. Used to expire stale SCHEDULED intents (24hr TTL). CLOUD intents do not expire.","example":"2026-05-25T08:00:00.000Z"},"targetSource":{"description":"Origin of the pending targetState. Drives TTL behavior on reconcile. Only CLOUD / SCHEDULED are ever written here.","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"location":{"type":"string","description":"Physical location or address where the device is installed","example":"Room 101, Building A"},"remarks":{"type":"string","description":"Remarks for the Device"},"deviceModel":{"type":"string","description":"Model name or identifier of the device","example":"TP-Link HS110"},"deviceBrand":{"type":"string","description":"Brand or manufacturer of the device","example":"TP-Link"},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups","example":1},"chipModel":{"description":"Hardware chip model identifier used in the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is connected to","example":"IoT-Network-5G"},"wifiError":{"type":"string","description":"Last WiFi shadow delta failure reported by the device (e.g. join_failed); cleared once it reports a successful join","example":"join_failed"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join. Set at send time on both the shadow and legacy changeWifi paths; convergence is `ssid` matching any desired SSID.","example":"Corp-IoT"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them.","example":["Corp-IoT","Corp-IoT-2","Corp-IoT-3"],"type":"array","items":{"type":"string"}},"config":{"description":"Settings as last reported by the device into its `config` shadow","allOf":[{"$ref":"#/components/schemas/AirconControlConfig"}]},"pendingConfig":{"description":"Settings written to the `config` shadow but not yet reported back. A key the device reports at a different value was rejected — except peopleCounter.enabled: true, which only confirms on the next boot","allOf":[{"$ref":"#/components/schemas/AirconControlConfig"}]},"configRequestedAt":{"format":"date-time","type":"string","description":"When settings were last written to the `config` shadow"},"configReportedAt":{"format":"date-time","type":"string","description":"When the device last reported into its `config` shadow"},"rssi":{"type":"number","description":"Latest WiFi signal strength reported by the device (dBm). Updated on each realtime MQTT report.","example":-65},"tenant":{"description":"Tagged Tenant","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"zone":{"description":"Tagged Zone","allOf":[{"$ref":"#/components/schemas/Zone"}]},"room":{"description":"Tagged Room","allOf":[{"$ref":"#/components/schemas/Room"}]},"system":{"description":"Tagged System","allOf":[{"$ref":"#/components/schemas/System"}]},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a physical unit. Such rows have no real MAC, never report telemetry, and are excluded from firmware, OTA, provisioning and panel-slot views.","example":false},"isRidgeChild":{"type":"boolean","description":"True when this row is a unit behind a RIDGE gateway rather than a directly-connected unit. Such rows have no real MAC, are never fleet-provisioned, and are excluded from firmware, OTA and liveness watchdogs — their gateway carries those.","example":false},"hasPhaseChildren":{"type":"boolean","description":"True when this clamp has been split into per-phase children. Such a clamp is org-detached and excluded from energy and organisational views — its children carry those — while remaining the sole target for firmware, OTA and diagnostics.","example":false},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2021-09-01"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device on/off status cannot be toggled.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"deviceSchedule":{"description":"Schedule configuration for the device. Contains recurring weekly schedules and one-time scheduled tasks.","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the single Schedule (in the schedules collection) this device currently follows. deviceSchedule is compiled from that schedule. When null, the device follows nothing.","example":"507f1f77bcf86cd799439011","nullable":true},"realTimePower":{"type":"number","description":"Real-time power consumption in watts","example":1500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes","example":6.5},"totalEnergyUsage":{"type":"number","description":"Total cumulative energy usage in kWh","example":0},"totalEnergySaved":{"type":"number","description":"Total energy saved through optimization in kWh","example":0},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update OTA attempts sent to this device","example":0},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether device is blacklisted from auto-updates (max retries exceeded)","example":false},"manualUpdateBlacklist":{"type":"boolean","description":"Whether device is manually blacklisted from auto-updates by admin","example":false},"lastAutoUpdateAttempt":{"format":"date-time","type":"string","description":"Timestamp of last auto-update OTA attempt"},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","default":false},"inactivityNotificationSentAt":{"format":"date-time","type":"string","description":"Timestamp when the last inactivity notification was sent for this device"},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"pendingThresholds":{"type":"object","description":"Threshold limits sent to the device but not yet confirmed by its firmware. Keys match DeviceThresholdsDto. Present only while a save is outstanding — a key that persists here is not being enforced by the hardware.","additionalProperties":{"type":"number"}},"thresholds":{"description":"This device's alert thresholds gathered into one object. Which fields are populated depends on deviceType — see DeviceThresholdsDto. Absent when no thresholds are configured. The same values remain available as flat top-level fields.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"isIRRemote":{"type":"boolean","description":"Whether this is an IR remote (true) or wired control (false)","example":true},"irType":{"description":"Type of IR device being controlled (AIRCON or FAN)","example":"AIRCON","default":"AIRCON","allOf":[{"$ref":"#/components/schemas/AirconControlIrType"}]},"fanBrand":{"description":"Brand of the fan unit (only applicable when irType is FAN)","example":"KDK_T60AW","allOf":[{"$ref":"#/components/schemas/AirconControlFanBrand"}]},"fanSpeed":{"type":"number","description":"Fan speed setting (only applicable when irType is FAN)","example":1},"brand":{"description":"Brand of the AC unit being controlled","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"modelNumber":{"type":"string","description":"Manufacturer model number (e.g. FTKM50QV1M). Required for consumption estimation via AirconModelSpec lookup. Stored uppercased and trimmed.","example":"FTKM50QV1M"},"temperature":{"type":"number","description":"Temperature setting in degrees Celsius","example":24},"mode":{"description":"AC operation mode (COOL, HEAT, DRY, FAN, AUTO)","example":"COOL","allOf":[{"$ref":"#/components/schemas/AirconControlMode"}]},"fan":{"description":"Fan speed setting (LOW, MEDIUM, HIGH, AUTO)","example":"AUTO","allOf":[{"$ref":"#/components/schemas/AirconControlFan"}]},"swing":{"description":"Swing/oscillation setting (ON, OFF, AUTO)","example":"OFF","allOf":[{"$ref":"#/components/schemas/AirconControlSwing"}]},"temperatureMin":{"type":"number","description":"Min ambient temperature threshold in Celsius for alerts","example":18},"temperatureMax":{"type":"number","description":"Max ambient temperature threshold in Celsius for alerts","example":30},"humidityMin":{"type":"number","description":"Min ambient humidity threshold percentage for alerts","example":30},"humidityMax":{"type":"number","description":"Max ambient humidity threshold percentage for alerts","example":80},"notificationThresholdHour":{"type":"number","description":"Minimum hours between repeated notifications for the same alert type","example":4,"default":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"seriesType":{"description":"Series type — MAXWELL (control only), MAXWELL_PRO (control + ambient temp/humidity sensing), or MAXWELL_ULTRA (control + ambient temp/humidity + motion sensing)","example":"MAXWELL","default":"MAXWELL","allOf":[{"$ref":"#/components/schemas/AirconControlSeriesType"}]},"realTimeMotionDetected":{"type":"boolean","description":"Latest real-time motion detection reading from the device (MAXWELL_ULTRA only)","example":false,"nullable":true},"realTimeOccupancy":{"type":"number","description":"Latest occupancy estimate from the BLE people counter. null when the last report carried no occupancy — never 0 by default","example":7,"nullable":true}},"required":["_id","deviceType","macId","deviceName","deviceNumber","onStatus","location","remarks","batchNumber","chipModel","versionNumber","ssid","tenant","zone","room","system","createdAt","updatedAt","lastSeen","isLocked","totalEnergyUsage","totalEnergySaved","autoUpdateAttempts","autoUpdateBlacklisted","manualUpdateBlacklist","inactivityNotificationEnabled","isIRRemote","irType","temperature"]},"FilteredAirconControlDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique database identifier of the device document in the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"Unique identifier assigned to the device by the IoT hardware (MAC ID)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Friendly name for the device for easy identification","example":"Living Room AC"},"onStatus":{"description":"Current operational status of the device (ON, OFF, INACTIVE, or ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend. Present when a toggle was issued but the device has not yet acknowledged (e.g. issued while INACTIVE). Frontend can derive a \"Pending\" indicator when this differs from onStatus.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when the pending targetState was last written.","example":"2026-05-25T08:00:00.000Z"},"totalEnergyUsage":{"type":"number","description":"Cumulative total energy consumption in kilowatt-hours (kWh) since device installation","example":500},"deviceNumber":{"type":"number","description":"Auto-incremented sequential number for tracking device registration order","example":3},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups and quality control","example":23},"chipModel":{"description":"Hardware chip model identifier used in the device (e.g., ESP32C2, ESP8266)","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"tenantId":{"type":"string","description":"Unique identifier of the tenant organization that owns this device","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantName":{"type":"string","description":"Name of the tenant organization that owns this device","example":"Faculty of Engineering"},"zoneId":{"type":"string","description":"Unique identifier of the zone that this device is assigned to","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"zoneName":{"type":"string","description":"Name of the zone that this device is assigned to","example":"Zone A"},"roomId":{"type":"string","description":"Unique identifier of the room where this device is physically located","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"roomName":{"type":"string","description":"Name of the room where this device is physically located","example":"Room 101"},"dbId":{"type":"string","description":"Unique identifier of the distribution box that this device is connected to (for MCBs)","example":"64b7f1e6f9c9d4e5b8a9f7c7"},"dbName":{"type":"string","description":"Name of the distribution box that this device is connected to (for MCBs)","example":"Main Distribution Box"},"systemId":{"type":"string","description":"Unique identifier of the system that this device belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system that this device belongs to","example":"NUS Engineering Building"},"realTimePower":{"type":"number","description":"Real-time power consumption in watts (W) measured by the device","example":115},"realTimeCurrent":{"type":"number","description":"Real-time current draw in amperes (A) measured by the device","example":0.5},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is currently connected to","example":"XH001"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join","example":"XH001"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join","example":"XH001-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join","example":"XH001-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them","example":["XH001","XH001-2","XH001-3"],"type":"array","items":{"type":"string"}},"deviceSchedule":{"description":"Weekly and one-time schedule configuration for automated device control","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the named Schedule this device currently follows. deviceSchedule is compiled from it. Absent or null when the device follows nothing.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c3"},"lastSeen":{"format":"date-time","type":"string","description":"Timestamp when the device last communicated with the system","example":"2024-10-15T12:30:00.000Z"},"isLocked":{"type":"boolean","description":"Lock status indicating if the device is locked","example":false},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device","example":true},"thresholds":{"description":"Alert thresholds configured for this device, gathered into one object. Which fields are populated depends on the device type — see DeviceThresholdsDto. Omitted entirely when nothing is set.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"isIRRemote":{"type":"boolean","description":"Whether this is an IR remote (true) or wired control (false)","example":true},"irType":{"description":"Type of IR device being controlled (AIRCON or FAN)","example":"AIRCON","default":"AIRCON","allOf":[{"$ref":"#/components/schemas/AirconControlIrType"}]},"fanBrand":{"description":"Brand of the fan unit (only applicable when irType is FAN)","example":"KDK_T60AW","allOf":[{"$ref":"#/components/schemas/AirconControlFanBrand"}]},"fanSpeed":{"type":"number","description":"Fan speed setting (only applicable when irType is FAN)","example":1},"brand":{"description":"Brand of the AC unit being controlled","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"modelNumber":{"type":"string","description":"Manufacturer model number (e.g. FTKM50QV1M). Used with brand for consumption estimation via AirconModelSpec lookup.","example":"FTKM50QV1M"},"temperature":{"type":"number","description":"Target temperature setting in degrees Celsius","example":24},"mode":{"description":"AC operation mode (COOL, HEAT, DRY, FAN, AUTO)","example":"COOL","allOf":[{"$ref":"#/components/schemas/AirconControlMode"}]},"fan":{"description":"Fan speed setting (LOW, MEDIUM, HIGH, AUTO)","example":"AUTO","allOf":[{"$ref":"#/components/schemas/AirconControlFan"}]},"swing":{"description":"Swing/oscillation setting (ON, OFF, AUTO)","example":"OFF","allOf":[{"$ref":"#/components/schemas/AirconControlSwing"}]},"seriesType":{"description":"Series type — MAXWELL (control only), MAXWELL_PRO (control + ambient temp/humidity sensing), or MAXWELL_ULTRA (control + ambient temp/humidity + motion sensing)","example":"MAXWELL","allOf":[{"$ref":"#/components/schemas/AirconControlSeriesType"}]},"realTimeMotionDetected":{"type":"boolean","description":"Latest real-time motion detection reading from the device (MAXWELL_ULTRA only)","example":false,"nullable":true},"realTimeOccupancy":{"type":"number","description":"Latest occupancy estimate from the people counter. null or absent means no reading, not an empty room","example":7,"nullable":true},"config":{"description":"Settings as last reported by the device into its config shadow","allOf":[{"$ref":"#/components/schemas/AirconControlConfig"}]},"pendingConfig":{"description":"Settings written to the config shadow but not yet reported back","allOf":[{"$ref":"#/components/schemas/AirconControlConfig"}]},"configRequestedAt":{"format":"date-time","type":"string","description":"When settings were last written to the config shadow"},"configReportedAt":{"format":"date-time","type":"string","description":"When the device last reported into its config shadow"}},"required":["_id","macId","deviceName","onStatus","totalEnergyUsage","tenantId","tenantName","zoneId","zoneName","roomId","roomName","dbId","dbName","systemId","systemName","realTimePower","realTimeCurrent","ssid","isIRRemote","irType","temperature"]},"GetAirconControlsDto":{"type":"object","properties":{"data":{"description":"List of aircon control devices with organizational hierarchy details","type":"array","items":{"$ref":"#/components/schemas/FilteredAirconControlDto"}},"total":{"type":"number","description":"Total number of aircon control devices","example":10},"page":{"type":"number","description":"Current page number","example":1},"size":{"type":"number","description":"Number of items per page","example":10}},"required":["data","total","page","size"]},"UpdateAirconControlDto":{"type":"object","properties":{"deviceName":{"type":"string","description":"Name of the device","example":"Living Room Light"},"location":{"type":"string","description":"Physical location of the device","example":"Block 123, Unit 01-23"},"remarks":{"type":"string","description":"Additional remarks or notes about the device","example":"Handle with care - critical equipment"},"tenantId":{"type":"string","description":"Tenant ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zoneId":{"type":"string","description":"Zone ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"roomId":{"type":"string","description":"Room ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device cannot be toggled ON/OFF.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"isIRRemote":{"type":"boolean","description":"Whether this is an IR remote (true) or wired control (false)","example":true},"brandType":{"description":"Brand of the AC unit being controlled","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"modelNumber":{"type":"string","description":"Manufacturer model number used to look up rated power for consumption estimation (e.g. FTKM50QV1M).","example":"FTKM50QV1M"},"irType":{"description":"Type of IR device being controlled (AIRCON or FAN)","example":"AIRCON","allOf":[{"$ref":"#/components/schemas/AirconControlIrType"}]},"fanBrand":{"description":"Brand of the fan unit (only applicable when irType is FAN)","example":"KDK_T60AW","allOf":[{"$ref":"#/components/schemas/AirconControlFanBrand"}]},"fanSpeed":{"type":"number","description":"Fan speed setting (only applicable when irType is FAN)","example":1}}},"ControlAirconDto":{"type":"object","properties":{"value":{"type":"number"},"onStatus":{"description":"Power state of the AC (ON/OFF/INACTIVE)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"temperature":{"type":"number","description":"Target temperature setting in degrees Celsius","example":24,"minimum":16,"maximum":30},"mode":{"description":"AC operation mode (COOL, HEAT, DRY, FAN, AUTO)","example":"COOL","allOf":[{"$ref":"#/components/schemas/AirconControlMode"}]},"fan":{"description":"Fan speed setting (LOW, MEDIUM, HIGH, AUTO)","example":"AUTO","allOf":[{"$ref":"#/components/schemas/AirconControlFan"}]},"swing":{"description":"Swing/oscillation setting (ON, OFF, AUTO)","example":"OFF","allOf":[{"$ref":"#/components/schemas/AirconControlSwing"}]},"fanSpeed":{"type":"number","description":"Fan speed setting (only applicable when irType is FAN)","example":1},"brandType":{"description":"Brand of the AC unit (only applicable when irType is AIRCON)","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]}}},"UpdateAirconControlThresholdDto":{"type":"object","properties":{"temperatureMin":{"type":"number","description":"Min ambient temperature threshold in Celsius","example":18},"temperatureMax":{"type":"number","description":"Max ambient temperature threshold in Celsius","example":30},"humidityMin":{"type":"number","description":"Min ambient humidity threshold percentage","example":30},"humidityMax":{"type":"number","description":"Max ambient humidity threshold percentage","example":80},"notificationThresholdHour":{"type":"number","description":"Minimum hours between repeated notifications for the same alert type","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device. MCB only.","example":true}}},"UpdatePeopleCounterConfigDto":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Disabling stops scanning immediately; enabling takes effect only on the next device boot","example":true},"rssiGate":{"type":"number","description":"Room boundary in dBm; weaker devices are treated as past the wall. Must be calibrated per site","minimum":-100,"maximum":-20,"example":-48},"connectDuration":{"type":"number","description":"Seconds a BLE device must stay inside the gate before it is counted as connected. Must stay above the 8 s walk-by bar. Device default 30","minimum":10,"maximum":3600,"example":30},"disconnectDuration":{"type":"number","description":"Seconds a BLE device must be unseen before it is counted as disconnected. Device default 20","minimum":5,"maximum":3600,"example":20},"debugModeEnabled":{"type":"boolean","description":"Publish the BLE devices the counter sees, with RSSI, on the generic debug topic for calibrating rssiGate. Stays on, reboots included, until set back to false","example":true}}},"MassUpdateBrandAirconDto":{"type":"object","properties":{"ids":{"description":"Array of Aircon Control IDs to update","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}},"brandType":{"description":"Brand to assign to all specified devices","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]}},"required":["ids","brandType"]},"BulkControlAirconDto":{"type":"object","properties":{"onStatus":{"description":"Power state of the AC (ON/OFF/INACTIVE)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"temperature":{"type":"number","description":"Target temperature setting in degrees Celsius","example":24,"minimum":16,"maximum":30},"mode":{"description":"AC operation mode (COOL, HEAT, DRY, FAN, AUTO)","example":"COOL","allOf":[{"$ref":"#/components/schemas/AirconControlMode"}]},"fan":{"description":"Fan speed setting (LOW, MEDIUM, HIGH, AUTO)","example":"AUTO","allOf":[{"$ref":"#/components/schemas/AirconControlFan"}]},"swing":{"description":"Swing/oscillation setting (ON, OFF, AUTO)","example":"OFF","allOf":[{"$ref":"#/components/schemas/AirconControlSwing"}]},"fanSpeed":{"type":"number","description":"Fan speed setting (only applicable when irType is FAN)","example":1},"brandType":{"description":"Brand of the AC unit (only applicable when irType is AIRCON)","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"ids":{"description":"Array of Aircon Control IDs to control","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"EstimateAirconConsumptionDto":{"type":"object","properties":{"startDate":{"type":"string","description":"Start of the date range (ISO 8601). Inclusive.","example":"2026-05-01T00:00:00.000Z"},"endDate":{"type":"string","description":"End of the date range (ISO 8601). Inclusive.","example":"2026-05-27T23:59:59.999Z"}},"required":["startDate","endDate"]},"AirconConsumptionByModeDto":{"type":"object","properties":{"kwh":{"type":"number","description":"Total kWh accumulated while in this mode","example":8.4},"hours":{"type":"number","description":"Total runtime hours while in this mode","example":9.2}},"required":["kwh","hours"]},"AirconAmbientSource":{"type":"string","enum":["SENSOR","FALLBACK"],"description":"Where the ambient temperature came from"},"AirconConsumptionIntervalDto":{"type":"object","properties":{"start":{"format":"date-time","type":"string","example":"2026-05-23T15:07:00.000Z"},"end":{"format":"date-time","type":"string","example":"2026-05-23T15:39:00.000Z"},"mode":{"example":"COOL","allOf":[{"$ref":"#/components/schemas/AirconControlMode"}]},"temperatureC":{"type":"number","example":22},"fan":{"example":"AUTO","allOf":[{"$ref":"#/components/schemas/AirconControlFan"}]},"hours":{"type":"number","description":"Duration in hours","example":0.533},"powerKw":{"type":"number","description":"Estimated instantaneous power draw in kW","example":1.05},"kwh":{"type":"number","description":"Estimated energy consumed during this interval in kWh","example":0.56}},"required":["start","end","mode","temperatureC","fan","hours","powerKw","kwh"]},"AirconConsumptionEstimateDto":{"type":"object","properties":{"airconId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"brand":{"example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"modelNumber":{"type":"string","example":"FTKM50QV1M"},"ratedInputPowerKw":{"type":"number","description":"Rated input power (kW) used as the load reference","example":1.42},"startDate":{"format":"date-time","type":"string","example":"2026-05-01T00:00:00.000Z"},"endDate":{"format":"date-time","type":"string","example":"2026-05-27T23:59:59.999Z"},"ambientTempC":{"type":"number","description":"Ambient temperature used in the load-factor calc","example":30},"ambientSource":{"description":"Where the ambient temperature came from","example":"FALLBACK","allOf":[{"$ref":"#/components/schemas/AirconAmbientSource"}]},"totalKwh":{"type":"number","example":12.4},"totalRuntimeHours":{"type":"number","example":14.7},"tariffCentsPerKwh":{"type":"number","description":"Current system tariff in cents/kWh used to compute cost. Omitted if no active tariff rate is set on the system.","example":28.5},"costSgd":{"type":"number","description":"Total cost in SGD = totalKwh × (tariffCentsPerKwh / 100). Omitted when no active tariff rate is set on the system.","example":3.97},"byMode":{"type":"object","description":"kWh and runtime hours broken down by AirconControlMode. Keys are mode names (COOL, DRY, FAN, AUTO, HEAT). Values are AirconConsumptionByModeDto.","additionalProperties":{"$ref":"#/components/schemas/AirconConsumptionByModeDto"}},"intervals":{"description":"Per-interval breakdown used to build the totals","type":"array","items":{"$ref":"#/components/schemas/AirconConsumptionIntervalDto"}},"accuracyNote":{"type":"string","description":"Disclaimer on accuracy","example":"Estimate ±20-30% — derived from settings history × rated input power × heuristic load factor. Install a circuit-level meter (MCB) for billing-grade accuracy."}},"required":["airconId","brand","modelNumber","ratedInputPowerKw","startDate","endDate","ambientTempC","ambientSource","totalKwh","totalRuntimeHours","byMode","intervals","accuracyNote"]},"CreateRoomDto":{"type":"object","properties":{"name":{"type":"string","description":"Display name for the room. This will be used to identify the room throughout the system.","example":"Living Room"},"squareMeter":{"type":"number","description":"Floor area of the room in square meters.","example":25.5},"remarks":{"type":"string","description":"Optional administrative notes or comments about the room. Used for describing the room purpose or equipment.","example":"Conference room with presentation equipment"},"systemId":{"type":"string","description":"Unique identifier of the system that this room belongs to. Automatically populated from URL parameter.","example":"64b7f1e6f9c9d4e5b8a9f7c0"},"tenantId":{"type":"string","description":"Optional identifier of the tenant organization that owns this room. Can be omitted for single-tenant systems.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"zoneId":{"type":"string","description":"Optional identifier of the zone to assign this room to. If provided, the room will be accessible by this zone.","example":"64b7f1e6f9c9d4e5b8a9f7c2"},"roomType":{"type":"string","description":"Room type key. Must be a valid type defined for this system via /entity-type/ROOM.","example":"MEETING_ROOM"}},"required":["name"]},"GetRoomsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of room objects matching the query parameters. Each room includes plug count and total energy usage.","type":"array","items":{"$ref":"#/components/schemas/GetRoomDto"}}},"required":["total","page","size","totalPages","data"]},"UpdateRoomDto":{"type":"object","properties":{"name":{"type":"string","description":"Updated display name for the room. This will replace the existing room name.","example":"Conference Room A"},"zoneId":{"type":"string","description":"Updated zone identifier to reassign room access to a different zone.","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"tenantId":{"type":"string","description":"Updated tenant identifier to reassign the room to a different organization.","example":"64b7f1e6f9c9d4e5b8a9f7c2"},"squareMeter":{"type":"number","description":"Updated floor area of the room in square meters. Pass null to clear the existing value.","nullable":true,"example":25.5},"remarks":{"type":"string","description":"Updated administrative notes or comments about the room. Replaces existing remarks if provided.","example":"Updated with new equipment"},"roomType":{"type":"string","description":"Room type key. Must be a valid type defined for this system via /entity-type/ROOM.","example":"MEETING_ROOM"}}},"AirconModelSpecDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"brand":{"example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"modelNumber":{"type":"string","example":"FTKM50QV1M"},"ratedCoolingKw":{"type":"number","example":5},"ratedInputPowerKw":{"type":"number","example":1.42},"cspf":{"type":"number","example":5.45},"annualKwhSg":{"type":"number","example":612},"verifiedAt":{"format":"date-time","type":"string","example":"2026-05-27T00:00:00.000Z"},"notes":{"type":"string","example":"R32 refrigerant, inverter"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","brand","modelNumber","ratedCoolingKw","ratedInputPowerKw","verifiedAt","createdAt","updatedAt"]},"AirconModelSpecsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Aircon model spec rows matching the query","type":"array","items":{"$ref":"#/components/schemas/AirconModelSpecDto"}}},"required":["total","page","size","totalPages","data"]},"CreateAirconModelSpecDto":{"type":"object","properties":{"brand":{"example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"modelNumber":{"type":"string","description":"Manufacturer model number. Server uppercases + trims.","example":"FTKM50QV1M"},"ratedCoolingKw":{"type":"number","example":5},"ratedInputPowerKw":{"type":"number","example":1.42},"cspf":{"type":"number","example":5.45},"annualKwhSg":{"type":"number","example":612},"verifiedAt":{"format":"date-time","type":"string","description":"When this row was verified. Defaults to now if omitted.","example":"2026-05-27T00:00:00.000Z"},"notes":{"type":"string","example":"R32 refrigerant, inverter"}},"required":["brand","modelNumber","ratedCoolingKw","ratedInputPowerKw"]},"UpdateAirconModelSpecDto":{"type":"object","properties":{"brand":{"example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"modelNumber":{"type":"string","description":"Manufacturer model number. Server uppercases + trims.","example":"FTKM50QV1M"},"ratedCoolingKw":{"type":"number","example":5},"ratedInputPowerKw":{"type":"number","example":1.42},"cspf":{"type":"number","example":5.45},"annualKwhSg":{"type":"number","example":612},"verifiedAt":{"format":"date-time","type":"string","description":"When this row was verified. Defaults to now if omitted.","example":"2026-05-27T00:00:00.000Z"},"notes":{"type":"string","example":"R32 refrigerant, inverter"}}},"RidgeIntegrationType":{"type":"string","enum":["UNASSIGNED","DAIKIN","GENERIC_BACNET"]},"RidgeCommunicationType":{"type":"string","enum":["UNASSIGNED","DAIKIN_BACNET","BACNET_GENERIC","ITM","MODBUS_TCP","MODBUS_RTU","KNX"]},"RidgeUnitStatusCountsDto":{"type":"object","properties":{"on":{"type":"number","description":"Units reporting power ON","example":12},"off":{"type":"number","description":"Units reporting power OFF","example":8},"inactive":{"type":"number","description":"Units the gateway cannot reach, or that have not reported power yet","example":1},"total":{"type":"number","description":"Every unit the gateway fronts","example":21}},"required":["on","off","inactive","total"]},"FilteredRidgeDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique database identifier of the device document in the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"Unique identifier assigned to the device by the IoT hardware (MAC ID)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Friendly name for the device for easy identification","example":"Living Room AC"},"onStatus":{"description":"Current operational status of the device (ON, OFF, INACTIVE, or ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend. Present when a toggle was issued but the device has not yet acknowledged (e.g. issued while INACTIVE). Frontend can derive a \"Pending\" indicator when this differs from onStatus.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when the pending targetState was last written.","example":"2026-05-25T08:00:00.000Z"},"totalEnergyUsage":{"type":"number","description":"Cumulative total energy consumption in kilowatt-hours (kWh) since device installation","example":500},"deviceNumber":{"type":"number","description":"Auto-incremented sequential number for tracking device registration order","example":3},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups and quality control","example":23},"chipModel":{"description":"Hardware chip model identifier used in the device (e.g., ESP32C2, ESP8266)","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"tenantId":{"type":"string","description":"Unique identifier of the tenant organization that owns this device","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantName":{"type":"string","description":"Name of the tenant organization that owns this device","example":"Faculty of Engineering"},"zoneId":{"type":"string","description":"Unique identifier of the zone that this device is assigned to","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"zoneName":{"type":"string","description":"Name of the zone that this device is assigned to","example":"Zone A"},"roomId":{"type":"string","description":"Unique identifier of the room where this device is physically located","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"roomName":{"type":"string","description":"Name of the room where this device is physically located","example":"Room 101"},"dbId":{"type":"string","description":"Unique identifier of the distribution box that this device is connected to (for MCBs)","example":"64b7f1e6f9c9d4e5b8a9f7c7"},"dbName":{"type":"string","description":"Name of the distribution box that this device is connected to (for MCBs)","example":"Main Distribution Box"},"systemId":{"type":"string","description":"Unique identifier of the system that this device belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system that this device belongs to","example":"NUS Engineering Building"},"realTimePower":{"type":"number","description":"Real-time power consumption in watts (W) measured by the device","example":115},"realTimeCurrent":{"type":"number","description":"Real-time current draw in amperes (A) measured by the device","example":0.5},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is currently connected to","example":"XH001"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join","example":"XH001"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join","example":"XH001-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join","example":"XH001-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them","example":["XH001","XH001-2","XH001-3"],"type":"array","items":{"type":"string"}},"deviceSchedule":{"description":"Weekly and one-time schedule configuration for automated device control","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the named Schedule this device currently follows. deviceSchedule is compiled from it. Absent or null when the device follows nothing.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c3"},"lastSeen":{"format":"date-time","type":"string","description":"Timestamp when the device last communicated with the system","example":"2024-10-15T12:30:00.000Z"},"isLocked":{"type":"boolean","description":"Lock status indicating if the device is locked","example":false},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device","example":true},"thresholds":{"description":"Alert thresholds configured for this device, gathered into one object. Which fields are populated depends on the device type — see DeviceThresholdsDto. Omitted entirely when nothing is set.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"integrationType":{"description":"The kind of external integration this gateway fronts","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/RidgeIntegrationType"}]},"communicationType":{"description":"Downstream transport protocol the gateway uses to reach its external devices","example":"DAIKIN_BACNET","allOf":[{"$ref":"#/components/schemas/RidgeCommunicationType"}]},"configuration":{"type":"object","description":"Per-integration connection configuration. Shape depends on integrationType.","example":{"deviceInstance":121,"portCount":4,"host":"192.168.1.10"}},"properties":{"type":"object","description":"Downstream member dictionary (member number → object spec)."},"unitStatusCounts":{"description":"ON / OFF / INACTIVE breakdown of the external devices this gateway fronts","allOf":[{"$ref":"#/components/schemas/RidgeUnitStatusCountsDto"}]}},"required":["_id","macId","deviceName","onStatus","totalEnergyUsage","tenantId","tenantName","zoneId","zoneName","roomId","roomName","dbId","dbName","systemId","systemName","realTimePower","realTimeCurrent","ssid","integrationType","communicationType"]},"GetRidgesDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of RIDGE gateways matching the query parameters","type":"array","items":{"$ref":"#/components/schemas/FilteredRidgeDto"}}},"required":["total","page","size","totalPages","data"]},"RidgeControllerInfo":{"type":"object","properties":{"version":{"type":"number","description":"iTM protocol version reported by the controller","example":2},"tempUnit":{"type":"string","description":"Temperature unit the controller reports setpoints/temps in","example":"C"},"mac":{"type":"string","description":"MAC address of the controller itself (not the RIDGE gateway macId)","example":"00804F85A717"},"firmware":{"type":"string","description":"Controller firmware version string","example":"Ver1.27.00"}}},"Ridge":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceType":{"description":"Type of device (discriminator field)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"macId":{"type":"string","description":"MAC ID of the Device (from IoT device)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Name of the Device","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"onStatus":{"description":"Status of the Device (ON / OFF / INACTIVE / ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend (cloud / scheduled). Set when a toggle is issued; cleared when the device reports a matching state or a physical (MANUAL) press overrides it. Always ON or OFF when present.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when targetState was last written. Used to expire stale SCHEDULED intents (24hr TTL). CLOUD intents do not expire.","example":"2026-05-25T08:00:00.000Z"},"targetSource":{"description":"Origin of the pending targetState. Drives TTL behavior on reconcile. Only CLOUD / SCHEDULED are ever written here.","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"location":{"type":"string","description":"Physical location or address where the device is installed","example":"Room 101, Building A"},"remarks":{"type":"string","description":"Remarks for the Device"},"deviceModel":{"type":"string","description":"Model name or identifier of the device","example":"TP-Link HS110"},"deviceBrand":{"type":"string","description":"Brand or manufacturer of the device","example":"TP-Link"},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups","example":1},"chipModel":{"description":"Hardware chip model identifier used in the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is connected to","example":"IoT-Network-5G"},"wifiError":{"type":"string","description":"Last WiFi shadow delta failure reported by the device (e.g. join_failed); cleared once it reports a successful join","example":"join_failed"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join. Set at send time on both the shadow and legacy changeWifi paths; convergence is `ssid` matching any desired SSID.","example":"Corp-IoT"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them.","example":["Corp-IoT","Corp-IoT-2","Corp-IoT-3"],"type":"array","items":{"type":"string"}},"config":{"description":"Settings as last reported by the device into its `config` shadow, one block per feature","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"pendingConfig":{"description":"Settings written to the `config` shadow but not yet reported back. A key the device reports at a different value was rejected","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"configRequestedAt":{"format":"date-time","type":"string","description":"When settings were last written to the `config` shadow"},"configReportedAt":{"format":"date-time","type":"string","description":"When the device last reported into its `config` shadow"},"rssi":{"type":"number","description":"Latest WiFi signal strength reported by the device (dBm). Updated on each realtime MQTT report.","example":-65},"tenant":{"description":"Tagged Tenant","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"zone":{"description":"Tagged Zone","allOf":[{"$ref":"#/components/schemas/Zone"}]},"room":{"description":"Tagged Room","allOf":[{"$ref":"#/components/schemas/Room"}]},"system":{"description":"Tagged System","allOf":[{"$ref":"#/components/schemas/System"}]},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a physical unit. Such rows have no real MAC, never report telemetry, and are excluded from firmware, OTA, provisioning and panel-slot views.","example":false},"isRidgeChild":{"type":"boolean","description":"True when this row is a unit behind a RIDGE gateway rather than a directly-connected unit. Such rows have no real MAC, are never fleet-provisioned, and are excluded from firmware, OTA and liveness watchdogs — their gateway carries those.","example":false},"hasPhaseChildren":{"type":"boolean","description":"True when this clamp has been split into per-phase children. Such a clamp is org-detached and excluded from energy and organisational views — its children carry those — while remaining the sole target for firmware, OTA and diagnostics.","example":false},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2021-09-01"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device on/off status cannot be toggled.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"deviceSchedule":{"description":"Schedule configuration for the device. Contains recurring weekly schedules and one-time scheduled tasks.","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the single Schedule (in the schedules collection) this device currently follows. deviceSchedule is compiled from that schedule. When null, the device follows nothing.","example":"507f1f77bcf86cd799439011","nullable":true},"realTimePower":{"type":"number","description":"Real-time power consumption in watts","example":1500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes","example":6.5},"totalEnergyUsage":{"type":"number","description":"Total cumulative energy usage in kWh","example":0},"totalEnergySaved":{"type":"number","description":"Total energy saved through optimization in kWh","example":0},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update OTA attempts sent to this device","example":0},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether device is blacklisted from auto-updates (max retries exceeded)","example":false},"manualUpdateBlacklist":{"type":"boolean","description":"Whether device is manually blacklisted from auto-updates by admin","example":false},"lastAutoUpdateAttempt":{"format":"date-time","type":"string","description":"Timestamp of last auto-update OTA attempt"},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","default":false},"inactivityNotificationSentAt":{"format":"date-time","type":"string","description":"Timestamp when the last inactivity notification was sent for this device"},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"pendingThresholds":{"type":"object","description":"Threshold limits sent to the device but not yet confirmed by its firmware. Keys match DeviceThresholdsDto. Present only while a save is outstanding — a key that persists here is not being enforced by the hardware.","additionalProperties":{"type":"number"}},"thresholds":{"description":"This device's alert thresholds gathered into one object. Which fields are populated depends on deviceType — see DeviceThresholdsDto. Absent when no thresholds are configured. The same values remain available as flat top-level fields.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"integrationType":{"description":"The kind of external integration this gateway fronts","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/RidgeIntegrationType"}]},"communicationType":{"description":"Downstream transport protocol the gateway uses to reach its external devices","example":"DAIKIN_BACNET","allOf":[{"$ref":"#/components/schemas/RidgeCommunicationType"}]},"configuration":{"type":"object","description":"Per-integration connection configuration (e.g. Daikin: BACnet deviceInstance, port count, host). Shape depends on integrationType.","example":{"deviceInstance":121,"portCount":4,"host":"192.168.1.10"}},"properties":{"type":"object","description":"Downstream object/member dictionary for the integration (member number → object type, R/W, value encodings). Seeded from the adapter spec; firmware-reported tables win.","example":{"members":{"1":{"name":"StartStopCommand","objectType":"binaryOutput","rw":"W","values":{"inactive":"stop","active":"operation"}}}}},"subscriptionGeneration":{"type":"number","description":"Monotonic generation of the watch set last published to the gateway. Every subscription packet carries it, so the firmware can tell a fresh set from a replay of an older one. Incremented on every publish.","example":3},"controllerInfo":{"description":"Identity the downstream controller reported in its last discovery session (iTM `systemInfo`). Absent on BACnet gateways.","allOf":[{"$ref":"#/components/schemas/RidgeControllerInfo"}]},"lastUnitNumber":{"type":"number","description":"Highest unitNumber handed to a unit behind this gateway. Only ever incremented, so a deleted unit's number is never reused.","example":12}},"required":["_id","deviceType","macId","deviceName","deviceNumber","onStatus","location","remarks","batchNumber","chipModel","versionNumber","ssid","tenant","zone","room","system","createdAt","updatedAt","lastSeen","isLocked","totalEnergyUsage","totalEnergySaved","autoUpdateAttempts","autoUpdateBlacklisted","manualUpdateBlacklist","inactivityNotificationEnabled","integrationType","communicationType"]},"CreateRidgeDto":{"type":"object","properties":{"macId":{"type":"string","description":"Unique identifier assigned to the device by the IoT hardware (MAC ID)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Friendly name for the device for easy identification","example":"Living Room Device"},"onStatus":{"description":"Initial operational status of the device when created","example":"INACTIVE","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"integrationType":{"description":"The kind of external integration this gateway fronts","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/RidgeIntegrationType"}]},"communicationType":{"description":"Downstream transport protocol the gateway uses to reach its external devices","example":"DAIKIN_BACNET","allOf":[{"$ref":"#/components/schemas/RidgeCommunicationType"}]},"configuration":{"type":"object","description":"Per-integration connection configuration (e.g. Daikin: BACnet deviceInstance, port count, host). Shape depends on integrationType.","example":{"deviceInstance":121,"portCount":4,"host":"192.168.1.10"}},"properties":{"type":"object","description":"Downstream member dictionary (member number → object spec). Omit to seed from the adapter defaults for the integration."}},"required":["macId","onStatus","integrationType","communicationType"]},"UpdateRidgeDto":{"type":"object","properties":{"integrationType":{"description":"Integration the gateway fronts (assign after an UNASSIGNED auto-provision)","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/RidgeIntegrationType"}]},"communicationType":{"description":"Downstream transport (assign after an UNASSIGNED auto-provision)","example":"DAIKIN_BACNET","allOf":[{"$ref":"#/components/schemas/RidgeCommunicationType"}]},"deviceName":{"type":"string","description":"Friendly name for the gateway","example":"Level 3 Daikin Gateway"},"location":{"type":"string","description":"Physical location of the gateway","example":"Riser Room 3"},"remarks":{"type":"string","description":"Admin notes","example":"Fronts AHU units on ports 1-2"},"isLocked":{"type":"boolean","description":"Whether the gateway is locked from control","example":false},"configuration":{"type":"object","description":"Per-integration connection configuration (e.g. Daikin: BACnet deviceInstance, port count, host). Shape depends on integrationType.","example":{"deviceInstance":121,"portCount":4,"host":"192.168.1.10"}},"properties":{"type":"object","description":"Downstream member dictionary (member number → object spec)."}}},"SyncRidgeConfigurationDto":{"type":"object","properties":{"username":{"type":"string","description":"iTM HTTP auth username. Relayed to the gateway, never stored cloud-side.","example":"svm"},"password":{"type":"string","description":"iTM HTTP auth password. Relayed to the gateway, never stored cloud-side.","example":"svm"}}},"RidgeControlFieldSettingDto":{"type":"object","properties":{"key":{"type":"string","description":"Command key this entry positions — matches ExternalDeviceCommand.key","example":"power"},"visible":{"type":"boolean","description":"Whether the control UI surfaces this command as a primary control","example":true}},"required":["key","visible"]},"GetRidgeControlLayoutDto":{"type":"object","properties":{"systemId":{"type":"string","description":"The system the layout belongs to"},"integrationType":{"description":"The integration type the layout applies to","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/RidgeIntegrationType"}]},"fields":{"description":"Ordered control fields with their visibility","type":"array","items":{"$ref":"#/components/schemas/RidgeControlFieldSettingDto"}},"offTargetThresholdC":{"type":"number","description":"How far room temperature may sit from setpoint before a unit is reported as not holding target (°C). Absent when the system has not configured one.","example":1.5},"isDefault":{"type":"boolean","description":"True when no layout has been saved and the integration default is returned","example":false}},"required":["systemId","integrationType","fields","isDefault"]},"UpdateRidgeControlLayoutDto":{"type":"object","properties":{"fields":{"description":"Ordered control fields with their visibility","type":"array","items":{"$ref":"#/components/schemas/RidgeControlFieldSettingDto"}},"offTargetThresholdC":{"type":"number","description":"How far room temperature may sit from setpoint before a unit is reported as not holding target (°C). Omit to leave the system on the client default.","example":1.5,"minimum":0.5,"maximum":10}},"required":["fields"]},"ExternalDeviceType":{"type":"string","enum":["AHU","FCU","VRV","CHILLER","ENERGY_METER","WATER_METER","BTU_METER","LIGHTING","VALVE","PUMP","SENSOR"]},"ExternalDeviceCategory":{"type":"string","enum":["HVAC","METER","LIGHTING","VALVE","PUMP","SENSOR"]},"DaikinMode":{"type":"number","enum":[1,2,3,4,5],"description":"AC mode (1=cool, 2=heat, 3=fan, 4=auto, 5=dry)"},"DaikinFanRate":{"type":"number","enum":[1,2,3,4],"description":"Fan rate (1=low, 2=high, 3=middle, 4=auto)"},"DaikinHvacControlDto":{"type":"object","properties":{"power":{"type":"boolean","description":"Power the unit on (true) or off (false)","example":true},"setpoint":{"type":"number","description":"Setpoint temperature in °C. The integration narrows this per mode (Daikin: cool 18–32, heat 14–28; rejected in auto).","example":23,"minimum":14,"maximum":32},"mode":{"description":"AC mode (1=cool, 2=heat, 3=fan, 4=auto, 5=dry)","example":1,"allOf":[{"$ref":"#/components/schemas/DaikinMode"}]},"fan":{"description":"Fan rate (1=low, 2=high, 3=middle, 4=auto)","example":4,"allOf":[{"$ref":"#/components/schemas/DaikinFanRate"}]},"lockPower":{"type":"boolean","description":"Lock (true) or unlock (false) the wired remote power button","example":false},"lockMode":{"type":"boolean","description":"Lock (true) or unlock (false) the wired remote mode selector","example":false},"lockSetpoint":{"type":"boolean","description":"Lock (true) or unlock (false) the wired remote setpoint adjust","example":false},"forcedOff":{"type":"boolean","description":"Forced system stop (true) or clear (false) — port-master units only (ac 0/64/128/192). Units stay OFF after clear.","example":false},"filterReset":{"type":"boolean","description":"Reset the dirty-filter sign","example":false},"forcedThermoOff":{"type":"boolean","description":"Force the thermostat off (true) or clear (false)","example":false},"energySaving":{"type":"boolean","description":"Enable (true) or clear (false) energy-saving operation","example":false},"airDirection":{"type":"number","description":"Louver / air-direction position. The valid positions are model-specific, so no range is enforced here — a position the unit does not support is rejected by the unit.","example":1}}},"ExternalDeviceOneTimeScheduleEntry":{"type":"object","properties":{"dateTime":{"type":"string","description":"When to apply the control (ISO 8601)","format":"date-time","example":"2026-06-15T01:00:00.000Z"},"control":{"description":"Full control snapshot to apply at the scheduled time","allOf":[{"$ref":"#/components/schemas/DaikinHvacControlDto"}]},"ruleName":{"type":"string","description":"EventBridge rule name for tracking and management"}},"required":["dateTime","control"]},"ExternalDeviceDailyScheduleEntry":{"type":"object","properties":{"time":{"type":"string","description":"Local time in HH:mm to apply the control","example":"09:00"},"control":{"description":"Full control snapshot to apply at the scheduled time","allOf":[{"$ref":"#/components/schemas/DaikinHvacControlDto"}]},"ruleName":{"type":"string","description":"EventBridge rule name for tracking and management"}},"required":["time","control"]},"ExternalDeviceSchedule":{"type":"object","properties":{"oneTimeSchedules":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceOneTimeScheduleEntry"}},"monday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceDailyScheduleEntry"}},"tuesday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceDailyScheduleEntry"}},"wednesday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceDailyScheduleEntry"}},"thursday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceDailyScheduleEntry"}},"friday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceDailyScheduleEntry"}},"saturday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceDailyScheduleEntry"}},"sunday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceDailyScheduleEntry"}}}},"ExternalDeviceDto":{"type":"object","properties":{"_id":{"type":"string","description":"Database ID","example":"64b7f1e6f9c9d4e5b8a9f7c9"},"ridgeId":{"type":"string","description":"Parent RIDGE gateway ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemId":{"type":"string","description":"System the parent gateway belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"tenantId":{"type":"string","description":"Tenant this unit serves, if assigned","example":"64b7f1e6f9c9d4e5b8a9f7c2"},"zoneId":{"type":"string","description":"Zone this unit serves, if assigned","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"roomId":{"type":"string","description":"Room this unit conditions, if assigned","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"externalId":{"type":"string","description":"Opaque downstream address on the gateway bus (e.g. Daikin \"ac\" 0–255, Modbus address) — NOT this document id","example":"3"},"unitNumber":{"type":"number","description":"Our 1-based number for this unit within its gateway, shown as \"<gateway No.>-<unitNumber>\". Absent only until the unit is numbered.","example":3},"pointType":{"type":"number","description":"iTM point type from discovery (101 = indoor unit). Absent on BACnet gateways.","example":101},"deviceName":{"type":"string","description":"Friendly name","example":"Level 3 AHU"},"remarks":{"type":"string","description":"Free-text remarks about this external device","example":"Noisy compressor, flagged for maintenance"},"equipmentType":{"description":"Specific equipment class of the external device","example":"FCU","allOf":[{"$ref":"#/components/schemas/ExternalDeviceType"}]},"category":{"description":"Broad equipment category, derived from equipmentType. Decides which control contract applies.","example":"HVAC","allOf":[{"$ref":"#/components/schemas/ExternalDeviceCategory"}]},"properties":{"type":"object","description":"Integration-specific configuration"},"state":{"type":"object","description":"Last-known state snapshot"},"lastUpdate":{"format":"date-time","type":"string","description":"When the state was last updated","example":"2026-06-09T12:00:00.000Z"},"watched":{"type":"boolean","description":"Whether the parent gateway actively polls this unit (iTM watch list)","example":true},"schedule":{"description":"Recurring + one-time control schedule for this device","allOf":[{"$ref":"#/components/schemas/ExternalDeviceSchedule"}]}},"required":["_id","ridgeId","systemId","externalId"]},"GetExternalDevicesDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of external devices matching the query parameters","type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceDto"}}},"required":["total","page","size","totalPages","data"]},"CreateExternalDeviceDto":{"type":"object","properties":{"externalId":{"type":"string","description":"Opaque downstream address on the gateway bus (e.g. Daikin \"ac\" 0–255, Modbus address)","example":"3"},"deviceName":{"type":"string","description":"Friendly name for the external device","example":"Level 3 AHU"},"equipmentType":{"description":"Equipment category of the external device","example":"FCU","allOf":[{"$ref":"#/components/schemas/ExternalDeviceType"}]},"properties":{"type":"object","description":"Integration-specific per-device configuration. Shape depends on the integration.","example":{"isPortMaster":false}},"remarks":{"type":"string","description":"Free-text remarks about this external device","example":"Noisy compressor, flagged for maintenance"}},"required":["externalId"]},"UpdateExternalDeviceDto":{"type":"object","properties":{"deviceName":{"type":"string","description":"Friendly name for the external device","example":"Level 3 AHU"},"equipmentType":{"description":"Equipment category of the external device","example":"FCU","allOf":[{"$ref":"#/components/schemas/ExternalDeviceType"}]},"properties":{"type":"object","description":"Integration-specific per-device configuration. Shape depends on the integration.","example":{"isPortMaster":false}},"remarks":{"type":"string","description":"Free-text remarks about this external device","example":"Noisy compressor, flagged for maintenance"},"watched":{"type":"boolean","description":"Whether the parent gateway actively polls this unit (iTM watch list). Toggling this changes the watch set sent on the next gateway config push.","example":true},"roomId":{"type":"string","description":"Place this unit in a room. Its zone and tenant are derived from the room, so send at most one of roomId / zoneId / tenantId. Must belong to the same system as the parent gateway. Pass null to clear the room only.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zoneId":{"type":"string","description":"Place this unit in a zone. Its tenant is derived from the zone and any room assignment is cleared. Pass null to clear the zone and room.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantId":{"type":"string","description":"Place this unit under a tenant directly. Clears any zone and room assignment. Pass null to clear the whole placement.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c2"}}},"ExternalDeviceCommandDirection":{"type":"string","enum":["READ","WRITE","READWRITE"],"description":"Whether the command is readable, writable, or both"},"ExternalDeviceCommandValueType":{"type":"string","enum":["BOOL","NUMBER","ENUM","STRING"],"description":"Value shape the command accepts / reports"},"ExternalDeviceCommandDto":{"type":"object","properties":{"key":{"type":"string","description":"Stable code to control / read by","example":"setpoint"},"name":{"type":"string","description":"Human-friendly label as discovery reported it","example":"Supply Air Setpoint"},"direction":{"description":"Whether the command is readable, writable, or both","example":"READWRITE","allOf":[{"$ref":"#/components/schemas/ExternalDeviceCommandDirection"}]},"valueType":{"description":"Value shape the command accepts / reports","example":"NUMBER","allOf":[{"$ref":"#/components/schemas/ExternalDeviceCommandValueType"}]},"values":{"type":"object","description":"Allowed values — an enum map (raw → label) or a numeric range { min, max }","example":{"min":18,"max":32}},"pointType":{"type":"number","description":"Point / equipment type this command applies to (iTM pointType)","example":101}},"required":["key","direction"]},"ExternalDeviceCommandWriteDto":{"type":"object","properties":{"key":{"type":"string","description":"Command key from the unit's catalog. Semantic where the protocol makes the meaning unambiguous (\"energy\", \"setpoint\"), otherwise the object address (\"analogValue:1\").","example":"setpoint"},"value":{"description":"Value to write. Validated against the command row: BOOL takes a boolean, NUMBER a number within the advertised range.","oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}],"example":23}},"required":["key","value"]},"ExternalDeviceCommandControlDto":{"type":"object","properties":{"commands":{"description":"Writes to apply, all in one command to the gateway","type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceCommandWriteDto"}}},"required":["commands"]},"BulkControlExternalDeviceDto":{"type":"object","properties":{"externalDeviceIds":{"description":"External device ids to apply this control request to","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"control":{"description":"Control snapshot applied to every listed unit. Omitted fields are left untouched.","allOf":[{"$ref":"#/components/schemas/DaikinHvacControlDto"}]}},"required":["externalDeviceIds","control"]},"BulkControlFailureDto":{"type":"object","properties":{"externalDeviceId":{"type":"string","description":"External device id that failed"},"reason":{"type":"string","description":"Why the control request could not be relayed"}},"required":["externalDeviceId","reason"]},"BulkControlResultDto":{"type":"object","properties":{"successful":{"description":"External device ids whose writes were published to a gateway","type":"array","items":{"type":"string"}},"failed":{"type":"array","items":{"$ref":"#/components/schemas/BulkControlFailureDto"}},"publishedCommands":{"type":"number","description":"Number of MQTT commands published — one per gateway with targets in this request","example":1}},"required":["successful","failed","publishedCommands"]},"ExternalDeviceOneTimeScheduleEntryDto":{"type":"object","properties":{"dateTime":{"type":"string","description":"When to apply the control (ISO 8601, must be in the future)","example":"2026-06-15T01:00:00.000Z","format":"date-time"},"control":{"description":"Full control snapshot to apply at the scheduled time","allOf":[{"$ref":"#/components/schemas/DaikinHvacControlDto"}]}},"required":["dateTime","control"]},"ExternalDeviceScheduleEntryDto":{"type":"object","properties":{"time":{"type":"string","description":"Local time in HH:mm","example":"09:00"},"control":{"description":"Full control snapshot to apply at the scheduled time","allOf":[{"$ref":"#/components/schemas/DaikinHvacControlDto"}]}},"required":["time","control"]},"SetExternalDeviceScheduleDto":{"type":"object","properties":{"oneTimeSchedules":{"description":"One-time control entries (execute once, then auto-remove)","type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceOneTimeScheduleEntryDto"}},"monday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceScheduleEntryDto"}},"tuesday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceScheduleEntryDto"}},"wednesday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceScheduleEntryDto"}},"thursday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceScheduleEntryDto"}},"friday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceScheduleEntryDto"}},"saturday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceScheduleEntryDto"}},"sunday":{"type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceScheduleEntryDto"}}}},"ExternalDevicePendingControl":{"type":"object","properties":{"commandId":{"type":"string","description":"Id the gateway echoes on its command result","example":"b0f1c2d3-4e5f-6789-abcd-ef0123456789"},"source":{"description":"Who issued the command","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"keys":{"description":"State keys the command writes and has not yet seen reported","example":["power","setpoint"],"type":"array","items":{"type":"string"}},"at":{"format":"date-time","type":"string","description":"When the command was published","example":"2026-06-09T12:00:00.000Z"},"confirmedAt":{"format":"date-time","type":"string","description":"When the gateway reported the command applied","example":"2026-06-09T12:00:02.000Z"}},"required":["commandId","source","keys","at"]},"ExternalDevice":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceType":{"description":"Type of device (discriminator field)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"macId":{"type":"string","description":"MAC ID of the Device (from IoT device)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Name of the Device","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"onStatus":{"description":"Status of the Device (ON / OFF / INACTIVE / ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend (cloud / scheduled). Set when a toggle is issued; cleared when the device reports a matching state or a physical (MANUAL) press overrides it. Always ON or OFF when present.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when targetState was last written. Used to expire stale SCHEDULED intents (24hr TTL). CLOUD intents do not expire.","example":"2026-05-25T08:00:00.000Z"},"targetSource":{"description":"Origin of the pending targetState. Drives TTL behavior on reconcile. Only CLOUD / SCHEDULED are ever written here.","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"location":{"type":"string","description":"Physical location or address where the device is installed","example":"Room 101, Building A"},"remarks":{"type":"string","description":"Free-text remarks about this external device","example":"Noisy compressor, flagged for maintenance"},"deviceModel":{"type":"string","description":"Model name or identifier of the device","example":"TP-Link HS110"},"deviceBrand":{"type":"string","description":"Brand or manufacturer of the device","example":"TP-Link"},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups","example":1},"chipModel":{"description":"Hardware chip model identifier used in the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is connected to","example":"IoT-Network-5G"},"wifiError":{"type":"string","description":"Last WiFi shadow delta failure reported by the device (e.g. join_failed); cleared once it reports a successful join","example":"join_failed"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join. Set at send time on both the shadow and legacy changeWifi paths; convergence is `ssid` matching any desired SSID.","example":"Corp-IoT"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them.","example":["Corp-IoT","Corp-IoT-2","Corp-IoT-3"],"type":"array","items":{"type":"string"}},"config":{"description":"Settings as last reported by the device into its `config` shadow, one block per feature","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"pendingConfig":{"description":"Settings written to the `config` shadow but not yet reported back. A key the device reports at a different value was rejected","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"configRequestedAt":{"format":"date-time","type":"string","description":"When settings were last written to the `config` shadow"},"configReportedAt":{"format":"date-time","type":"string","description":"When the device last reported into its `config` shadow"},"rssi":{"type":"number","description":"Latest WiFi signal strength reported by the device (dBm). Updated on each realtime MQTT report.","example":-65},"tenant":{"description":"Tagged Tenant","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"zone":{"description":"Tagged Zone","allOf":[{"$ref":"#/components/schemas/Zone"}]},"room":{"description":"Tagged Room","allOf":[{"$ref":"#/components/schemas/Room"}]},"system":{"description":"Tagged System","allOf":[{"$ref":"#/components/schemas/System"}]},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a physical unit. Such rows have no real MAC, never report telemetry, and are excluded from firmware, OTA, provisioning and panel-slot views.","example":false},"isRidgeChild":{"type":"boolean","description":"True when this row is a unit behind a RIDGE gateway rather than a directly-connected unit. Such rows have no real MAC, are never fleet-provisioned, and are excluded from firmware, OTA and liveness watchdogs — their gateway carries those.","example":false},"hasPhaseChildren":{"type":"boolean","description":"True when this clamp has been split into per-phase children. Such a clamp is org-detached and excluded from energy and organisational views — its children carry those — while remaining the sole target for firmware, OTA and diagnostics.","example":false},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2021-09-01"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device on/off status cannot be toggled.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"deviceSchedule":{"description":"Schedule configuration for the device. Contains recurring weekly schedules and one-time scheduled tasks.","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the single Schedule (in the schedules collection) this device currently follows. deviceSchedule is compiled from that schedule. When null, the device follows nothing.","example":"507f1f77bcf86cd799439011","nullable":true},"realTimePower":{"type":"number","description":"Real-time power consumption in watts","example":1500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes","example":6.5},"totalEnergyUsage":{"type":"number","description":"Total cumulative energy usage in kWh","example":0},"totalEnergySaved":{"type":"number","description":"Total energy saved through optimization in kWh","example":0},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update OTA attempts sent to this device","example":0},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether device is blacklisted from auto-updates (max retries exceeded)","example":false},"manualUpdateBlacklist":{"type":"boolean","description":"Whether device is manually blacklisted from auto-updates by admin","example":false},"lastAutoUpdateAttempt":{"format":"date-time","type":"string","description":"Timestamp of last auto-update OTA attempt"},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","default":false},"inactivityNotificationSentAt":{"format":"date-time","type":"string","description":"Timestamp when the last inactivity notification was sent for this device"},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"pendingThresholds":{"type":"object","description":"Threshold limits sent to the device but not yet confirmed by its firmware. Keys match DeviceThresholdsDto. Present only while a save is outstanding — a key that persists here is not being enforced by the hardware.","additionalProperties":{"type":"number"}},"thresholds":{"description":"This device's alert thresholds gathered into one object. Which fields are populated depends on deviceType — see DeviceThresholdsDto. Absent when no thresholds are configured. The same values remain available as flat top-level fields.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"ridgeParent":{"type":"string","description":"Parent RIDGE gateway that fronts this device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"externalId":{"type":"string","description":"Opaque downstream address (e.g. Daikin \"ac\" id, Modbus address)","example":"3"},"unitNumber":{"type":"number","description":"Our number for this unit, 1-based and unique within its gateway. Assigned once when the unit is first provisioned and never reassigned; a deleted unit leaves a gap. Unlike `externalId`, which the gateway dictates, this is readable and sequential.","example":3},"pointType":{"type":"number","description":"iTM point type reported at discovery (101 = indoor unit). Together with the pointId (stored as externalId) this identifies one equipment point. Absent on BACnet gateways.","example":101},"equipmentType":{"description":"Equipment class of the unit. Distinct from the base `deviceType`, which is always EXTERNAL for these rows.","example":"FCU","allOf":[{"$ref":"#/components/schemas/ExternalDeviceType"}]},"watched":{"type":"boolean","description":"Whether the parent gateway actively polls this unit (iTM watch list). The gateway config push derives its point list from the watched units; when none are watched the gateway polls every discovered point (commissioning).","example":true},"properties":{"type":"object","description":"Integration-specific configuration. Shape depends on the integration."},"state":{"type":"object","description":"Last-known state snapshot. Shape depends on the integration."},"lastUpdate":{"format":"date-time","type":"string","description":"When the state was last updated","example":"2026-06-09T12:00:00.000Z"},"schedule":{"description":"Recurring weekly + one-time control schedule for this device. Each entry applies a full control snapshot.","allOf":[{"$ref":"#/components/schemas/ExternalDeviceSchedule"}]},"pendingControl":{"description":"Cloud command awaiting confirmation in a realtime report","allOf":[{"$ref":"#/components/schemas/ExternalDevicePendingControl"}]}},"required":["_id","deviceType","macId","deviceName","deviceNumber","onStatus","location","batchNumber","chipModel","versionNumber","ssid","tenant","zone","room","system","createdAt","updatedAt","lastSeen","isLocked","totalEnergyUsage","totalEnergySaved","autoUpdateAttempts","autoUpdateBlacklisted","manualUpdateBlacklist","inactivityNotificationEnabled","ridgeParent","externalId"]},"BulkApplyExternalDeviceScheduleDto":{"type":"object","properties":{"externalDeviceIds":{"description":"External device ids to apply this schedule to","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"schedule":{"description":"Schedule applied to every listed unit, replacing whatever each one has (PUT semantics).","allOf":[{"$ref":"#/components/schemas/SetExternalDeviceScheduleDto"}]}},"required":["externalDeviceIds","schedule"]},"BulkScheduleFailureDto":{"type":"object","properties":{"externalDeviceId":{"type":"string","description":"External device id that failed"},"reason":{"type":"string","description":"Why the schedule could not be applied"}},"required":["externalDeviceId","reason"]},"BulkApplyScheduleResultDto":{"type":"object","properties":{"successful":{"description":"External device ids the schedule was applied to","type":"array","items":{"type":"string"}},"failed":{"type":"array","items":{"$ref":"#/components/schemas/BulkScheduleFailureDto"}}},"required":["successful","failed"]},"ExternalDeviceHistoryDto":{"type":"object","properties":{"_id":{"type":"string","description":"Database ID","example":"64b7f1e6f9c9d4e5b8a9f7d1"},"externalDeviceId":{"type":"string","description":"External device this snapshot belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c9"},"state":{"type":"object","description":"State snapshot at this point in time"},"recordedAt":{"format":"date-time","type":"string","description":"When the gateway observed this state","example":"2026-06-09T12:00:00.000Z"},"triggerSource":{"description":"Who changed the unit, when this snapshot changed a control field. MANUAL means no cloud command explains the change (wall remote, BMS). Absent otherwise.","example":"MANUAL","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"changedKeys":{"description":"Control fields this snapshot changed","example":["power"],"type":"array","items":{"type":"string"}},"createdAt":{"format":"date-time","type":"string","description":"When the snapshot row was written","example":"2026-06-09T12:00:01.000Z"}},"required":["_id","externalDeviceId","state","createdAt"]},"GetExternalDeviceHistoryDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of state snapshots in the requested date range","type":"array","items":{"$ref":"#/components/schemas/ExternalDeviceHistoryDto"}}},"required":["total","page","size","totalPages","data"]},"EnvironmentSensorType":{"type":"string","enum":["FRIDGE_SENSOR","IAQ4","IAQ9"],"description":"Type of the environment sensor device (e.g., IAQ4, IAQ9)"},"EnvironmentSensor":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceType":{"description":"Type of device (discriminator field)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"macId":{"type":"string","description":"MAC ID of the Device (from IoT device)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Name of the Device","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"onStatus":{"description":"Status of the Device (ON / OFF / INACTIVE / ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend (cloud / scheduled). Set when a toggle is issued; cleared when the device reports a matching state or a physical (MANUAL) press overrides it. Always ON or OFF when present.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when targetState was last written. Used to expire stale SCHEDULED intents (24hr TTL). CLOUD intents do not expire.","example":"2026-05-25T08:00:00.000Z"},"targetSource":{"description":"Origin of the pending targetState. Drives TTL behavior on reconcile. Only CLOUD / SCHEDULED are ever written here.","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"location":{"type":"string","description":"Physical location or address where the device is installed","example":"Room 101, Building A"},"remarks":{"type":"string","description":"Remarks for the Device"},"deviceModel":{"type":"string","description":"Model name or identifier of the device","example":"TP-Link HS110"},"deviceBrand":{"type":"string","description":"Brand or manufacturer of the device","example":"TP-Link"},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups","example":1},"chipModel":{"description":"Hardware chip model identifier used in the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is connected to","example":"IoT-Network-5G"},"wifiError":{"type":"string","description":"Last WiFi shadow delta failure reported by the device (e.g. join_failed); cleared once it reports a successful join","example":"join_failed"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join. Set at send time on both the shadow and legacy changeWifi paths; convergence is `ssid` matching any desired SSID.","example":"Corp-IoT"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them.","example":["Corp-IoT","Corp-IoT-2","Corp-IoT-3"],"type":"array","items":{"type":"string"}},"config":{"description":"Settings as last reported by the device into its `config` shadow, one block per feature","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"pendingConfig":{"description":"Settings written to the `config` shadow but not yet reported back. A key the device reports at a different value was rejected","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"configRequestedAt":{"format":"date-time","type":"string","description":"When settings were last written to the `config` shadow"},"configReportedAt":{"format":"date-time","type":"string","description":"When the device last reported into its `config` shadow"},"rssi":{"type":"number","description":"Latest WiFi signal strength reported by the device (dBm). Updated on each realtime MQTT report.","example":-65},"tenant":{"description":"Tagged Tenant","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"zone":{"description":"Tagged Zone","allOf":[{"$ref":"#/components/schemas/Zone"}]},"room":{"description":"Tagged Room","allOf":[{"$ref":"#/components/schemas/Room"}]},"system":{"description":"Tagged System","allOf":[{"$ref":"#/components/schemas/System"}]},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a physical unit. Such rows have no real MAC, never report telemetry, and are excluded from firmware, OTA, provisioning and panel-slot views.","example":false},"isRidgeChild":{"type":"boolean","description":"True when this row is a unit behind a RIDGE gateway rather than a directly-connected unit. Such rows have no real MAC, are never fleet-provisioned, and are excluded from firmware, OTA and liveness watchdogs — their gateway carries those.","example":false},"hasPhaseChildren":{"type":"boolean","description":"True when this clamp has been split into per-phase children. Such a clamp is org-detached and excluded from energy and organisational views — its children carry those — while remaining the sole target for firmware, OTA and diagnostics.","example":false},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2021-09-01"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device on/off status cannot be toggled.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"deviceSchedule":{"description":"Schedule configuration for the device. Contains recurring weekly schedules and one-time scheduled tasks.","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the single Schedule (in the schedules collection) this device currently follows. deviceSchedule is compiled from that schedule. When null, the device follows nothing.","example":"507f1f77bcf86cd799439011","nullable":true},"realTimePower":{"type":"number","description":"Real-time power consumption in watts","example":1500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes","example":6.5},"totalEnergyUsage":{"type":"number","description":"Total cumulative energy usage in kWh","example":0},"totalEnergySaved":{"type":"number","description":"Total energy saved through optimization in kWh","example":0},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update OTA attempts sent to this device","example":0},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether device is blacklisted from auto-updates (max retries exceeded)","example":false},"manualUpdateBlacklist":{"type":"boolean","description":"Whether device is manually blacklisted from auto-updates by admin","example":false},"lastAutoUpdateAttempt":{"format":"date-time","type":"string","description":"Timestamp of last auto-update OTA attempt"},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","default":false},"inactivityNotificationSentAt":{"format":"date-time","type":"string","description":"Timestamp when the last inactivity notification was sent for this device"},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"pendingThresholds":{"type":"object","description":"Threshold limits sent to the device but not yet confirmed by its firmware. Keys match DeviceThresholdsDto. Present only while a save is outstanding — a key that persists here is not being enforced by the hardware.","additionalProperties":{"type":"number"}},"thresholds":{"description":"This device's alert thresholds gathered into one object. Which fields are populated depends on deviceType — see DeviceThresholdsDto. Absent when no thresholds are configured. The same values remain available as flat top-level fields.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"environmentSensorType":{"description":"Type of the environment sensor device (e.g., IAQ4, IAQ9)","example":"IAQ4","allOf":[{"$ref":"#/components/schemas/EnvironmentSensorType"}]},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold for the Environment Sensor","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"carbonDioxide":{"type":"number","description":"Threshold CO2 reading in ppm","example":400},"lux":{"type":"number","description":"Threshold light level in lux (lx)","example":500},"formaldehyde":{"type":"number","description":"Threshold formaldehyde (HCHO) reading in µg/m³","example":0.03},"temperatureMin":{"type":"number","description":"Min Threshold temperature reading in degrees Celsius","example":24},"temperatureMax":{"type":"number","description":"MaxThreshold temperature reading in degrees Celsius","example":24},"humidity":{"type":"number","description":"Threshold humidity percentage ","example":24},"pm1":{"type":"number","description":"Threshold PM1 in µg/m³","example":1},"pm2_5":{"type":"number","description":"Threshold PM2.5 in µg/m³","example":1},"pm10":{"type":"number","description":"Threshold PM10 in µg/m³","example":1},"totalVolatileOrganicCompounds":{"type":"number","description":"Threshold Total Volatile Organic Compounds in ppb","example":1},"soundLevel":{"type":"number","description":"Threshold Sound Level in dB","example":1},"motionDetected":{"type":"boolean","description":"Motion detection status","example":true},"thresholdGroupName":{"type":"string","description":"Name of the threshold group this sensor belongs to","example":"Office Sensors","nullable":true},"realTimeTemperature":{"type":"number","description":"Real-time Temperature reading in degrees Celsius","example":24,"nullable":true},"realTimeCarbonDioxide":{"type":"number","description":"Real-time CarbonDioxide reading in ppm","example":24,"nullable":true},"realTimeHumidity":{"type":"number","description":"Real-time Humidity reading in percentage","example":24,"nullable":true},"realTimePm1":{"type":"number","description":"Real-time PM1 reading in µg/m³","example":24,"nullable":true},"realTimePm2_5":{"type":"number","description":"Real-time PM2.5 reading in µg/m³","example":24,"nullable":true},"realTimePm10":{"type":"number","description":"Real-time PM10 reading in µg/m³","example":24,"nullable":true},"realTimeTotalVolatileOrganicCompounds":{"type":"number","description":"Real-time TotalVolatileOrganicCompounds reading in ppb","example":24,"nullable":true},"realTimeSoundLevel":{"type":"number","description":"Real-time SoundLevel reading in dB","example":24,"nullable":true},"realTimeMotionDetected":{"type":"boolean","description":"Real-time MotionDetected reading","example":true,"nullable":true},"realTimeLux":{"type":"number","description":"Real-time Lux reading in lux","example":24,"nullable":true},"realTimeFormaldehyde":{"type":"number","description":"Real-time Formaldehyde reading in ppm","example":24,"nullable":true}},"required":["_id","deviceType","macId","deviceName","deviceNumber","onStatus","location","remarks","batchNumber","chipModel","versionNumber","ssid","tenant","zone","room","system","createdAt","updatedAt","lastSeen","isLocked","totalEnergyUsage","totalEnergySaved","autoUpdateAttempts","autoUpdateBlacklisted","manualUpdateBlacklist","inactivityNotificationEnabled","environmentSensorType","notificationThresholdHour","carbonDioxide","lux","formaldehyde","temperatureMin","temperatureMax","humidity","pm1","pm2_5","pm10","totalVolatileOrganicCompounds","soundLevel","motionDetected","thresholdGroupName","realTimeTemperature","realTimeCarbonDioxide","realTimeHumidity","realTimePm1","realTimePm2_5","realTimePm10","realTimeTotalVolatileOrganicCompounds","realTimeSoundLevel","realTimeMotionDetected","realTimeLux","realTimeFormaldehyde"]},"FilteredEnvironmentSensorDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique database identifier of the device document in the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"Unique identifier assigned to the device by the IoT hardware (MAC ID)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Friendly name for the device for easy identification","example":"Living Room AC"},"onStatus":{"description":"Current operational status of the device (ON, OFF, INACTIVE, or ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend. Present when a toggle was issued but the device has not yet acknowledged (e.g. issued while INACTIVE). Frontend can derive a \"Pending\" indicator when this differs from onStatus.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when the pending targetState was last written.","example":"2026-05-25T08:00:00.000Z"},"totalEnergyUsage":{"type":"number","description":"Cumulative total energy consumption in kilowatt-hours (kWh) since device installation","example":500},"deviceNumber":{"type":"number","description":"Auto-incremented sequential number for tracking device registration order","example":3},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups and quality control","example":23},"chipModel":{"description":"Hardware chip model identifier used in the device (e.g., ESP32C2, ESP8266)","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"tenantId":{"type":"string","description":"Unique identifier of the tenant organization that owns this device","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantName":{"type":"string","description":"Name of the tenant organization that owns this device","example":"Faculty of Engineering"},"zoneId":{"type":"string","description":"Unique identifier of the zone that this device is assigned to","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"zoneName":{"type":"string","description":"Name of the zone that this device is assigned to","example":"Zone A"},"roomId":{"type":"string","description":"Unique identifier of the room where this device is physically located","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"roomName":{"type":"string","description":"Name of the room where this device is physically located","example":"Room 101"},"dbId":{"type":"string","description":"Unique identifier of the distribution box that this device is connected to (for MCBs)","example":"64b7f1e6f9c9d4e5b8a9f7c7"},"dbName":{"type":"string","description":"Name of the distribution box that this device is connected to (for MCBs)","example":"Main Distribution Box"},"systemId":{"type":"string","description":"Unique identifier of the system that this device belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system that this device belongs to","example":"NUS Engineering Building"},"realTimePower":{"type":"number","description":"Real-time power consumption in watts (W) measured by the device","example":115},"realTimeCurrent":{"type":"number","description":"Real-time current draw in amperes (A) measured by the device","example":0.5},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is currently connected to","example":"XH001"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join","example":"XH001"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join","example":"XH001-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join","example":"XH001-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them","example":["XH001","XH001-2","XH001-3"],"type":"array","items":{"type":"string"}},"deviceSchedule":{"description":"Weekly and one-time schedule configuration for automated device control","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the named Schedule this device currently follows. deviceSchedule is compiled from it. Absent or null when the device follows nothing.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c3"},"lastSeen":{"format":"date-time","type":"string","description":"Timestamp when the device last communicated with the system","example":"2024-10-15T12:30:00.000Z"},"isLocked":{"type":"boolean","description":"Lock status indicating if the device is locked","example":false},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device","example":true},"thresholds":{"description":"Alert thresholds configured for this device, gathered into one object. Which fields are populated depends on the device type — see DeviceThresholdsDto. Omitted entirely when nothing is set.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"environmentSensorType":{"description":"Type of environment sensor (IAQ4, IAQ9, or temperature sensor)","example":"IAQ4","allOf":[{"$ref":"#/components/schemas/EnvironmentSensorType"}]},"temperatureMin":{"type":"number","description":"Minimum threshold temperature reading in degrees Celsius","example":18},"temperatureMax":{"type":"number","description":"Maximum threshold temperature reading in degrees Celsius","example":26},"temperature":{"type":"number","description":"Current temperature reading in degrees Celsius","example":24.5},"carbonDioxide":{"type":"number","description":"Threshold CO2 reading in ppm","example":800},"lux":{"type":"number","description":"Threshold light level in lux (lx)","example":500},"formaldehyde":{"type":"number","description":"Threshold formaldehyde (HCHO) reading in ppm","example":0.08},"humidity":{"type":"number","description":"Threshold humidity percentage","example":60},"pm1":{"type":"number","description":"Threshold PM1 in µg/m³","example":25},"pm2_5":{"type":"number","description":"Threshold PM2.5 in µg/m³","example":35},"pm10":{"type":"number","description":"Threshold PM10 in µg/m³","example":50},"totalVolatileOrganicCompounds":{"type":"number","description":"Threshold Total Volatile Organic Compounds in ppm","example":0.5},"soundLevel":{"type":"number","description":"Threshold Sound Level in dB","example":70},"motionDetected":{"type":"boolean","description":"Motion detection status","example":false},"thresholdGroupName":{"type":"string","description":"Name of the threshold group this sensor belongs to","example":"Office Sensors"},"realTimeCarbonDioxide":{"type":"number","description":"Real-time CO2 reading in ppm","example":650,"nullable":true},"realTimeLux":{"type":"number","description":"Real-time light level in lux (lx)","example":300,"nullable":true},"realTimeFormaldehyde":{"type":"number","description":"Real-time formaldehyde (HCHO) reading in ppm","example":0.02,"nullable":true},"realTimeHumidity":{"type":"number","description":"Real-time humidity percentage","example":55,"nullable":true},"realTimePm1":{"type":"number","description":"Real-time PM1 in µg/m³","example":5,"nullable":true},"realTimePm2_5":{"type":"number","description":"Real-time PM2.5 in µg/m³","example":12,"nullable":true},"realTimePm10":{"type":"number","description":"Real-time PM10 in µg/m³","example":25,"nullable":true},"realTimeTotalVolatileOrganicCompounds":{"type":"number","description":"Real-time Total Volatile Organic Compounds in ppm","example":0.3,"nullable":true},"realTimeSoundLevel":{"type":"number","description":"Real-time Sound Level in dB","example":45,"nullable":true},"realTimeMotionDetected":{"type":"boolean","description":"Real-time motion detection status","example":false,"nullable":true}},"required":["_id","macId","deviceName","onStatus","totalEnergyUsage","tenantId","tenantName","zoneId","zoneName","roomId","roomName","dbId","dbName","systemId","systemName","realTimePower","realTimeCurrent","ssid","temperatureMin","temperatureMax"]},"GetEnvironmentSensorsDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"List of environment sensor devices","type":"array","items":{"$ref":"#/components/schemas/FilteredEnvironmentSensorDto"}}},"required":["total","page","size","totalPages","data"]},"UpdateEnvironmentSensorDeviceDto":{"type":"object","properties":{"deviceName":{"type":"string","description":"Name of the device","example":"Living Room Light"},"location":{"type":"string","description":"Physical location of the device","example":"Block 123, Unit 01-23"},"remarks":{"type":"string","description":"Additional remarks or notes about the device","example":"Handle with care - critical equipment"},"tenantId":{"type":"string","description":"Tenant ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zoneId":{"type":"string","description":"Zone ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"roomId":{"type":"string","description":"Room ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device cannot be toggled ON/OFF.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"onStatus":{"description":"Operational status of the environment sensor device","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}},"required":["onStatus"]},"BulkUpdateEnvironmentSensorThresholdsDto":{"type":"object","properties":{"environmentSensorIds":{"description":"IDs of environment sensors to update","type":"array","items":{"type":"string"}},"carbonDioxide":{"type":"number","description":"Threshold CO2 reading in ppm","example":400},"lux":{"type":"number","description":"Threshold light level in lux (lx)","example":500},"formaldehyde":{"type":"number","description":"Threshold formaldehyde (HCHO) in µg/m³","example":0.03},"temperatureMin":{"type":"number","description":"Min Threshold temperature in °C","example":18},"temperatureMax":{"type":"number","description":"Max Threshold temperature in °C","example":26},"humidity":{"type":"number","description":"Threshold humidity percentage","example":60},"pm1":{"type":"number","description":"Threshold PM1 in µg/m³","example":25},"pm2_5":{"type":"number","description":"Threshold PM2.5 in µg/m³","example":15},"pm10":{"type":"number","description":"Threshold PM10 in µg/m³","example":35},"totalVolatileOrganicCompounds":{"type":"number","description":"Threshold Total Volatile Organic Compounds in ppb","example":1},"soundLevel":{"type":"number","description":"Threshold Sound Level in dB","example":70},"motionDetected":{"type":"boolean","description":"Motion detection enabled","example":true},"thresholdEnabled":{"type":"boolean","description":"Whether threshold notifications are enabled","example":true},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold — minimum hours between repeat notifications","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000}},"required":["environmentSensorIds"]},"RecommendedThresholdBandsDto":{"type":"object","properties":{"temperatureMin":{"type":"number","description":"Outer low temperature bound (°C)","example":22},"temperatureWarnMin":{"type":"number","description":"Inner comfort low temperature bound (°C)","example":24},"temperatureWarnMax":{"type":"number","description":"Inner comfort high temperature bound (°C)","example":26},"temperatureMax":{"type":"number","description":"Outer high temperature bound (°C)","example":28},"humidityMin":{"type":"number","description":"Outer low humidity bound (%)","example":30},"humidityWarnMin":{"type":"number","description":"Inner comfort low humidity bound (%)","example":40},"humidityWarnMax":{"type":"number","description":"Inner comfort high humidity bound (%)","example":60},"humidity":{"type":"number","description":"Outer high humidity bound (%)","example":70},"carbonDioxideWarn":{"type":"number","description":"CO2 good cutoff (ppm)","example":700},"carbonDioxide":{"type":"number","description":"CO2 poor cutoff (ppm)","example":1000},"pm1Warn":{"type":"number","description":"PM1 good cutoff (µg/m³)","example":10},"pm1":{"type":"number","description":"PM1 poor cutoff (µg/m³)","example":25},"pm2_5Warn":{"type":"number","description":"PM2.5 good cutoff (µg/m³)","example":9},"pm2_5":{"type":"number","description":"PM2.5 poor cutoff (µg/m³)","example":35.4},"pm10Warn":{"type":"number","description":"PM10 good cutoff (µg/m³)","example":54},"pm10":{"type":"number","description":"PM10 poor cutoff (µg/m³)","example":154},"formaldehydeWarn":{"type":"number","description":"Formaldehyde good cutoff (µg/m³)","example":0.05},"formaldehyde":{"type":"number","description":"Formaldehyde poor cutoff (µg/m³)","example":0.1},"totalVolatileOrganicCompoundsWarn":{"type":"number","description":"TVOC good cutoff (ppb)","example":0.3},"totalVolatileOrganicCompounds":{"type":"number","description":"TVOC poor cutoff (ppb)","example":0.6},"soundLevelWarn":{"type":"number","description":"Sound level good cutoff (dB)","example":50},"soundLevel":{"type":"number","description":"Sound level poor cutoff (dB)","example":65}}},"EnvironmentSensorThresholdGroup":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Threshold Group","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"User-defined group name","example":"Office Sensors"},"systemId":{"type":"string","description":"System this group belongs to","example":"507f1f77bcf86cd799439011"},"environmentSensorType":{"description":"Type of environment sensors in this group","example":"IAQ4","allOf":[{"$ref":"#/components/schemas/EnvironmentSensorType"}]},"sensorIds":{"description":"Sensors assigned to this group","example":["507f1f77bcf86cd799439011"],"type":"array","items":{"type":"string"}},"temperatureMin":{"type":"number","description":"Min temperature threshold in degrees Celsius","example":18},"temperatureMax":{"type":"number","description":"Max temperature threshold in degrees Celsius","example":28},"temperatureWarnMin":{"type":"number","description":"Inner comfort lower temperature bound (good cutoff). Band: good within [temperatureWarnMin, temperatureWarnMax]; moderate within [temperatureMin, temperatureMax]; else poor. Display-only — not pushed to device.","example":24},"temperatureWarnMax":{"type":"number","description":"Inner comfort upper temperature bound (good cutoff). See temperatureWarnMin. Display-only — not pushed to device.","example":26},"humidity":{"type":"number","description":"Max humidity threshold percentage (outer high / poor cutoff)","example":70},"humidityMin":{"type":"number","description":"Min humidity threshold percentage (outer low / poor cutoff). Below this is poor. Display-only — not pushed to device.","example":30},"humidityWarnMin":{"type":"number","description":"Inner comfort lower humidity bound (good cutoff). Band: good within [humidityWarnMin, humidityWarnMax]; moderate within [humidityMin, humidity]; else poor. Display-only — not pushed to device.","example":40},"humidityWarnMax":{"type":"number","description":"Inner comfort upper humidity bound (good cutoff). See humidityWarnMin. Display-only — not pushed to device.","example":60},"carbonDioxide":{"type":"number","description":"Max CO2 threshold in ppm (poor cutoff)","example":1000},"carbonDioxideWarn":{"type":"number","description":"CO2 good cutoff in ppm. Band: good ≤ warn; moderate ≤ carbonDioxide; else poor. Display-only — not pushed to device.","example":700},"lux":{"type":"number","description":"Max light level threshold in lux (lx)","example":1000},"formaldehyde":{"type":"number","description":"Max formaldehyde (HCHO) threshold in ppm (poor cutoff)","example":0.08},"formaldehydeWarn":{"type":"number","description":"Formaldehyde good cutoff in ppm. Band: good ≤ warn; moderate ≤ formaldehyde; else poor. Display-only — not pushed to device.","example":0.05},"pm1":{"type":"number","description":"Max PM1 threshold in µg/m³ (poor cutoff)","example":25},"pm1Warn":{"type":"number","description":"PM1 good cutoff in µg/m³. Band: good ≤ warn; moderate ≤ pm1; else poor. Display-only — not pushed to device.","example":10},"pm2_5":{"type":"number","description":"Max PM2.5 threshold in µg/m³ (poor cutoff)","example":35},"pm2_5Warn":{"type":"number","description":"PM2.5 good cutoff in µg/m³. Band: good ≤ warn; moderate ≤ pm2_5; else poor. Display-only — not pushed to device.","example":9},"pm10":{"type":"number","description":"Max PM10 threshold in µg/m³ (poor cutoff)","example":150},"pm10Warn":{"type":"number","description":"PM10 good cutoff in µg/m³. Band: good ≤ warn; moderate ≤ pm10; else poor. Display-only — not pushed to device.","example":54},"totalVolatileOrganicCompounds":{"type":"number","description":"Max TVOC threshold in ppb (poor cutoff)","example":0.5},"totalVolatileOrganicCompoundsWarn":{"type":"number","description":"TVOC good cutoff in ppb. Band: good ≤ warn; moderate ≤ totalVolatileOrganicCompounds; else poor. Display-only — not pushed to device.","example":0.3},"soundLevel":{"type":"number","description":"Max sound level threshold in dB (poor cutoff)","example":65},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"soundLevelWarn":{"type":"number","description":"Sound level good cutoff in dB. Band: good ≤ warn; moderate ≤ soundLevel; else poor. Display-only — not pushed to device.","example":50}},"required":["_id","name","systemId","environmentSensorType","sensorIds"]},"CreateEnvironmentSensorThresholdGroupDto":{"type":"object","properties":{"name":{"type":"string","description":"User-defined group name","example":"Office Sensors"},"environmentSensorType":{"description":"Type of environment sensors in this group","example":"IAQ4","allOf":[{"$ref":"#/components/schemas/EnvironmentSensorType"}]},"sensorIds":{"description":"Sensor IDs to assign to this group. Each sensor must match the group's environmentSensorType. Sensors are automatically removed from their previous group.","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}},"temperatureMin":{"type":"number","description":"Min temperature threshold in degrees Celsius","example":18},"temperatureMax":{"type":"number","description":"Max temperature threshold in degrees Celsius","example":28},"temperatureWarnMin":{"type":"number","description":"Inner comfort lower temperature bound (good cutoff)","example":24},"temperatureWarnMax":{"type":"number","description":"Inner comfort upper temperature bound (good cutoff)","example":26},"humidity":{"type":"number","description":"Max humidity threshold percentage (outer high / poor cutoff)","example":70},"humidityMin":{"type":"number","description":"Min humidity threshold percentage (outer low / poor cutoff)","example":30},"humidityWarnMin":{"type":"number","description":"Inner comfort lower humidity bound (good cutoff)","example":40},"humidityWarnMax":{"type":"number","description":"Inner comfort upper humidity bound (good cutoff)","example":60},"carbonDioxide":{"type":"number","description":"Max CO2 threshold in ppm (poor cutoff)","example":1000},"carbonDioxideWarn":{"type":"number","description":"CO2 good cutoff in ppm","example":700},"lux":{"type":"number","description":"Max light level threshold in lux (lx)","example":1000},"formaldehyde":{"type":"number","description":"Max formaldehyde (HCHO) threshold in ppm (poor cutoff)","example":0.08},"formaldehydeWarn":{"type":"number","description":"Formaldehyde good cutoff in ppm","example":0.05},"pm1":{"type":"number","description":"Max PM1 threshold in µg/m³ (poor cutoff)","example":25},"pm1Warn":{"type":"number","description":"PM1 good cutoff in µg/m³","example":10},"pm2_5":{"type":"number","description":"Max PM2.5 threshold in µg/m³ (poor cutoff)","example":35},"pm2_5Warn":{"type":"number","description":"PM2.5 good cutoff in µg/m³","example":9},"pm10":{"type":"number","description":"Max PM10 threshold in µg/m³ (poor cutoff)","example":150},"pm10Warn":{"type":"number","description":"PM10 good cutoff in µg/m³","example":54},"totalVolatileOrganicCompounds":{"type":"number","description":"Max TVOC threshold in ppb (poor cutoff)","example":0.5},"totalVolatileOrganicCompoundsWarn":{"type":"number","description":"TVOC good cutoff in ppb","example":0.3},"soundLevel":{"type":"number","description":"Max sound level threshold in dB (poor cutoff)","example":65},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"soundLevelWarn":{"type":"number","description":"Sound level good cutoff in dB","example":50}},"required":["name","environmentSensorType"]},"UpdateEnvironmentSensorThresholdGroupDto":{"type":"object","properties":{"name":{"type":"string","description":"Updated group name","example":"Office Sensors Updated"},"sensorIds":{"description":"Full list of sensor IDs to assign to this group (replaces existing assignments). Each sensor must match the group's environmentSensorType. Sensors are automatically removed from their previous group.","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}},"temperatureMin":{"type":"number","description":"Min temperature threshold in degrees Celsius","example":18},"temperatureMax":{"type":"number","description":"Max temperature threshold in degrees Celsius","example":28},"temperatureWarnMin":{"type":"number","description":"Inner comfort lower temperature bound (good cutoff)","example":24},"temperatureWarnMax":{"type":"number","description":"Inner comfort upper temperature bound (good cutoff)","example":26},"humidity":{"type":"number","description":"Max humidity threshold percentage (outer high / poor cutoff)","example":70},"humidityMin":{"type":"number","description":"Min humidity threshold percentage (outer low / poor cutoff)","example":30},"humidityWarnMin":{"type":"number","description":"Inner comfort lower humidity bound (good cutoff)","example":40},"humidityWarnMax":{"type":"number","description":"Inner comfort upper humidity bound (good cutoff)","example":60},"carbonDioxide":{"type":"number","description":"Max CO2 threshold in ppm (poor cutoff)","example":1000},"carbonDioxideWarn":{"type":"number","description":"CO2 good cutoff in ppm","example":700},"lux":{"type":"number","description":"Max light level threshold in lux (lx)","example":1000},"formaldehyde":{"type":"number","description":"Max formaldehyde (HCHO) threshold in ppm (poor cutoff)","example":0.08},"formaldehydeWarn":{"type":"number","description":"Formaldehyde good cutoff in ppm","example":0.05},"pm1":{"type":"number","description":"Max PM1 threshold in µg/m³ (poor cutoff)","example":25},"pm1Warn":{"type":"number","description":"PM1 good cutoff in µg/m³","example":10},"pm2_5":{"type":"number","description":"Max PM2.5 threshold in µg/m³ (poor cutoff)","example":35},"pm2_5Warn":{"type":"number","description":"PM2.5 good cutoff in µg/m³","example":9},"pm10":{"type":"number","description":"Max PM10 threshold in µg/m³ (poor cutoff)","example":150},"pm10Warn":{"type":"number","description":"PM10 good cutoff in µg/m³","example":54},"totalVolatileOrganicCompounds":{"type":"number","description":"Max TVOC threshold in ppb (poor cutoff)","example":0.5},"totalVolatileOrganicCompoundsWarn":{"type":"number","description":"TVOC good cutoff in ppb","example":0.3},"soundLevel":{"type":"number","description":"Max sound level threshold in dB (poor cutoff)","example":65},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000},"soundLevelWarn":{"type":"number","description":"Sound level good cutoff in dB","example":50}}},"PendingUpdateDto":{"type":"object","properties":{"jobId":{"type":"string","description":"Active OTA job the device matches"},"targetVersion":{"type":"string","description":"Version the rollout will bring it to","example":"2.1.0"}},"required":["jobId","targetVersion"]},"FilteredSmartSwitchDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique database identifier of the device document in the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"Unique identifier assigned to the device by the IoT hardware (MAC ID)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Friendly name for the device for easy identification","example":"Living Room AC"},"onStatus":{"description":"Current operational status of the device (ON, OFF, INACTIVE, or ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend. Present when a toggle was issued but the device has not yet acknowledged (e.g. issued while INACTIVE). Frontend can derive a \"Pending\" indicator when this differs from onStatus.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when the pending targetState was last written.","example":"2026-05-25T08:00:00.000Z"},"totalEnergyUsage":{"type":"number","description":"Cumulative total energy consumption in kilowatt-hours (kWh) since device installation","example":500},"deviceNumber":{"type":"number","description":"Auto-incremented sequential number for tracking device registration order","example":3},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups and quality control","example":23},"chipModel":{"description":"Hardware chip model identifier used in the device (e.g., ESP32C2, ESP8266)","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"tenantId":{"type":"string","description":"Unique identifier of the tenant organization that owns this device","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantName":{"type":"string","description":"Name of the tenant organization that owns this device","example":"Faculty of Engineering"},"zoneId":{"type":"string","description":"Unique identifier of the zone that this device is assigned to","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"zoneName":{"type":"string","description":"Name of the zone that this device is assigned to","example":"Zone A"},"roomId":{"type":"string","description":"Unique identifier of the room where this device is physically located","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"roomName":{"type":"string","description":"Name of the room where this device is physically located","example":"Room 101"},"dbId":{"type":"string","description":"Unique identifier of the distribution box that this device is connected to (for MCBs)","example":"64b7f1e6f9c9d4e5b8a9f7c7"},"dbName":{"type":"string","description":"Name of the distribution box that this device is connected to (for MCBs)","example":"Main Distribution Box"},"systemId":{"type":"string","description":"Unique identifier of the system that this device belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system that this device belongs to","example":"NUS Engineering Building"},"realTimePower":{"type":"number","description":"Real-time power consumption in watts (W) measured by the device","example":115},"realTimeCurrent":{"type":"number","description":"Real-time current draw in amperes (A) measured by the device","example":0.5},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is currently connected to","example":"XH001"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join","example":"XH001"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join","example":"XH001-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join","example":"XH001-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them","example":["XH001","XH001-2","XH001-3"],"type":"array","items":{"type":"string"}},"deviceSchedule":{"description":"Weekly and one-time schedule configuration for automated device control","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the named Schedule this device currently follows. deviceSchedule is compiled from it. Absent or null when the device follows nothing.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c3"},"lastSeen":{"format":"date-time","type":"string","description":"Timestamp when the device last communicated with the system","example":"2024-10-15T12:30:00.000Z"},"isLocked":{"type":"boolean","description":"Lock status indicating if the device is locked","example":false},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device","example":true},"thresholds":{"description":"Alert thresholds configured for this device, gathered into one object. Which fields are populated depends on the device type — see DeviceThresholdsDto. Omitted entirely when nothing is set.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"switchType":{"description":"Type of appliance the switch controls (LIGHT, FAN, AIRCON, or UNREGISTERED)","example":"LIGHT","allOf":[{"$ref":"#/components/schemas/SwitchType"}]},"numberOfGangs":{"type":"number","description":"Number of gangs/buttons on the switch (1-4)","example":2,"minimum":1,"maximum":4},"gangSwitchStates":{"description":"Array of gang switch states - each gang has its own independent on/off state","example":[{"gangNumber":1,"onStatus":"OFF","label":"Main Light"},{"gangNumber":2,"onStatus":"OFF","label":"Accent Light"}],"type":"array","items":{"$ref":"#/components/schemas/GangSwitchState"}},"pendingUpdate":{"description":"Active OTA rollout this smart switch matches and still needs (Mongo-derived, not live progress). Absent if no rollout targets its current version.","allOf":[{"$ref":"#/components/schemas/PendingUpdateDto"}]}},"required":["_id","macId","deviceName","onStatus","totalEnergyUsage","tenantId","tenantName","zoneId","zoneName","roomId","roomName","dbId","dbName","systemId","systemName","realTimePower","realTimeCurrent","ssid","switchType","numberOfGangs","gangSwitchStates"]},"GetSmartSwitchesDto":{"type":"object","properties":{"data":{"description":"List of Smart Switches with organizational hierarchy details","type":"array","items":{"$ref":"#/components/schemas/FilteredSmartSwitchDto"}},"total":{"type":"number","description":"Total number of Smart Switches","example":42},"page":{"type":"number","description":"Current page number","example":1},"size":{"type":"number","description":"Number of items per page","example":10}},"required":["data","total","page","size"]},"UpdateSmartSwitchDto":{"type":"object","properties":{"deviceName":{"type":"string","description":"Name of the device","example":"Living Room Light"},"location":{"type":"string","description":"Physical location of the device","example":"Block 123, Unit 01-23"},"remarks":{"type":"string","description":"Additional remarks or notes about the device","example":"Handle with care - critical equipment"},"tenantId":{"type":"string","description":"Tenant ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zoneId":{"type":"string","description":"Zone ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"roomId":{"type":"string","description":"Room ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device cannot be toggled ON/OFF.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"onStatus":{"description":"Status of the Smart Switch","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"switchType":{"description":"Type of appliance the switch controls","example":"LIGHT","allOf":[{"$ref":"#/components/schemas/SwitchType"}]},"numberOfGangs":{"type":"number","description":"Number of gangs/buttons on the switch (1-4)","example":2,"minimum":1,"maximum":4}}},"SmartSwitch":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceType":{"description":"Type of device (discriminator field)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"macId":{"type":"string","description":"MAC ID of the Device (from IoT device)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Name of the Device","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"onStatus":{"description":"Status of the Device (ON / OFF / INACTIVE / ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend (cloud / scheduled). Set when a toggle is issued; cleared when the device reports a matching state or a physical (MANUAL) press overrides it. Always ON or OFF when present.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when targetState was last written. Used to expire stale SCHEDULED intents (24hr TTL). CLOUD intents do not expire.","example":"2026-05-25T08:00:00.000Z"},"targetSource":{"description":"Origin of the pending targetState. Drives TTL behavior on reconcile. Only CLOUD / SCHEDULED are ever written here.","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"location":{"type":"string","description":"Physical location or address where the device is installed","example":"Room 101, Building A"},"remarks":{"type":"string","description":"Remarks for the Device"},"deviceModel":{"type":"string","description":"Model name or identifier of the device","example":"TP-Link HS110"},"deviceBrand":{"type":"string","description":"Brand or manufacturer of the device","example":"TP-Link"},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups","example":1},"chipModel":{"description":"Hardware chip model identifier used in the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is connected to","example":"IoT-Network-5G"},"wifiError":{"type":"string","description":"Last WiFi shadow delta failure reported by the device (e.g. join_failed); cleared once it reports a successful join","example":"join_failed"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join. Set at send time on both the shadow and legacy changeWifi paths; convergence is `ssid` matching any desired SSID.","example":"Corp-IoT"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them.","example":["Corp-IoT","Corp-IoT-2","Corp-IoT-3"],"type":"array","items":{"type":"string"}},"config":{"description":"Settings as last reported by the device into its `config` shadow, one block per feature","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"pendingConfig":{"description":"Settings written to the `config` shadow but not yet reported back. A key the device reports at a different value was rejected","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"configRequestedAt":{"format":"date-time","type":"string","description":"When settings were last written to the `config` shadow"},"configReportedAt":{"format":"date-time","type":"string","description":"When the device last reported into its `config` shadow"},"rssi":{"type":"number","description":"Latest WiFi signal strength reported by the device (dBm). Updated on each realtime MQTT report.","example":-65},"tenant":{"description":"Tagged Tenant","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"zone":{"description":"Tagged Zone","allOf":[{"$ref":"#/components/schemas/Zone"}]},"room":{"description":"Tagged Room","allOf":[{"$ref":"#/components/schemas/Room"}]},"system":{"description":"Tagged System","allOf":[{"$ref":"#/components/schemas/System"}]},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a physical unit. Such rows have no real MAC, never report telemetry, and are excluded from firmware, OTA, provisioning and panel-slot views.","example":false},"isRidgeChild":{"type":"boolean","description":"True when this row is a unit behind a RIDGE gateway rather than a directly-connected unit. Such rows have no real MAC, are never fleet-provisioned, and are excluded from firmware, OTA and liveness watchdogs — their gateway carries those.","example":false},"hasPhaseChildren":{"type":"boolean","description":"True when this clamp has been split into per-phase children. Such a clamp is org-detached and excluded from energy and organisational views — its children carry those — while remaining the sole target for firmware, OTA and diagnostics.","example":false},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2021-09-01"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device on/off status cannot be toggled.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"deviceSchedule":{"description":"Schedule configuration for the device. Contains recurring weekly schedules and one-time scheduled tasks.","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the single Schedule (in the schedules collection) this device currently follows. deviceSchedule is compiled from that schedule. When null, the device follows nothing.","example":"507f1f77bcf86cd799439011","nullable":true},"realTimePower":{"type":"number","description":"Real-time power consumption in watts","example":1500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes","example":6.5},"totalEnergyUsage":{"type":"number","description":"Total cumulative energy usage in kWh","example":0},"totalEnergySaved":{"type":"number","description":"Total energy saved through optimization in kWh","example":0},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update OTA attempts sent to this device","example":0},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether device is blacklisted from auto-updates (max retries exceeded)","example":false},"manualUpdateBlacklist":{"type":"boolean","description":"Whether device is manually blacklisted from auto-updates by admin","example":false},"lastAutoUpdateAttempt":{"format":"date-time","type":"string","description":"Timestamp of last auto-update OTA attempt"},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","default":false},"inactivityNotificationSentAt":{"format":"date-time","type":"string","description":"Timestamp when the last inactivity notification was sent for this device"},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"pendingThresholds":{"type":"object","description":"Threshold limits sent to the device but not yet confirmed by its firmware. Keys match DeviceThresholdsDto. Present only while a save is outstanding — a key that persists here is not being enforced by the hardware.","additionalProperties":{"type":"number"}},"thresholds":{"description":"This device's alert thresholds gathered into one object. Which fields are populated depends on deviceType — see DeviceThresholdsDto. Absent when no thresholds are configured. The same values remain available as flat top-level fields.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"switchType":{"description":"Type of appliance the switch controls (LIGHT, FAN, AIRCON, or UNREGISTERED)","example":"LIGHT","allOf":[{"$ref":"#/components/schemas/SwitchType"}]},"numberOfGangs":{"type":"number","description":"Number of gangs/buttons on the switch (1-4)","example":2,"minimum":1,"maximum":4},"gangSwitchStates":{"description":"Array of gang switch states - each gang has its own independent on/off state","example":[{"gangNumber":1,"onStatus":"OFF","label":"Main Light"},{"gangNumber":2,"onStatus":"OFF","label":"Accent Light"}],"type":"array","items":{"$ref":"#/components/schemas/GangSwitchState"}}},"required":["_id","deviceType","macId","deviceName","deviceNumber","onStatus","location","remarks","batchNumber","chipModel","versionNumber","ssid","tenant","zone","room","system","createdAt","updatedAt","lastSeen","isLocked","totalEnergyUsage","totalEnergySaved","autoUpdateAttempts","autoUpdateBlacklisted","manualUpdateBlacklist","inactivityNotificationEnabled","switchType","numberOfGangs","gangSwitchStates"]},"ControlSmartSwitchDto":{"type":"object","properties":{"gangStates":{"description":"Array of binary gang switch states where 1 = ON and 0 = OFF. Array index corresponds to gang number (e.g., [1, 0, 0, 1] means gang 1 ON, gang 2 OFF, gang 3 OFF, gang 4 ON)","example":[1,0,0,1],"type":"array","items":{"type":"number"}},"triggerSource":{"description":"Source that triggered the control command","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]}},"required":["gangStates"]},"BulkControlSmartSwitchDto":{"type":"object","properties":{"ids":{"description":"Array of Smart Switch IDs to control","example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"onStatus":{"description":"Desired power state for all gangs on all Smart Switches. Use ON to turn all gangs on, or OFF to turn all gangs off.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}},"required":["ids"]},"CreateDistributionBoxDto":{"type":"object","properties":{"name":{"type":"string","description":"Name/identifier of the Distribution Box","example":"Main DB Box - Floor 1"},"numberOfRows":{"type":"number","description":"Number of rows in the distribution box","example":1},"hasMainBreaker":{"type":"boolean","description":"Whether the distribution box has main circuit breaker","example":true},"mainBreakerRating":{"type":"number","description":"Main breaker rating in amperes (if hasMainBreaker is true)","example":63},"systemId":{"type":"string","description":"System ID to which this Distribution Box belongs","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"tenantId":{"type":"string","description":"Tenant ID to which this Distribution Box belongs"},"zoneId":{"type":"string","description":"Zone ID to which this Distribution Box belongs"},"roomId":{"type":"string","description":"Room ID to which this Distribution Box belongs"},"mcbIds":{"description":"Array of MCB IDs to be assigned to this distribution box","example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"location":{"type":"string","description":"Location of the Distribution Box","example":"Ground Floor, Electrical Room"},"remarks":{"type":"string","description":"Remarks for the Distribution Box"},"digitalTwinWidth":{"type":"number","description":"Width of the digital twin canvas in pixels","example":1200},"digitalTwinHeight":{"type":"number","description":"Height of the digital twin canvas in pixels","example":800}},"required":["name","systemId"]},"GetDistributionBoxesDto":{"type":"object","properties":{"data":{"description":"Array of Distribution Boxes","type":"array","items":{"$ref":"#/components/schemas/DistributionBox"}},"total":{"type":"number","description":"Total number of Distribution Boxes","example":100},"page":{"type":"number","description":"Current page number","example":1},"size":{"type":"number","description":"Number of items per page","example":10}},"required":["data","total","page","size"]},"SandboxDeviceInputDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the sandbox device","example":"sandbox-device-1"},"type":{"type":"string","description":"Type of sandbox device","enum":["DUMB_MCB","RCCB","RCBO","MCCB","ISOLATOR","SPD","PLANNED_SMART"],"example":"DUMB_MCB"},"name":{"type":"string","description":"User-defined name for the device","example":"Kitchen Lights"},"digitalTwinX":{"type":"number","description":"X position on the digital twin canvas","example":400},"rowLine":{"type":"number","description":"Row line position (1-6)","example":2},"poles":{"type":"number","description":"Number of poles. Sets the module width for everything except an MCCB.","example":2},"widthModules":{"type":"number","description":"How many DIN modules wide the device body is, at 18mm per module. Equals `poles` for every modular device; an MCCB is sized by frame instead, so it carries its own value.","example":2},"curve":{"description":"Trip curve stamped on the breaker","example":"C","allOf":[{"$ref":"#/components/schemas/BreakerCurve"}]},"confidence":{"type":"number","description":"How sure the reading was when this device came from a digitised photo, 0 to 1. Omit for hand-placed devices.","example":0.82},"ratedCurrent":{"type":"number","description":"Rated current in amperes","example":16},"ratedVoltage":{"type":"number","description":"Rated voltage in volts","example":230},"mcbType":{"description":"MCB type (for DUMB_MCB devices)","example":"BUILT_IN","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"leakageCurrent":{"type":"number","description":"Leakage current in milliamperes (for RCCB devices)","example":30},"color":{"type":"string","description":"Custom color for visual identification (hex code)","example":"#94a3b8"},"coilClampPositions":{"description":"Absolute canvas positions of the 3 CT clamps, for a PLANNED_SMART device standing in for a Wattson Pro.","type":"array","items":{"$ref":"#/components/schemas/CoilClampPositionDto"}},"notes":{"type":"string","description":"Additional notes about the device","example":"Connected to kitchen circuit"}},"required":["id","type","name","digitalTwinX","rowLine"]},"WireConnectionDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Device ID (MCB._id or SandboxDevice.id)","example":"sandbox-device-1"},"deviceType":{"type":"string","description":"Type of device being connected","enum":["SMART_MCB","DUMB_MCB","RCCB","RCBO","MCCB","ISOLATOR","SPD","PLANNED_SMART"],"example":"DUMB_MCB"},"side":{"type":"string","description":"Side of the device where the connection is made","enum":["LEFT","RIGHT","TOP","BOTTOM"],"example":"LEFT"}},"required":["deviceId","deviceType","side"]},"SandboxWireInputDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the wire connection","example":"wire-1"},"from":{"description":"Source connection point","allOf":[{"$ref":"#/components/schemas/WireConnectionDto"}]},"to":{"description":"Target connection point","allOf":[{"$ref":"#/components/schemas/WireConnectionDto"}]},"color":{"type":"string","description":"Wire color (hex code)","example":"#fbbf24","default":"#fbbf24"},"thickness":{"type":"number","description":"Wire thickness in pixels","example":3,"default":3},"label":{"type":"string","description":"Optional label for the wire","example":"Phase 1"}},"required":["id","from","to"]},"UpdateDistributionBoxDto":{"type":"object","properties":{"name":{"type":"string","description":"Name/identifier of the Distribution Box","example":"Main DB Box - Floor 2"},"numberOfRows":{"type":"number","description":"Number of rows in the distribution box","example":1},"hasMainBreaker":{"type":"boolean","description":"Whether the distribution box has main circuit breaker","example":true},"mainBreakerRating":{"type":"number","description":"Main breaker rating in amperes (if hasMainBreaker is true)","example":63},"tenantId":{"type":"string","description":"Tenant ID to which this Distribution Box belongs"},"zoneId":{"type":"string","description":"Zone ID to which this Distribution Box belongs"},"roomId":{"type":"string","description":"Room ID to which this Distribution Box belongs"},"mcbIds":{"description":"Array of MCB IDs to be assigned to this distribution box","example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"location":{"type":"string","description":"Location of the Distribution Box","example":"Ground Floor, Electrical Room"},"remarks":{"type":"string","description":"Remarks for the Distribution Box"},"sandboxDevices":{"description":"Array of sandbox (non-smart) devices in the distribution box","type":"array","items":{"$ref":"#/components/schemas/SandboxDeviceInputDto"}},"sandboxWires":{"description":"Array of wire connections between devices in the distribution box","type":"array","items":{"$ref":"#/components/schemas/SandboxWireInputDto"}},"digitalTwinWidth":{"type":"number","description":"Width of the digital twin canvas in pixels","example":1200},"digitalTwinHeight":{"type":"number","description":"Height of the digital twin canvas in pixels","example":800}}},"AddMCBToDistributionBoxDto":{"type":"object","properties":{"mcbId":{"type":"string","description":"MCB ID to add to the distribution box","example":"64b7f1e6f9c9d4e5b8a9f7c3"}},"required":["mcbId"]},"RemoveMCBFromDistributionBoxDto":{"type":"object","properties":{"mcbId":{"type":"string","description":"MCB ID to remove from the distribution box","example":"64b7f1e6f9c9d4e5b8a9f7c3"}},"required":["mcbId"]},"GetDistributionBoxMcbDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique database identifier of the device document in the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"Unique identifier assigned to the device by the IoT hardware (MAC ID)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Friendly name for the device for easy identification","example":"Living Room AC"},"onStatus":{"description":"Current operational status of the device (ON, OFF, INACTIVE, or ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend. Present when a toggle was issued but the device has not yet acknowledged (e.g. issued while INACTIVE). Frontend can derive a \"Pending\" indicator when this differs from onStatus.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when the pending targetState was last written.","example":"2026-05-25T08:00:00.000Z"},"totalEnergyUsage":{"type":"number","description":"Cumulative total energy consumption in kilowatt-hours (kWh) since device installation","example":500},"deviceNumber":{"type":"number","description":"Auto-incremented sequential number for tracking device registration order","example":3},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups and quality control","example":23},"chipModel":{"description":"Hardware chip model identifier used in the device (e.g., ESP32C2, ESP8266)","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"tenantId":{"type":"string","description":"Unique identifier of the tenant organization that owns this device","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantName":{"type":"string","description":"Name of the tenant organization that owns this device","example":"Faculty of Engineering"},"zoneId":{"type":"string","description":"Unique identifier of the zone that this device is assigned to","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"zoneName":{"type":"string","description":"Name of the zone that this device is assigned to","example":"Zone A"},"roomId":{"type":"string","description":"Unique identifier of the room where this device is physically located","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"roomName":{"type":"string","description":"Name of the room where this device is physically located","example":"Room 101"},"dbId":{"type":"string","description":"Unique identifier of the distribution box that this device is connected to (for MCBs)","example":"64b7f1e6f9c9d4e5b8a9f7c7"},"dbName":{"type":"string","description":"Name of the distribution box that this device is connected to (for MCBs)","example":"Main Distribution Box"},"systemId":{"type":"string","description":"Unique identifier of the system that this device belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system that this device belongs to","example":"NUS Engineering Building"},"realTimePower":{"type":"number","description":"Real-time power consumption in watts (W) measured by the device","example":115},"realTimeCurrent":{"type":"number","description":"Real-time current draw in amperes (A) measured by the device","example":0.5},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is currently connected to","example":"XH001"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join","example":"XH001"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join","example":"XH001-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join","example":"XH001-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them","example":["XH001","XH001-2","XH001-3"],"type":"array","items":{"type":"string"}},"deviceSchedule":{"description":"Weekly and one-time schedule configuration for automated device control","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the named Schedule this device currently follows. deviceSchedule is compiled from it. Absent or null when the device follows nothing.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c3"},"lastSeen":{"format":"date-time","type":"string","description":"Timestamp when the device last communicated with the system","example":"2024-10-15T12:30:00.000Z"},"isLocked":{"type":"boolean","description":"Lock status indicating if the device is locked","example":false},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","example":true,"default":false},"thresholds":{"description":"Alert thresholds configured for this device, gathered into one object. Which fields are populated depends on the device type — see DeviceThresholdsDto. Omitted entirely when nothing is set.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"rangeEnergyUsage":{"type":"number","description":"Energy usage in kWh over the `from`..`till` window, aggregated from daily usage history. Present only when both query params are supplied — unlike the cumulative `totalEnergyUsage`, this is windowed.","example":42.75},"mcbType":{"description":"Type of MCB (trip curve type)","example":"BUILT_IN","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"measurementType":{"description":"Type of load/circuit being measured by this MCB","example":"SSO","allOf":[{"$ref":"#/components/schemas/McbMeasurementType"}]},"ratedCurrent":{"type":"number","description":"Rated current capacity in amperes","example":16},"ratedVoltage":{"type":"number","description":"Rated voltage in volts","example":230},"realTimeVoltage":{"type":"number","description":"Real-time voltage in volts","example":230},"powerFactor":{"type":"number","description":"Power factor (0-1)","example":0.95},"digitalTwinX":{"type":"number","description":"X coordinate for digital twin SVG positioning","example":150.5},"digitalTwinY":{"type":"number","description":"Y coordinate for digital twin SVG positioning (free-placed devices such as coils)","example":130.5},"coilClampPositions":{"description":"Absolute canvas positions of the 3 CT clamps (COIL3P devices only)","type":"array","items":{"$ref":"#/components/schemas/CoilClampPositionDto"}},"coilRotation":{"type":"number","description":"Rotation of the coil body in the digital twin, in degrees (e.g. 0, 90, 180, 270).","example":90},"rowLine":{"type":"number","description":"Row line number (1-6) the MCB is connected to","example":1},"slotNumber":{"type":"number","description":"Slot/position number on the busbar","example":3},"isReversePower":{"type":"boolean","description":"Whether power/current readings are reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isAReversePower":{"type":"boolean","description":"Whether power/current readings are reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isBReversePower":{"type":"boolean","description":"Whether power/current readings are reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isCReversePower":{"type":"boolean","description":"Whether power/current readings are reversed (made absolute). When true, negative values are converted to positive.","example":false,"default":false},"isSingle":{"type":"boolean","description":"COIL3P only. How the clamp is wired. false = 3 x 1P, three independent circuits, each shown as its own row with per-phase readings; true = one combined three-phase circuit shown as a single row, with one threshold shared by all three phases.","example":false,"default":false},"phaseLabels":{"description":"COIL3P only. Optional labels for phases A, B and C (in order).","example":["Lighting","Sockets","Aircon"],"type":"array","items":{"type":"string"}},"realTimePowerA":{"type":"number","description":"Phase A real-time power (W)"},"realTimePowerB":{"type":"number","description":"Phase B real-time power (W)"},"realTimePowerC":{"type":"number","description":"Phase C real-time power (W)"},"realTimeVoltageA":{"type":"number","description":"Phase A real-time voltage (V)"},"realTimeVoltageB":{"type":"number","description":"Phase B real-time voltage (V)"},"realTimeVoltageC":{"type":"number","description":"Phase C real-time voltage (V)"},"realTimeCurrentA":{"type":"number","description":"Phase A real-time current (A)"},"realTimeCurrentB":{"type":"number","description":"Phase B real-time current (A)"},"realTimeCurrentC":{"type":"number","description":"Phase C real-time current (A)"},"totalEnergyUsageA":{"type":"number","description":"Phase A cumulative energy usage (kWh)"},"totalEnergyUsageB":{"type":"number","description":"Phase B cumulative energy usage (kWh)"},"totalEnergyUsageC":{"type":"number","description":"Phase C cumulative energy usage (kWh)"},"powerFactorA":{"type":"number","description":"Phase A power factor (0-1)"},"powerFactorB":{"type":"number","description":"Phase B power factor (0-1)"},"powerFactorC":{"type":"number","description":"Phase C power factor (0-1)"},"phase":{"description":"Set only when the list is requested with expandPhases=true and this row represents a single phase of a COIL3P device. In that mode realTimePower, realTimeCurrent, realTimeVoltage, totalEnergyUsage and powerFactor hold the values of this phase.","example":"A","allOf":[{"$ref":"#/components/schemas/McbPhase"}]},"phaseParent":{"type":"string","description":"Set when this row is one phase of a split COIL3P. The hardware settings — rated current, thresholds, reverse-power, firmware, panel position — belong to the clamp at this id, not to the leg; only the name and the Room / Zone / Tenant assignment are the leg's own.","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a whole device.","example":false},"phaseName":{"type":"string","description":"User-defined name of this row's phase (expandPhases=true rows only)","example":"Kitchen"}},"required":["_id","macId","deviceName","onStatus","totalEnergyUsage","tenantId","tenantName","zoneId","zoneName","roomId","roomName","dbId","dbName","systemId","systemName","realTimePower","realTimeCurrent","ssid","mcbType","ratedCurrent"]},"GetMCBEnergyUsageDto":{"type":"object","properties":{"mcbId":{"type":"string","description":"MCB ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceName":{"type":"string","description":"MCB name","example":"Living Room AC MCB"},"totalEnergyUsage":{"type":"number","description":"Total energy usage in kWh","example":125.5},"realTimePower":{"type":"number","description":"Real-time power consumption in watts","example":1500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes","example":6.5}},"required":["mcbId","deviceName","totalEnergyUsage"]},"GetDistributionBoxEnergyUsageDto":{"type":"object","properties":{"distributionBoxId":{"type":"string","description":"Distribution Box ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"Distribution Box name","example":"Main DB Box - Floor 1"},"totalEnergyUsage":{"type":"number","description":"Total energy usage in kWh (sum of all MCBs)","example":1250.75},"mcbCount":{"type":"number","description":"Number of MCBs in this distribution box","example":12},"mcbs":{"description":"Individual MCB energy usage details","type":"array","items":{"$ref":"#/components/schemas/GetMCBEnergyUsageDto"}}},"required":["distributionBoxId","name","totalEnergyUsage","mcbCount","mcbs"]},"GetAggregatedDistributionBoxEnergyUsageDto":{"type":"object","properties":{"totalEnergyUsage":{"type":"number","description":"Total aggregated energy usage in kWh","example":5250.75},"distributionBoxCount":{"type":"number","description":"Number of distribution boxes included","example":5},"distributionBoxes":{"description":"Individual distribution box energy usage details","type":"array","items":{"$ref":"#/components/schemas/GetDistributionBoxEnergyUsageDto"}}},"required":["totalEnergyUsage","distributionBoxCount","distributionBoxes"]},"CreateSandboxDeviceDto":{"type":"object","properties":{"type":{"type":"string","description":"Type of sandbox device","enum":["DUMB_MCB","RCCB","RCBO","MCCB","ISOLATOR","SPD","PLANNED_SMART"],"example":"DUMB_MCB"},"name":{"type":"string","description":"User-defined name for the device","example":"Kitchen Lights"},"digitalTwinX":{"type":"number","description":"X position on the digital twin canvas","example":400},"rowLine":{"type":"number","description":"Row line position (1-6)","example":2},"ratedCurrent":{"type":"number","description":"Rated current in amperes","example":16},"ratedVoltage":{"type":"number","description":"Rated voltage in volts","example":230},"mcbType":{"type":"string","description":"MCB type (for DUMB_MCB devices)","enum":["UNREGISTERED","SWITCH","BUILT_IN","COIL","COIL3P"],"example":"BUILT_IN"},"leakageCurrent":{"type":"number","description":"Leakage current in milliamperes (for RCCB devices)","example":30},"color":{"type":"string","description":"Custom color for visual identification (hex code)","example":"#94a3b8"},"notes":{"type":"string","description":"Additional notes about the device","example":"Connected to kitchen circuit"}},"required":["type","name","digitalTwinX","rowLine"]},"UpdateSandboxDeviceDto":{"type":"object","properties":{"type":{"type":"string","description":"Type of sandbox device","enum":["DUMB_MCB","RCCB","RCBO","MCCB","ISOLATOR","SPD","PLANNED_SMART"],"example":"DUMB_MCB"},"name":{"type":"string","description":"User-defined name for the device","example":"Kitchen Lights"},"digitalTwinX":{"type":"number","description":"X position on the digital twin canvas","example":400},"rowLine":{"type":"number","description":"Row line position (1-6)","example":2},"ratedCurrent":{"type":"number","description":"Rated current in amperes","example":16},"ratedVoltage":{"type":"number","description":"Rated voltage in volts","example":230},"mcbType":{"description":"MCB type (for DUMB_MCB devices)","example":"BUILT_IN","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"leakageCurrent":{"type":"number","description":"Leakage current in milliamperes (for RCCB devices)","example":30},"color":{"type":"string","description":"Custom color for visual identification (hex code)","example":"#94a3b8"},"notes":{"type":"string","description":"Additional notes about the device","example":"Connected to kitchen circuit"}}},"ReplaceSandboxDeviceWithMcbDto":{"type":"object","properties":{"mcbId":{"type":"string","description":"ID of the registered MCB that has been installed in place of the planned device","example":"64b7f1e6f9c9d4e5b8a9f7c3"}},"required":["mcbId"]},"CreateSandboxWireDto":{"type":"object","properties":{"from":{"description":"Source connection point","allOf":[{"$ref":"#/components/schemas/WireConnectionDto"}]},"to":{"description":"Target connection point","allOf":[{"$ref":"#/components/schemas/WireConnectionDto"}]},"color":{"type":"string","description":"Wire color (hex code)","example":"#fbbf24","default":"#fbbf24"},"thickness":{"type":"number","description":"Wire thickness in pixels","example":3,"default":3},"label":{"type":"string","description":"Optional label for the wire","example":"Phase 1"}},"required":["from","to"]},"UpdateSandboxWireDto":{"type":"object","properties":{"from":{"description":"Source connection point","allOf":[{"$ref":"#/components/schemas/WireConnectionDto"}]},"to":{"description":"Target connection point","allOf":[{"$ref":"#/components/schemas/WireConnectionDto"}]},"color":{"type":"string","description":"Wire color (hex code)","example":"#fbbf24"},"thickness":{"type":"number","description":"Wire thickness in pixels","example":3},"label":{"type":"string","description":"Optional label for the wire","example":"Phase 1"}}},"UpdateSandboxElementsDto":{"type":"object","properties":{"sandboxDevices":{"description":"Array of all sandbox devices (replaces existing)","type":"array","items":{"$ref":"#/components/schemas/SandboxDeviceInputDto"}},"sandboxWires":{"description":"Array of all sandbox wires (replaces existing)","type":"array","items":{"$ref":"#/components/schemas/SandboxWireInputDto"}}},"required":["sandboxDevices","sandboxWires"]},"DeviceTypeForWire":{"type":"string","enum":["SMART_MCB","DUMB_MCB","RCCB","RCBO","MCCB","ISOLATOR","SPD","PLANNED_SMART"],"description":"What the device was detected as. SMART_MCB means an Ecovolt breaker, which the client binds to an already-registered MCB rather than creating."},"DeviceRole":{"type":"string","enum":["INCOMER","RCD_GROUP","WAY"],"description":"How the device is fed. The wiring on a real board is mostly hidden behind the busbar, so roles are read off the photo and the wires derived from them."},"TwinDeviceDto":{"type":"object","properties":{"id":{"type":"string","description":"Temporary identifier for this detected device, unique within the response. Wires reference it; the client replaces it with a real MCB id or a generated sandbox device id on save.","example":"ai-device-3"},"type":{"description":"What the device was detected as. SMART_MCB means an Ecovolt breaker, which the client binds to an already-registered MCB rather than creating.","example":"DUMB_MCB","allOf":[{"$ref":"#/components/schemas/DeviceTypeForWire"}]},"name":{"type":"string","description":"Name read off the breaker label or directory card","example":"Kitchen Lights"},"digitalTwinX":{"type":"number","description":"X position on the digital twin canvas, snapped to the rail","example":420},"rowLine":{"type":"number","description":"Row line position (1-6), counted top to bottom","example":2},"slotNumber":{"type":"number","description":"Module slot the device occupies on its row, left to right","example":3},"role":{"description":"How the device is fed. The wiring on a real board is mostly hidden behind the busbar, so roles are read off the photo and the wires derived from them.","example":"WAY","allOf":[{"$ref":"#/components/schemas/DeviceRole"}]},"widthModules":{"type":"number","description":"How many DIN modules wide the device body is, at 18mm per module. Equals `poles` for every modular device; an MCCB is sized by frame instead.","example":1},"poles":{"type":"number","description":"Number of poles","example":1},"confidence":{"type":"number","description":"Detection confidence from 0 to 1. Anything below ~0.6 is worth flagging in the UI for the user to verify against the photo.","example":0.82},"ratedCurrent":{"type":"number","description":"Rated current in amperes, if legible","example":16},"ratedVoltage":{"type":"number","description":"Rated voltage in volts, if legible","example":230},"mcbType":{"description":"MCB type (manual devices only)","example":"SWITCH","allOf":[{"$ref":"#/components/schemas/MCBType"}]},"curve":{"description":"Trip curve stamped on the breaker, if legible","example":"C","allOf":[{"$ref":"#/components/schemas/BreakerCurve"}]},"leakageCurrent":{"type":"number","description":"Leakage current in milliamperes (residual current devices)","example":30},"color":{"type":"string","description":"Colour for visual identification","example":"#64748b"},"notes":{"type":"string","description":"What the model read, when it is worth keeping as a note","example":"Label partially obscured"}},"required":["id","type","name","digitalTwinX","rowLine","slotNumber","role","widthModules","poles","confidence"]},"GenerateDigitalTwinResponseDto":{"type":"object","properties":{"numberOfRows":{"type":"number","description":"Number of DIN rails detected on the board","example":3},"digitalTwinWidth":{"type":"number","description":"Suggested canvas width in pixels","example":1620},"digitalTwinHeight":{"type":"number","description":"Suggested canvas height in pixels","example":1880},"devices":{"description":"Every breaker detected, smart and manual alike, already snapped to the rail","type":"array","items":{"$ref":"#/components/schemas/TwinDeviceDto"}},"wires":{"description":"Proposed wiring between detected devices, referencing TwinDeviceDto ids","type":"array","items":{"$ref":"#/components/schemas/SandboxWireInputDto"}}},"required":["numberOfRows","digitalTwinWidth","digitalTwinHeight","devices","wires"]},"CreateSurgeOverrideDto":{"type":"object","properties":{"name":{"type":"string","description":"Display name","example":"Boost 8 kWh"},"description":{"type":"string","description":"Customer-facing description"},"applicableEntityTypes":{"type":"array","description":"Entity types this override can be applied to","items":{"$ref":"#/components/schemas/MonitoringEntityType"}},"thresholdKwh":{"type":"number","description":"Absolute daily threshold (kWh) granted while the override is active","example":8},"price":{"type":"number","description":"Price","example":9.9},"isActive":{"type":"boolean","description":"Whether the override is sellable","default":true}},"required":["name","applicableEntityTypes","thresholdKwh","price"]},"SurgeOverrideResponseDto":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"applicableEntityTypes":{"type":"array","items":{"$ref":"#/components/schemas/MonitoringEntityType"}},"thresholdKwh":{"type":"number"},"price":{"type":"number"},"isActive":{"type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","name","applicableEntityTypes","thresholdKwh","price","isActive","createdAt","updatedAt"]},"UpdateSurgeOverrideDto":{"type":"object","properties":{"name":{"type":"string","description":"Display name","example":"Boost 8 kWh"},"description":{"type":"string","description":"Customer-facing description"},"applicableEntityTypes":{"type":"array","description":"Entity types this override can be applied to","items":{"$ref":"#/components/schemas/MonitoringEntityType"}},"thresholdKwh":{"type":"number","description":"Absolute daily threshold (kWh) granted while the override is active","example":8},"price":{"type":"number","description":"Price","example":9.9},"isActive":{"type":"boolean","description":"Whether the override is sellable","default":true}}},"PurchaseSurgeOverrideDto":{"type":"object","properties":{"surgeOverrideId":{"type":"string","description":"SurgeOverride catalog id"},"entityType":{"description":"Entity type the override should apply to","allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"entityId":{"type":"string","description":"Entity id (room/tenant/zone)"},"paymentId":{"type":"string","description":"Optional Payment record id to link to this purchase (refunds handled via payment module)"}},"required":["surgeOverrideId","entityType","entityId"]},"PurchaseStatus":{"type":"string","enum":["ACTIVE","CANCELLED"]},"SurgeOverridePurchaseResponseDto":{"type":"object","properties":{"_id":{"type":"string"},"surgeOverride":{"type":"string"},"system":{"type":"string"},"entityType":{"allOf":[{"$ref":"#/components/schemas/MonitoringEntityType"}]},"entityId":{"type":"string"},"thresholdKwh":{"type":"number"},"activeFrom":{"format":"date-time","type":"string"},"status":{"allOf":[{"$ref":"#/components/schemas/PurchaseStatus"}]},"payment":{"type":"string"},"purchasedBy":{"type":"string"},"purchasedByName":{"type":"string","description":"Display name of the admin who purchased"},"entityName":{"type":"string","description":"Display name of the entity (room/tenant/zone)"},"cancelledAt":{"format":"date-time","type":"string"},"cancelledBy":{"type":"string"},"cancelReason":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","surgeOverride","system","entityType","entityId","thresholdKwh","activeFrom","status","purchasedBy","createdAt","updatedAt"]},"CancelSurgeOverrideDto":{"type":"object","properties":{"reason":{"type":"string","description":"Reason supplied at cancellation time","example":"Customer requested refund"}}},"SurgeOverridePurchaseListResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"type":"array","items":{"$ref":"#/components/schemas/SurgeOverridePurchaseResponseDto"}}},"required":["total","page","size","totalPages","data"]},"SurgeOverridePurchaseActiveMapResponseDto":{"type":"object","properties":{"data":{"type":"object","description":"Map of entityId → active SurgeOverridePurchase. Only entities with an active override are included.","additionalProperties":{"$ref":"#/components/schemas/SurgeOverridePurchaseResponseDto"}},"total":{"type":"number"}},"required":["data","total"]},"OnboardingStatus":{"type":"string","enum":["PENDING","IN_PROGRESS","COMPLETED"]},"PhaseStatus":{"type":"string","enum":["PENDING","ACTIVE","SUGGESTIONS_READY","COMPLETED"]},"OnboardingPhase":{"type":"object","properties":{"startDate":{"format":"date-time","type":"string"},"activationScheduledTaskName":{"type":"string","description":"AWS Scheduler task name for auto-activating this phase on startDate"},"endDate":{"format":"date-time","type":"string"},"status":{"allOf":[{"$ref":"#/components/schemas/PhaseStatus"}]}},"required":["status"]},"DefaultStaticSchedule":{"type":"object","properties":{"monday":{"type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"tuesday":{"type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"wednesday":{"type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"thursday":{"type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"friday":{"type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"saturday":{"type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}},"sunday":{"type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntry"}}}},"ScheduleGroup":{"type":"object","properties":{"plugIds":{"description":"Plug IDs assigned to this schedule group","type":"array","items":{"type":"string"}},"schedule":{"$ref":"#/components/schemas/DefaultStaticSchedule"}},"required":["plugIds","schedule"]},"StaticSchedulePhase":{"type":"object","properties":{"startDate":{"format":"date-time","type":"string"},"activationScheduledTaskName":{"type":"string","description":"AWS Scheduler task name for auto-activating this phase on startDate"},"endDate":{"format":"date-time","type":"string"},"status":{"allOf":[{"$ref":"#/components/schemas/PhaseStatus"}]},"includePublicHolidays":{"type":"boolean"},"defaultStaticSchedule":{"description":"Fallback schedule applied to unlocked plugs not in any scheduleGroup","allOf":[{"$ref":"#/components/schemas/DefaultStaticSchedule"}]},"scheduleGroups":{"description":"Per-group device schedules. Devices in a group get the group schedule; remaining unlocked devices get defaultStaticSchedule.","type":"array","items":{"$ref":"#/components/schemas/ScheduleGroup"}},"ascentAIScheduledTaskName":{"type":"string"}},"required":["status","includePublicHolidays"]},"AscentAIPhase":{"type":"object","properties":{"startDate":{"format":"date-time","type":"string"},"activationScheduledTaskName":{"type":"string","description":"AWS Scheduler task name for auto-activating this phase on startDate"},"endDate":{"format":"date-time","type":"string"},"status":{"allOf":[{"$ref":"#/components/schemas/PhaseStatus"}]},"suggestionsGeneratedAt":{"format":"date-time","type":"string"},"suggestionsActivatedAt":{"format":"date-time","type":"string"},"autoSuggestionsEnabled":{"type":"boolean"}},"required":["status","autoSuggestionsEnabled"]},"OnboardingIterationDto":{"type":"object","properties":{"iterationNumber":{"type":"number"},"name":{"type":"string"},"status":{"allOf":[{"$ref":"#/components/schemas/OnboardingStatus"}]},"startedAt":{"format":"date-time","type":"string"},"completedAt":{"format":"date-time","type":"string"},"monitoring":{"$ref":"#/components/schemas/OnboardingPhase"},"staticSchedule":{"$ref":"#/components/schemas/StaticSchedulePhase"},"ascentAI":{"$ref":"#/components/schemas/AscentAIPhase"},"closing":{"$ref":"#/components/schemas/OnboardingPhase"},"ascentAiRecommendations":{"type":"object"}},"required":["iterationNumber","status","monitoring","staticSchedule","ascentAI","closing"]},"SystemOnboardingResponseDto":{"type":"object","properties":{"_id":{"type":"string"},"system":{"type":"string"},"activeIterationIndex":{"type":"number"},"iterations":{"type":"array","items":{"$ref":"#/components/schemas/OnboardingIterationDto"}},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","system","activeIterationIndex","iterations","createdAt","updatedAt"]},"DefaultStaticScheduleDto":{"type":"object","properties":{"monday":{"description":"Monday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"tuesday":{"description":"Tuesday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"wednesday":{"description":"Wednesday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"thursday":{"description":"Thursday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"friday":{"description":"Friday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"saturday":{"description":"Saturday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"sunday":{"description":"Sunday schedule entries","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}}}},"ScheduleGroupDto":{"type":"object","properties":{"plugIds":{"type":"array","items":{"type":"string"}},"schedule":{"$ref":"#/components/schemas/DefaultStaticScheduleDto"}},"required":["plugIds","schedule"]},"UpdateOnboardingPhaseDto":{"type":"object","properties":{"startDate":{"type":"string","description":"ISO 8601 date string"},"endDate":{"type":"string","description":"ISO 8601 date string"},"includePublicHolidays":{"type":"boolean"},"defaultStaticSchedule":{"description":"Default schedule auto-applied to unlocked plugs on Phase 2 activation","allOf":[{"$ref":"#/components/schemas/DefaultStaticScheduleDto"}]},"scheduleGroups":{"description":"Per-group device schedules for Phase 2. Devices in a group get the group schedule; remaining unlocked devices get defaultStaticSchedule.","type":"array","items":{"$ref":"#/components/schemas/ScheduleGroupDto"}}}},"EcoguardRecommendationDto":{"type":"object","properties":{"suitable":{"type":"boolean","description":"Whether this device category benefits from EcoGuard","example":true},"suggestedIdleThreshold":{"type":"number","description":"Suggested idle power threshold in watts (with 1.1x buffer). Only present if suitable is true.","example":5.5},"suggestedTimer":{"type":"number","description":"Suggested auto-shutoff timer in minutes. Only present if suitable is true.","example":30},"reason":{"type":"string","description":"Human-readable explanation shown to users","example":"This device type is suitable for EcoGuard idle detection."}},"required":["suitable","reason"]},"PlugRecommendationDto":{"type":"object","properties":{"monday":{"description":"Array of AI-recommended schedule entries for Monday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"tuesday":{"description":"Array of AI-recommended schedule entries for Tuesday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"wednesday":{"description":"Array of AI-recommended schedule entries for Wednesday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"thursday":{"description":"Array of AI-recommended schedule entries for Thursday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"friday":{"description":"Array of AI-recommended schedule entries for Friday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"saturday":{"description":"Array of AI-recommended schedule entries for Saturday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"sunday":{"description":"Array of AI-recommended schedule entries for Sunday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"summary":{"type":"string","description":"AI-generated summary explaining the recommended schedule and detected behavioral patterns","example":"Work-from-home behavior detected on weekdays with consistent 9 AM-5 PM active periods. Weekend usage minimal."},"predictedIdleEnergyUsage":{"type":"number","description":"Predicted average power consumption during idle periods in watts based on historical data","example":12.3},"totalNumberOfIdleHours":{"type":"number","description":"Total count of hours identified as idle or low-usage periods across the analysis timeframe","example":84},"averageUsagePerDay":{"type":"number","description":"Average daily power consumption in watts calculated over the analyzed time period","example":42.7},"flags":{"description":"Array of flags highlighting anomalies, data gaps, or notable behavioral patterns detected by AI","example":["Insufficient data for Tuesday","Constant usage detected on Saturday","Casual/WFH usage pattern on Friday"],"type":"array","items":{"type":"string"}},"ecoguardRecommendation":{"description":"EcoGuard suitability and suggested configuration for this device","allOf":[{"$ref":"#/components/schemas/EcoguardRecommendationDto"}]},"deviceId":{"type":"string","description":"The unique identifier of the device (Plug or MCB)","example":"507f1f77bcf86cd799439011"},"macId":{"type":"string","description":"The MAC address of the device","example":"AA:BB:CC:DD:EE:FF"},"deviceNumber":{"type":"number","description":"The device number assigned to the device","example":42},"deviceName":{"type":"string","description":"The name of the device for easy identification","example":"Office AC Unit"},"plugCategory":{"type":"string","description":"The category of the plug device (only for Plugs, empty for MCBs)","example":"Air Conditioning"},"tier":{"description":"Recommendation tier used: \"rule-based\" (fast, ~5ms), \"clustering\" (medium, ~10ms), or \"llm\" (slow, ~60s). Indicates the analysis method used.","example":"rule-based","allOf":[{"$ref":"#/components/schemas/RecommendationTier"}]},"confidenceScore":{"type":"number","description":"Confidence score (0-1) indicating pattern clarity. Higher score = simpler, more predictable pattern.","example":0.95}},"required":["deviceId","macId","deviceName","plugCategory"]},"BulkPlugRecommendationsResponseDto":{"type":"object","properties":{"recommendations":{"type":"object","description":"Map of plug IDs to their AI-generated ON/OFF schedule recommendations. Each key is a plug ID, and the value contains the complete recommendation data including weekday schedules, summary, and metrics.","additionalProperties":{"$ref":"#/components/schemas/PlugRecommendationDto"}},"bulkSummary":{"type":"string","description":"AI-generated overall summary analyzing all plugs together. Identifies common patterns (e.g., \"Working hours appear to be 9 AM - 6 PM across all devices\"), optimization opportunities (e.g., \"Coffee machine only used on Tuesdays and Thursdays\"), cross-device insights, and collective energy-saving recommendations.","example":"Overall Pattern: Most devices show consistent weekday usage from 8 AM to 6 PM, suggesting office hours. The coffee machine is only active on Tuesday and Thursday mornings (7:30-9:00 AM), indicating team meeting days. Weekend usage is minimal across all devices. Optimization Opportunity: By implementing the recommended schedules, you could save an estimated 156 kWh per week (23% reduction). The AC unit has the highest optimization potential with 45 idle hours detected."}},"required":["recommendations","bulkSummary"]},"ReportFormat":{"type":"string","enum":["pdf","docx","json","excel"]},"StartIterationDto":{"type":"object","properties":{"name":{"type":"string","description":"Optional name for this iteration, e.g. \"Pilot\", \"Full Rollout\""}}},"UpdateIterationNameDto":{"type":"object","properties":{"name":{"type":"string","description":"New name for the iteration"}},"required":["name"]},"IterationSummaryDto":{"type":"object","properties":{"iterationNumber":{"type":"number"},"index":{"type":"number","description":"Zero-based index in the iterations array"},"name":{"type":"string"},"status":{"allOf":[{"$ref":"#/components/schemas/OnboardingStatus"}]},"isActive":{"type":"boolean"},"startedAt":{"format":"date-time","type":"string"},"completedAt":{"format":"date-time","type":"string"}},"required":["iterationNumber","index","status","isActive"]},"IterationListResponseDto":{"type":"object","properties":{"iterations":{"type":"array","items":{"$ref":"#/components/schemas/IterationSummaryDto"}},"total":{"type":"number"},"activeIterationIndex":{"type":"number"}},"required":["iterations","total","activeIterationIndex"]},"ReportBlockKind":{"type":"string","enum":["NARRATIVE","LIST","KPI","CHART","TABLE","NOTE","PANEL","MANUAL"]},"ReportSectionCatalogDto":{"type":"object","properties":{"id":{"type":"string","example":"trend-chart"},"title":{"type":"string","example":"Daily consumption"},"description":{"type":"string","description":"What this section covers, one line."},"kind":{"allOf":[{"$ref":"#/components/schemas/ReportBlockKind"}]},"chartOrFigure":{"type":"boolean","description":"True when the section renders a computed figure rather than prose, so it cannot be reworded by an instruction."},"requires":{"type":"string","description":"What the data must contain for this section to appear. Show it greyed out rather than letting someone pick a section that silently vanishes.","example":"a comparable prior period"}},"required":["id","title","description","kind","chartOrFigure"]},"ReportSelectorType":{"type":"string","enum":["SYSTEM","TENANT","ZONE","ROOM","DEVICE"]},"ReportSelectorLabelDto":{"type":"object","properties":{"type":{"allOf":[{"$ref":"#/components/schemas/ReportSelectorType"}]},"id":{"type":"string"},"name":{"type":"string","description":"Undefined when the entity has since been deleted."}},"required":["type","id"]},"ReportWindowKind":{"type":"string","enum":["RELATIVE","ABSOLUTE"],"description":"RELATIVE re-resolves against the clock each time the report is generated; ABSOLUTE pins it to one period."},"ReportWindowDto":{"type":"object","properties":{"kind":{"description":"RELATIVE re-resolves against the clock each time the report is generated; ABSOLUTE pins it to one period.","example":"RELATIVE","allOf":[{"$ref":"#/components/schemas/ReportWindowKind"}]},"days":{"type":"number","description":"Days back from now. Required for RELATIVE, at most 60.","example":30},"from":{"type":"string","description":"First instant covered (ISO 8601). Required for ABSOLUTE.","format":"date-time","example":"2026-06-01T00:00:00.000Z"},"till":{"type":"string","description":"Last instant covered (ISO 8601). Required for ABSOLUTE.","format":"date-time","example":"2026-06-30T00:00:00.000Z"}},"required":["kind"]},"EnergyReportSummaryDto":{"type":"object","properties":{"id":{"type":"string","description":"Report id"},"name":{"type":"string","description":"Name the reader gave it"},"selectors":{"description":"What the report covers, with each entity’s current name","type":"array","items":{"$ref":"#/components/schemas/ReportSelectorLabelDto"}},"window":{"$ref":"#/components/schemas/ReportWindowDto"},"sectionCount":{"type":"number","description":"How many sections the reader curated. 0 means the default set.","example":8},"latestRunId":{"type":"string","description":"Most recent run. Absent until the report is generated."},"lastGeneratedAt":{"format":"date-time","type":"string","description":"When the report was last generated"},"updatedAt":{"format":"date-time","type":"string"},"createdAt":{"format":"date-time","type":"string"}},"required":["id","name","selectors","window","sectionCount","updatedAt","createdAt"]},"ReportSelectorDto":{"type":"object","properties":{"type":{"example":"ROOM","allOf":[{"$ref":"#/components/schemas/ReportSelectorType"}]},"id":{"type":"string","description":"Id of the entity at that level","example":"64b7f1e6f9c9d4e5b8a9f7c3"}},"required":["type","id"]},"CreateEnergyReportDto":{"type":"object","properties":{"name":{"type":"string","description":"Name the reader gives this report.","example":"Campus Q3 review"},"selectors":{"description":"What the report covers. Selectors are unioned, and a room or zone stays a room or zone — the devices behind it are resolved fresh each time the report is generated.","type":"array","items":{"$ref":"#/components/schemas/ReportSelectorDto"}},"window":{"description":"Period the report covers.","allOf":[{"$ref":"#/components/schemas/ReportWindowDto"}]},"brief":{"type":"string","description":"What this report is for, in your own words. Steers both the proposed outline and the writing.","example":"Quarterly review for the building owners, who are deciding whether to fund the chiller replacement."},"language":{"type":"string","description":"Language to write the report in, as a BCP 47 tag (e.g. \"en\", \"zh-Hans\", \"ms\"). Defaults to English.","example":"zh-Hans"}},"required":["name","selectors","window"]},"EcovoltEntityType":{"type":"string","enum":["SYSTEM","TENANT","ZONE","ROOM","PLUG","MCB","ENV_SENSOR","AIRCON_CONTROL","SMART_SWITCH","WATER_METER","TOGGLEABLE"],"description":"The entity type to query"},"EcovoltPanelScopeDto":{"type":"object","properties":{"entityType":{"description":"The entity type to query","allOf":[{"$ref":"#/components/schemas/EcovoltEntityType"}]},"entityIds":{"description":"One or more MongoDB ObjectIds of the target entities. Hierarchy types (SYSTEM, TENANT, ZONE, ROOM) require exactly one. Device types (PLUG) accept 1–10 to compare multiple devices on the same panel.","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}}},"required":["entityType","entityIds"]},"EcovoltMetric":{"type":"string","enum":["ENERGY_KWH_HOURLY","ENERGY_KWH_DAILY","ENERGY_KWH_TO_DATE","COST_DAILY","COST_HOURLY","COST_MONTHLY_TO_DATE","DEVICE_TYPE_BREAKDOWN","DEVICE_STATUS_BREAKDOWN","VOLTAGE","CURRENT","POWER","POWER_FACTOR","FREQUENCY","TEMPERATURE","HUMIDITY","CO2","LUX","MOTION_DETECTED","FORMALDEHYDE","PM1","FINE_PARTICULATE_MATTER","COARSE_PARTICULATE_MATTER","TVOC","SOUND_LEVEL","TEMPERATURE_HOURLY","HUMIDITY_HOURLY","CO2_HOURLY","LUX_HOURLY","MOTION_DETECTED_HOURLY","FORMALDEHYDE_HOURLY","PM1_HOURLY","FINE_PARTICULATE_MATTER_HOURLY","COARSE_PARTICULATE_MATTER_HOURLY","TVOC_HOURLY","SOUND_LEVEL_HOURLY","TEMPERATURE_DAILY","HUMIDITY_DAILY","CO2_DAILY","LUX_DAILY","MOTION_DETECTED_DAILY","FORMALDEHYDE_DAILY","PM1_DAILY","FINE_PARTICULATE_MATTER_DAILY","COARSE_PARTICULATE_MATTER_DAILY","TVOC_DAILY","SOUND_LEVEL_DAILY","ON_STATUS","DOWN_TIME","NET_VOLUME_HOURLY","NET_VOLUME_DAILY","FLOW_RATE","FLOW_RATE_HOURLY","FLOW_RATE_DAILY","VELOCITY","VELOCITY_HOURLY","VELOCITY_DAILY","SOUND_SPEED","SOUND_SPEED_HOURLY","SOUND_SPEED_DAILY","SIGNAL_QUALITY","SIGNAL_QUALITY_HOURLY","SIGNAL_QUALITY_DAILY","INLET_TEMPERATURE","INLET_TEMPERATURE_HOURLY","INLET_TEMPERATURE_DAILY","OUTLET_TEMPERATURE","OUTLET_TEMPERATURE_HOURLY","OUTLET_TEMPERATURE_DAILY","ENERGY_FLOW_RATE","ENERGY_FLOW_RATE_HOURLY","ENERGY_FLOW_RATE_DAILY","ENERGY_HOURLY","ENERGY_DAILY"],"description":"The measurement to plot."},"ChartType":{"type":"string","enum":["LINE","BAR","PIE","AREA","STAT","JSON","TABLE","BUTTON","TOGGLE"],"description":"How to draw it."},"EcovoltTimeRange":{"type":"string","enum":["LAST_1H","LAST_6H","LAST_24H","LAST_7D","LAST_30D","TODAY","THIS_MONTH"],"description":"How far back to query. Omit to let the report’s own period choose."},"ReportPanelSpecDto":{"type":"object","properties":{"scope":{"description":"What to query: an entity type and the ids at that level.","allOf":[{"$ref":"#/components/schemas/EcovoltPanelScopeDto"}]},"metric":{"description":"The measurement to plot.","allOf":[{"$ref":"#/components/schemas/EcovoltMetric"}]},"chartType":{"description":"How to draw it.","allOf":[{"$ref":"#/components/schemas/ChartType"}]},"timeRange":{"description":"How far back to query. Omit to let the report’s own period choose.","allOf":[{"$ref":"#/components/schemas/EcovoltTimeRange"}]}},"required":["scope","metric","chartType"]},"ReportChartType":{"type":"string","enum":["LINE","AREA","BAR","DONUT","HEATMAP"],"description":"How to draw a PANEL or MANUAL section."},"ReportChartPointDto":{"type":"object","properties":{"x":{"type":"string","description":"Category or timestamp for this point","example":"2026-08-11"},"y":{"type":"number","example":363.8}},"required":["x","y"]},"ReportChartSeriesDto":{"type":"object","properties":{"label":{"type":"string","example":"Lab 2 chiller"},"unit":{"type":"string","example":"kWh"},"points":{"type":"array","items":{"$ref":"#/components/schemas/ReportChartPointDto"}}},"required":["label","points"]},"ReportOutlineEntryDto":{"type":"object","properties":{"id":{"type":"string","description":"A catalog section id (see GET report-sections), or your own id for a section you are adding.","example":"executive-summary"},"title":{"type":"string","description":"Heading to use. Required for a section you are adding; overrides the catalog title otherwise.","example":"Chiller performance"},"kind":{"description":"Required for a section you are adding. NARRATIVE and LIST are written from `instruction`; PANEL draws a dashboard panel; MANUAL plots figures you supply.","allOf":[{"$ref":"#/components/schemas/ReportBlockKind"}]},"instruction":{"type":"string","description":"What to write here, in plain English. Required for a section you are adding — without it the section is an empty heading.","example":"Compare the two chillers and say which is carrying the load."},"panel":{"type":"string","description":"An existing dashboard panel to draw. A PANEL section gives either this or panelSpec, never both."},"panelSpec":{"description":"A chart built here, belonging to this report alone rather than the dashboard. A PANEL section gives either this or panel, never both.","allOf":[{"$ref":"#/components/schemas/ReportPanelSpecDto"}]},"chartType":{"description":"How to draw a PANEL or MANUAL section.","allOf":[{"$ref":"#/components/schemas/ReportChartType"}]},"series":{"description":"Figures to plot. Required for a MANUAL section — numbers you type in yourself, such as a target line or a utility-bill total we do not meter. They are part of the report, so every generation draws the same chart.","type":"array","items":{"$ref":"#/components/schemas/ReportChartSeriesDto"}},"unit":{"type":"string","description":"Unit for a MANUAL section’s figures."}},"required":["id"]},"ReportRunStatus":{"type":"string","enum":["GENERATING","COMPLETE","FAILED"]},"EnergyReportDailyPointDto":{"type":"object","properties":{"date":{"type":"string","description":"Calendar day in the system timezone (YYYY-MM-DD)","example":"2026-07-28"},"kWh":{"type":"number","description":"Energy consumed that day","example":271.4}},"required":["date","kWh"]},"EnergyReportCategorySliceDto":{"type":"object","properties":{"kWh":{"type":"number","example":302.6},"percentOfTotal":{"type":"number","description":"Share of the window total, 0-100","example":37.2},"deviceCount":{"type":"number","description":"Devices in this group","example":8},"category":{"type":"string","description":"Plug category, MCB measurement type, or device type — whichever the devices in this group are classified by","example":"PANTRY_APPLIANCES"}},"required":["kWh","percentOfTotal","deviceCount","category"]},"EnergyReportDeviceRowDto":{"type":"object","properties":{"deviceId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","example":"HydroTap Water Cooler"},"deviceType":{"type":"string","example":"PLUG"},"category":{"type":"string","description":"Classification the device is grouped by, when it has one","example":"WATERCOOLER"},"path":{"type":"string","description":"Frontend route for the \"view\" action","example":"/system/plugs/64b7f1e6f9c9d4e5b8a9f7c3"},"totalKWh":{"type":"number","description":"Energy over the window","example":184.6},"avgDailyKWh":{"type":"number","example":2.78},"percentOfTotal":{"type":"number","description":"Share of the window total, 0-100","example":19},"dailySeries":{"description":"Per-day series for the row sparkline. Omitted when the device reported on fewer than two days.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportDailyPointDto"}},"trendPercent":{"type":"number","description":"Percent change between the first and second half of the window. Omitted when either half has no data, so a flat \"0%\" is never invented.","example":-12.4}},"required":["deviceId","name","deviceType","path","totalKWh","avgDailyKWh","percentOfTotal"]},"EnergyReportHourPointDto":{"type":"object","properties":{"hour":{"type":"number","description":"Hour of day, 0-23","example":14},"avgKWh":{"type":"number","description":"Mean energy consumed in this hour across the days observed","example":18.2}},"required":["hour","avgKWh"]},"EnergyReportWeekSplitDto":{"type":"object","properties":{"weekdayAvgKWh":{"type":"number","example":31.2},"weekendAvgKWh":{"type":"number","example":18.7},"weekendReductionPercent":{"type":"number","description":"How much lower the weekend average is, in percent. Negative when weekends consume more.","example":40.1}},"required":["weekdayAvgKWh","weekendAvgKWh","weekendReductionPercent"]},"EnergyReportWeekdayPointDto":{"type":"object","properties":{"weekday":{"type":"number","description":"0 = Sunday through 6 = Saturday","example":2},"avgKWh":{"type":"number","example":31.7},"sampleDays":{"type":"number","description":"Days of this weekday observed","example":4}},"required":["weekday","avgKWh","sampleDays"]},"EnergyReportDeviceTypeSliceDto":{"type":"object","properties":{"kWh":{"type":"number","example":302.6},"percentOfTotal":{"type":"number","description":"Share of the window total, 0-100","example":37.2},"deviceCount":{"type":"number","description":"Devices in this group","example":8},"deviceType":{"example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]}},"required":["kWh","percentOfTotal","deviceCount","deviceType"]},"EnergyReportHeatmapCellDto":{"type":"object","properties":{"weekday":{"type":"number","description":"Day of week, 0 = Sunday through 6 = Saturday","example":2},"hour":{"type":"number","description":"Hour of day, 0-23","example":14},"avgKWh":{"type":"number","description":"Mean energy in this weekday/hour slot across the window","example":21.4},"sampleDays":{"type":"number","description":"How many days contributed to this cell — low counts are weak evidence","example":4}},"required":["weekday","hour","avgKWh","sampleDays"]},"EnergyReportDeviceTypeHeatmapDto":{"type":"object","properties":{"deviceType":{"example":"MCB","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"totalKWh":{"type":"number","description":"Energy this type accounts for across the window","example":512.3},"cells":{"type":"array","items":{"$ref":"#/components/schemas/EnergyReportHeatmapCellDto"}}},"required":["deviceType","totalKWh","cells"]},"EnergyReportAnomalyDayDto":{"type":"object","properties":{"date":{"type":"string","example":"2026-08-11"},"kWh":{"type":"number","example":363.8},"sigma":{"type":"number","description":"Distance from the window mean in standard deviations. Negative for unusually low days.","example":2.7}},"required":["date","kWh","sigma"]},"EnergyReportDeviceMoverDto":{"type":"object","properties":{"deviceId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","example":"Nescafe Coffee Machine"},"previousAvgDailyKWh":{"type":"number","description":"Daily average in the preceding window","example":1.62},"currentAvgDailyKWh":{"type":"number","description":"Daily average in this window","example":2.6},"changePercent":{"type":"number","description":"Change in daily average, percent. Negative means it improved.","example":60.5}},"required":["deviceId","name","previousAvgDailyKWh","currentAvgDailyKWh","changePercent"]},"EnergyReportComparisonDto":{"type":"object","properties":{"previousTotalKWh":{"type":"number","description":"Total for the preceding window","example":901.2},"previousDaysObserved":{"type":"number","description":"Days of data in the preceding window","example":29},"avgDailyChangePercent":{"type":"number","description":"Change in average daily consumption, in percent. Compared per-day rather than per-total so an unequal number of observed days does not read as a change in behaviour.","example":-9.8}},"required":["previousTotalKWh","previousDaysObserved","avgDailyChangePercent"]},"EnergyReportCostDto":{"type":"object","properties":{"totalCost":{"type":"number","description":"Cost of the window in dollars","example":242.75},"avgDailyCost":{"type":"number","example":8.09},"costPerKWh":{"type":"number","description":"Rate applied, in cents per kWh","example":29.88},"rateSource":{"description":"Scope level that supplied the rate. Null when the national default was used.","example":"ZONE","allOf":[{"$ref":"#/components/schemas/RateSource"}]}},"required":["totalCost","avgDailyCost","costPerKWh"]},"EnergyReportMetricsDto":{"type":"object","properties":{"periodStart":{"format":"date-time","type":"string","example":"2026-07-25T16:00:00.000Z"},"periodEnd":{"format":"date-time","type":"string","example":"2026-08-24T16:00:00.000Z"},"daysObserved":{"type":"number","description":"Distinct days that actually carried data. Falls short of the window length when devices were offline — read every average against this, not against the window.","example":30},"deviceCount":{"type":"number","description":"Devices in scope","example":32},"reportingDeviceCount":{"type":"number","description":"Devices that reported at least one day. A gap against deviceCount is itself a finding.","example":30},"totalKWh":{"type":"number","example":812.4},"avgDailyKWh":{"type":"number","description":"totalKWh over daysObserved","example":27.08},"peakDayDate":{"type":"string","description":"Heaviest day in the window (YYYY-MM-DD). Omitted when nothing reported.","example":"2026-08-11"},"peakDayKWh":{"type":"number","example":363.8},"dailySeries":{"description":"Daily totals for the trend chart. Omitted when nothing reported.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportDailyPointDto"}},"categoryBreakdown":{"description":"Donut wedges, largest first. Omitted when every device falls in one category — a single-wedge donut says nothing.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportCategorySliceDto"}},"devices":{"description":"High-load device table, heaviest first. Omitted when no device reported.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportDeviceRowDto"}},"topThreeSharePercent":{"type":"number","description":"Share of total drawn by the three heaviest devices, 0-100. Omitted below five reporting devices, where \"top 3\" is most of the estate anyway.","example":45.3},"devicesWithSchedule":{"type":"number","description":"Devices with a schedule configured. Omitted when none has one — nothing to report compliance against.","example":12},"hourlyProfile":{"description":"Mean consumption by hour of day. Omitted when no hourly records exist for the window.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportHourPointDto"}},"peakHour":{"type":"number","description":"Busiest hour of day, 0-23. Present whenever hourlyProfile is.","example":14},"weekSplit":{"$ref":"#/components/schemas/EnergyReportWeekSplitDto"},"weekdayProfile":{"description":"Average consumption per day of the week. Omitted when the window covered only one weekday.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportWeekdayPointDto"}},"deviceTypeBreakdown":{"description":"Energy split by hardware type (PLUG, MCB, ...), largest first. Distinct from categoryBreakdown, which groups by what a device is for. Omitted when the entity has only one device type.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportDeviceTypeSliceDto"}},"measurementOverlap":{"type":"boolean","description":"True when both PLUG and MCB devices are in scope. An MCB meters a whole circuit and the plugs on that circuit meter the same electricity again, so totalKWh double counts by an unknown amount and every percentOfTotal is diluted. Present only when the overlap exists — surface a caveat and prefer the per-type figures over the combined total.","example":true},"heatmapByDeviceType":{"description":"Per-device-type weekday x hour grids, for reading plug load against circuit load. Omitted unless at least two device types each have a week of hourly data.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportDeviceTypeHeatmapDto"}},"heatmap":{"description":"Weekday x hour-of-day grid. Sparse — only observed cells are present, and an absent cell means never measured, not zero. Omitted below a week of data, where the grid would be mostly holes.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportHeatmapCellDto"}},"baseloadKWh":{"type":"number","description":"Always-on load: each fully-reported day's quietest hour held for 24 hours, summed. This is the energy that never switches off, and the ceiling on what scheduling alone can save.","example":214.8},"baseloadSharePercent":{"type":"number","description":"baseloadKWh as a share of the window total, 0-100","example":26.4},"offHoursKWh":{"type":"number","description":"Energy consumed outside business hours (see BUSINESS_HOUR_START/END) plus all weekend energy. The headline number for scheduling work.","example":388.1},"offHoursSharePercent":{"type":"number","description":"offHoursKWh as a share of the window total, 0-100","example":47.8},"anomalyDays":{"description":"Days more than two standard deviations from the window mean, strongest first, for annotating the trend line. Omitted when the window is too short for a standard deviation to mean anything, or when no day qualifies.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportAnomalyDayDto"}},"movers":{"description":"Devices whose daily average moved most against the preceding window, largest absolute change first. Present only alongside `comparison`.","type":"array","items":{"$ref":"#/components/schemas/EnergyReportDeviceMoverDto"}},"comparison":{"$ref":"#/components/schemas/EnergyReportComparisonDto"},"cost":{"$ref":"#/components/schemas/EnergyReportCostDto"},"carbonKg":{"type":"number","description":"Scope 2 emissions for the window, in kg CO2e, at the grid factor in CARBON_KG_PER_KWH. Omitted when nothing was consumed.","example":341.2}},"required":["periodStart","periodEnd","daysObserved","deviceCount","reportingDeviceCount","totalKWh","avgDailyKWh"]},"ReportBlockAuthor":{"type":"string","enum":["AI","USER"]},"ReportBlockCitationDto":{"type":"object","properties":{"text":{"type":"string","description":"The figure exactly as it appears in the prose","example":"340.2 kWh"},"metricKey":{"type":"string","description":"Field of the run’s metrics the figure came from","example":"totalKWh"},"blockId":{"type":"string","description":"Block that renders that figure, when one is on the report. The client scrolls there on click.","example":"kpi-total-kwh"}},"required":["text","metricKey"]},"EnergyReportBlockDto":{"type":"object","properties":{"id":{"type":"string","example":"executive-summary"},"kind":{"allOf":[{"$ref":"#/components/schemas/ReportBlockKind"}]},"authoredBy":{"allOf":[{"$ref":"#/components/schemas/ReportBlockAuthor"}]},"title":{"type":"string","example":"Executive summary"},"body":{"type":"string","description":"Markdown body. NARRATIVE and NOTE blocks."},"items":{"description":"Bullet lines. LIST blocks.","type":"array","items":{"type":"string"}},"chartType":{"allOf":[{"$ref":"#/components/schemas/ReportChartType"}]},"metricKey":{"type":"string","description":"Field of the run's metrics this block renders","example":"dailySeries"},"series":{"description":"The block’s own data: a PANEL block’s resolved series, or the numbers a reader typed into a MANUAL block. Frozen at generation and never re-queried.","type":"array","items":{"$ref":"#/components/schemas/ReportChartSeriesDto"}},"panel":{"type":"string","description":"Dashboard panel this block was resolved from."},"unit":{"type":"string","description":"Unit the series is measured in."},"periodStart":{"format":"date-time","type":"string","description":"First instant this block’s series covers. Stored, never re-derived — a chart drawn over June still says June when opened in September."},"periodEnd":{"format":"date-time","type":"string","description":"Last instant this block’s series covers."},"unavailableReason":{"type":"string","description":"Why this block has no series, when it was asked for but could not be drawn."},"x":{"type":"number","description":"Column offset","example":6},"y":{"type":"number","description":"Row offset","example":14},"w":{"type":"number","description":"Width in columns","example":6},"h":{"type":"number","description":"Height in grid rows","example":17},"hidden":{"type":"boolean","description":"Removed by the reader, kept so it can come back","example":false},"citations":{"description":"Figures this block’s prose quotes, each traced to the metric it came from. Derived server-side — sent back unchanged.","type":"array","items":{"$ref":"#/components/schemas/ReportBlockCitationDto"}}},"required":["id","kind","authoredBy","x","y","w","h","hidden"]},"ReportSuggestionKind":{"type":"string","enum":["ENERGY_ACTION","REPORT_IMPROVEMENT"],"description":"Whether this is an action on the site or an edit to the report"},"ReportSuggestionPriority":{"type":"string","enum":["HIGH","MEDIUM","LOW"]},"ReportSuggestionStatus":{"type":"string","enum":["OPEN","ACCEPTED","DISMISSED"]},"ReportSuggestionDto":{"type":"object","properties":{"id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"kind":{"description":"Whether this is an action on the site or an edit to the report","example":"ENERGY_ACTION","allOf":[{"$ref":"#/components/schemas/ReportSuggestionKind"}]},"title":{"type":"string","description":"One-line statement of what to do","example":"Schedule the Level 3 water coolers off between 19:00 and 07:00"},"detail":{"type":"string","description":"What to do and why, citing the figures it is drawn from","example":"Those four plugs draw a combined 0.9 kW overnight with no occupancy, which is 14% of the period total."},"priority":{"example":"HIGH","allOf":[{"$ref":"#/components/schemas/ReportSuggestionPriority"}]},"status":{"example":"OPEN","allOf":[{"$ref":"#/components/schemas/ReportSuggestionStatus"}]},"blockId":{"type":"string","description":"Block this is pinned to. Absent when it proposes something the report does not cover yet.","example":"usage-trends"},"anchorText":{"type":"string","description":"Excerpt of the block this refers to, so the comment can anchor to the sentence rather than the whole section.","example":"especially on Tuesdays and Thursdays"},"appliedBlockId":{"type":"string","description":"Block the acceptance wrote to. ACCEPTED suggestions only."},"resolvedAt":{"format":"date-time","type":"string","description":"When it was accepted or dismissed"}},"required":["id","kind","title","detail","priority","status"]},"ReportRunProvenanceDto":{"type":"object","properties":{"model":{"type":"string","description":"Model id that wrote the narrative","example":"gpt-5.4-2026-03-05"},"promptVersion":{"type":"string","description":"Version of the report prompt. Bumped whenever the prompt changes in a way that changes output.","example":"2026-09-15"},"writingStyleHash":{"type":"string","description":"Digest of the author’s Writing Style at generation time. Not the style itself — enough to tell that it changed, without copying a personal document onto every run.","example":"9f2b1c4e"},"tokensUsed":{"type":"number","description":"Tokens spent on the narrative.","example":18420},"costCents":{"type":"number","description":"Estimated cost of the narrative in cents, at the rates in OPENAI_MODEL_PRICING.","example":12.4}},"required":["model","promptVersion"]},"ReportFidelityDto":{"type":"object","properties":{"figuresChecked":{"type":"number","description":"Figures quoted in the narrative","example":34},"unsupported":{"description":"Figures the narrative states that do not appear in the metrics, with the sentence each came from. Empty is the expected result.","type":"array","items":{"type":"string"}},"passed":{"type":"boolean","description":"True when every figure in the narrative traces to a computed one.","example":true}},"required":["figuresChecked","unsupported","passed"]},"EnergyReportRunDto":{"type":"object","properties":{"id":{"type":"string","description":"Run id"},"reportId":{"type":"string","description":"Report this run generated"},"status":{"allOf":[{"$ref":"#/components/schemas/ReportRunStatus"}]},"deviceIds":{"description":"Devices the selectors resolved to at generation time — what the figures were computed over, whatever the rooms hold now.","type":"array","items":{"type":"string"}},"periodStart":{"format":"date-time","type":"string"},"periodEnd":{"format":"date-time","type":"string"},"metrics":{"$ref":"#/components/schemas/EnergyReportMetricsDto"},"blocks":{"type":"array","items":{"$ref":"#/components/schemas/EnergyReportBlockDto"}},"suggestions":{"description":"Proposals made against this run, each open until accepted or dismissed.","type":"array","items":{"$ref":"#/components/schemas/ReportSuggestionDto"}},"provenance":{"description":"What produced this run, beyond the data.","allOf":[{"$ref":"#/components/schemas/ReportRunProvenanceDto"}]},"fidelity":{"description":"Whether every figure the narrative states traces back to a computed one.","allOf":[{"$ref":"#/components/schemas/ReportFidelityDto"}]},"error":{"type":"string","description":"Why the run failed. FAILED runs only."},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","reportId","status","deviceIds","periodStart","periodEnd","blocks","suggestions","createdAt","updatedAt"]},"EnergyReportDto":{"type":"object","properties":{"id":{"type":"string","description":"Report id"},"systemId":{"type":"string","description":"System the report belongs to"},"name":{"type":"string","description":"Name the reader gave it"},"selectors":{"description":"What the report covers, with each entity’s current name","type":"array","items":{"$ref":"#/components/schemas/ReportSelectorLabelDto"}},"window":{"$ref":"#/components/schemas/ReportWindowDto"},"brief":{"type":"string","description":"What this report is for, in the reader’s own words."},"language":{"type":"string","description":"Language the report is written in, as a BCP 47 tag.","example":"zh-Hans"},"outline":{"description":"The table of contents, in reading order. Empty means the default set of sections.","type":"array","items":{"$ref":"#/components/schemas/ReportOutlineEntryDto"}},"run":{"description":"Latest run, or the one asked for. Absent until the report is generated.","allOf":[{"$ref":"#/components/schemas/EnergyReportRunDto"}]},"updatedAt":{"format":"date-time","type":"string"},"createdAt":{"format":"date-time","type":"string"}},"required":["id","systemId","name","selectors","window","outline","updatedAt","createdAt"]},"UpdateEnergyReportDto":{"type":"object","properties":{"name":{"type":"string","description":"New name","example":"Q3 final"},"selectors":{"description":"Replaces what the report covers. Existing runs keep the devices they were computed over.","type":"array","items":{"$ref":"#/components/schemas/ReportSelectorDto"}},"window":{"description":"Replaces the period the report covers.","allOf":[{"$ref":"#/components/schemas/ReportWindowDto"}]},"brief":{"type":"string","description":"What this report is for, in your own words. Steers both the proposed outline and the writing.","example":"Quarterly review for the building owners, who are deciding whether to fund the chiller replacement."},"language":{"type":"string","description":"Language to write the report in, as a BCP 47 tag (e.g. \"en\", \"zh-Hans\", \"ms\"). Defaults to English.","example":"zh-Hans"}}},"QuestionAnswerDto":{"type":"object","properties":{"question":{"type":"string","description":"The question as it was put to them","example":"When the news is bad, what do you do?"},"answer":{"type":"string","description":"What they answered, in their own words","example":"Say it plainly in the first sentence. No cushioning."}},"required":["question","answer"]},"DraftReportOutlineDto":{"type":"object","properties":{"brief":{"type":"string","description":"What the report is for, in their own words. Saved to the report so later generations keep the same intent.","example":"Quarterly review for the building owners, who are deciding whether to fund the chiller replacement."},"answers":{"description":"Answers to whatever else the client asked during setup.","type":"array","items":{"$ref":"#/components/schemas/QuestionAnswerDto"}}}},"ReportOutlineDto":{"type":"object","properties":{"outline":{"type":"array","items":{"$ref":"#/components/schemas/ReportOutlineEntryDto"}}},"required":["outline"]},"PreviewReportChartDto":{"type":"object","properties":{"title":{"type":"string","description":"Heading the section will carry. Used only for error messages here.","example":"Zone energy"},"panelSpec":{"$ref":"#/components/schemas/ReportPanelSpecDto"}},"required":["panelSpec"]},"ReportChartPreviewDto":{"type":"object","properties":{"series":{"description":"Absent when the chart could not be drawn — see unavailableReason.","type":"array","items":{"$ref":"#/components/schemas/ReportChartSeriesDto"}},"unit":{"type":"string","example":"kWh"},"periodStart":{"format":"date-time","type":"string","description":"The period the points actually cover."},"periodEnd":{"format":"date-time","type":"string"},"unavailableReason":{"type":"string","description":"Why this chart cannot be drawn over the report’s period. Present instead of series — show it in the builder rather than letting someone add a section that will be empty."}}},"SaveReportOutlineDto":{"type":"object","properties":{"outline":{"description":"The table of contents in reading order. An empty array restores the default set of sections.","type":"array","items":{"$ref":"#/components/schemas/ReportOutlineEntryDto"}}},"required":["outline"]},"SaveReportBlockDto":{"type":"object","properties":{"id":{"type":"string","example":"executive-summary"},"kind":{"allOf":[{"$ref":"#/components/schemas/ReportBlockKind"}]},"title":{"type":"string","example":"Executive summary"},"body":{"type":"string","description":"Markdown body. NARRATIVE and NOTE blocks."},"items":{"description":"Bullet lines. LIST blocks.","type":"array","items":{"type":"string"}},"chartType":{"allOf":[{"$ref":"#/components/schemas/ReportChartType"}]},"metricKey":{"type":"string","description":"Field of the run's metrics this block renders","example":"dailySeries"},"x":{"type":"number","description":"Column offset","example":6},"y":{"type":"number","description":"Row offset","example":14},"w":{"type":"number","description":"Width in columns","example":6},"h":{"type":"number","description":"Height in grid rows","example":17},"hidden":{"type":"boolean","description":"Removed by the reader, kept so it can come back","example":false},"series":{"description":"Points for a chart the reader added. Ignored on a block the run already carries.","type":"array","items":{"$ref":"#/components/schemas/ReportChartSeriesDto"}},"panel":{"type":"string","description":"Dashboard panel a reader-added block is drawn from."},"unit":{"type":"string","description":"Unit a reader-added series is measured in."}},"required":["id","kind","x","y","w","h","hidden"]},"SaveReportBlocksDto":{"type":"object","properties":{"blocks":{"type":"array","items":{"$ref":"#/components/schemas/SaveReportBlockDto"}}},"required":["blocks"]},"ReportRefineAction":{"type":"string","enum":["IMPROVE","ADD"],"description":"Whether the highlighted text should be improved in place, or new content added around it"},"RefineReportBlockDto":{"type":"object","properties":{"blockId":{"type":"string","description":"Id of the block to rewrite, as it appears on the run being read.","example":"usage-trends"},"action":{"description":"Whether the highlighted text should be improved in place, or new content added around it","example":"IMPROVE","allOf":[{"$ref":"#/components/schemas/ReportRefineAction"}]},"blockContent":{"type":"string","description":"Current full text of the block, used as the rewrite base","example":"Energy usage peaks during weekdays, especially on Tuesdays and Thursdays."},"selectedText":{"type":"string","description":"Excerpt the user highlighted. When omitted the whole block is treated as the target.","example":"especially on Tuesdays and Thursdays"},"instruction":{"type":"string","description":"What the user wants changed or added, in their own words. Omit to let the model decide how to improve the excerpt.","example":"Explain why those two days are higher and quantify the gap."}},"required":["blockId","action","blockContent"]},"RefineReportBlockResponseDto":{"type":"object","properties":{"blockId":{"type":"string","description":"Block that was rewritten","example":"usage-trends"},"content":{"type":"string","description":"Full replacement text for the block, with the requested change applied","example":"Energy usage peaks during weekdays, with Tuesday and Thursday running 18% above the weekly average because both days carry full lab occupancy."}},"required":["blockId","content"]},"AnalyseReportDto":{"type":"object","properties":{"instruction":{"type":"string","description":"What the reader wants the analysis to focus on, in their own words. Omit for a general pass over the whole run.","example":"We are trying to cut weekend baseload. Focus on anything that runs outside office hours."}}},"AnalyseReportResponseDto":{"type":"object","properties":{"suggestions":{"description":"Every suggestion on the run — the ones just written plus any already settled — highest priority first.","type":"array","items":{"$ref":"#/components/schemas/ReportSuggestionDto"}}},"required":["suggestions"]},"ReportChatRole":{"type":"string","enum":["user","assistant"],"description":"Who sent the message"},"ReportChatMessageDto":{"type":"object","properties":{"role":{"description":"Who sent the message","example":"user","allOf":[{"$ref":"#/components/schemas/ReportChatRole"}]},"content":{"type":"string","description":"Message text","example":"Why is Tuesday so much higher than the other weekdays?"}},"required":["role","content"]},"ReportChatDto":{"type":"object","properties":{"messages":{"description":"Conversation so far, oldest first, ending with the user turn to answer. Stateless — resend the history each turn.","type":"array","items":{"$ref":"#/components/schemas/ReportChatMessageDto"}}},"required":["messages"]},"ReportMetricChangeDto":{"type":"object","properties":{"key":{"type":"string","example":"totalKWh"},"label":{"type":"string","example":"Total consumption"},"unit":{"type":"string","example":"kWh"},"before":{"type":"number","example":812.4},"after":{"type":"number","example":744.1},"changePercent":{"type":"number","description":"Absent when the earlier figure was zero, so a share is meaningless.","example":-8.4}},"required":["key","label","unit","before","after"]},"ReportDiffDto":{"type":"object","properties":{"fromRunId":{"type":"string"},"toRunId":{"type":"string"},"fromPeriodStart":{"format":"date-time","type":"string"},"toPeriodStart":{"format":"date-time","type":"string"},"metrics":{"description":"Headline figures that moved by more than a couple of percent. A figure absent from either run is left out — absent means unknown, not zero.","type":"array","items":{"$ref":"#/components/schemas/ReportMetricChangeDto"}},"sectionsAdded":{"description":"Sections the newer run has and the older did not","type":"array","items":{"type":"string"}},"sectionsRemoved":{"description":"Sections the older run had and the newer does not","type":"array","items":{"type":"string"}},"devicesAdded":{"description":"Devices that entered scope","type":"array","items":{"type":"string"}},"devicesRemoved":{"description":"Devices that left scope","type":"array","items":{"type":"string"}}},"required":["fromRunId","toRunId","fromPeriodStart","toPeriodStart","metrics","sectionsAdded","sectionsRemoved","devicesAdded","devicesRemoved"]},"EnergyUsageRecommendationReportDto":{"type":"object","properties":{"monday":{"description":"Array of AI-recommended schedule entries for Monday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"tuesday":{"description":"Array of AI-recommended schedule entries for Tuesday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"wednesday":{"description":"Array of AI-recommended schedule entries for Wednesday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"thursday":{"description":"Array of AI-recommended schedule entries for Thursday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"friday":{"description":"Array of AI-recommended schedule entries for Friday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"saturday":{"description":"Array of AI-recommended schedule entries for Saturday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"sunday":{"description":"Array of AI-recommended schedule entries for Sunday based on usage patterns","type":"array","items":{"$ref":"#/components/schemas/DailyScheduleEntryDto"}},"summary":{"type":"string","description":"AI-generated summary explaining the recommended schedule and detected behavioral patterns","example":"Work-from-home behavior detected on weekdays with consistent 9 AM-5 PM active periods. Weekend usage minimal."},"predictedIdleEnergyUsage":{"type":"number","description":"Predicted average power consumption during idle periods in watts based on historical data","example":12.3},"totalNumberOfIdleHours":{"type":"number","description":"Total count of hours identified as idle or low-usage periods across the analysis timeframe","example":84},"averageUsagePerDay":{"type":"number","description":"Average daily power consumption in watts calculated over the analyzed time period","example":42.7},"flags":{"description":"Array of flags highlighting anomalies, data gaps, or notable behavioral patterns detected by AI","example":["Insufficient data for Tuesday","Constant usage detected on Saturday","Casual/WFH usage pattern on Friday"],"type":"array","items":{"type":"string"}},"ecoguardRecommendation":{"description":"EcoGuard suitability and suggested configuration for this device","allOf":[{"$ref":"#/components/schemas/EcoguardRecommendationDto"}]}}},"BulkPlugRecommendationsRequestDto":{"type":"object","properties":{"plugIds":{"description":"Array of plug IDs to generate AI recommendations for. Minimum 1, maximum 200 plugs per request. For larger batches, split into multiple requests.","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012","507f1f77bcf86cd799439013"],"minItems":1,"maxItems":200,"type":"array","items":{"type":"string"}}},"required":["plugIds"]},"AscentDeviceOutcome":{"type":"string","enum":["suggested","already_optimal","unchanged","deferred_to_override","constant_usage","no_schedule","failed"],"description":"What the run concluded for this device"},"AscentRunDevice":{"type":"object","properties":{"device":{"type":"string","description":"Device analysed"},"deviceName":{"type":"string","description":"Device name at run time","example":"Pantry AC"},"outcome":{"description":"What the run concluded for this device","allOf":[{"$ref":"#/components/schemas/AscentDeviceOutcome"}]},"suggestion":{"type":"string","description":"The suggestion this device produced, when the outcome is SUGGESTED."},"deltaMinutes":{"type":"number","description":"Signed ON-minutes the proposal would change. Positive = less ON time. Present whenever a schedule was derived, including outcomes that wrote no suggestion — that is how a near-miss stays visible.","example":45},"confidenceScore":{"type":"number","description":"Pattern confidence 0-1","example":0.82},"reasons":{"description":"Short operator-facing reasons, same form as a suggestion","type":"array","items":{"type":"string"}}},"required":["device","deviceName","outcome"]},"AscentRunResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Run ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"startedAt":{"format":"date-time","type":"string","description":"When the run started","example":"2026-06-01T20:00:00.000Z"},"finishedAt":{"format":"date-time","type":"string","description":"When the run finished","example":"2026-06-01T20:04:11.000Z"},"devicesWalked":{"type":"number","description":"Devices walked","example":120},"suggested":{"type":"number","description":"Devices that produced a suggestion","example":12},"alreadyOptimal":{"type":"number","description":"Devices whose schedule already matched demand","example":96},"unchanged":{"type":"number","description":"Devices whose analysis matched the previous proposal","example":4},"deferredToOverride":{"type":"number","description":"Devices skipped because a manual override was mid-review","example":2},"constantUsage":{"type":"number","description":"Devices skipped as constant load","example":5},"noSchedule":{"type":"number","description":"Devices whose data supported no schedule","example":1},"failed":{"type":"number","description":"Devices whose analysis threw","example":0},"devices":{"description":"Every device row matching the filters. The summary counts above always cover the whole run, filters or not.","type":"array","items":{"$ref":"#/components/schemas/AscentRunDevice"}}},"required":["_id","startedAt","finishedAt","devicesWalked","suggested","alreadyOptimal","unchanged","deferredToOverride","constantUsage","noSchedule","failed","devices"]},"AscentRunDateDto":{"type":"object","properties":{"runId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"startedAt":{"format":"date-time","type":"string"},"devicesWalked":{"type":"number","example":203}},"required":["runId","startedAt","devicesWalked"]},"LatestAscentRunResponseDto":{"type":"object","properties":{"run":{"description":"The most recent run, or null when the weekly cron has not covered this System yet. Null is not an error — it distinguishes \"never ran\" from \"ran and found nothing\".","nullable":true,"type":"object","allOf":[{"$ref":"#/components/schemas/AscentRunResponseDto"}]},"availableRuns":{"description":"Every run this System has, newest first — the date picker. Fetch one by id rather than loading them all.","type":"array","items":{"$ref":"#/components/schemas/AscentRunDateDto"}}},"required":["availableRuns"]},"AscentRunSummaryResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Run ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"startedAt":{"format":"date-time","type":"string","description":"When the run started","example":"2026-06-01T20:00:00.000Z"},"finishedAt":{"format":"date-time","type":"string","description":"When the run finished","example":"2026-06-01T20:04:11.000Z"},"devicesWalked":{"type":"number","description":"Devices walked","example":120},"suggested":{"type":"number","description":"Devices that produced a suggestion","example":12},"alreadyOptimal":{"type":"number","description":"Devices whose schedule already matched demand","example":96},"unchanged":{"type":"number","description":"Devices whose analysis matched the previous proposal","example":4},"deferredToOverride":{"type":"number","description":"Devices skipped because a manual override was mid-review","example":2},"constantUsage":{"type":"number","description":"Devices skipped as constant load","example":5},"noSchedule":{"type":"number","description":"Devices whose data supported no schedule","example":1},"failed":{"type":"number","description":"Devices whose analysis threw","example":0}},"required":["_id","startedAt","finishedAt","devicesWalked","suggested","alreadyOptimal","unchanged","deferredToOverride","constantUsage","noSchedule","failed"]},"GetAscentRunsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Runs in the requested window, newest first. Counts only — fetch a single run for its per-device rows.","type":"array","items":{"$ref":"#/components/schemas/AscentRunSummaryResponseDto"}}},"required":["total","page","size","totalPages","data"]},"WritingStyleDto":{"type":"object","properties":{"writingStyle":{"type":"string","description":"Freeform markdown describing how this account writes. Null until they write one.","example":"Blunt. Short sentences. Never say \"sustainable\" — we cannot back it."},"prompted":{"type":"boolean","description":"True once the account has been offered the first-run step, whether they wrote a profile or declined. The client shows the setup flow only while this is false, so declining sticks.","example":false}},"required":["prompted"]},"UpdateWritingStyleDto":{"type":"object","properties":{"writingStyle":{"type":"string","description":"Freeform markdown, at most 4000 characters. No required sections — three sentences is a valid profile. Send an empty string to clear it.","example":"Blunt. Short sentences. Always put the estimated saving next to a recommendation."}},"required":["writingStyle"]},"DraftWritingStyleDto":{"type":"object","properties":{"samples":{"description":"Things the account holder has actually written — an email, a memo, a past report. Their own words matter more than the length.","type":"array","items":{"type":"string"}},"answers":{"description":"Answers to whatever the client asked during setup. The questions are not defined here, so the wizard can change without a backend deploy.","type":"array","items":{"$ref":"#/components/schemas/QuestionAnswerDto"}}}},"WritingStyleDraftDto":{"type":"object","properties":{"writingStyle":{"type":"string","description":"Markdown for the reader to edit and then save through PUT /writing-style/me."}},"required":["writingStyle"]},"PanelType":{"type":"string","enum":["EXTERNAL","ECOVOLT","BUILTIN"]},"HttpMethod":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE"],"description":"HTTP method for the proxy request"},"TransformType":{"type":"string","enum":["EXTRACT","SELECT","RENAME","FIELDS","FILTER","SORT","LIMIT","REDUCE","DF_RENAME_FIELDS","DF_ORGANIZE_FIELDS","DF_SELECT_FIELDS","DF_FILTER_FIELDS_BY_NAME","DF_FILTER_BY_VALUE","DF_FILTER_BY_QUERY","DF_CALCULATE","DF_CUMULATIVE","DF_REDUCE","DF_GROUP_BY","DF_JOIN","DF_MERGE","DF_CONCATENATE","DF_TIME_SERIES_LONG_TO_WIDE","DF_TIME_SERIES_WIDE_TO_LONG","DF_TIME_SERIES_TO_ROWS","DF_TIME_SERIES_TO_TABLE","DF_PIVOT","DF_ROWS_TO_FIELDS","DF_LABELS_TO_FIELDS","DF_LOOKUP","DF_CONFIG_FROM_QUERY","DF_REGRESSION","DF_EXTRACT_LABELS"],"description":"Type of transformation. Raw JSON types: EXTRACT, SELECT, RENAME, FIELDS, FILTER, SORT, LIMIT, REDUCE. DataFrame types: DF_RENAME_FIELDS, DF_ORGANIZE_FIELDS, DF_SELECT_FIELDS, DF_FILTER_FIELDS_BY_NAME, DF_FILTER_BY_VALUE, DF_FILTER_BY_QUERY, DF_CALCULATE, DF_CUMULATIVE, DF_REDUCE, DF_GROUP_BY, DF_JOIN, DF_MERGE, DF_CONCATENATE, DF_TIME_SERIES_LONG_TO_WIDE, DF_TIME_SERIES_WIDE_TO_LONG, DF_TIME_SERIES_TO_ROWS, DF_TIME_SERIES_TO_TABLE, DF_PIVOT, DF_ROWS_TO_FIELDS, DF_LABELS_TO_FIELDS, DF_LOOKUP, DF_CONFIG_FROM_QUERY, DF_REGRESSION, DF_EXTRACT_LABELS."},"TransformerResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique transformer identifier","example":"64b8c3d2e1f4a2b3c4d5e6f7"},"panel":{"type":"string","description":"Panel ID this transformer belongs to","example":"64b8c3d2e1f4a2b3c4d5e6f8"},"name":{"type":"string","description":"Human-readable name of the transformer","example":"Calculate Profit Margin"},"type":{"description":"Type of transformation. Raw JSON types: EXTRACT, SELECT, RENAME, FIELDS, FILTER, SORT, LIMIT, REDUCE. DataFrame types: DF_RENAME_FIELDS, DF_ORGANIZE_FIELDS, DF_SELECT_FIELDS, DF_FILTER_FIELDS_BY_NAME, DF_FILTER_BY_VALUE, DF_FILTER_BY_QUERY, DF_CALCULATE, DF_CUMULATIVE, DF_REDUCE, DF_GROUP_BY, DF_JOIN, DF_MERGE, DF_CONCATENATE, DF_TIME_SERIES_LONG_TO_WIDE, DF_TIME_SERIES_WIDE_TO_LONG, DF_TIME_SERIES_TO_ROWS, DF_TIME_SERIES_TO_TABLE, DF_PIVOT, DF_ROWS_TO_FIELDS, DF_LABELS_TO_FIELDS, DF_LOOKUP, DF_CONFIG_FROM_QUERY, DF_REGRESSION, DF_EXTRACT_LABELS.","allOf":[{"$ref":"#/components/schemas/TransformType"}]},"config":{"type":"object","description":"Configuration object whose shape depends on the transform type. See CreateTransformerDto config documentation for schemas per type.","example":{"outputField":"profit","operation":"subtract","fieldA":"revenue","fieldB":"cost"}},"enabled":{"type":"boolean","description":"Whether this transformer is enabled in the pipeline. Disabled transformers are skipped.","example":true},"order":{"type":"number","description":"Execution order within the pipeline. Lower numbers execute first.","example":0},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the transformer was created","example":"2026-03-14T10:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the transformer was last updated","example":"2026-03-14T10:30:00.000Z"}},"required":["_id","panel","name","type","enabled","order","createdAt","updatedAt"]},"ProxyCallUsageResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Usage document ID"},"system":{"type":"string","description":"System ID"},"method":{"description":"HTTP method used","allOf":[{"$ref":"#/components/schemas/HttpMethod"}]},"url":{"type":"string","description":"Final URL used for the proxy call"},"statusCode":{"type":"number","description":"HTTP status code from external source","example":200},"responseTimeMs":{"type":"number","description":"Response time in milliseconds","example":150},"authorization":{"type":"boolean","description":"Whether Authorization header was present"},"dataSource":{"type":"string","description":"Related DataSource ID"},"dashboard":{"type":"string","description":"Related Dashboard ID"},"panel":{"type":"string","description":"Related Panel ID"},"body":{"type":"object","description":"Custom body for the proxy request (for POST/PUT)"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","system","method","url","statusCode","authorization","createdAt","updatedAt"]},"ExternalPanelResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Panel ID"},"system":{"type":"string","description":"System ID"},"title":{"type":"string","description":"Title of the panel"},"panelType":{"default":"EXTERNAL","allOf":[{"$ref":"#/components/schemas/PanelType"}]},"description":{"type":"string","description":"Description of the panel"},"colorPalette":{"description":"Color palette for chart series","type":"array","items":{"type":"string"}},"chartType":{"description":"Type of chart","allOf":[{"$ref":"#/components/schemas/ChartType"}]},"dataSource":{"type":"string","description":"Data source ID or populated object"},"endpoint":{"type":"string","description":"API endpoint path"},"httpMethod":{"description":"HTTP method for the proxy request","allOf":[{"$ref":"#/components/schemas/HttpMethod"}]},"headers":{"type":"object","description":"Custom headers for the proxy request"},"xAxisField":{"type":"string","description":"X-Axis field"},"yAxisField":{"type":"string","description":"Y-Axis field"},"transforms":{"description":"Populated transformers linked to this panel","type":"array","items":{"$ref":"#/components/schemas/TransformerResponseDto"}},"options":{"type":"object","description":"Visualization options"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"proxyUsages":{"description":"Recent proxy call usages for this panel","type":"array","items":{"$ref":"#/components/schemas/ProxyCallUsageResponseDto"}},"body":{"type":"object","description":"Custom body for the panel request (for POST/PUT)"}},"required":["_id","system","title","panelType","chartType","dataSource","createdAt","updatedAt"]},"GetExternalPanelsResponseDto":{"type":"object","properties":{"results":{"description":"External panels matching the query","type":"array","items":{"$ref":"#/components/schemas/ExternalPanelResponseDto"}},"total":{"type":"number","description":"Total number of results","example":100}},"required":["results","total"]},"GetProxyUsagesResponseDto":{"type":"object","properties":{"results":{"description":"Proxy call usage rows matching the query","type":"array","items":{"$ref":"#/components/schemas/ProxyCallUsageResponseDto"}},"total":{"type":"number","description":"Total number of results","example":100}},"required":["results","total"]},"DebugSnapshotDto":{"type":"object","properties":{"transformType":{"type":"string","description":"Transform type applied at this step"},"order":{"type":"number","description":"Execution order of this step"},"data":{"type":"object","description":"Data state after this transform step"}},"required":["transformType","order","data"]},"ProxyDebugResponseDto":{"type":"object","properties":{"data":{"type":"object","description":"The final transformed data"},"debugSnapshots":{"description":"Debug snapshots from the compute service for each transform step (only present when debug=true)","type":"array","items":{"$ref":"#/components/schemas/DebugSnapshotDto"}}},"required":["data"]},"PanelThresholdDto":{"type":"object","properties":{"value":{"type":"number","description":"Threshold value","example":100},"color":{"type":"string","description":"Threshold color","example":"#ff0000"},"label":{"type":"string","description":"Threshold label","example":"High"}}},"PanelVisualizationOptionsDto":{"type":"object","properties":{"colors":{"type":"object","description":"Color mappings","example":{"voltage":"#00ff00"}},"yMin":{"type":"number","description":"Y-axis minimum value"},"yMax":{"type":"number","description":"Y-axis maximum value"},"showLegend":{"type":"boolean","description":"Whether to show legend"},"showGrid":{"type":"boolean","description":"Whether to show grid lines"},"thresholds":{"description":"Thresholds for gauge/alert visualizations","type":"array","items":{"$ref":"#/components/schemas/PanelThresholdDto"}},"unit":{"type":"string","description":"Unit label","example":"kWh"},"decimals":{"type":"number","description":"Number of decimal places"}}},"CreateExternalPanelDto":{"type":"object","properties":{"title":{"type":"string","description":"Title of the panel","example":"System Voltage History"},"description":{"type":"string","description":"Description of the panel","example":"Displays voltage trends over the last 24 hours","default":""},"colorPalette":{"description":"Color palette for chart series. The frontend cycles through these colors.","example":["#FF6384","#36A2EB","#FFCE56","#4BC0C0"],"default":[],"type":"array","items":{"type":"string"}},"chartType":{"description":"Type of chart","allOf":[{"$ref":"#/components/schemas/ChartType"}]},"dataSource":{"type":"string","description":"ID of the Data Source"},"endpoint":{"type":"string","description":"API endpoint path appended to the data source base URL","example":"/api/v1/energy?range=1h"},"httpMethod":{"description":"HTTP method for the proxy request","default":"GET","allOf":[{"$ref":"#/components/schemas/HttpMethod"}]},"headers":{"type":"object","description":"Custom headers to include in the proxy request","example":{"X-Custom-Header":"value"}},"xAxisField":{"type":"string","description":"X-Axis field name","example":"time"},"yAxisField":{"type":"string","description":"Y-Axis field name","example":"value"},"options":{"description":"Visualization options","allOf":[{"$ref":"#/components/schemas/PanelVisualizationOptionsDto"}]},"body":{"type":"object","description":"Custom body for the panel request (for POST/PUT)"}},"required":["title","chartType","dataSource"]},"UpdateExternalPanelDto":{"type":"object","properties":{"title":{"type":"string","description":"Title of the panel","example":"System Voltage History"},"description":{"type":"string","description":"Description of the panel","example":"Displays voltage trends over the last 24 hours","default":""},"colorPalette":{"description":"Color palette for chart series. The frontend cycles through these colors.","example":["#FF6384","#36A2EB","#FFCE56","#4BC0C0"],"default":[],"type":"array","items":{"type":"string"}},"chartType":{"description":"Type of chart","allOf":[{"$ref":"#/components/schemas/ChartType"}]},"dataSource":{"type":"string","description":"ID of the Data Source"},"endpoint":{"type":"string","description":"API endpoint path appended to the data source base URL","example":"/api/v1/energy?range=1h"},"httpMethod":{"description":"HTTP method for the proxy request","default":"GET","allOf":[{"$ref":"#/components/schemas/HttpMethod"}]},"headers":{"type":"object","description":"Custom headers to include in the proxy request","example":{"X-Custom-Header":"value"}},"xAxisField":{"type":"string","description":"X-Axis field name","example":"time"},"yAxisField":{"type":"string","description":"Y-Axis field name","example":"value"},"options":{"description":"Visualization options","allOf":[{"$ref":"#/components/schemas/PanelVisualizationOptionsDto"}]},"body":{"type":"object","description":"Custom body for the panel request (for POST/PUT)"}}},"EcovoltPanelResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Panel ID"},"system":{"type":"string","description":"System ID"},"title":{"type":"string","description":"Title of the panel"},"description":{"type":"string","description":"Description of the panel"},"colorPalette":{"description":"Color palette","type":"array","items":{"type":"string"}},"chartType":{"allOf":[{"$ref":"#/components/schemas/ChartType"}]},"panelType":{"default":"ECOVOLT","allOf":[{"$ref":"#/components/schemas/PanelType"}]},"scope":{"$ref":"#/components/schemas/EcovoltPanelScopeDto"},"metric":{"allOf":[{"$ref":"#/components/schemas/EcovoltMetric"}]},"timeRange":{"allOf":[{"$ref":"#/components/schemas/EcovoltTimeRange"}]},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","system","title","chartType","panelType","scope","metric","createdAt","updatedAt"]},"GetEcovoltPanelsResponseDto":{"type":"object","properties":{"results":{"description":"Ecovolt panels matching the query","type":"array","items":{"$ref":"#/components/schemas/EcovoltPanelResponseDto"}},"total":{"type":"number","description":"Total number of results","example":100}},"required":["results","total"]},"EcovoltDataPointDto":{"type":"object","properties":{"timestamp":{"format":"date-time","type":"string","description":"Timestamp for time-series data points. Absent for stat metrics (e.g. ENERGY_KWH_TO_DATE)."},"value":{"type":"object","description":"The measured value. Null when the device does not report this metric (e.g. AIRCON_AMBIENT_TEMPERATURE_C on a non-Pro unit)."}},"required":["value"]},"EcovoltDataSeriesDto":{"type":"object","properties":{"label":{"type":"string","description":"Human-readable series label (device name, entity name, etc.)"},"entityId":{"type":"string","description":"MongoDB ObjectId string of the entity this series represents"},"entityType":{"description":"The entity type this series represents","allOf":[{"$ref":"#/components/schemas/EcovoltEntityType"}]},"kind":{"type":"string","description":"Whether this is a primary series or a comparison period series","enum":["primary","comparison"]},"unit":{"type":"string","description":"Unit label for the values in this series (e.g. kWh, °C, W)"},"data":{"description":"The data points in this series","type":"array","items":{"$ref":"#/components/schemas/EcovoltDataPointDto"}}},"required":["label","entityId","entityType","kind","unit","data"]},"EcovoltPanelDataResponseDto":{"type":"object","properties":{"panelId":{"type":"string","description":"Panel ID. Absent when resolving a preview of an unsaved panel."},"metric":{"description":"The metric that was resolved","allOf":[{"$ref":"#/components/schemas/EcovoltMetric"}]},"timeRange":{"description":"The time range used. Absent for stat metrics (e.g. ENERGY_KWH_TO_DATE) where the query always reflects the current month-to-date.","allOf":[{"$ref":"#/components/schemas/EcovoltTimeRange"}]},"unit":{"type":"string","description":"Unit label for values in this response (e.g. kWh, °C)"},"series":{"description":"All series for this panel. Primary series have kind=\"primary\", comparison period series have kind=\"comparison\".","type":"array","items":{"$ref":"#/components/schemas/EcovoltDataSeriesDto"}},"resolvedAt":{"format":"date-time","type":"string","description":"Timestamp when this data was resolved"}},"required":["metric","unit","series"]},"CreateEcovoltPanelDto":{"type":"object","properties":{"title":{"type":"string","description":"Title of the panel","example":"System Energy This Week"},"description":{"type":"string","description":"Description of the panel","default":""},"colorPalette":{"description":"Color palette for chart series","example":["#FF6384","#36A2EB"],"type":"array","items":{"type":"string"}},"chartType":{"description":"Type of chart to render","allOf":[{"$ref":"#/components/schemas/ChartType"}]},"scope":{"description":"Scope: system entity to query with optional device type filter","allOf":[{"$ref":"#/components/schemas/EcovoltPanelScopeDto"}]},"metric":{"description":"The measurement to display","allOf":[{"$ref":"#/components/schemas/EcovoltMetric"}]},"timeRange":{"description":"How far back to query. Defaults to LAST_24H. Has no effect on stat metrics (TO_DATE, FORECAST, COST_MONTHLY_TO_DATE).","default":"LAST_24H","allOf":[{"$ref":"#/components/schemas/EcovoltTimeRange"}]}},"required":["title","chartType","scope","metric"]},"UpdateEcovoltPanelDto":{"type":"object","properties":{"title":{"type":"string","description":"Title of the panel","example":"System Energy This Week"},"description":{"type":"string","description":"Description of the panel","default":""},"colorPalette":{"description":"Color palette for chart series","example":["#FF6384","#36A2EB"],"type":"array","items":{"type":"string"}},"chartType":{"description":"Type of chart to render","allOf":[{"$ref":"#/components/schemas/ChartType"}]},"scope":{"description":"Scope: system entity to query with optional device type filter","allOf":[{"$ref":"#/components/schemas/EcovoltPanelScopeDto"}]},"metric":{"description":"The measurement to display","allOf":[{"$ref":"#/components/schemas/EcovoltMetric"}]},"timeRange":{"description":"How far back to query. Defaults to LAST_24H. Has no effect on stat metrics (TO_DATE, FORECAST, COST_MONTHLY_TO_DATE).","default":"LAST_24H","allOf":[{"$ref":"#/components/schemas/EcovoltTimeRange"}]}}},"ToggleAction":{"type":"string","enum":["ON","OFF"],"description":"The action to perform: ON or OFF."},"ToggleEcovoltPanelDto":{"type":"object","properties":{"entityId":{"type":"string","description":"ID of the device entity to toggle. Must be one of the entity IDs in the panel scope.","example":"64f1a2b3c4d5e6f7a8b9c0d1"},"action":{"description":"The action to perform: ON or OFF.","allOf":[{"$ref":"#/components/schemas/ToggleAction"}]}},"required":["entityId","action"]},"PanelGroup":{"type":"string","enum":["FEATURED","EXTERNAL"]},"PanelCategory":{"type":"string","enum":["OVERVIEW","ENERGY","COST","INVENTORY","ELECTRICAL","AIR_QUALITY","WATER","STATUS","SCHEDULING"]},"BuiltinWidgetKey":{"type":"string","enum":["STAT_TOTAL_ENERGY","STAT_TOTAL_BILL","STAT_DEVICE_STATUS","SCHEDULE_RECOMMENDATIONS","ENERGY_USAGE_BREAKDOWN","DEVICE_TYPE_BREAKDOWN","ENERGY_USAGE_CUSTOM_BREAKDOWN","TENANT_ENERGY_RANKING","ENERGY_USAGE_BY_HOUR","ENVIRONMENT_SENSOR_BOXPLOT","ENERGY_BOXPLOT","ENERGY_BY_PLUG_CATEGORY","DEVICE_PLUGGED_IN_BREAKDOWN"],"description":"BUILTIN entries only — the frontend registry key"},"PanelCatalogLayoutDto":{"type":"object","properties":{"w":{"type":"number","description":"Default width in grid units","example":6},"h":{"type":"number","description":"Default height in grid units","example":8},"minW":{"type":"number","description":"Minimum width"},"minH":{"type":"number","description":"Minimum height"}},"required":["w","h"]},"PanelCatalogEntryDto":{"type":"object","properties":{"id":{"type":"string","description":"Stable id for this catalog entry. Format: BUILTIN:<widgetKey>.","example":"BUILTIN:STAT_TOTAL_ENERGY"},"panelType":{"allOf":[{"$ref":"#/components/schemas/PanelType"}]},"group":{"allOf":[{"$ref":"#/components/schemas/PanelGroup"}]},"category":{"allOf":[{"$ref":"#/components/schemas/PanelCategory"}]},"title":{"type":"string","description":"Label shown in the picker"},"description":{"type":"string","description":"One-line explanation shown under the label"},"icon":{"type":"string","description":"Icon name for the frontend"},"allowedChartTypes":{"type":"array","description":"Chart types this entry accepts. Empty for BUILTIN entries.","items":{"$ref":"#/components/schemas/ChartType"}},"allowedTimeRanges":{"type":"array","description":"Time ranges this entry accepts. Empty for stat metrics (which reject a time range outright) and for BUILTIN entries.","items":{"$ref":"#/components/schemas/EcovoltTimeRange"}},"widgetKey":{"description":"BUILTIN entries only — the frontend registry key","allOf":[{"$ref":"#/components/schemas/BuiltinWidgetKey"}]},"defaultConfig":{"type":"object","description":"BUILTIN entries only — starting widget settings"},"requiresEntitySelection":{"type":"boolean","description":"Whether the user must pick target entities before the panel can be created, for the preselected scope. False for SYSTEM-scoped and builtin entries."},"defaultLayout":{"$ref":"#/components/schemas/PanelCatalogLayoutDto"},"available":{"type":"boolean","description":"Whether this entry can be added to this system at any of its scopes"},"disabledReason":{"type":"string","description":"Why the entry is unavailable. Absent when available.","example":"No environment sensors on this system"}},"required":["id","panelType","group","category","title","description","icon","allowedChartTypes","allowedTimeRanges","requiresEntitySelection","defaultLayout","available"]},"PanelCatalogGroupDto":{"type":"object","properties":{"group":{"allOf":[{"$ref":"#/components/schemas/PanelGroup"}]},"entries":{"description":"Entries in this group, ordered by category then title","type":"array","items":{"$ref":"#/components/schemas/PanelCatalogEntryDto"}}},"required":["group","entries"]},"PanelCatalogResponseDto":{"type":"object","properties":{"groups":{"description":"Every panel addable on this system, grouped server-side so two clients cannot disagree on ordering. Flat-list consumers can flatMap.","type":"array","items":{"$ref":"#/components/schemas/PanelCatalogGroupDto"}}},"required":["groups"]},"BuiltinPanelResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Panel ID"},"system":{"type":"string","description":"System ID"},"title":{"type":"string","description":"Title of the panel"},"description":{"type":"string","description":"Description of the panel"},"colorPalette":{"description":"Color palette","type":"array","items":{"type":"string"}},"panelType":{"default":"BUILTIN","allOf":[{"$ref":"#/components/schemas/PanelType"}]},"widgetKey":{"allOf":[{"$ref":"#/components/schemas/BuiltinWidgetKey"}]},"config":{"type":"object","description":"Widget-specific settings, opaque to the backend"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","system","title","panelType","widgetKey","config","createdAt","updatedAt"]},"GetBuiltinPanelsResponseDto":{"type":"object","properties":{"results":{"description":"Builtin panels matching the query","type":"array","items":{"$ref":"#/components/schemas/BuiltinPanelResponseDto"}},"total":{"type":"number","description":"Total number of results","example":100}},"required":["results","total"]},"CreateBuiltinPanelDto":{"type":"object","properties":{"title":{"type":"string","description":"Title of the panel","example":"Total Energy"},"description":{"type":"string","description":"Description of the panel","default":""},"colorPalette":{"description":"Color palette for the widget","example":["#FF6384","#36A2EB"],"type":"array","items":{"type":"string"}},"widgetKey":{"description":"Which frontend-registered widget renders this panel","allOf":[{"$ref":"#/components/schemas/BuiltinWidgetKey"}]},"config":{"type":"object","description":"Widget-specific settings. Stored opaquely — the shape is owned by the frontend registry entry for `widgetKey`.","default":{}}},"required":["title","widgetKey"]},"UpdateBuiltinPanelDto":{"type":"object","properties":{"title":{"type":"string","description":"Title of the panel","example":"Total Energy"},"description":{"type":"string","description":"Description of the panel","default":""},"colorPalette":{"description":"Color palette for the widget","example":["#FF6384","#36A2EB"],"type":"array","items":{"type":"string"}},"widgetKey":{"description":"Which frontend-registered widget renders this panel","allOf":[{"$ref":"#/components/schemas/BuiltinWidgetKey"}]},"config":{"type":"object","description":"Widget-specific settings. Stored opaquely — the shape is owned by the frontend registry entry for `widgetKey`.","default":{}}}},"DeviceStatusBreakdownDto":{"type":"object","properties":{"total":{"type":"number","description":"Total number of devices","example":5000},"active":{"type":"number","description":"Number of active devices","example":4200},"inactive":{"type":"number","description":"Number of inactive devices","example":600},"offline":{"type":"number","description":"Number of offline devices","example":200},"spare":{"type":"number","description":"Number of spare devices (excluded from active/inactive/offline counts)","example":50}},"required":["total","active","inactive","offline","spare"]},"TrendDirection":{"type":"string","enum":["up","down","stable"],"description":"Trend direction"},"CostMetricDto":{"type":"object","properties":{"value":{"type":"number","description":"Current cost value (SGD)","example":37500.25},"trend":{"type":"number","description":"Trend indicator (positive = increase, negative = decrease)","example":5.2},"trendDirection":{"description":"Trend direction","example":"up","allOf":[{"$ref":"#/components/schemas/TrendDirection"}]}},"required":["value","trend","trendDirection"]},"Indicator":{"type":"string","enum":["positive","neutral","negative"],"description":"Indicator status"},"CarbonMetricDto":{"type":"object","properties":{"value":{"type":"number","description":"Carbon emissions saved (kg CO₂)","example":52500.75},"indicator":{"description":"Indicator status","example":"positive","allOf":[{"$ref":"#/components/schemas/Indicator"}]}},"required":["value","indicator"]},"PlatformOverviewDto":{"type":"object","properties":{"totalSystems":{"type":"number","description":"Total number of systems registered","example":150},"totalTenants":{"type":"number","description":"Total number of active tenants","example":450},"totalDevices":{"description":"Total number of plugs with status breakdown","allOf":[{"$ref":"#/components/schemas/DeviceStatusBreakdownDto"}]},"totalZones":{"type":"number","description":"Total number of active zones","example":1200},"totalRooms":{"type":"number","description":"Total number of active rooms","example":800},"totalEnergyMonitored":{"type":"number","description":"Total energy monitored across all systems (kWh)","example":125000.5},"estimatedEnergyCost":{"description":"Estimated total energy cost (SGD)","allOf":[{"$ref":"#/components/schemas/CostMetricDto"}]},"carbonEmissionsSaved":{"description":"Total carbon emissions avoided (kg CO₂)","allOf":[{"$ref":"#/components/schemas/CarbonMetricDto"}]}},"required":["totalSystems","totalTenants","totalDevices","totalZones","totalRooms","totalEnergyMonitored","estimatedEnergyCost","carbonEmissionsSaved"]},"TimePeriod":{"type":"string","enum":["daily","weekly","monthly"]},"EnergyDataPointDto":{"type":"object","properties":{"label":{"type":"string","description":"Date/time label for the data point","example":"2024-01-15"},"value":{"type":"number","description":"Energy value (kWh)","example":5250.5},"timestamp":{"format":"date-time","type":"string","description":"Timestamp of the data point","example":"2024-01-15T00:00:00.000Z"}},"required":["label","value","timestamp"]},"EnergyUsageDto":{"type":"object","properties":{"period":{"type":"string","description":"Time period used for aggregation","example":"daily","enum":["daily","weekly","monthly"]},"totalEnergy":{"type":"number","description":"Total energy consumed in the period (kWh)","example":125000.5},"dataPoints":{"description":"Energy usage data points","type":"array","items":{"$ref":"#/components/schemas/EnergyDataPointDto"}},"averageEnergy":{"type":"number","description":"Average energy per time unit (kWh)","example":5000.25}},"required":["period","totalEnergy","dataPoints","averageEnergy"]},"SystemEnergyDto":{"type":"object","properties":{"systemId":{"type":"string","description":"System ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"System name","example":"Building A - North Tower"},"energyConsumed":{"type":"number","description":"Total energy consumed (kWh)","example":52500.25},"plugCount":{"type":"number","description":"Number of plugs in the system","example":250},"percentageOfTotal":{"type":"number","description":"Percentage of total energy","example":21.5},"rank":{"type":"number","description":"Rank position","example":1}},"required":["systemId","systemName","energyConsumed","plugCount","percentageOfTotal","rank"]},"TopSystemsDto":{"type":"object","properties":{"systems":{"description":"List of top systems by energy consumption","type":"array","items":{"$ref":"#/components/schemas/SystemEnergyDto"}},"totalEnergy":{"type":"number","description":"Total energy consumed by all systems (kWh)","example":250000.5}},"required":["systems","totalEnergy"]},"HourlyUsageDto":{"type":"object","properties":{"hour":{"type":"number","description":"Hour of day (0-23)","example":18},"energy":{"type":"number","description":"Energy consumed during this hour (kWh)","example":8500.5},"percentageOfDay":{"type":"number","description":"Percentage of daily total","example":12.5},"isPeak":{"type":"boolean","description":"Is this a peak hour?","example":true}},"required":["hour","energy","percentageOfDay","isPeak"]},"PeakHoursDto":{"type":"object","properties":{"peakHours":{"description":"Peak usage hours data","type":"array","items":{"$ref":"#/components/schemas/HourlyUsageDto"}},"overallPeakHour":{"type":"number","description":"Overall peak hour","example":18},"peakHourEnergy":{"type":"number","description":"Peak hour energy value (kWh)","example":8500.5},"offPeakAverageEnergy":{"type":"number","description":"Off-peak average energy (kWh)","example":3200.25}},"required":["peakHours","overallPeakHour","peakHourEnergy","offPeakAverageEnergy"]},"GrowthDataPointDto":{"type":"object","properties":{"month":{"type":"string","description":"Month label","example":"Jan 2024"},"count":{"type":"number","description":"Number of new active plugs (>1kWh)","example":125},"cumulative":{"type":"number","description":"Cumulative total","example":1250},"date":{"format":"date-time","type":"string","description":"Date of the data point","example":"2024-01-01T00:00:00.000Z"}},"required":["month","count","cumulative","date"]},"GrowthTrendDto":{"type":"object","properties":{"dataPoints":{"description":"Monthly growth data points","type":"array","items":{"$ref":"#/components/schemas/GrowthDataPointDto"}},"totalGrowth":{"type":"number","description":"Total growth count","example":1250},"averageMonthlyGrowth":{"type":"number","description":"Average monthly growth","example":104.2},"growthRate":{"type":"number","description":"Growth rate percentage","example":25.5}},"required":["dataPoints","totalGrowth","averageMonthlyGrowth","growthRate"]},"ProjectionDataPointDto":{"type":"object","properties":{"month":{"type":"string","description":"Month label","example":"Apr 2024"},"projectedCount":{"type":"number","description":"Projected device count","example":145},"lowerBound":{"type":"number","description":"Lower bound estimate","example":130},"upperBound":{"type":"number","description":"Upper bound estimate","example":160},"date":{"format":"date-time","type":"string","description":"Date of projection","example":"2024-04-01T00:00:00.000Z"}},"required":["month","projectedCount","lowerBound","upperBound","date"]},"RegressionParametersDto":{"type":"object","properties":{"slope":{"type":"number","description":"Slope coefficient","example":12.5},"intercept":{"type":"number","description":"Y-intercept","example":1000},"rSquared":{"type":"number","description":"R-squared value (goodness of fit)","example":0.92}},"required":["slope","intercept","rSquared"]},"GrowthProjectionDto":{"type":"object","properties":{"projections":{"description":"Projected growth for next 3 months","type":"array","items":{"$ref":"#/components/schemas/ProjectionDataPointDto"}},"regressionParams":{"description":"Linear regression parameters","allOf":[{"$ref":"#/components/schemas/RegressionParametersDto"}]},"expectedTotal":{"type":"number","description":"Expected total devices after 3 months","example":1650},"confidence":{"type":"number","description":"Confidence level (0-1)","example":0.85}},"required":["projections","regressionParams","expectedTotal","confidence"]},"RoleCountDto":{"type":"object","properties":{"role":{"type":"string","description":"Role name","example":"SUPERADMIN"},"count":{"type":"number","description":"Number of admins with this role","example":25},"percentage":{"type":"number","description":"Percentage of total","example":10}},"required":["role","count","percentage"]},"RoleDistributionDto":{"type":"object","properties":{"roles":{"description":"Role distribution data","type":"array","items":{"$ref":"#/components/schemas/RoleCountDto"}},"totalAdmins":{"type":"number","description":"Total number of admins","example":250}},"required":["roles","totalAdmins"]},"CategoryCountDto":{"type":"object","properties":{"category":{"type":"string","description":"Plug category","example":"AIR_CONDITIONER"},"count":{"type":"number","description":"Number of plugs in this category","example":850},"percentage":{"type":"number","description":"Percentage of total plugs","example":17}},"required":["category","count","percentage"]},"PlugCategoryDistributionDto":{"type":"object","properties":{"categories":{"description":"Category distribution data","type":"array","items":{"$ref":"#/components/schemas/CategoryCountDto"}},"totalPlugs":{"type":"number","description":"Total number of plugs","example":5000}},"required":["categories","totalPlugs"]},"CategoryEnergyDto":{"type":"object","properties":{"category":{"type":"string","description":"Plug category","example":"AIRCONDITIONER"},"energy":{"type":"number","description":"Total energy consumed by this category (kWh)","example":52500.25},"percentage":{"type":"number","description":"Percentage of total energy","example":42},"plugCount":{"type":"number","description":"Number of plugs in category","example":850},"averageEnergyPerPlug":{"type":"number","description":"Average energy per plug (kWh)","example":61.76}},"required":["category","energy","percentage","plugCount","averageEnergyPerPlug"]},"PlugCategoryEnergyDistributionDto":{"type":"object","properties":{"categories":{"description":"Category energy distribution data","type":"array","items":{"$ref":"#/components/schemas/CategoryEnergyDto"}},"totalEnergy":{"type":"number","description":"Total energy consumed (kWh)","example":125000.5}},"required":["categories","totalEnergy"]},"SystemPlugCountDto":{"type":"object","properties":{"systemId":{"type":"string","description":"System ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"System name","example":"Building A - North Tower"},"plugCount":{"type":"number","description":"Number of plugs in this system","example":250},"percentage":{"type":"number","description":"Percentage of total plugs","example":5}},"required":["systemId","systemName","plugCount","percentage"]},"PlugSystemDistributionDto":{"type":"object","properties":{"systems":{"description":"System distribution data","type":"array","items":{"$ref":"#/components/schemas/SystemPlugCountDto"}},"totalPlugs":{"type":"number","description":"Total number of plugs","example":5000},"totalSystems":{"type":"number","description":"Total number of systems","example":150}},"required":["systems","totalPlugs","totalSystems"]},"GranularityType":{"type":"string","enum":["hour","day","week","month"],"description":"Time granularity for data aggregation"},"EnergyBreakdownRequestDto":{"type":"object","properties":{"systemId":{"type":"string","description":"System ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"startDate":{"type":"string","description":"Start date for energy data (ISO 8601 format)","example":"2024-01-01"},"endDate":{"type":"string","description":"End date for energy data (ISO 8601 format)","example":"2024-01-31"},"granularity":{"description":"Time granularity for data aggregation","example":"day","allOf":[{"$ref":"#/components/schemas/GranularityType"}]},"compareStartDate":{"type":"string","description":"Start date for comparison period (optional)","example":"2023-01-01"},"compareEndDate":{"type":"string","description":"End date for comparison period (optional)","example":"2023-01-31"}},"required":["systemId","startDate","endDate","granularity"]},"EnergyBreakdownPointDto":{"type":"object","properties":{"timestamp":{"type":"string","description":"Timestamp of this data point","example":"2024-01-01T00:00:00.000Z"},"energy":{"type":"number","description":"Energy consumption in kWh","example":12.45},"cost":{"type":"number","description":"Cost in SGD (calculated using tariff rate active at this timestamp)","example":3.82},"tariffRate":{"type":"number","description":"Tariff rate used for this calculation (cents per kWh)","example":30.65}},"required":["timestamp","energy","cost","tariffRate"]},"EnergyPeriodDto":{"type":"object","properties":{"totalEnergy":{"type":"number","description":"Total energy consumption for period (kWh)","example":450.25},"totalCost":{"type":"number","description":"Total cost for period (SGD)","example":138.02},"breakdown":{"description":"Breakdown by time bucket","type":"array","items":{"$ref":"#/components/schemas/EnergyBreakdownPointDto"}}},"required":["totalEnergy","totalCost","breakdown"]},"EnergyComparisonDto":{"type":"object","properties":{"totalEnergy":{"type":"number","description":"Total energy consumption for period (kWh)","example":450.25},"totalCost":{"type":"number","description":"Total cost for period (SGD)","example":138.02},"breakdown":{"description":"Breakdown by time bucket","type":"array","items":{"$ref":"#/components/schemas/EnergyBreakdownPointDto"}},"energyDelta":{"type":"number","description":"Energy delta compared to main period (kWh)","example":-45.5},"costDelta":{"type":"number","description":"Cost delta compared to main period (SGD)","example":-13.96}},"required":["totalEnergy","totalCost","breakdown","energyDelta","costDelta"]},"EnergyBreakdownResponseDto":{"type":"object","properties":{"period":{"description":"Main period energy and cost data","allOf":[{"$ref":"#/components/schemas/EnergyPeriodDto"}]},"comparison":{"description":"Comparison period data (if comparison dates provided)","allOf":[{"$ref":"#/components/schemas/EnergyComparisonDto"}]},"calculatedAt":{"type":"string","description":"Timestamp when these costs were calculated","example":"2024-01-15T10:30:00.000Z"}},"required":["period","calculatedAt"]},"SmartModeSuggestedDeviceDto":{"type":"object","properties":{"id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","example":"Bedroom Fan"}},"required":["id","name"]},"TriggerMetric":{"type":"string","enum":["TEMPERATURE","HUMIDITY","CARBONDIOXIDE","PM1","FINE_PARTICULATE_MATTER","COARSE_PARTICULATE_MATTER","FORMALDEHYDE","SOUND","LIGHT","MOTION","TOTALVOLATILEORGANICCOMPOUNDS","OCCUPANCY","FLOWRATE","VELOCITY","SOUND_SPEED","SIGNAL_QUALITY","METER_CONNECTED","INLET_TEMPERATURE","OUTLET_TEMPERATURE","ENERGY_FLOW_RATE","VOLTAGE","CURRENT","POWER","TOGGLE","WEATHER_RAIN","WEATHER_TEMP","WEATHER_HUMIDITY"]},"TriggerOperator":{"type":"string","enum":["GREATER_THAN","LESS_THAN","EQUAL_TO","NOT_EQUAL_TO","GREATER_THAN_OR_EQUAL_TO","LESS_THAN_OR_EQUAL_TO","IS_TRUE","IS_FALSE"]},"ActionType":{"type":"string","enum":["TOGGLE_ON","TOGGLE_OFF","CONTROL_AIRCON","AIRCON_SMART_TEMP","CONTROL_EXTERNAL_DEVICE_DAIKIN_AIRCON"]},"SmartModeSuggestionItemDto":{"type":"object","properties":{"rank":{"type":"number","example":1},"confidence":{"type":"number","description":"Confidence score between 0 and 1","example":0.89},"occurrences":{"type":"number","description":"Number of times this pattern was observed in the window","example":12},"description":{"type":"string","example":"Living Room AC turns on shortly after Bedroom Fan"},"triggerDevice":{"$ref":"#/components/schemas/SmartModeSuggestedDeviceDto"},"metric":{"allOf":[{"$ref":"#/components/schemas/TriggerMetric"}]},"operator":{"allOf":[{"$ref":"#/components/schemas/TriggerOperator"}]},"thresholdValue":{"type":"number","example":0},"actionDevice":{"$ref":"#/components/schemas/SmartModeSuggestedDeviceDto"},"actionType":{"allOf":[{"$ref":"#/components/schemas/ActionType"}]},"suggestedName":{"type":"string","description":"Pre-filled trigger name the user can accept or edit","example":"Bedroom Fan on → Living Room AC on"}},"required":["rank","confidence","occurrences","description","triggerDevice","metric","operator","thresholdValue","actionDevice","actionType","suggestedName"]},"SmartModeSuggestionResponseDto":{"type":"object","properties":{"suggestions":{"type":"array","items":{"$ref":"#/components/schemas/SmartModeSuggestionItemDto"}}},"required":["suggestions"]},"CreateFeatureDto":{"type":"object","properties":{"name":{"type":"string","description":"Display name of the feature. Used for identification in the user interface.","example":"Tenant Management"},"featureType":{"description":"Feature type identifier from the predefined feature enum. Determines the functional category of the feature.","example":"TENANT_MANAGEMENT","allOf":[{"$ref":"#/components/schemas/FeatureType"}]},"description":{"type":"string","description":"Detailed description of what the feature does and its purpose within the system.","example":"Allows management of tenants within the system, including creation, updates, and relationship assignments"}},"required":["name","featureType","description"]},"UpdateFeatureDto":{"type":"object","properties":{"name":{"type":"string","description":"Display name of the feature. Used for identification in the user interface.","example":"Tenant Management"},"featureType":{"description":"Feature type identifier from the predefined feature enum. Determines the functional category of the feature.","example":"TENANT_MANAGEMENT","allOf":[{"$ref":"#/components/schemas/FeatureType"}]},"description":{"type":"string","description":"Detailed description of what the feature does and its purpose within the system.","example":"Allows management of tenants within the system, including creation, updates, and relationship assignments"}},"required":["name","featureType","description"]},"OccupancyGranularity":{"type":"string","enum":["QUARTER_HOUR","HOUR","DAY"]},"OccupancyTimelineBucketDto":{"type":"object","properties":{"start":{"format":"date-time","type":"string","description":"Bucket start"},"occupiedMinutes":{"type":"number","description":"Minutes the room was occupied","example":30},"observedMinutes":{"type":"number","description":"Minutes a sensor was reporting. occupied < observed is vacancy; the rest of the bucket is a data gap, not vacancy","example":60},"peakOccupancy":{"type":"number","description":"Highest people count; null for motion-only sensors","nullable":true,"example":6}},"required":["start","occupiedMinutes","observedMinutes","peakOccupancy"]},"OccupancySessionDto":{"type":"object","properties":{"start":{"format":"date-time","type":"string","description":"First occupied reading"},"end":{"format":"date-time","type":"string","description":"Last occupied reading"},"durationMinutes":{"type":"number","description":"Dwell length in minutes","example":45},"peakOccupancy":{"type":"number","description":"Highest people count in the session; null for motion-only sensors","nullable":true,"example":6}},"required":["start","end","durationMinutes","peakOccupancy"]},"OccupancyTimelineResponseDto":{"type":"object","properties":{"roomId":{"type":"string","description":"Room id","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"timezone":{"type":"string","description":"System timezone buckets are aligned to","example":"Asia/Singapore"},"granularity":{"allOf":[{"$ref":"#/components/schemas/OccupancyGranularity"}]},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/OccupancyTimelineBucketDto"}},"sessions":{"description":"Continuous occupied periods, for enter/exit tooltips","type":"array","items":{"$ref":"#/components/schemas/OccupancySessionDto"}}},"required":["roomId","timezone","granularity","buckets","sessions"]},"OccupancyState":{"type":"string","enum":["OCCUPIED","VACANT","NO_DATA"]},"OccupancyCurrentStatusDto":{"type":"object","properties":{"state":{"allOf":[{"$ref":"#/components/schemas/OccupancyState"}]},"since":{"format":"date-time","type":"string","description":"When the room entered this state, within the last 24 hours","nullable":true},"occupancy":{"type":"number","description":"Latest people count; null for motion-only sensors","nullable":true}},"required":["state"]},"OccupancyDaySummaryDto":{"type":"object","properties":{"date":{"type":"string","description":"Local date, yyyy-MM-dd","example":"2026-09-22"},"firstInAt":{"format":"date-time","type":"string","nullable":true},"lastOutAt":{"format":"date-time","type":"string","nullable":true},"occupiedMinutes":{"type":"number","example":240},"sessions":{"type":"number","description":"Sessions that started this day","example":5},"peakOccupancy":{"type":"number","nullable":true,"example":8},"peakAt":{"format":"date-time","type":"string","nullable":true}},"required":["date","firstInAt","lastOutAt","occupiedMinutes","sessions","peakOccupancy","peakAt"]},"OccupancySummaryResponseDto":{"type":"object","properties":{"roomId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"current":{"$ref":"#/components/schemas/OccupancyCurrentStatusDto"},"utilisationPct":{"type":"number","description":"Occupied ÷ observed minutes, as a percentage. null when no sensor reported","nullable":true,"example":62.5},"previousUtilisationPct":{"type":"number","description":"Same figure for the equal-length period immediately before","nullable":true,"example":55},"utilisationDeltaPct":{"type":"number","description":"utilisationPct − previousUtilisationPct, in percentage points","nullable":true,"example":7.5},"peakOccupancy":{"type":"number","nullable":true,"example":12},"peakAt":{"format":"date-time","type":"string","nullable":true},"occupiedMinutes":{"type":"number","example":1260},"sessionCount":{"type":"number","example":14},"avgDwellMinutes":{"type":"number","nullable":true,"example":90},"vacantEnergisedMinutes":{"type":"number","description":"Vacant minutes while the room drew at least 10 W","example":300},"days":{"type":"array","items":{"$ref":"#/components/schemas/OccupancyDaySummaryDto"}}},"required":["roomId","current","utilisationPct","previousUtilisationPct","utilisationDeltaPct","peakOccupancy","peakAt","occupiedMinutes","sessionCount","avgDwellMinutes","vacantEnergisedMinutes","days"]},"OccupancyScopeType":{"type":"string","enum":["SYSTEM","TENANT","ZONE","ROOM"]},"OccupancyHeatmapCellDto":{"type":"object","properties":{"weekday":{"allOf":[{"$ref":"#/components/schemas/DayOfWeek"}]},"hour":{"type":"number","description":"Local hour, 0–23","example":14},"utilisationPct":{"type":"number","description":"Occupied ÷ observed minutes; null when no sensor reported","nullable":true,"example":40},"observedMinutes":{"type":"number","example":480}},"required":["weekday","hour","utilisationPct","observedMinutes"]},"OccupancyHeatmapResponseDto":{"type":"object","properties":{"timezone":{"type":"string","example":"Asia/Singapore"},"cells":{"description":"7 × 24 cells, every weekday/hour present","type":"array","items":{"$ref":"#/components/schemas/OccupancyHeatmapCellDto"}}},"required":["timezone","cells"]},"OccupancyRoomSortField":{"type":"string","enum":["roomName","utilisationPct","occupiedHours","sessions","avgDwellMinutes","peakOccupancy"]},"OccupancySensorStatus":{"type":"string","enum":["ONLINE","OFFLINE","NO_DATA","SUSPECT","NO_SENSOR"],"description":"Worst status among the room sensors"},"OccupancyRoomRowDto":{"type":"object","properties":{"roomId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"roomName":{"type":"string","example":"Meeting Room 1"},"utilisationPct":{"type":"number","nullable":true,"example":62.5},"occupiedHours":{"type":"number","example":21},"sessions":{"type":"number","example":14},"avgDwellMinutes":{"type":"number","nullable":true,"example":90},"peakOccupancy":{"type":"number","nullable":true,"example":12},"sensorStatus":{"description":"Worst status among the room sensors","allOf":[{"$ref":"#/components/schemas/OccupancySensorStatus"}]}},"required":["roomId","roomName","utilisationPct","occupiedHours","sessions","avgDwellMinutes","peakOccupancy","sensorStatus"]},"GetOccupancyRoomsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Room rows matching the scope, sorted and paginated","type":"array","items":{"$ref":"#/components/schemas/OccupancyRoomRowDto"}}},"required":["total","page","size","totalPages","data"]},"OccupancyWasteWindowDto":{"type":"object","properties":{"roomId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"roomName":{"type":"string","example":"Meeting Room 1"},"start":{"format":"date-time","type":"string"},"end":{"format":"date-time","type":"string"},"minutes":{"type":"number","example":45}},"required":["roomId","roomName","start","end","minutes"]},"OccupancyWasteResponseDto":{"type":"object","properties":{"totalVacantEnergisedMinutes":{"type":"number","example":300},"windows":{"description":"Contiguous vacant-but-energised periods, oldest first","type":"array","items":{"$ref":"#/components/schemas/OccupancyWasteWindowDto"}}},"required":["totalVacantEnergisedMinutes","windows"]},"OccupancySensorCapability":{"type":"string","enum":["MOTION","COUNT"]},"OccupancySensorHealthDto":{"type":"object","properties":{"deviceId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceName":{"type":"string","example":"Meeting Room PIR"},"deviceType":{"allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"capability":{"allOf":[{"$ref":"#/components/schemas/OccupancySensorCapability"}]},"roomId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"roomName":{"type":"string","example":"Meeting Room 1"},"status":{"description":"NO_DATA = online but no occupancy readings in the last day (counter disabled, BLE down); SUSPECT = occupied for the whole last day, likely a stuck reading","allOf":[{"$ref":"#/components/schemas/OccupancySensorStatus"}]},"lastSeen":{"format":"date-time","type":"string"}},"required":["deviceId","deviceType","capability","roomId","roomName","status"]},"OccupancyDevicesHealthResponseDto":{"type":"object","properties":{"unhealthyCount":{"type":"number","description":"Sensors not ONLINE","example":1},"devices":{"description":"Unhealthy sensors first","type":"array","items":{"$ref":"#/components/schemas/OccupancySensorHealthDto"}}},"required":["unhealthyCount","devices"]},"ToolDefinitionDto":{"type":"object","properties":{"name":{"type":"string","description":"Tool name (unique identifier)"},"description":{"type":"string","description":"Tool description for LLM"},"inputSchema":{"type":"object","description":"JSON Schema for tool input parameters","additionalProperties":true}},"required":["name","description","inputSchema"]},"ToolConfirmationDto":{"type":"object","properties":{"toolCallId":{"type":"string","description":"Tool call ID this confirmation resolves"},"approved":{"type":"boolean","description":"Whether the user approved this tool call"}},"required":["toolCallId","approved"]},"AgentChatRequestDto":{"type":"object","properties":{"messages":{"description":"Conversation history including user and assistant messages","type":"array","items":{"$ref":"#/components/schemas/ChatMessageDto"}},"tools":{"deprecated":true,"description":"Deprecated and ignored. Tool definitions are owned by the server; anything sent here has no effect.","type":"array","items":{"$ref":"#/components/schemas/ToolDefinitionDto"}},"systemId":{"type":"string","description":"System ID the chat operates on. Required — used to enforce the AI_FEATURE entitlement and to scope context-aware tool calls."},"sessionId":{"type":"string","description":"Session ID for chat history persistence"},"toolConfirmations":{"description":"Approve/deny decisions for pending tool calls that required user confirmation (resolves a prior CONFIRMATION_REQUIRED event on this same conversation).","type":"array","items":{"$ref":"#/components/schemas/ToolConfirmationDto"}}},"required":["messages","systemId"]},"AiToolSummaryDto":{"type":"object","properties":{"name":{"type":"string","description":"Stable tool name — the same identifier used for OpenAI function calling and MCP.","example":"get_room_energy"},"description":{"type":"string","description":"Natural-language description shown to the model."},"kind":{"type":"string","description":"Whether the tool reads data or mutates it. Write tools are limited to ADMIN and SUPERADMIN.","enum":["read","write"],"example":"read"},"requiresConfirmation":{"type":"boolean","description":"Whether running the tool prompts the user to confirm first. Tools that decide this per-arguments report true.","example":false}},"required":["name","description","kind","requiresConfirmation"]},"TranscribeAudioResponseDto":{"type":"object","properties":{"text":{"type":"string","description":"Transcribed text from the audio recording"}},"required":["text"]},"RangerChatHistorySummaryDto":{"type":"object","properties":{"sessionId":{"type":"string","description":"UUID identifying the chat session","example":"550e8400-e29b-41d4-a716-446655440000"},"messageCount":{"type":"number","description":"Total number of messages in the session","example":6},"preview":{"type":"string","description":"First user message truncated to 120 chars","example":"How much energy did Building A consume last week?"},"adminEmail":{"type":"string","description":"Email of the account that initiated the session","example":"admin@example.com"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the session was first created","example":"2024-01-15T08:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp of the last message saved to this session","example":"2024-01-15T08:35:00.000Z"}},"required":["sessionId","messageCount","preview","createdAt","updatedAt"]},"RangerChatHistoryPaginatedDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Chat session summaries for the current page","type":"array","items":{"$ref":"#/components/schemas/RangerChatHistorySummaryDto"}}},"required":["total","page","size","totalPages","data"]},"RangerChatHistoryDetailDto":{"type":"object","properties":{"sessionId":{"type":"string","description":"UUID identifying the chat session","example":"550e8400-e29b-41d4-a716-446655440000"},"messageCount":{"type":"number","description":"Total number of messages in the session","example":6},"preview":{"type":"string","description":"First user message truncated to 120 chars","example":"How much energy did Building A consume last week?"},"adminEmail":{"type":"string","description":"Email of the account that initiated the session","example":"admin@example.com"},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the session was first created","example":"2024-01-15T08:30:00.000Z"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp of the last message saved to this session","example":"2024-01-15T08:35:00.000Z"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/ChatMessageDto"}}},"required":["sessionId","messageCount","preview","createdAt","updatedAt","messages"]},"CalendarPreviewRoomDto":{"type":"object","properties":{"roomId":{"type":"string","description":"Room ID"},"roomName":{"type":"string","description":"Room name as stored in the system"},"deviceCount":{"type":"number","description":"Number of devices in this room that would receive schedules"}},"required":["roomId","roomName","deviceCount"]},"RecurrenceType":{"type":"string","enum":["DAILY","WEEKLY"],"description":"Recurrence pattern type. Only present when isRecurring is true."},"CalendarPreviewEventDto":{"type":"object","properties":{"uid":{"type":"string","description":"External event ID — Google event ID, iCal UID, or Microsoft event ID"},"eventTitle":{"type":"string","description":"Original event title from the calendar"},"startTime":{"format":"date-time","type":"string","description":"Event start time"},"endTime":{"format":"date-time","type":"string","description":"Event end time"},"matched":{"type":"boolean","description":"True if at least one room matched the event title"},"include":{"type":"boolean","description":"Whether to include this event in the confirm step. Defaults to true when matched, false when unmatched."},"rooms":{"description":"Rooms matched from the event title. Empty when matched=false.","type":"array","items":{"$ref":"#/components/schemas/CalendarPreviewRoomDto"}},"isRecurring":{"type":"boolean","description":"True if the event is a recurring event (daily or weekly pattern)"},"recurrenceType":{"description":"Recurrence pattern type. Only present when isRecurring is true.","allOf":[{"$ref":"#/components/schemas/RecurrenceType"}]},"recurrenceDays":{"description":"Days of week the event recurs on (0=Sun … 6=Sat). Only present for weekly recurrence.","example":[1,3,5],"type":"array","items":{"type":"number"}}},"required":["uid","eventTitle","startTime","endTime","matched","include","rooms","isRecurring"]},"CalendarPreviewResponseDto":{"type":"object","properties":{"events":{"description":"List of calendar events parsed from the calendar source, each with room-match results.","type":"array","items":{"$ref":"#/components/schemas/CalendarPreviewEventDto"}},"totalMatched":{"type":"number","description":"Number of events that matched at least one room"},"totalUnmatched":{"type":"number","description":"Number of events that matched no room"}},"required":["events","totalMatched","totalUnmatched"]},"ScheduleType":{"type":"string","enum":["SINGLE","RECURRING"],"description":"Schedule type: single one-time schedule or recurring weekly schedule"},"ConfirmCalendarEventDto":{"type":"object","properties":{"uid":{"type":"string","description":"External event ID (from preview response)"},"eventTitle":{"type":"string","description":"Event title (used for mapping storage)"},"startTime":{"type":"string","description":"Event start time (may be user-adjusted)"},"endTime":{"type":"string","description":"Event end time (may be user-adjusted)"},"include":{"type":"boolean","description":"Set to false to skip this event — no schedules will be created for it"},"roomIds":{"description":"Room IDs to schedule (may be user-adjusted from preview)","type":"array","items":{"type":"string"}},"scheduleType":{"description":"Schedule type: single one-time schedule or recurring weekly schedule","allOf":[{"$ref":"#/components/schemas/ScheduleType"}]},"daysOfWeek":{"description":"Days of week for recurring schedule (0=Sun … 6=Sat). Required when scheduleType is RECURRING.","example":[1,3],"type":"array","items":{"type":"number"}},"onTime":{"type":"string","description":"ON time for recurring schedule in HH:MM format. Required when scheduleType is RECURRING.","example":"09:00"},"offTime":{"type":"string","description":"OFF time for recurring schedule in HH:MM format. Required when scheduleType is RECURRING.","example":"17:00"}},"required":["uid","eventTitle","startTime","endTime","include","roomIds","scheduleType"]},"ConfirmCalendarUploadDto":{"type":"object","properties":{"events":{"description":"List of calendar events to confirm. Must contain at least one entry.","type":"array","items":{"$ref":"#/components/schemas/ConfirmCalendarEventDto"}},"gracePeriodMinutes":{"type":"number","description":"Grace period in minutes. Devices turn ON this many minutes before the event starts and turn OFF this many minutes after it ends. Only applies to one-time (SINGLE) schedules. Defaults to 0.","example":15,"default":0}},"required":["events"]},"CalendarImportResultDto":{"type":"object","properties":{"imported":{"type":"number","description":"Number of events for which schedules were successfully created","example":5},"skipped":{"type":"number","description":"Number of events skipped (include=false, past, or no devices in room)","example":2}},"required":["imported","skipped"]},"PreviewIcsUrlDto":{"type":"object","properties":{"icsUrl":{"type":"string","description":"Publicly accessible ICS calendar feed URL. Must be a valid HTTPS URL pointing to a .ics file. Private/internal network addresses are not permitted.","example":"https://calendar.google.com/calendar/ical/example%40gmail.com/public/basic.ics"}},"required":["icsUrl"]},"ConfirmIcsUrlDto":{"type":"object","properties":{"icsUrl":{"type":"string","description":"Publicly accessible ICS calendar feed URL. Saved for ongoing background polling. Must be HTTPS; private/internal addresses are not permitted.","example":"https://calendar.google.com/calendar/ical/example%40gmail.com/public/basic.ics"},"events":{"description":"List of calendar events to confirm. Must contain at least one entry.","type":"array","items":{"$ref":"#/components/schemas/ConfirmCalendarEventDto"}},"gracePeriodMinutes":{"type":"number","description":"Grace period in minutes. Devices turn ON this many minutes before the event starts and turn OFF this many minutes after it ends. Saved for ongoing syncs. Defaults to 0.","example":15,"default":0}},"required":["icsUrl","events"]},"SubscribeIcsUrlDto":{"type":"object","properties":{"icsUrl":{"type":"string","description":"Publicly accessible ICS calendar feed URL. Must be a valid HTTPS URL pointing to a .ics file. Private/internal network addresses are not permitted.","example":"https://calendar.google.com/calendar/ical/example%40gmail.com/public/basic.ics"},"gracePeriodMinutes":{"type":"number","description":"Grace period in minutes. Devices turn ON this many minutes before the event starts and turn OFF this many minutes after it ends. Defaults to 0.","example":15,"default":0}},"required":["icsUrl"]},"CalendarType":{"type":"string","enum":["GOOGLE","MICROSOFT","ICS_URL","ICS_FILE"],"description":"The calendar provider type for this connection (e.g., GOOGLE, MICROSOFT, ICS)"},"ConnectionScope":{"type":"string","enum":["SYSTEM","USER"],"description":"Scope of this connection — whether it applies system-wide or per-user"},"CalendarConnectionResponseDto":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier of the calendar connection","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemId":{"type":"string","description":"System this calendar connection belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c1"},"userId":{"type":"string","description":"Admin user ID that owns this connection; null indicates a system-wide connection","example":"64b7f1e6f9c9d4e5b8a9f7c2"},"calendarType":{"description":"The calendar provider type for this connection (e.g., GOOGLE, MICROSOFT, ICS)","example":"GOOGLE","allOf":[{"$ref":"#/components/schemas/CalendarType"}]},"scope":{"description":"Scope of this connection — whether it applies system-wide or per-user","example":"SYSTEM","allOf":[{"$ref":"#/components/schemas/ConnectionScope"}]},"googleCalendarId":{"type":"string","description":"Google Calendar ID used for this connection (e.g., \"primary\")","example":"primary"},"icsUrl":{"type":"string","description":"ICS feed URL for ICS-type calendar connections","example":"https://calendar.google.com/calendar/ical/example%40gmail.com/public/basic.ics"},"lastSyncedAt":{"format":"date-time","type":"string","description":"Timestamp of the last successful sync for this connection"},"isActive":{"type":"boolean","description":"Whether this calendar connection is currently active","example":true},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when this calendar connection was created"}},"required":["id","systemId","calendarType","scope","isActive","createdAt"]},"Generate2dFloorPlanResponseDto":{"type":"object","properties":{"floorPlanId":{"type":"string","description":"Floor plan document ID (upserted)"},"plan2d":{"type":"object","description":"2D geometry preview — not yet saved"}},"required":["floorPlanId","plan2d"]},"Room2DDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"polygon":{"type":"array","description":"CCW polygon vertices [[x,z],...]","items":{"type":"array","items":{"type":"number"}}}},"required":["id","name","type","polygon"]},"Opening2DDto":{"type":"object","properties":{"start":{"type":"number"},"end":{"type":"number"},"kind":{"type":"string"}},"required":["start","end","kind"]},"Wall2DDto":{"type":"object","properties":{"start":{"type":"array","description":"[x, z] coordinate","items":{"type":"number"}},"end":{"type":"array","description":"[x, z] coordinate","items":{"type":"number"}},"kind":{"type":"string"},"openings":{"type":"array","items":{"$ref":"#/components/schemas/Opening2DDto"}}},"required":["start","end","kind"]},"Stair2DDto":{"type":"object","properties":{"id":{"type":"string"},"bottom":{"type":"array","description":"[x, z] coordinate","items":{"type":"number"}},"top":{"type":"array","description":"[x, z] coordinate","items":{"type":"number"}},"width":{"type":"number"}},"required":["id","bottom","top","width"]},"FloorPlanStyle":{"type":"string","enum":["DEFAULT","WARM","COOL","NIGHT"],"description":"Visual style/theme for the 3D floor plan"},"Save2dFloorPlanDto":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"},"rooms":{"type":"array","items":{"$ref":"#/components/schemas/Room2DDto"}},"walls":{"type":"array","items":{"$ref":"#/components/schemas/Wall2DDto"}},"stairs":{"type":"array","items":{"$ref":"#/components/schemas/Stair2DDto"}},"style":{"description":"Visual style/theme for the 3D floor plan","default":"DEFAULT","allOf":[{"$ref":"#/components/schemas/FloorPlanStyle"}]}},"required":["width","height","rooms","walls","stairs"]},"Plan2dStatus":{"type":"string","enum":["none","draft","ready"]},"FloorPlan":{"type":"object","properties":{"_id":{"type":"string"},"zone":{"type":"string","description":"Zone this floor plan belongs to"},"system":{"type":"string","description":"System this floor plan belongs to"},"name":{"type":"string","description":"Display name for this floor plan"},"schemaVersion":{"type":"number","description":"Floor plan JSON schema version","example":1},"plan2d":{"type":"object","description":"2D geometry before 3D conversion"},"plan2dStatus":{"allOf":[{"$ref":"#/components/schemas/Plan2dStatus"}]},"style":{"description":"Visual style/theme for the 3D floor plan","default":"DEFAULT","allOf":[{"$ref":"#/components/schemas/FloorPlanStyle"}]},"roomMappings":{"description":"Mappings from AI room IDs to real Room documents","type":"array","items":{"type":"string"}}},"required":["_id","zone","system","name","schemaVersion","plan2dStatus","style"]},"RenameRoomDto":{"type":"object","properties":{"jsonRoomId":{"type":"string","example":"living"},"newName":{"type":"string","example":"Living Area"}},"required":["jsonRoomId","newName"]},"MapRoomDto":{"type":"object","properties":{"jsonRoomId":{"type":"string","example":"living"},"realRoomId":{"type":"string","description":"Real Room ObjectId — omit or null to unmap"}},"required":["jsonRoomId"]},"RoomDocRefDto":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"}},"required":["_id","name"]},"GetFloorPlanV2RoomDto":{"type":"object","properties":{"_id":{"type":"string"},"room":{"$ref":"#/components/schemas/RoomDocRefDto"},"color":{"type":"string"},"polygon":{"type":"array","items":{"type":"array","items":{"type":"number"}}}},"required":["_id","room","polygon"]},"GetDevicePlacementV2Dto":{"type":"object","properties":{"_id":{"type":"string"},"deviceType":{"allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"device":{"type":"string"},"x":{"type":"number"},"y":{"type":"number"}},"required":["_id","deviceType","device","x","y"]},"GetDistributionBoxPlacementV2Dto":{"type":"object","properties":{"_id":{"type":"string"},"distributionBox":{"type":"string"},"x":{"type":"number"},"y":{"type":"number"}},"required":["_id","distributionBox","x","y"]},"GetFloorPlanV2ResponseDto":{"type":"object","properties":{"_id":{"type":"string"},"zone":{"type":"string"},"system":{"type":"string"},"imageUrl":{"type":"string","description":"CDN URL of the image"},"imageWidth":{"type":"number"},"imageHeight":{"type":"number"},"rooms":{"type":"array","items":{"$ref":"#/components/schemas/GetFloorPlanV2RoomDto"}},"devicePlacements":{"type":"array","items":{"$ref":"#/components/schemas/GetDevicePlacementV2Dto"}},"distributionBoxPlacements":{"type":"array","items":{"$ref":"#/components/schemas/GetDistributionBoxPlacementV2Dto"}},"status":{"allOf":[{"$ref":"#/components/schemas/Plan2dStatus"}]}},"required":["_id","zone","system","imageWidth","imageHeight","rooms","devicePlacements","distributionBoxPlacements","status"]},"OverlayReadingDto":{"type":"object","properties":{"temperature":{"type":"number","description":"Temperature in °C (ENV_SENSOR)"},"humidity":{"type":"number","description":"Humidity in % (ENV_SENSOR)"},"co2":{"type":"number","description":"CO₂ in ppm (ENV_SENSOR)"},"power":{"type":"number","description":"Real-time power draw in W (PLUG/MCB)"},"energy":{"type":"number","description":"Accumulated energy in kWh (PLUG/MCB)"},"status":{"type":"boolean","description":"On/off state (PLUG/MCB/SMART_SWITCH)"},"setpoint":{"type":"number","description":"Aircon setpoint in °C (AIRCON_CONTROL)"},"mode":{"type":"string","description":"Aircon mode: cool/heat/fan/auto (AIRCON_CONTROL)"},"occupancy":{"type":"number","nullable":true,"description":"People count (AIRCON_CONTROL with people counter). null means no reading, not an empty room"},"motionDetected":{"type":"boolean","nullable":true,"description":"Motion detected (AIRCON_CONTROL, MAXWELL_ULTRA only)"},"flowRate":{"type":"number","description":"Flow rate in m³/h (WATER_METER)"}}},"OverlayPlacementDto":{"type":"object","properties":{"_id":{"type":"string"},"deviceType":{"allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"device":{"type":"string"},"deviceName":{"type":"string"},"x":{"type":"number"},"y":{"type":"number"},"reading":{"nullable":true,"type":"object","allOf":[{"$ref":"#/components/schemas/OverlayReadingDto"}]}},"required":["_id","deviceType","device","deviceName","x","y"]},"OverlayDistributionBoxPlacementDto":{"type":"object","properties":{"_id":{"type":"string"},"distributionBox":{"type":"string"},"name":{"type":"string"},"x":{"type":"number"},"y":{"type":"number"}},"required":["_id","distributionBox","name","x","y"]},"FloorPlanV2OverlayResponseDto":{"type":"object","properties":{"imageWidth":{"type":"number","description":"Image width in pixels — use to size the canvas"},"imageHeight":{"type":"number","description":"Image height in pixels — use to size the canvas"},"placements":{"type":"array","items":{"$ref":"#/components/schemas/OverlayPlacementDto"}},"distributionBoxPlacements":{"type":"array","items":{"$ref":"#/components/schemas/OverlayDistributionBoxPlacementDto"}}},"required":["imageWidth","imageHeight","placements","distributionBoxPlacements"]},"RoomDrawingV2Dto":{"type":"object","properties":{"room":{"type":"string","description":"ID of the existing Room document this drawing represents"},"color":{"type":"string","description":"Hex fill colour for canvas rendering","example":"#4A90E2"},"polygon":{"type":"array","description":"CCW polygon vertices as [x, y] pixel pairs. Minimum 3 points. Origin top-left.","items":{"type":"array","items":{"type":"number"}}}},"required":["room","polygon"]},"UpsertRoomsV2Dto":{"type":"object","properties":{"rooms":{"description":"Full replacement array of room drawings. Send empty array to clear.","type":"array","items":{"$ref":"#/components/schemas/RoomDrawingV2Dto"}}},"required":["rooms"]},"PlacementType":{"type":"string","enum":["PLUG","MCB","ENV_SENSOR","SMART_SWITCH","AIRCON_CONTROL","WATER_METER","RIDGE","EXTERNAL","DISTRIBUTION_BOX"],"description":"Placement type — DISTRIBUTION_BOX places a distribution box; all others place a Device"},"DevicePlacementV2Dto":{"type":"object","properties":{"deviceType":{"description":"Placement type — DISTRIBUTION_BOX places a distribution box; all others place a Device","allOf":[{"$ref":"#/components/schemas/PlacementType"}]},"device":{"type":"string","description":"Device or DistributionBox ID — must belong to this system"},"x":{"type":"number","description":"Pixel distance from left edge of image"},"y":{"type":"number","description":"Pixel distance from top edge of image"}},"required":["deviceType","device","x","y"]},"UpsertDevicePlacementsV2Dto":{"type":"object","properties":{"devicePlacements":{"description":"Full replacement array of device placements. Send empty array to clear all pins.","type":"array","items":{"$ref":"#/components/schemas/DevicePlacementV2Dto"}}},"required":["devicePlacements"]},"AssetTypeResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Asset type ID"},"name":{"type":"string","description":"Unique type key","example":"WiFi Router"},"displayName":{"type":"string","description":"Human-readable display name","example":"WiFi Router"},"description":{"type":"string","description":"What this class of asset is for"},"assetCount":{"type":"number","description":"Number of assets across every system currently using this type","example":3}},"required":["_id","name","displayName","assetCount"]},"AssetTypeListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AssetTypeResponseDto"}},"total":{"type":"number","example":5}},"required":["data","total"]},"CreateAssetTypeDto":{"type":"object","properties":{"name":{"type":"string","description":"Unique type key, unique across the platform","example":"WiFi Router"},"displayName":{"type":"string","description":"Human-readable display name","example":"WiFi Router"},"description":{"type":"string","description":"What this class of asset is for","example":"Provides wireless network coverage for Ecovolt devices."}},"required":["name","displayName"]},"UpdateAssetTypeDto":{"type":"object","properties":{"displayName":{"type":"string","description":"Human-readable display name","example":"WiFi Router"},"description":{"type":"string","description":"What this class of asset is for","example":"Provides wireless network coverage for Ecovolt devices."}}},"CreateAssetDto":{"type":"object","properties":{"name":{"type":"string","description":"Optional label for this specific unit. Omit it to fall back to the AssetType display name.","example":"Main Uplink Router"},"assetType":{"type":"string","description":"Asset type key. Must match an existing AssetType (see GET /asset-type).","example":"WiFi Router"},"notes":{"type":"string","description":"Everything specific to this unit in this system — where it sits, how it is wired, install quirks. The universal description of the equipment lives on the AssetType.","example":"Level 3 Storeroom, mounted above the ceiling panel. Feeds the east wing APs."},"serialNumber":{"type":"string","description":"Manufacturer serial number","example":"SN-8842-XK"},"installedAt":{"type":"string","description":"Date the asset was installed on site (ISO 8601)","example":"2026-03-14T00:00:00.000Z"},"room":{"type":"string","description":"Id of the room this asset is installed in. Must be a room in the same system. Omit it for an asset that sits outside any room.","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"files":{"type":"array","description":"Install photos (max 10MB each, images only)","items":{"type":"string","format":"binary"}}},"required":["assetType"]},"AssetTypeSummaryDto":{"type":"object","properties":{"name":{"type":"string","description":"Asset type key","example":"WiFi Router"},"displayName":{"type":"string","description":"Human-readable display name, shared across all systems","example":"WiFi Router"},"description":{"type":"string","description":"Universal description of what this class of equipment is for, shared across all systems","example":"Provides wireless network coverage for Ecovolt devices."}},"required":["name","displayName"]},"AssetRoomSummaryDto":{"type":"object","properties":{"_id":{"type":"string","description":"Room ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"Room name","example":"Level 3 Storeroom"}},"required":["_id","name"]},"GetAssetDto":{"type":"object","properties":{"_id":{"type":"string","description":"Asset ID"},"name":{"type":"string","description":"Label for this unit. Falls back to the AssetType displayName when the asset has no name of its own, so this is always safe to render.","example":"Main Uplink Router"},"customName":{"type":"string","description":"The per-unit label as stored, absent when the asset relies on the type name. Use `name` to display.","example":"Main Uplink Router"},"assetType":{"description":"Universal, catalogue-level facts about this asset type","allOf":[{"$ref":"#/components/schemas/AssetTypeSummaryDto"}]},"notes":{"type":"string","description":"Everything specific to this unit in this system — where it sits, how it is wired, install quirks. The universal description lives on assetType.description.","example":"Level 3 Storeroom, mounted above the ceiling panel. Feeds the east wing APs."},"serialNumber":{"type":"string","description":"Manufacturer serial number","example":"SN-8842-XK"},"installedAt":{"format":"date-time","type":"string","description":"Date the asset was installed on site"},"room":{"description":"Room this asset is installed in. Absent when the asset is not linked to a room, or when the linked room has since been deleted.","allOf":[{"$ref":"#/components/schemas/AssetRoomSummaryDto"}]},"imageKeys":{"description":"S3 object keys of the install photos","type":"array","items":{"type":"string"}},"imageUrls":{"description":"CDN URLs of the install photos, in the same order as keys","type":"array","items":{"type":"string"}},"system":{"type":"string","description":"System ID this asset belongs to"},"systemName":{"type":"string","description":"Name of the owning system. Only set on cross-system reads, where the caller has no single system in context."},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"}},"required":["_id","name","assetType","imageKeys","imageUrls","system","createdAt"]},"GetAssetsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of asset objects matching the query parameters","type":"array","items":{"$ref":"#/components/schemas/GetAssetDto"}}},"required":["total","page","size","totalPages","data"]},"UpdateAssetDto":{"type":"object","properties":{"name":{"type":"string","description":"Label for this specific unit. Send an empty string to clear it and fall back to the AssetType display name.","example":"Main Uplink Router"},"assetType":{"type":"string","description":"Asset type key. Must match an existing AssetType (see GET /asset-type).","example":"WiFi Router"},"notes":{"type":"string","description":"Everything specific to this unit in this system — where it sits, how it is wired, install quirks. The universal description of the equipment lives on the AssetType.","example":"Level 3 Storeroom, mounted above the ceiling panel. Feeds the east wing APs."},"serialNumber":{"type":"string","description":"Manufacturer serial number","example":"SN-8842-XK"},"installedAt":{"type":"string","description":"Date the asset was installed on site (ISO 8601). Send an empty string to clear it.","example":"2026-03-14T00:00:00.000Z"},"room":{"type":"string","description":"Id of the room this asset is installed in. Must be a room in the same system. Send an empty string to unlink the asset from its room.","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"existingImageKeys":{"description":"S3 keys of existing images to keep. Any current image not listed here is deleted from S3. Omit the field to keep every existing image; send it empty to drop them all.","example":["assets/6f1c1f6a-6f2b-4c1e-9a7a-1f2c3d4e5f60.jpg"],"type":"array","items":{"type":"string"}},"files":{"type":"array","description":"New install photos to append (max 10MB each, images only)","items":{"type":"string","format":"binary"}}}},"GetCreditDto":{"type":"object","properties":{"roomId":{"type":"string","description":"Room ID"},"systemId":{"type":"string","description":"System ID"},"balance":{"type":"number","description":"Current balance in SGD"},"updatedAt":{"format":"date-time","type":"string","description":"Last updated timestamp"}},"required":["roomId","systemId","balance","updatedAt"]},"TopupCreditDto":{"type":"object","properties":{"amount":{"type":"number","description":"Amount to top up in SGD (min $5)","example":10}},"required":["amount"]},"TopupCreditResponseDto":{"type":"object","properties":{"balance":{"type":"number","description":"Updated balance after top-up"},"amount":{"type":"number","description":"Amount that was topped up"},"transactionId":{"type":"string","description":"Transaction ID"}},"required":["balance","amount","transactionId"]},"CreditTransactionType":{"type":"string","enum":["TOPUP","USAGE_DEDUCTION","MANUAL_ADJUSTMENT"]},"CreditTransactionStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"]},"GetCreditTransactionDto":{"type":"object","properties":{"id":{"type":"string","description":"Transaction ID"},"roomId":{"type":"string"},"type":{"allOf":[{"$ref":"#/components/schemas/CreditTransactionType"}]},"status":{"allOf":[{"$ref":"#/components/schemas/CreditTransactionStatus"}]},"amount":{"type":"number","description":"Amount in SGD"},"balanceAfter":{"type":"number","description":"Balance after transaction"},"description":{"type":"string"},"kwhConsumed":{"type":"number","description":"kWh consumed (USAGE_DEDUCTION only)"},"createdAt":{"format":"date-time","type":"string"}},"required":["id","roomId","type","status","amount","balanceAfter","createdAt"]},"GetCreditTransactionsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of transactions","type":"array","items":{"$ref":"#/components/schemas/GetCreditTransactionDto"}}},"required":["total","page","size","totalPages","data"]},"ReleaseNotesRepository":{"type":"string","enum":["software","hardware"]},"ReleaseNoteDto":{"type":"object","properties":{"tagName":{"type":"string","description":"Git tag version of the release","example":"v1.2.0"},"title":{"type":"string","description":"Title of the release (usually same as tag or human readable)","example":"Version 1.2.0"},"body":{"type":"string","description":"Full markdown content of the release note","example":"## 🚀 Features\n- Added dashboard\n- Improved sync\n\n## 🐛 Fixes\n- Fixed login bug"},"publishedAt":{"format":"date-time","type":"string","description":"Date when the release was published","example":"2024-07-06T10:00:00Z"},"url":{"type":"string","description":"URL linking to the release on GitHub","example":"https://github.com/ecovolt-sg/ecovolt-external-apis/releases/tag/v1.2.0"},"developerName":{"type":"string","description":"Name of the developer who created the release","example":"Glenn Quah Han Rong"},"developerUrl":{"type":"string","description":"URL of the developer's GitHub profile","example":"https://github.com/glennquahhanrong"},"developerAvatarUrl":{"type":"string","description":"Avatar URL of the developer who created the release","example":"https://avatars.githubusercontent.com/u/12345678?v=4"}},"required":["tagName","title","body","publishedAt","url","developerName","developerUrl","developerAvatarUrl"]},"ReportingApp":{"type":"string","enum":["BASE","TREK","LANDING_PAGE","EVEREST","EMULATOR","VISTA"],"description":"Which product the feedback is about"},"FeedbackType":{"type":"string","enum":["bug","feature","improvement","general"],"description":"What kind of submission this is"},"CreateProductFeedbackDto":{"type":"object","properties":{"product":{"description":"Which product the feedback is about","allOf":[{"$ref":"#/components/schemas/ReportingApp"}]},"type":{"description":"What kind of submission this is","allOf":[{"$ref":"#/components/schemas/FeedbackType"}]},"title":{"type":"string","description":"Short summary of the feedback"},"details":{"type":"string","description":"The full description the reporter wrote"},"urgency":{"type":"number","description":"How urgent the reporter thinks this is (1-5). A hint only — triage sets the real ClickUp priority.","minimum":1,"maximum":5},"contactNumber":{"type":"string","description":"Number to follow up on, if the reporter opted in"},"pageUrl":{"type":"string","description":"Page the reporter was on. Captured by the app, not asked."},"appVersion":{"type":"string","description":"Frontend build version. Captured by the app, not asked."},"userAgent":{"type":"string","description":"Reporter user agent. Captured by the app, not asked."},"systemName":{"type":"string","description":"System in context, when the app has one."},"files":{"type":"array","description":"Supporting files — up to 5, 10 MB each","items":{"type":"string","format":"binary"}}},"required":["product","type","title","details"]},"ProductFeedbackDto":{"type":"object","properties":{"clickupTaskId":{"type":"string","description":"Id of the ClickUp task created for this submission","example":"86abc1234"},"clickupUrl":{"type":"string","description":"Direct link to the task in ClickUp","example":"https://app.clickup.com/t/86abc1234"},"notified":{"type":"boolean","description":"Whether the Telegram notification was sent. False does not mean the submission failed — the ClickUp task is the record.","example":true}},"required":["clickupTaskId","clickupUrl","notified"]},"ProductReviewPromptDto":{"type":"object","properties":{"show":{"type":"boolean","description":"Whether the quarterly review dialog should be shown to this account now. False once they have reviewed or dismissed this app for the current quarter.","example":true},"periodKey":{"type":"string","description":"Calendar quarter this answer applies to (Singapore). Returned for display and debugging — the client never sends it back.","example":"2026-Q3"}},"required":["show","periodKey"]},"CreateProductReviewDto":{"type":"object","properties":{"app":{"description":"App being reviewed — the one the dialog was shown in","example":"EVEREST","allOf":[{"$ref":"#/components/schemas/ReportingApp"}]},"rating":{"type":"number","description":"Star rating the account gave, 1-5","minimum":1,"maximum":5,"example":4},"comments":{"type":"string","description":"Free-text comment. Optional — a rating alone is a valid review.","example":"Dashboard is much faster this quarter."}},"required":["app","rating"]},"ProductReviewDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the stored review","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"app":{"description":"App that was reviewed","example":"EVEREST","allOf":[{"$ref":"#/components/schemas/ReportingApp"}]},"periodKey":{"type":"string","description":"Calendar quarter the review was collected for (Singapore)","example":"2026-Q3"},"rating":{"type":"number","description":"Star rating the account gave, 1-5","example":4},"comments":{"type":"string","description":"Free-text comment, when the account wrote one","example":"Dashboard is much faster this quarter."},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the review was submitted"}},"required":["_id","app","periodKey","rating","createdAt"]},"DismissProductReviewDto":{"type":"object","properties":{"app":{"description":"App whose prompt is being dismissed","example":"EVEREST","allOf":[{"$ref":"#/components/schemas/ReportingApp"}]}},"required":["app"]},"WifiConfigStatus":{"type":"string","enum":["CONFIRMED","PENDING","FAILED"]},"WifiDeviceStatusDto":{"type":"object","properties":{"deviceId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","example":"F0:F5:BD:89:D5:00"},"deviceType":{"example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"desiredSsid":{"type":"string","description":"SSID the device was last told to join","example":"Corp-IoT"},"reportedSsid":{"type":"string","description":"SSID the device last reported being connected to","example":"Corp-IoT-Old"},"wifiError":{"type":"string","description":"Reason the last delta failed on the device, when status is FAILED","example":"join_failed"},"status":{"example":"PENDING","allOf":[{"$ref":"#/components/schemas/WifiConfigStatus"}]}},"required":["deviceId","macId","deviceType","desiredSsid","status"]},"WifiDeviceStatusResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"type":"array","items":{"$ref":"#/components/schemas/WifiDeviceStatusDto"}},"pendingCount":{"type":"number","description":"Devices in the system not yet converged to the desired SSID. Counted across the whole system, not just this page.","example":7}},"required":["total","page","size","totalPages","data","pendingCount"]},"CreateSubscriptionTierDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the subscription tier. Used for identification and display to customers.","example":"Premium"},"description":{"type":"string","description":"Detailed description of what the subscription tier includes. Helps customers understand the value proposition.","example":"Access to all premium features including advanced analytics, unlimited users, and priority support"},"features":{"description":"Array of feature identifiers included in this subscription tier. Determines which features are accessible to subscribers.","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}}},"required":["name","description","features"]},"UpdateSubscriptionTierDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the subscription tier. Used for identification and display to customers.","example":"Premium"},"description":{"type":"string","description":"Detailed description of what the subscription tier includes. Helps customers understand the value proposition.","example":"Access to all premium features including advanced analytics, unlimited users, and priority support"},"features":{"description":"Array of feature identifiers included in this subscription tier. Determines which features are accessible to subscribers.","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}}},"required":["name","description","features"]},"TelegramStatusResponseDto":{"type":"object","properties":{"connected":{"type":"boolean","description":"Whether this account has a personal Telegram chat linked","example":true},"groupName":{"type":"string","description":"Name of the linked Telegram group","example":"EcoVolt Alerts"}},"required":["connected"]},"TelegramPairingCodeResponseDto":{"type":"object","properties":{"code":{"type":"string","description":"Short pairing code the admin sends in their Telegram group","example":"ECO-A3B7"},"expiresIn":{"type":"number","description":"Seconds until the code expires","example":600}},"required":["code","expiresIn"]},"TelegramWebhookDto":{"type":"object","properties":{}},"RegisterWebhookResponseDto":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether the request was successful","example":true},"result":{"type":"boolean","description":"Telegram API result","example":true},"description":{"type":"string","description":"Description from Telegram","example":"Webhook was set"}},"required":["ok","result"]},"ForecastGranularity":{"type":"string","enum":["HOURLY","DAILY"]},"ForecastPointDto":{"type":"object","properties":{"hour":{"type":"string","description":"ISO 8601 UTC timestamp"},"kWh":{"type":"number","description":"Predicted energy usage (kWh)"},"lower":{"type":"number","description":"Lower confidence bound (kWh)"},"upper":{"type":"number","description":"Upper confidence bound (kWh)"}},"required":["hour","kWh","lower","upper"]},"ForecastReason":{"type":"string","enum":["insufficient_data","no_forecast_available"],"description":"Set only when predictions is empty. `insufficient_data`: device has < 30 days of usage history (expected for new devices). `no_forecast_available`: device qualifies but no fresh forecast doc exists (precompute failed or has not yet run for this device)."},"DeviceForecastResponseDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Unique identifier for the device"},"predictions":{"description":"Forecast data points for the device","type":"array","items":{"$ref":"#/components/schemas/ForecastPointDto"}},"modelVersion":{"type":"string","description":"Model version"},"granularity":{"type":"string","description":"Granularity of returned data"},"hasSufficientData":{"type":"boolean","description":"Whether this device has sufficient data for forecasting"},"reason":{"description":"Set only when predictions is empty. `insufficient_data`: device has < 30 days of usage history (expected for new devices). `no_forecast_available`: device qualifies but no fresh forecast doc exists (precompute failed or has not yet run for this device).","allOf":[{"$ref":"#/components/schemas/ForecastReason"}]},"daysUntilEligible":{"type":"number","description":"Only present when `reason = insufficient_data`. Number of additional days of usage history needed before the device becomes eligible."}},"required":["deviceId","predictions","modelVersion","granularity","hasSufficientData"]},"AggregatedForecastResponseDto":{"type":"object","properties":{"entityId":{"type":"string","description":"Entity ID (zone, tenant, or system)"},"entityType":{"type":"string","description":"Entity type (zone, tenant, or system)"},"predictions":{"description":"Aggregated forecast (sum of all device forecasts under this entity)","type":"array","items":{"$ref":"#/components/schemas/ForecastPointDto"}},"totalDevices":{"type":"number","description":"Total devices included in aggregation"},"devicesWithSufficientData":{"type":"number","description":"Devices that contributed predictions to the aggregated forecast"},"devicesWithInsufficientData":{"type":"number","description":"Devices with < 30 days of usage history"},"devicesWithoutForecast":{"type":"number","description":"Devices that qualify (>= 30 days) but have no fresh forecast doc — precompute has not yet reached them, or failed for those devices"},"granularity":{"type":"string","description":"Granularity of returned data"}},"required":["entityId","entityType","predictions","totalDevices","devicesWithSufficientData","devicesWithInsufficientData","devicesWithoutForecast","granularity"]},"TrainingStatus":{"type":"string","enum":["ok","failed"],"description":"Outcome of the training run"},"TrainingStatusDto":{"type":"object","properties":{"status":{"description":"Outcome of the training run","example":"ok","allOf":[{"$ref":"#/components/schemas/TrainingStatus"}]},"completedAt":{"type":"string","description":"ISO 8601 timestamp of when training completed","example":"2026-05-18T10:30:00.000Z"},"error":{"type":"string","description":"Error message if training failed","example":"Insufficient data for model convergence"}},"required":["status","completedAt"]},"PrecomputeStatusDto":{"type":"object","properties":{"completedAt":{"type":"string","description":"ISO 8601 timestamp of when precompute completed"},"ok":{"type":"number","description":"Forecast documents successfully written"},"failed":{"type":"number","description":"Devices that failed during precompute"},"eligibleDevices":{"type":"number","description":"Total eligible devices processed"},"skippedDevices":{"type":"number","description":"Devices skipped due to insufficient data"}},"required":["completedAt","ok","failed","eligibleDevices","skippedDevices"]},"ErrorSource":{"type":"string","enum":["FRONTEND","BACKEND"],"description":"Origin of the error"},"HttpRequestMethod":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"],"description":"HTTP method of the failed request"},"CreateErrorLogDto":{"type":"object","properties":{"source":{"description":"Origin of the error","example":"FRONTEND","allOf":[{"$ref":"#/components/schemas/ErrorSource"}]},"reportedApp":{"description":"Frontend app the error was reported from (FRONTEND errors)","example":"EVEREST","allOf":[{"$ref":"#/components/schemas/ReportingApp"}]},"pageUrl":{"type":"string","description":"Frontend page URL where the error occurred (FRONTEND errors)","example":"https://app.ecovolt.sg/system/64b7/dashboard"},"httpMethod":{"description":"HTTP method of the failed request","example":"GET","allOf":[{"$ref":"#/components/schemas/HttpRequestMethod"}]},"requestUrl":{"type":"string","description":"URL of the HTTP request that failed","example":"/api/plug/system/64b7/list"},"system":{"type":"string","description":"System the account was operating in when the error occurred","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"errorMessage":{"type":"string","description":"Description of the error","example":"Network Error: Failed to fetch plug list"},"statusCode":{"type":"number","description":"HTTP status code received. Omitted when no response was involved — an uncaught JavaScript error or a rejected promise never had one.","example":500},"stackTrace":{"type":"string","description":"Stack trace string (BACKEND errors)"}},"required":["source","httpMethod","requestUrl","errorMessage"]},"ErrorLogSystemRefDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","example":"Ecovolt HQ"}},"required":["_id","systemName"]},"ErrorLogAdminRefDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","example":"Jett Tan"},"email":{"type":"string","example":"jett@ecovolt.sg"}},"required":["_id","name","email"]},"GetErrorLogDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the error log","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"source":{"description":"Origin of the error","example":"FRONTEND","allOf":[{"$ref":"#/components/schemas/ErrorSource"}]},"reportedApp":{"description":"Frontend app the error was reported from (FRONTEND only)","example":"EVEREST","allOf":[{"$ref":"#/components/schemas/ReportingApp"}]},"pageUrl":{"type":"string","description":"Frontend page URL where the error occurred (FRONTEND only)","example":"https://app.ecovolt.sg/system/64b7/dashboard"},"httpMethod":{"description":"HTTP method of the failed request","example":"GET","allOf":[{"$ref":"#/components/schemas/HttpRequestMethod"}]},"requestUrl":{"type":"string","description":"URL of the HTTP request that failed","example":"/api/plug/system/64b7/list"},"system":{"description":"System the account was operating in when the error occurred","allOf":[{"$ref":"#/components/schemas/ErrorLogSystemRefDto"}]},"admin":{"description":"Admin (Account) that encountered the error — present for FRONTEND errors, absent for BACKEND errors","allOf":[{"$ref":"#/components/schemas/ErrorLogAdminRefDto"}]},"errorMessage":{"type":"string","description":"Description of the error","example":"Network Error: Failed to fetch plug list"},"statusCode":{"type":"number","description":"HTTP status code received. Absent when no response was involved — an uncaught JavaScript error or a rejected promise never had one.","example":500},"stackTrace":{"type":"string","description":"Stack trace string (BACKEND errors only)"},"solved":{"type":"boolean","description":"Whether this error has been resolved","example":false},"resolvedAt":{"format":"date-time","type":"string","description":"Timestamp when the error was marked as resolved","example":"2026-06-02T10:00:00.000Z"},"resolvedBy":{"description":"Admin (SUPERADMIN) who resolved the error","allOf":[{"$ref":"#/components/schemas/ErrorLogAdminRefDto"}]},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when the log was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when the log was last updated"}},"required":["_id","source","httpMethod","requestUrl","errorMessage","solved","createdAt","updatedAt"]},"GetErrorLogsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of error log entries","type":"array","items":{"$ref":"#/components/schemas/GetErrorLogDto"}}},"required":["total","page","size","totalPages","data"]},"BulkUpdateErrorLogsDto":{"type":"object","properties":{"errorLogIds":{"description":"Array of error log IDs to update","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}},"solved":{"type":"boolean","description":"Whether to mark the error logs as solved or unsolved","example":true}},"required":["errorLogIds","solved"]},"FailedErrorLogOperationDto":{"type":"object","properties":{"errorLogId":{"type":"string","description":"ID of the error log that failed"},"reason":{"type":"string","description":"Reason the operation failed on this error log"}},"required":["errorLogId","reason"]},"BulkUpdateErrorLogsResponseDto":{"type":"object","properties":{"successCount":{"type":"number","description":"Number of error logs successfully updated"},"failedCount":{"type":"number","description":"Number of error logs that failed to update"},"successful":{"description":"Successfully updated error logs","type":"array","items":{"$ref":"#/components/schemas/GetErrorLogDto"}},"failed":{"description":"Error logs that failed to update with reasons","type":"array","items":{"$ref":"#/components/schemas/FailedErrorLogOperationDto"}}},"required":["successCount","failedCount","successful","failed"]},"BulkDeleteErrorLogsDto":{"type":"object","properties":{"errorLogIds":{"description":"Array of error log IDs to delete","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}}},"required":["errorLogIds"]},"BulkDeleteErrorLogsResponseDto":{"type":"object","properties":{"successCount":{"type":"number","description":"Number of error logs successfully deleted"},"failedCount":{"type":"number","description":"Number of error logs that failed to delete"},"successful":{"description":"IDs of error logs successfully deleted","type":"array","items":{"type":"string"}},"failed":{"description":"Error logs that failed to delete with reasons","type":"array","items":{"$ref":"#/components/schemas/FailedErrorLogOperationDto"}}},"required":["successCount","failedCount","successful","failed"]},"PublicDatapoint":{"type":"string","enum":["REALTIME_POWER","TEMPERATURE","HUMIDITY","WATER_FLOW"],"description":"Public datapoints to enable. TEMPERATURE/HUMIDITY need an ENV_SENSOR (Euna, Kelvin) or AIRCON_CONTROL (Maxwell Pro/Ultra) in the target. Defaults to [REALTIME_POWER] if omitted."},"NfcMode":{"type":"string","enum":["CONTROL","VIEW"],"description":"Whether this link controls the target or only shows a public view. Defaults to CONTROL if omitted, for compatibility with callers that predate this field."},"GenerateNfcTokenDto":{"type":"object","properties":{"publicDatapoints":{"type":"array","description":"Public datapoints to enable. TEMPERATURE/HUMIDITY need an ENV_SENSOR (Euna, Kelvin) or AIRCON_CONTROL (Maxwell Pro/Ultra) in the target. Defaults to [REALTIME_POWER] if omitted.","items":{"$ref":"#/components/schemas/PublicDatapoint"}},"rangeDays":{"type":"number","description":"How many days of history to show, for every enabled datapoint. Defaults to 2 if omitted.","minimum":1,"maximum":14,"example":1},"expiresAt":{"format":"date-time","type":"string","description":"When this link stops working. Omit for a token that never expires — prefer setting it for any tag that is handed out temporarily.","example":"2026-12-31T23:59:59.000Z"},"mode":{"description":"Whether this link controls the target or only shows a public view. Defaults to CONTROL if omitted, for compatibility with callers that predate this field.","example":"CONTROL","allOf":[{"$ref":"#/components/schemas/NfcMode"}]}}},"NfcTargetType":{"type":"string","enum":["ROOM","DEVICE","ZONE","TENANT"],"description":"Target type"},"GenerateNfcTokenResponseDto":{"type":"object","properties":{"token":{"type":"string","description":"Encrypted NFC token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},"publicUrl":{"type":"string","description":"Public URL for NFC page","example":"http://localhost:3000/nfc/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},"nfcId":{"type":"string","description":"NFC document ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"targetType":{"description":"Target type","example":"ROOM","allOf":[{"$ref":"#/components/schemas/NfcTargetType"}]},"targetId":{"type":"string","description":"Target ID (room or device)","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"targetName":{"type":"string","description":"Target name","example":"Conference Room A"},"mode":{"description":"Whether this link controls devices or only shows a public view","example":"CONTROL","allOf":[{"$ref":"#/components/schemas/NfcMode"}]},"publicDatapoints":{"type":"array","description":"Public datapoints enabled for a VIEW-mode link","items":{"$ref":"#/components/schemas/PublicDatapoint"}},"rangeDays":{"type":"number","description":"Days of realtime power history shown on a VIEW-mode link, counted back from now","example":2},"createdAt":{"format":"date-time","type":"string","description":"Created at timestamp","example":"2026-02-06T08:30:00.000Z"},"expiresAt":{"format":"date-time","type":"string","description":"When this link stops working. Absent if it never expires.","example":"2026-12-31T23:59:59.000Z"}},"required":["token","publicUrl","nfcId","targetType","targetId","targetName","createdAt"]},"NfcViewOptionsDto":{"type":"object","properties":{"publicDatapoints":{"type":"array","description":"Public datapoints to enable. TEMPERATURE/HUMIDITY need an ENV_SENSOR (Euna, Kelvin) or AIRCON_CONTROL (Maxwell Pro/Ultra) in the target. Defaults to [REALTIME_POWER] if omitted.","items":{"$ref":"#/components/schemas/PublicDatapoint"}},"rangeDays":{"type":"number","description":"How many days of history to show, for every enabled datapoint. Defaults to 2 if omitted.","minimum":1,"maximum":14,"example":1},"expiresAt":{"format":"date-time","type":"string","description":"When this link stops working. Omit for a token that never expires — prefer setting it for any tag that is handed out temporarily.","example":"2026-12-31T23:59:59.000Z"}}},"NfcTokenInfoDto":{"type":"object","properties":{"nfcId":{"type":"string","description":"NFC document ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"targetType":{"description":"Target type","example":"ROOM","allOf":[{"$ref":"#/components/schemas/NfcTargetType"}]},"targetId":{"type":"string","description":"Target ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"targetName":{"type":"string","description":"Target name","example":"Conference Room A"},"mode":{"description":"Whether this link controls devices or only shows a public view","example":"CONTROL","allOf":[{"$ref":"#/components/schemas/NfcMode"}]},"publicDatapoints":{"type":"array","description":"Public datapoints enabled for a VIEW-mode link","items":{"$ref":"#/components/schemas/PublicDatapoint"}},"rangeDays":{"type":"number","description":"Days of realtime power history shown on a VIEW-mode link, counted back from now","example":2},"usageCount":{"type":"number","description":"Token usage count","example":42},"isActive":{"type":"boolean","description":"Whether token is active","example":true},"token":{"type":"string","description":"Encrypted token string","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},"createdAt":{"format":"date-time","type":"string","description":"Created at timestamp","example":"2026-02-06T08:30:00.000Z"},"lastUsed":{"format":"date-time","type":"string","description":"When this token was last used to control or read a target","example":"2026-09-18T08:30:00.000Z"},"expiresAt":{"format":"date-time","type":"string","description":"Optional expiration date","example":"2026-12-06T08:30:00.000Z"}},"required":["nfcId","targetType","targetId","targetName","usageCount","isActive","token","createdAt"]},"ListNfcTokensResponseDto":{"type":"object","properties":{"tokens":{"description":"List of NFC tokens","type":"array","items":{"$ref":"#/components/schemas/NfcTokenInfoDto"}},"total":{"type":"number","description":"Total number of tokens","example":5}},"required":["tokens","total"]},"NfcToggleDto":{"type":"object","properties":{"onStatus":{"description":"Target device status","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"deviceId":{"type":"string","description":"Optional device ID to toggle specific device in room (room tokens only)","example":"64b7f1e6f9c9d4e5b8a9f7c3"}},"required":["onStatus"]},"NfcToggleResponseDto":{"type":"object","properties":{"successful":{"description":"Successfully toggled device IDs","example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"failed":{"description":"Failed device IDs","example":[],"type":"array","items":{"type":"string"}},"total":{"type":"number","description":"Total devices attempted","example":2},"action":{"description":"Action performed","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetName":{"type":"string","description":"Target name","example":"Conference Room A"}},"required":["successful","failed","total","action","targetName"]},"NfcAnalyticsQueryDto":{"type":"object","properties":{"startDate":{"format":"date-time","type":"string","description":"Start date for analytics query","example":"2026-02-01T00:00:00.000Z"},"endDate":{"format":"date-time","type":"string","description":"End date for analytics query","example":"2026-02-06T23:59:59.999Z"}},"required":["startDate","endDate"]},"NfcTargetInfoDto":{"type":"object","properties":{"id":{"type":"string","description":"Target ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"Target name","example":"Conference Room A"},"type":{"description":"Type of NFC target (DEVICE, ROOM, USER)","example":"ROOM","allOf":[{"$ref":"#/components/schemas/NfcTargetType"}]},"onStatus":{"description":"Device ON/OFF status (only for DEVICE targets)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}},"required":["id","name","type"]},"NfcControllableFeaturesDto":{"type":"object","properties":{"temperature":{"type":"boolean","description":"Whether temperature control is supported","example":true},"mode":{"type":"boolean","description":"Whether operating mode control is supported","example":true},"fan":{"type":"boolean","description":"Whether fan speed control is supported","example":true},"swing":{"type":"boolean","description":"Whether swing control is supported","example":false}},"required":["temperature","mode","fan","swing"]},"NfcGangSwitchStateDto":{"type":"object","properties":{"gangNumber":{"type":"number","description":"Gang number (1-4)","example":1},"onStatus":{"description":"ON/OFF status of this gang","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"label":{"type":"string","description":"Optional label for the gang","example":"Main Light"}},"required":["gangNumber","onStatus"]},"NfcDeviceInfoDto":{"type":"object","properties":{"id":{"type":"string","description":"Device ID","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"Device name","example":"Coffee Machine"},"deviceType":{"description":"Type of device (PLUG, MCB, etc.)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"onStatus":{"description":"Device ON/OFF status","example":"OFF","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"temperature":{"type":"number","description":"Temperature setting in Celsius (AIRCON_CONTROL only)","example":24},"mode":{"description":"Aircon operating mode (AIRCON_CONTROL only)","example":"COOL","allOf":[{"$ref":"#/components/schemas/AirconControlMode"}]},"fan":{"description":"Fan speed setting (AIRCON_CONTROL only)","example":"AUTO","allOf":[{"$ref":"#/components/schemas/AirconControlFan"}]},"swing":{"description":"Swing setting (AIRCON_CONTROL only)","example":"OFF","allOf":[{"$ref":"#/components/schemas/AirconControlSwing"}]},"brand":{"description":"Aircon brand (AIRCON_CONTROL only)","example":"DAIKIN","allOf":[{"$ref":"#/components/schemas/AirconControlBrand"}]},"controllableFeatures":{"description":"Indicates which control features are physically supported by this aircon (AIRCON_CONTROL only)","allOf":[{"$ref":"#/components/schemas/NfcControllableFeaturesDto"}]},"numberOfGangs":{"type":"number","description":"Number of gangs/buttons on the switch (SMART_SWITCH only)","example":2,"minimum":1,"maximum":4},"gangSwitchStates":{"description":"Individual gang switch states (SMART_SWITCH only)","example":[{"gangNumber":1,"onStatus":"ON","label":"Main Light"},{"gangNumber":2,"onStatus":"OFF","label":"Accent Light"}],"type":"array","items":{"$ref":"#/components/schemas/NfcGangSwitchStateDto"}},"switchType":{"description":"Type of appliance the switch controls (SMART_SWITCH only)","example":"LIGHT","allOf":[{"$ref":"#/components/schemas/SwitchType"}]},"totalEnergyUsage":{"type":"number","description":"Total energy usage in watt-hours (PLUG/MCB only)","example":125000}},"required":["id","name","deviceType","onStatus"]},"DateRangeDto":{"type":"object","properties":{"startDate":{"format":"date-time","type":"string","description":"Start date of the queried range","example":"2026-02-01T00:00:00.000Z"},"endDate":{"format":"date-time","type":"string","description":"End date of the queried range","example":"2026-02-06T23:59:59.999Z"}},"required":["startDate","endDate"]},"EnvironmentRealtimeDataPointDto":{"type":"object","properties":{"createdAt":{"format":"date-time","type":"string","description":"Start timestamp of the downsampled bucket","example":"2026-09-18T07:00:00.000Z"},"temperature":{"type":"number","description":"Average temperature for the bucket, in degrees Celsius","example":24.5},"humidity":{"type":"number","description":"Average relative humidity for the bucket, as a percentage","example":65}},"required":["createdAt"]},"EnvironmentRealtimeSeriesDto":{"type":"object","properties":{"deviceName":{"type":"string","example":"Meeting Room Euna"},"deviceType":{"description":"Subtype the readings came from — ENV_SENSOR (Euna/Kelvin) or AIRCON_CONTROL (Maxwell Pro/Ultra ambient readings)","example":"ENV_SENSOR","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"data":{"type":"array","items":{"$ref":"#/components/schemas/EnvironmentRealtimeDataPointDto"}}},"required":["deviceName","deviceType","data"]},"EnvironmentRealtimeResponseDto":{"type":"object","properties":{"series":{"description":"One series per contributing sensor in the target","type":"array","items":{"$ref":"#/components/schemas/EnvironmentRealtimeSeriesDto"}},"averageTemperature":{"type":"number","description":"Mean temperature across the sensors, in degrees Celsius. Omitted when only one sensor reported — its own series is already the answer.","example":24.5},"averageHumidity":{"type":"number","description":"Mean relative humidity across the sensors, as a percentage. Omitted when only one sensor reported.","example":65}},"required":["series"]},"WaterRealtimeDataPointDto":{"type":"object","properties":{"createdAt":{"format":"date-time","type":"string","description":"Start timestamp of the downsampled bucket","example":"2026-09-18T07:00:00.000Z"},"flowRate":{"type":"number","description":"Mean flow rate over the bucket, in m³/h","example":1.4}},"required":["createdAt","flowRate"]},"WaterRealtimeSeriesDto":{"type":"object","properties":{"deviceName":{"type":"string","example":"Pantry Water Meter"},"data":{"type":"array","items":{"$ref":"#/components/schemas/WaterRealtimeDataPointDto"}}},"required":["deviceName","data"]},"WaterRealtimeResponseDto":{"type":"object","properties":{"series":{"description":"One series per contributing water meter in the target","type":"array","items":{"$ref":"#/components/schemas/WaterRealtimeSeriesDto"}},"totalFlowRate":{"type":"number","description":"Flow rate summed across the meters, in m³/h — meters on separate pipes add up rather than averaging. Omitted when only one meter reported.","example":2.8}},"required":["series"]},"NfcAnalyticsResponseDto":{"type":"object","properties":{"target":{"description":"Target information","allOf":[{"$ref":"#/components/schemas/NfcTargetInfoDto"}]},"mode":{"description":"Whether this link is CONTROL or VIEW mode","example":"CONTROL","allOf":[{"$ref":"#/components/schemas/NfcMode"}]},"deviceType":{"description":"Device type (for device-level tokens only)","example":"AIRCON_CONTROL","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"device":{"description":"Device details with current state (for device-level tokens)","allOf":[{"$ref":"#/components/schemas/NfcDeviceInfoDto"}]},"room":{"description":"Room details (for room-level tokens)","allOf":[{"$ref":"#/components/schemas/NfcTargetInfoDto"}]},"usageHistory":{"description":"Usage history data by day with cost breakdown","type":"array","items":{"$ref":"#/components/schemas/UsageHistoryDayWithCostBreakdownDto"}},"dateRange":{"description":"Date range queried","allOf":[{"$ref":"#/components/schemas/DateRangeDto"}]},"devices":{"description":"Device list (only for ROOM tokens, includes all toggleable devices in the room)","type":"array","items":{"$ref":"#/components/schemas/NfcDeviceInfoDto"}},"realtimePower":{"description":"Live realtime power series, present only for VIEW-mode links with REALTIME_POWER enabled. Always covers the token rangeDays back from now, not the requested dateRange.","allOf":[{"$ref":"#/components/schemas/McbRealtimeResponseDto"}]},"environment":{"description":"Live ambient temperature/humidity series from the ENV_SENSOR and AIRCON_CONTROL devices in the target, present on any link that enables TEMPERATURE and/or HUMIDITY. A metric that was not enabled is stripped from every point. Always covers the token rangeDays back from now, not the requested dateRange, which may differ from the window usageHistory was computed over.","allOf":[{"$ref":"#/components/schemas/EnvironmentRealtimeResponseDto"}]},"water":{"description":"Live water flow-rate series from the WATER_METER devices in the target, present only on links that enable WATER_FLOW. Always covers the token rangeDays back from now, not the requested dateRange.","allOf":[{"$ref":"#/components/schemas/WaterRealtimeResponseDto"}]}},"required":["target","usageHistory","dateRange"]},"NfcAirconControlDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Specific device ID to control (for room-level tokens)","example":"507f1f77bcf86cd799439011"},"power":{"description":"Power ON/OFF control","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"temperature":{"type":"number","description":"Temperature setting in Celsius (16-30)","example":24,"minimum":16,"maximum":30},"mode":{"description":"Aircon operating mode","example":"COOL","allOf":[{"$ref":"#/components/schemas/AirconControlMode"}]},"fan":{"description":"Fan speed setting","example":"AUTO","allOf":[{"$ref":"#/components/schemas/AirconControlFan"}]},"swing":{"description":"Swing setting for aircon vents","example":"ON","allOf":[{"$ref":"#/components/schemas/AirconControlSwing"}]}}},"NfcControlResponseDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the control operation was successful","example":true},"message":{"type":"string","description":"Human-readable message describing the result","example":"Device controlled successfully"},"device":{"description":"Updated device information after control operation","allOf":[{"$ref":"#/components/schemas/NfcDeviceInfoDto"}]}},"required":["success","message","device"]},"NfcSmartSwitchControlDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Specific device ID to control (for room-level tokens)","example":"507f1f77bcf86cd799439011"},"power":{"description":"Power ON/OFF control","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"gangNumber":{"type":"number","description":"Gang number to control (1-4). Omit to control all gangs.","example":2,"minimum":1,"maximum":4},"onStatus":{"description":"ON/OFF status for the targeted gang(s)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}}},"NfcControlDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Specific device ID to control (for room-level tokens)","example":"507f1f77bcf86cd799439011"},"power":{"description":"Power ON/OFF control","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}}},"GenerateSignupTokensDto":{"type":"object","properties":{"role":{"description":"Role the created accounts receive, which decides what each claim creates: TENANT → a new Tenant entity in the system, USER → a new Zone under `parentId`, ROOM → a new Room under `parentId`, ADMIN → nothing (the account just joins the system), DEVELOPER → nothing (a sandbox-only account on the system, issued as a 6-digit code instead of a QR token). SUPERADMIN is never accepted.","example":"TENANT","allOf":[{"$ref":"#/components/schemas/Role"}]},"count":{"type":"number","description":"How many stickers to print. Tokens are interchangeable — none is tied to a particular space — so this is simply the number of boxes you are handing out, and it doubles as the cap on how many accounts the batch can create.","minimum":1,"maximum":500,"example":30},"parentId":{"type":"string","description":"The container each claim creates its space inside. Required for USER (a Tenant id) and ROOM (a Zone id), since a Zone needs a parent Tenant and a Room needs a parent Zone. Omit for TENANT and ADMIN.","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"expiresInDays":{"type":"number","description":"Days until the stickers stop working. Set this to the rollout window — a sticker that outlives its handover is a standing credential.","minimum":1,"maximum":365,"default":90,"example":90}},"required":["role","count"]},"GeneratedSignupTokenDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the signup token record","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"role":{"description":"Role the account created from this sticker will receive","example":"TENANT","allOf":[{"$ref":"#/components/schemas/Role"}]},"token":{"type":"string","description":"The plaintext token. Returned once, at generation, and never again.","example":"x7Fq2mR8vLpN4wKdY6bT1sHgJ3cZaQeU5oXiV9nMrPk"},"signupUrl":{"type":"string","description":"Full URL to encode as the QR code. Host depends on `role` — TENANT/USER land on base, ROOM on trek, ADMIN on everest.","example":"https://base.ecovolt.ai/join/x7Fq2mR8vLpN4wKdY6bT1sHgJ3cZaQeU5oXiV9nMrPk"},"expiresAt":{"format":"date-time","type":"string","description":"When the sticker stops working","example":"2026-11-20T08:30:00.000Z"}},"required":["_id","role","token","signupUrl","expiresAt"]},"GenerateSignupTokensResponseDto":{"type":"object","properties":{"data":{"description":"One entry per printable sticker. Capture these now — the plaintext tokens are not retrievable later.","type":"array","items":{"$ref":"#/components/schemas/GeneratedSignupTokenDto"}}},"required":["data"]},"SignupTokenStatus":{"type":"string","enum":["UNUSED","SCANNED","CONSUMED","EXPIRED","REVOKED"]},"GetSignupTokenDto":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the signup token record","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"role":{"description":"Role the account created from this sticker receives","example":"TENANT","allOf":[{"$ref":"#/components/schemas/Role"}]},"status":{"description":"Derived lifecycle state","example":"UNUSED","allOf":[{"$ref":"#/components/schemas/SignupTokenStatus"}]},"createdEntityLabel":{"type":"string","description":"Name of the space this sticker created when it was claimed. Absent until then — stickers are interchangeable and name nothing in advance.","example":"Block 123 #04-12"},"createdEntityId":{"type":"string","description":"ID of the space created on claim","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"claimedByEmail":{"type":"string","description":"Email of the account that claimed this sticker","example":"felicia@gmail.com"},"firstScannedAt":{"format":"date-time","type":"string","description":"When the sticker was first scanned — the gap to now tells the operator whether a box was ever opened","example":"2026-08-22T08:30:00.000Z"},"consumedAt":{"format":"date-time","type":"string","description":"When an account was created from this sticker","example":"2026-08-22T08:34:00.000Z"},"expiresAt":{"format":"date-time","type":"string","description":"When the sticker stops working","example":"2026-11-20T08:30:00.000Z"},"createdAt":{"format":"date-time","type":"string","description":"When the sticker was generated","example":"2026-08-22T08:00:00.000Z"}},"required":["_id","role","status","expiresAt","createdAt"]},"GetSignupTokensResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Signup tokens matching the query parameters","type":"array","items":{"$ref":"#/components/schemas/GetSignupTokenDto"}}},"required":["total","page","size","totalPages","data"]},"SignupLinkStatus":{"type":"string","enum":["ACTIVE","CONSUMED","EXPIRED"],"description":"Which of the three screens the page should render"},"SignupLinkInfoDto":{"type":"object","properties":{"status":{"description":"Which of the three screens the page should render","example":"ACTIVE","allOf":[{"$ref":"#/components/schemas/SignupLinkStatus"}]},"systemName":{"type":"string","description":"System the account will be created in, for the page heading","example":"Marina Bay Residences"},"role":{"description":"Role the created account will receive","example":"TENANT","allOf":[{"$ref":"#/components/schemas/Role"}]},"requiresSpaceName":{"type":"boolean","description":"Whether the form must collect a name for the space being created. False only for ADMIN links, which create no space.","example":true},"windowExpiresAt":{"format":"date-time","type":"string","description":"When the current activation window closes. Only present while ACTIVE — a form submitted after this is rejected, and the page should tell the user to scan again.","example":"2026-08-22T09:30:00.000Z"},"createdEntityLabel":{"type":"string","description":"Name of the space that was created, once the link has been claimed. Lets the CONSUMED screen say what it registered.","example":"Block 123 #04-12"}},"required":["status","systemName","role","requiresSpaceName"]},"SignUpWithLinkDto":{"type":"object","properties":{"name":{"type":"string","description":"Full name of the person registering","example":"Felicia Chia"},"spaceName":{"type":"string","description":"Name for the space being created — the unit, zone or room this person occupies. Required unless the link reports `requiresSpaceName: false` (ADMIN links, which create no space).","example":"Block 123 #04-12"},"email":{"type":"string","description":"Email address for the new account (must be unique)","example":"felicia@gmail.com"},"password":{"type":"string","description":"Account password (minimum 6 characters)","example":"SecurePass123"},"acceptedTerms":{"type":"boolean","description":"Whether the person accepted the Terms & Conditions. Must be true.","example":true},"app":{"description":"Frontend app the signup came from. A named web app receives the session as httpOnly cookies instead of in the response body.","example":"base","allOf":[{"$ref":"#/components/schemas/FrontendApp"}]}},"required":["name","email","password","acceptedTerms"]},"OAuthSignUpWithLinkDto":{"type":"object","properties":{"credential":{"type":"string","description":"Provider ID token (JWT credential) obtained client-side by the in-page sign-in button. Verified server-side.","example":"eyJhbGciOiJSUzI1NiIsImtpZCI6..."},"spaceName":{"type":"string","description":"Name for the space being created — the unit, zone or room this person occupies. Required unless the link reports `requiresSpaceName: false` (ADMIN links, which create no space).","example":"Block 123 #04-12"},"acceptedTerms":{"type":"boolean","description":"Whether the person accepted the Terms & Conditions. Must be true — the provider cannot consent on their behalf.","example":true},"app":{"description":"Frontend app the signup came from. A named web app receives the session as httpOnly cookies instead of in the response body.","example":"base","allOf":[{"$ref":"#/components/schemas/FrontendApp"}]}},"required":["credential","acceptedTerms"]},"WaterMeterSize":{"type":"string","enum":["DN15","DN20","DN25","DN32","DN40","DN50"],"description":"Pipe diameter size (DN15, DN20, DN25, etc.)"},"WaterMeter":{"type":"object","properties":{"_id":{"type":"string","description":"Primary ID of the Device","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceType":{"description":"Type of device (discriminator field)","example":"PLUG","allOf":[{"$ref":"#/components/schemas/DeviceType"}]},"macId":{"type":"string","description":"MAC ID of the Device (from IoT device)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Name of the Device","example":"Living Room AC"},"deviceNumber":{"type":"number","description":"Unique device number for identification","example":1},"onStatus":{"description":"Status of the Device (ON / OFF / INACTIVE / ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend (cloud / scheduled). Set when a toggle is issued; cleared when the device reports a matching state or a physical (MANUAL) press overrides it. Always ON or OFF when present.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when targetState was last written. Used to expire stale SCHEDULED intents (24hr TTL). CLOUD intents do not expire.","example":"2026-05-25T08:00:00.000Z"},"targetSource":{"description":"Origin of the pending targetState. Drives TTL behavior on reconcile. Only CLOUD / SCHEDULED are ever written here.","example":"CLOUD","allOf":[{"$ref":"#/components/schemas/StatusTriggerSource"}]},"location":{"type":"string","description":"Physical location or address where the device is installed","example":"Room 101, Building A"},"remarks":{"type":"string","description":"Remarks for the Device"},"deviceModel":{"type":"string","description":"Model name or identifier of the device","example":"TP-Link HS110"},"deviceBrand":{"type":"string","description":"Brand or manufacturer of the device","example":"TP-Link"},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups","example":1},"chipModel":{"description":"Hardware chip model identifier used in the device","example":"ESP32C2","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is connected to","example":"IoT-Network-5G"},"wifiError":{"type":"string","description":"Last WiFi shadow delta failure reported by the device (e.g. join_failed); cleared once it reports a successful join","example":"join_failed"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join. Set at send time on both the shadow and legacy changeWifi paths; convergence is `ssid` matching any desired SSID.","example":"Corp-IoT"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join. See {@link wifiDesiredSsid}.","example":"Corp-IoT-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them.","example":["Corp-IoT","Corp-IoT-2","Corp-IoT-3"],"type":"array","items":{"type":"string"}},"config":{"description":"Settings as last reported by the device into its `config` shadow, one block per feature","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"pendingConfig":{"description":"Settings written to the `config` shadow but not yet reported back. A key the device reports at a different value was rejected","allOf":[{"$ref":"#/components/schemas/DeviceConfig"}]},"configRequestedAt":{"format":"date-time","type":"string","description":"When settings were last written to the `config` shadow"},"configReportedAt":{"format":"date-time","type":"string","description":"When the device last reported into its `config` shadow"},"rssi":{"type":"number","description":"Latest WiFi signal strength reported by the device (dBm). Updated on each realtime MQTT report.","example":-65},"tenant":{"description":"Tagged Tenant","allOf":[{"$ref":"#/components/schemas/Tenant"}]},"zone":{"description":"Tagged Zone","allOf":[{"$ref":"#/components/schemas/Zone"}]},"room":{"description":"Tagged Room","allOf":[{"$ref":"#/components/schemas/Room"}]},"system":{"description":"Tagged System","allOf":[{"$ref":"#/components/schemas/System"}]},"isPhaseChild":{"type":"boolean","description":"True when this row is one phase of a split COIL3P rather than a physical unit. Such rows have no real MAC, never report telemetry, and are excluded from firmware, OTA, provisioning and panel-slot views.","example":false},"isRidgeChild":{"type":"boolean","description":"True when this row is a unit behind a RIDGE gateway rather than a directly-connected unit. Such rows have no real MAC, are never fleet-provisioned, and are excluded from firmware, OTA and liveness watchdogs — their gateway carries those.","example":false},"hasPhaseChildren":{"type":"boolean","description":"True when this clamp has been split into per-phase children. Such a clamp is org-detached and excluded from energy and organisational views — its children carry those — while remaining the sole target for firmware, OTA and diagnostics.","example":false},"createdAt":{"format":"date-time","type":"string","description":"Date Time of the Creation","example":"2021-09-01"},"updatedAt":{"format":"date-time","type":"string","description":"Date Time of the Update","example":"2021-09-01"},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device was seen/reported","example":"2021-09-01"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device on/off status cannot be toggled.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"deviceSchedule":{"description":"Schedule configuration for the device. Contains recurring weekly schedules and one-time scheduled tasks.","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the single Schedule (in the schedules collection) this device currently follows. deviceSchedule is compiled from that schedule. When null, the device follows nothing.","example":"507f1f77bcf86cd799439011","nullable":true},"realTimePower":{"type":"number","description":"Real-time power consumption in watts","example":1500},"realTimeCurrent":{"type":"number","description":"Real-time current in amperes","example":6.5},"totalEnergyUsage":{"type":"number","description":"Total cumulative energy usage in kWh","example":0},"totalEnergySaved":{"type":"number","description":"Total energy saved through optimization in kWh","example":0},"autoUpdateAttempts":{"type":"number","description":"Number of auto-update OTA attempts sent to this device","example":0},"autoUpdateBlacklisted":{"type":"boolean","description":"Whether device is blacklisted from auto-updates (max retries exceeded)","example":false},"manualUpdateBlacklist":{"type":"boolean","description":"Whether device is manually blacklisted from auto-updates by admin","example":false},"lastAutoUpdateAttempt":{"format":"date-time","type":"string","description":"Timestamp of last auto-update OTA attempt"},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","default":false},"inactivityNotificationSentAt":{"format":"date-time","type":"string","description":"Timestamp when the last inactivity notification was sent for this device"},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"pendingThresholds":{"type":"object","description":"Threshold limits sent to the device but not yet confirmed by its firmware. Keys match DeviceThresholdsDto. Present only while a save is outstanding — a key that persists here is not being enforced by the hardware.","additionalProperties":{"type":"number"}},"thresholds":{"description":"This device's alert thresholds gathered into one object. Which fields are populated depends on deviceType — see DeviceThresholdsDto. Absent when no thresholds are configured. The same values remain available as flat top-level fields.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"meterSize":{"description":"Pipe diameter size (DN15, DN20, DN25, etc.)","allOf":[{"$ref":"#/components/schemas/WaterMeterSize"}]},"meterModel":{"type":"string","description":"Meter model/manufacturer"},"meterConnected":{"type":"boolean","description":"Whether the meter is physically connected"},"flowRate":{"type":"number","description":"Current flow rate (m³/h)"},"velocity":{"type":"number","description":"Flow velocity (m/s)"},"soundSpeed":{"type":"number","description":"Speed of sound in fluid (m/s)"},"signalQuality":{"type":"number","description":"Ultrasonic signal quality (no units)"},"inletTemperature":{"type":"number","description":"Latest inlet water temperature (°C)"},"outletTemperature":{"type":"number","description":"Latest outlet water temperature (°C)"},"energyFlowRate":{"type":"number","description":"Latest thermal energy flow rate (GJ/h)"},"flowRateMax":{"type":"number","description":"Max threshold flow rate (m³/h)","example":10},"flowRateMin":{"type":"number","description":"Min threshold flow rate (m³/h)","example":0},"inletTemperatureMax":{"type":"number","description":"Max threshold inlet temperature (°C)","example":60},"inletTemperatureMin":{"type":"number","description":"Min threshold inlet temperature (°C)","example":0},"outletTemperatureMax":{"type":"number","description":"Max threshold outlet temperature (°C)","example":60},"outletTemperatureMin":{"type":"number","description":"Min threshold outlet temperature (°C)","example":0},"totalNetVolume":{"type":"number","description":"Total net water volume since device registration (L)","default":0},"isLeakDetected":{"type":"boolean","description":"Whether a leak is currently detected"},"lastLeakNotificationAt":{"format":"date-time","type":"string","description":"Timestamp of last leak notification received"},"leakThresholdFlowRate":{"type":"number","description":"Current leak detection threshold (m³/h)","default":5},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold — minimum hours between repeat notifications of the same type","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000}},"required":["_id","deviceType","macId","deviceName","deviceNumber","onStatus","location","remarks","batchNumber","chipModel","versionNumber","ssid","tenant","zone","room","system","createdAt","updatedAt","lastSeen","isLocked","totalEnergyUsage","totalEnergySaved","autoUpdateAttempts","autoUpdateBlacklisted","manualUpdateBlacklist","inactivityNotificationEnabled","meterSize","flowRate","velocity","soundSpeed","signalQuality","totalNetVolume","isLeakDetected","leakThresholdFlowRate"]},"FilteredWaterMeterDto":{"type":"object","properties":{"_id":{"type":"string","description":"Unique database identifier of the device document in the system","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"macId":{"type":"string","description":"Unique identifier assigned to the device by the IoT hardware (MAC ID)","example":"1092384701928347"},"deviceName":{"type":"string","description":"Friendly name for the device for easy identification","example":"Living Room AC"},"onStatus":{"description":"Current operational status of the device (ON, OFF, INACTIVE, or ERROR)","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetState":{"description":"Pending desired on/off state commanded by backend. Present when a toggle was issued but the device has not yet acknowledged (e.g. issued while INACTIVE). Frontend can derive a \"Pending\" indicator when this differs from onStatus.","example":"ON","allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"targetStateSetAt":{"format":"date-time","type":"string","description":"Timestamp when the pending targetState was last written.","example":"2026-05-25T08:00:00.000Z"},"totalEnergyUsage":{"type":"number","description":"Cumulative total energy consumption in kilowatt-hours (kWh) since device installation","example":500},"deviceNumber":{"type":"number","description":"Auto-incremented sequential number for tracking device registration order","example":3},"batchNumber":{"type":"number","description":"Manufacturing batch number for tracking production groups and quality control","example":23},"chipModel":{"description":"Hardware chip model identifier used in the device (e.g., ESP32C2, ESP8266)","allOf":[{"$ref":"#/components/schemas/DeviceChipModel"}]},"versionNumber":{"type":"string","description":"Firmware version number currently running on the device","example":"1.0.0"},"tenantId":{"type":"string","description":"Unique identifier of the tenant organization that owns this device","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"tenantName":{"type":"string","description":"Name of the tenant organization that owns this device","example":"Faculty of Engineering"},"zoneId":{"type":"string","description":"Unique identifier of the zone that this device is assigned to","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"zoneName":{"type":"string","description":"Name of the zone that this device is assigned to","example":"Zone A"},"roomId":{"type":"string","description":"Unique identifier of the room where this device is physically located","example":"64b7f1e6f9c9d4e5b8a9f7c6"},"roomName":{"type":"string","description":"Name of the room where this device is physically located","example":"Room 101"},"dbId":{"type":"string","description":"Unique identifier of the distribution box that this device is connected to (for MCBs)","example":"64b7f1e6f9c9d4e5b8a9f7c7"},"dbName":{"type":"string","description":"Name of the distribution box that this device is connected to (for MCBs)","example":"Main Distribution Box"},"systemId":{"type":"string","description":"Unique identifier of the system that this device belongs to","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"systemName":{"type":"string","description":"Name of the system that this device belongs to","example":"NUS Engineering Building"},"realTimePower":{"type":"number","description":"Real-time power consumption in watts (W) measured by the device","example":115},"realTimeCurrent":{"type":"number","description":"Real-time current draw in amperes (A) measured by the device","example":0.5},"ssid":{"type":"string","description":"Network SSID (Wi-Fi name) that the device is currently connected to","example":"XH001"},"wifiDesiredSsid":{"type":"string","description":"Primary SSID the device was last told to join","example":"XH001"},"wifiDesiredSsid2":{"type":"string","description":"Secondary SSID the device was last told to join","example":"XH001-2"},"wifiDesiredSsid3":{"type":"string","description":"Tertiary SSID the device was last told to join","example":"XH001-3"},"wifiSsidPriority":{"description":"Desired SSIDs in the order the device should attempt them","example":["XH001","XH001-2","XH001-3"],"type":"array","items":{"type":"string"}},"deviceSchedule":{"description":"Weekly and one-time schedule configuration for automated device control","allOf":[{"$ref":"#/components/schemas/DeviceSchedule"}]},"scheduleId":{"type":"string","description":"Id of the named Schedule this device currently follows. deviceSchedule is compiled from it. Absent or null when the device follows nothing.","nullable":true,"example":"64b7f1e6f9c9d4e5b8a9f7c3"},"lastSeen":{"format":"date-time","type":"string","description":"Timestamp when the device last communicated with the system","example":"2024-10-15T12:30:00.000Z"},"isLocked":{"type":"boolean","description":"Lock status indicating if the device is locked","example":false},"inactivityNotificationEnabled":{"type":"boolean","description":"Whether inactivity notifications are enabled for this device","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled","example":true},"thresholds":{"description":"Alert thresholds configured for this device, gathered into one object. Which fields are populated depends on the device type — see DeviceThresholdsDto. Omitted entirely when nothing is set.","allOf":[{"$ref":"#/components/schemas/DeviceThresholdsDto"}]},"meterSize":{"description":"Pipe diameter size (DN15, DN20, DN25, etc.)","allOf":[{"$ref":"#/components/schemas/WaterMeterSize"}]},"meterModel":{"type":"string","description":"Meter model/manufacturer"},"meterConnected":{"type":"boolean","description":"Whether the meter is physically connected"},"flowRate":{"type":"number","description":"Current flow rate (m³/h)","example":0},"velocity":{"type":"number","description":"Flow velocity (m/s)","example":0},"soundSpeed":{"type":"number","description":"Speed of sound in fluid (m/s)","example":0},"signalQuality":{"type":"number","description":"Ultrasonic signal quality (no units)","example":0},"totalNetVolume":{"type":"number","description":"Total net water volume since device registration (L)","default":0},"isLeakDetected":{"type":"boolean","description":"Whether a leak is currently detected","example":false},"lastLeakNotificationAt":{"format":"date-time","type":"string","description":"Timestamp of last leak notification received"},"leakThresholdFlowRate":{"type":"number","description":"Current leak detection threshold (m³/h)","default":5},"flowRateMax":{"type":"number","description":"Max threshold flow rate (m³/h)","example":10},"flowRateMin":{"type":"number","description":"Min threshold flow rate (m³/h)","example":0},"inletTemperature":{"type":"number","description":"Latest inlet water temperature (°C)","example":25},"outletTemperature":{"type":"number","description":"Latest outlet water temperature (°C)","example":23.5},"energyFlowRate":{"type":"number","description":"Latest thermal energy flow rate (GJ/h)","example":0.05},"inletTemperatureMax":{"type":"number","description":"Max threshold inlet temperature (°C)","example":60},"inletTemperatureMin":{"type":"number","description":"Min threshold inlet temperature (°C)","example":0},"outletTemperatureMax":{"type":"number","description":"Max threshold outlet temperature (°C)","example":60},"outletTemperatureMin":{"type":"number","description":"Min threshold outlet temperature (°C)","example":0}},"required":["_id","macId","deviceName","onStatus","totalEnergyUsage","tenantId","tenantName","zoneId","zoneName","roomId","roomName","dbId","dbName","systemId","systemName","realTimePower","realTimeCurrent","ssid","meterSize","flowRate","velocity","soundSpeed","signalQuality","totalNetVolume","isLeakDetected","leakThresholdFlowRate"]},"GetWaterMetersResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"List of water meters with organizational hierarchy details","type":"array","items":{"$ref":"#/components/schemas/FilteredWaterMeterDto"}}},"required":["total","page","size","totalPages","data"]},"UpdateWaterMeterDto":{"type":"object","properties":{"deviceName":{"type":"string","description":"Name of the device","example":"Living Room Light"},"location":{"type":"string","description":"Physical location of the device","example":"Block 123, Unit 01-23"},"remarks":{"type":"string","description":"Additional remarks or notes about the device","example":"Handle with care - critical equipment"},"tenantId":{"type":"string","description":"Tenant ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"zoneId":{"type":"string","description":"Zone ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c4"},"roomId":{"type":"string","description":"Room ID to associate the device with","example":"64b7f1e6f9c9d4e5b8a9f7c5"},"isLocked":{"type":"boolean","description":"Whether the device is locked. When locked, the device cannot be toggled ON/OFF.","example":false},"isSpare":{"type":"boolean","description":"Whether the device is a spare/inventory device. Spare devices can be filtered out of user-facing dashboards.","example":false},"thresholdEnabled":{"type":"boolean","description":"Whether threshold monitoring is enabled on this device.","default":false},"meterSize":{"description":"Size/diameter of the water meter","allOf":[{"$ref":"#/components/schemas/WaterMeterSize"}]},"meterModel":{"type":"string","description":"Model name/number of the water meter","example":"Sensus 620M"}}},"SetLeakThresholdDto":{"type":"object","properties":{"flowRateMax":{"type":"number","description":"Leak detection threshold flow rate (m³/h)","example":5,"minimum":0}},"required":["flowRateMax"]},"LeakHistoryResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of leak detection notifications","type":"array","items":{"$ref":"#/components/schemas/Notification"}}},"required":["total","page","size","totalPages","data"]},"AlertThresholdHistory":{"type":"object","properties":{"waterMeter":{"type":"string","description":"Water meter ID"},"flowRateMax":{"type":"number","description":"Max flow rate threshold confirmed by device (m³/h)","example":10},"inletTemperatureMax":{"type":"number","description":"Max inlet temperature threshold confirmed by device (°C)","example":60},"inletTemperatureMin":{"type":"number","description":"Min inlet temperature threshold confirmed by device (°C)","example":0},"outletTemperatureMax":{"type":"number","description":"Max outlet temperature threshold confirmed by device (°C)","example":60},"outletTemperatureMin":{"type":"number","description":"Min outlet temperature threshold confirmed by device (°C)","example":0},"createdAt":{"format":"date-time","type":"string","description":"Timestamp when this record was created"},"updatedAt":{"format":"date-time","type":"string","description":"Timestamp when this record was last updated"}},"required":["waterMeter","flowRateMax","createdAt","updatedAt"]},"ThresholdHistoryResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of threshold configuration history records","type":"array","items":{"$ref":"#/components/schemas/AlertThresholdHistory"}}},"required":["total","page","size","totalPages","data"]},"WaterMeterLatestReadingDto":{"type":"object","properties":{"meterConnected":{"type":"boolean","description":"Whether the meter is physically connected"},"flowRate":{"type":"number","description":"Current flow rate (m³/h)","example":1.5},"velocity":{"type":"number","description":"Flow velocity (m/s)","example":0.8},"soundSpeed":{"type":"number","description":"Speed of sound in fluid (m/s)","example":1480.5},"signalQuality":{"type":"number","description":"Ultrasonic signal quality (no units)","example":85},"inletTemperature":{"type":"number","description":"Inlet water temperature (°C)","example":25},"outletTemperature":{"type":"number","description":"Outlet water temperature (°C)","example":23.5},"energyFlowRate":{"type":"number","description":"Thermal energy flow rate (GJ/h)","example":0.05},"lastSeen":{"format":"date-time","type":"string","description":"Last time the device sent data","example":"2026-01-14T10:08:20.717Z"}},"required":["flowRate","velocity","soundSpeed","signalQuality","lastSeen"]},"BulkSetLeakThresholdDto":{"type":"object","properties":{"waterMeterIds":{"description":"Array of water meter IDs to update","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"flowRateMax":{"type":"number","description":"Leak detection threshold flow rate (m³/h)","example":5}},"required":["waterMeterIds","flowRateMax"]},"FailedWaterMeterDto":{"type":"object","properties":{"waterMeterId":{"type":"string","description":"Water meter ID that failed","example":"507f1f77bcf86cd799439011"},"error":{"type":"string","description":"Error message explaining why it failed","example":"Water meter not found"}},"required":["waterMeterId","error"]},"BulkWaterMeterResponseDto":{"type":"object","properties":{"successful":{"description":"Array of successfully processed water meters","type":"array","items":{"$ref":"#/components/schemas/WaterMeter"}},"failed":{"description":"Array of failed water meters with error details","type":"array","items":{"$ref":"#/components/schemas/FailedWaterMeterDto"}},"summary":{"type":"string","description":"Summary of the bulk operation","example":"Successfully processed 8 out of 10 water meters"}},"required":["successful","failed","summary"]},"BulkUpdateWaterMeterThresholdsDto":{"type":"object","properties":{"waterMeterIds":{"description":"IDs of water meters to update","type":"array","items":{"type":"string"}},"flowRateMax":{"type":"number","description":"Max threshold flow rate (m³/h)","example":10},"flowRateMin":{"type":"number","description":"Min threshold flow rate (m³/h)","example":0},"notificationThresholdHour":{"type":"number","description":"Hourly notification threshold — minimum hours between repeat notifications","example":4},"notificationCooldown":{"type":"number","description":"Hardware notification cooldown in milliseconds — minimum time the device waits between sending repeated notifications","example":300000,"default":300000}},"required":["waterMeterIds"]},"HealthCheckDto":{"type":"object","properties":{"status":{"type":"string","description":"Server health status","example":"ok"},"timestamp":{"type":"string","description":"ISO timestamp of the health check","example":"2026-01-18T12:00:00.000Z"}},"required":["status","timestamp"]},"DashboardLayoutItemDto":{"type":"object","properties":{"i":{"type":"string","description":"Panel ID reference","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"x":{"type":"number","description":"X position in grid","example":0},"y":{"type":"number","description":"Y position in grid","example":0},"w":{"type":"number","description":"Width in grid units","example":6},"h":{"type":"number","description":"Height in grid units","example":4},"minW":{"type":"number","description":"Minimum width"},"minH":{"type":"number","description":"Minimum height"},"maxW":{"type":"number","description":"Maximum width"},"maxH":{"type":"number","description":"Maximum height"},"static":{"type":"boolean","description":"Whether the item is static/immovable"}},"required":["i","x","y","w","h"]},"CreateDashboardDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the dashboard","example":"Main Controller Dashboard"},"description":{"type":"string","description":"Short explanation of what this dashboard shows","example":"Building-wide energy, cost and device health"},"icon":{"type":"string","description":"Icon name rendered by the frontend beside the dashboard name","example":"building"},"refreshInterval":{"type":"number","description":"Auto-refresh interval in seconds. Set to 0 to disable auto-refresh.","example":5,"default":5,"minimum":0},"layout":{"description":"Layout configuration","type":"array","items":{"$ref":"#/components/schemas/DashboardLayoutItemDto"}},"panelIds":{"description":"Panel IDs to include","type":"array","items":{"type":"string"}},"globalTransformIds":{"description":"Global Transform IDs","type":"array","items":{"type":"string"}}},"required":["name"]},"DashboardResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Dashboard ID"},"system":{"type":"string","description":"System ID"},"name":{"type":"string","description":"Name of the dashboard"},"description":{"type":"string","description":"Short explanation of what this dashboard shows"},"icon":{"type":"string","description":"Icon name for the frontend"},"isDefault":{"type":"boolean","description":"Whether this is the system default dashboard","default":false},"templateKey":{"type":"string","description":"Key of the template this dashboard was created from","example":"PROPERTY_OVERVIEW"},"refreshInterval":{"type":"number","description":"Auto-refresh interval in seconds. 0 means disabled.","example":5},"layout":{"description":"Layout configuration","type":"array","items":{"$ref":"#/components/schemas/DashboardLayoutItemDto"}},"panels":{"description":"Panel IDs or populated panels","type":"array","items":{"type":"string"}},"globalTransforms":{"description":"Global transform IDs or populated transforms","type":"array","items":{"type":"string"}},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","system","name","isDefault","layout","panels","createdAt","updatedAt"]},"GetMyDeviceTypesResponseDto":{"type":"object","properties":{"deviceTypes":{"type":"array","description":"Distinct device types accessible to the authenticated account","items":{"$ref":"#/components/schemas/DeviceType"}}},"required":["deviceTypes"]},"DashboardTemplatePreviewItemDto":{"type":"object","properties":{"title":{"type":"string","description":"Panel title as the template creates it"},"x":{"type":"number","description":"Column offset in a 12-column grid"},"y":{"type":"number","description":"Row offset in grid units"},"w":{"type":"number","description":"Width in grid columns"},"h":{"type":"number","description":"Height in grid units"}},"required":["title","x","y","w","h"]},"DashboardTemplateResponseDto":{"type":"object","properties":{"key":{"type":"string","description":"Stable template key, passed to createDashboardFromTemplate","example":"PROPERTY_OVERVIEW"},"name":{"type":"string","description":"Template name shown on the card"},"description":{"type":"string","description":"What the template sets up"},"icon":{"type":"string","description":"Icon name for the frontend"},"panelCount":{"type":"number","description":"How many panels the template creates"},"requiredFeatures":{"type":"array","description":"Features the system must have enabled","items":{"$ref":"#/components/schemas/FeatureType"}},"requiredDeviceTypes":{"type":"array","description":"The system must own at least one device of one of these types. Empty means the template works anywhere.","items":{"$ref":"#/components/schemas/DeviceType"}},"preview":{"description":"The panels this template lays out, in creation order, so the picker can show what the dashboard will look like before it is created.","type":"array","items":{"$ref":"#/components/schemas/DashboardTemplatePreviewItemDto"}},"matchedDeviceTypes":{"type":"array","description":"The required device types this system actually owns. Drives the recommendation and lets the picker say why a template was suggested.","items":{"$ref":"#/components/schemas/DeviceType"}},"recommended":{"type":"boolean","description":"True for the single template that best fits this system's devices. Falls back to the default template when nothing matches more strongly."},"available":{"type":"boolean","description":"Whether this template can be applied to this system now"},"disabledReason":{"type":"string","description":"Why the template is unavailable. Absent when available.","example":"No env sensor devices on this system"}},"required":["key","name","description","panelCount","requiredFeatures","requiredDeviceTypes","preview","matchedDeviceTypes","recommended","available"]},"ListDashboardTemplatesResponseDto":{"type":"object","properties":{"templates":{"description":"Every template, including ones this system cannot use — those come back with available=false and a reason rather than being omitted.","type":"array","items":{"$ref":"#/components/schemas/DashboardTemplateResponseDto"}}},"required":["templates"]},"DashboardDetailResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Dashboard ID"},"system":{"type":"string","description":"System ID"},"name":{"type":"string","description":"Name of the dashboard"},"description":{"type":"string","description":"Short explanation of what this dashboard shows"},"icon":{"type":"string","description":"Icon name for the frontend"},"isDefault":{"type":"boolean","description":"Whether this is the system default dashboard","default":false},"templateKey":{"type":"string","description":"Key of the template this dashboard was created from"},"refreshInterval":{"type":"number","description":"Auto-refresh interval in seconds. 0 means disabled.","example":5},"layout":{"description":"Layout configuration","type":"array","items":{"$ref":"#/components/schemas/DashboardLayoutItemDto"}},"panels":{"type":"array","description":"Populated panels. Discriminate on `panelType` — EXTERNAL, ECOVOLT or BUILTIN.","items":{"oneOf":[{"$ref":"#/components/schemas/ExternalPanelResponseDto"},{"$ref":"#/components/schemas/EcovoltPanelResponseDto"},{"$ref":"#/components/schemas/BuiltinPanelResponseDto"}]}},"globalTransforms":{"description":"Populated global transformers","type":"array","items":{"$ref":"#/components/schemas/TransformerResponseDto"}},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","system","name","isDefault","layout","panels","createdAt","updatedAt"]},"UpdateDashboardDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the dashboard","example":"Main Controller Dashboard"},"description":{"type":"string","description":"Short explanation of what this dashboard shows","example":"Building-wide energy, cost and device health"},"icon":{"type":"string","description":"Icon name rendered by the frontend beside the dashboard name","example":"building"},"refreshInterval":{"type":"number","description":"Auto-refresh interval in seconds. Set to 0 to disable auto-refresh.","example":5,"default":5,"minimum":0},"layout":{"description":"Layout configuration","type":"array","items":{"$ref":"#/components/schemas/DashboardLayoutItemDto"}},"panelIds":{"description":"Panel IDs to include","type":"array","items":{"type":"string"}},"globalTransformIds":{"description":"Global Transform IDs","type":"array","items":{"type":"string"}}}},"DataSourceType":{"type":"string","enum":["REST_API"],"description":"Type of data source"},"AuthType":{"type":"string","enum":["BEARER","API_KEY_HEADER","NONE"],"description":"How the API key is sent. BEARER = Authorization: Bearer <key>, API_KEY_HEADER = custom header (default x-api-key), NONE = no auth"},"CreateDataSourceDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the data source","example":"Main InfluxDB"},"type":{"description":"Type of data source","allOf":[{"$ref":"#/components/schemas/DataSourceType"}]},"url":{"type":"string","description":"Base URL","example":"https://influxdb.example.com"},"authType":{"description":"How the API key is sent. BEARER = Authorization: Bearer <key>, API_KEY_HEADER = custom header (default x-api-key), NONE = no auth","default":"BEARER","allOf":[{"$ref":"#/components/schemas/AuthType"}]},"apiKeyHeaderName":{"type":"string","description":"Custom header name when authType is API_KEY_HEADER (defaults to x-api-key)","example":"x-api-key"},"apiKey":{"type":"string","description":"API Key (will be encrypted before storage)"}},"required":["name","type","url"]},"DataSourceResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Data source ID"},"system":{"type":"string","description":"System ID"},"name":{"type":"string","description":"Name of the data source"},"type":{"description":"Type of data source","allOf":[{"$ref":"#/components/schemas/DataSourceType"}]},"url":{"type":"string","description":"Base URL"},"authType":{"description":"How the API key is sent","allOf":[{"$ref":"#/components/schemas/AuthType"}]},"apiKeyHeaderName":{"type":"string","description":"Custom header name when authType is API_KEY_HEADER"},"connectionParams":{"type":"object","description":"Connection parameters"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","system","name","type","url","createdAt","updatedAt"]},"DiscoveredFieldDto":{"type":"object","properties":{"name":{"type":"string","description":"Property name in the response payload"},"kind":{"type":"string","description":"Best-effort classification used to suggest chart axes. Time fields suit the X axis, numeric fields the Y axis.","enum":["time","number","other"]}},"required":["name","kind"]},"DiscoveredOperationDto":{"type":"object","properties":{"path":{"type":"string","description":"Path relative to the data source base URL"},"method":{"type":"string","description":"HTTP method","enum":["GET","POST","PUT","PATCH","DELETE"]},"summary":{"type":"string","description":"Operation summary from the spec"},"operationId":{"type":"string","description":"operationId declared in the spec"},"fields":{"description":"Properties of the success response schema","type":"array","items":{"$ref":"#/components/schemas/DiscoveredFieldDto"}}},"required":["path","method","fields"]},"DiscoverEndpointsResponseDto":{"type":"object","properties":{"operations":{"description":"Operations found in the data source API spec","type":"array","items":{"$ref":"#/components/schemas/DiscoveredOperationDto"}},"specUrl":{"type":"string","description":"URL the spec was ultimately read from, when one was found"}},"required":["operations"]},"UpdateDataSourceDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the data source","example":"Main InfluxDB"},"type":{"description":"Type of data source","allOf":[{"$ref":"#/components/schemas/DataSourceType"}]},"url":{"type":"string","description":"Base URL","example":"https://influxdb.example.com"},"authType":{"description":"How the API key is sent. BEARER = Authorization: Bearer <key>, API_KEY_HEADER = custom header (default x-api-key), NONE = no auth","default":"BEARER","allOf":[{"$ref":"#/components/schemas/AuthType"}]},"apiKeyHeaderName":{"type":"string","description":"Custom header name when authType is API_KEY_HEADER (defaults to x-api-key)","example":"x-api-key"},"apiKey":{"type":"string","description":"API Key (will be encrypted before storage)"}}},"CreateTransformerDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the transformer","example":"Extract Temperature"},"type":{"description":"Type of transformation to apply. Raw JSON types: EXTRACT, SELECT, RENAME, FIELDS, FILTER, SORT, LIMIT, REDUCE. DataFrame types (DF_ prefix): DF_RENAME_FIELDS, DF_ORGANIZE_FIELDS, DF_SELECT_FIELDS, DF_FILTER_FIELDS_BY_NAME, DF_FILTER_BY_VALUE, DF_FILTER_BY_QUERY, DF_CALCULATE, DF_CUMULATIVE, DF_REDUCE, DF_GROUP_BY, DF_JOIN, DF_MERGE, DF_CONCATENATE, DF_TIME_SERIES_LONG_TO_WIDE, DF_TIME_SERIES_WIDE_TO_LONG, DF_TIME_SERIES_TO_ROWS, DF_TIME_SERIES_TO_TABLE, DF_PIVOT, DF_ROWS_TO_FIELDS, DF_LABELS_TO_FIELDS, DF_LOOKUP, DF_CONFIG_FROM_QUERY, DF_REGRESSION, DF_EXTRACT_LABELS.","allOf":[{"$ref":"#/components/schemas/TransformType"}]},"config":{"type":"object","description":"Configuration object for the transform. Shape depends on the transform type. See TRANSFORMATIONS.md for the full config reference per type.","default":{},"example":{}},"enabled":{"type":"boolean","description":"Whether this transformer is enabled in the pipeline. Disabled transformers are skipped during execution.","default":true,"example":true},"order":{"type":"number","description":"Execution order within the pipeline. Lower numbers execute first.","default":0,"example":0,"minimum":0}},"required":["name","type"]},"UpdateTransformerDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the transformer","example":"Extract Temperature"},"type":{"description":"Type of transformation to apply.","allOf":[{"$ref":"#/components/schemas/TransformType"}]},"config":{"type":"object","description":"Configuration object for the transform. Shape depends on the transform type. See TRANSFORMATIONS.md for the full config reference per type.","example":{}},"enabled":{"type":"boolean","description":"Whether this transformer is enabled in the pipeline. Disabled transformers are skipped during execution.","default":true,"example":true},"order":{"type":"number","description":"Execution order within the pipeline. Lower numbers execute first.","default":0,"example":0,"minimum":0}}},"HealthStatus":{"type":"string","enum":["HEALTHY","WARNING","CRITICAL","OFFLINE"]},"DeviceHealthItemDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Device ID"},"deviceName":{"type":"string","description":"Device name or macId"},"healthScore":{"type":"number","description":"Health score 0-100","example":85},"healthStatus":{"description":"Health status","allOf":[{"$ref":"#/components/schemas/HealthStatus"}]},"openAnomalyCount":{"type":"number","description":"Number of open anomalies","example":1},"monitoringEnabled":{"type":"boolean","description":"Whether predictive maintenance monitoring is active for this device","default":true,"example":true},"mlRiskScore":{"type":"number","description":"ML model anomaly risk score 0.0–1.0"},"mlIsAnomaly":{"type":"boolean","description":"Whether ML model flagged this device as anomalous"}},"required":["deviceId","deviceName","healthScore","healthStatus","openAnomalyCount","monitoringEnabled"]},"DeviceHealthSummaryDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DeviceHealthItemDto"}},"total":{"type":"number","description":"Total device count (after filtering)","example":25},"page":{"type":"number","description":"Current page number","example":1},"size":{"type":"number","description":"Items per page","example":50}},"required":["data","total","page","size"]},"AnomalyType":{"type":"string","enum":["SPIKE","DEGRADATION","DEAD_DEVICE","SPIKE_FREQUENCY","ERRATIC_CONSUMPTION","STANDBY_ANOMALY","POWER_FACTOR_TREND"]},"AnomalySeverity":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"AnomalyStatus":{"type":"string","enum":["OPEN","ACKNOWLEDGED","RESOLVED","FALSE_POSITIVE"]},"DeviceAnomalyResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Anomaly ID"},"device":{"type":"string","description":"Device ID"},"deviceName":{"type":"string","description":"Device name (populated for convenience)"},"system":{"type":"string","description":"System ID"},"anomalyType":{"allOf":[{"$ref":"#/components/schemas/AnomalyType"}]},"severity":{"allOf":[{"$ref":"#/components/schemas/AnomalySeverity"}]},"detectedAt":{"format":"date-time","type":"string"},"baselineValue":{"type":"number","example":1.5},"actualValue":{"type":"number","example":8.4},"deviationPercentage":{"type":"number","example":460},"explanation":{"type":"string"},"status":{"allOf":[{"$ref":"#/components/schemas/AnomalyStatus"}]},"notes":{"type":"string","description":"Staff notes about this anomaly"}},"required":["_id","device","system","anomalyType","severity","detectedAt","baselineValue","actualValue","deviationPercentage","explanation","status"]},"PaginatedAnomaliesDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DeviceAnomalyResponseDto"}},"total":{"type":"number"},"page":{"type":"number"},"size":{"type":"number"}},"required":["data","total","page","size"]},"DayType":{"type":"string","enum":["weekday","weekend"]},"DeviceBaselineDataDto":{"type":"object","properties":{"dayType":{"example":"weekday","allOf":[{"$ref":"#/components/schemas/DayType"}]},"calculatedAt":{"format":"date-time","type":"string","description":"When baseline was last calculated"},"windowDays":{"type":"number","description":"Rolling window in days","example":28},"healthScore":{"type":"number","description":"Health score 0-100","example":85},"hourlyAvgUsage":{"type":"number","description":"Average hourly energy usage (kWh) while ON","example":1.5},"hourlyStdDev":{"type":"number","description":"Std dev of hourly usage","example":0.3},"dailyAvgUsage":{"type":"number","description":"Average daily energy usage (kWh)","example":12},"dailyStdDev":{"type":"number","description":"Std dev of daily usage","example":1.2},"peakHourAvg":{"type":"number","description":"Peak hour average usage (kWh)","example":2.1},"offPeakHourAvg":{"type":"number","description":"Off-peak hour average usage (kWh)","example":0.8},"avgPowerFactor":{"type":"number","description":"Average power factor","example":0.95},"avgCurrent":{"type":"number","description":"Average current (A)","example":2.1},"avgVoltage":{"type":"number","description":"Average voltage (V)","example":230},"avgPower":{"type":"number","description":"Average power (W)","example":480},"avgCyclesPerDay":{"type":"number","description":"Average on/off cycles per day","example":4},"avgActiveHoursPerDay":{"type":"number","description":"Average active hours per day","example":8},"monitoringEnabled":{"type":"boolean","description":"Whether monitoring is enabled","example":true},"baselineResetAt":{"format":"date-time","type":"string","description":"When baseline was last reset","nullable":true},"mlRiskScore":{"type":"number","description":"ML anomaly risk score 0.0–1.0"},"mlIsAnomaly":{"type":"boolean","description":"Whether ML flagged as anomalous"},"mlFailureProbability":{"type":"number","description":"Supervised P(real fault), once the system has enough triaged anomalies"},"mlModelType":{"type":"string","description":"Which model produced the ML fields: isolation_forest, hybrid, or unavailable","example":"hybrid"},"mlScoredAt":{"format":"date-time","type":"string","description":"When ML score was last updated"},"sampleDays":{"type":"number","description":"Comparable days that contributed to the reference window","example":20},"recentHourlyAvgUsage":{"type":"number","description":"Mean hourly usage (kWh) over the last 7 comparable days","example":0.42},"recentHourlyStdDev":{"type":"number","description":"Std dev of hourly usage over the last 7 comparable days","example":0.18},"recentActiveHoursPerDay":{"type":"number","description":"Mean ON hours per day over the last 7 comparable days","example":7.5},"recentCyclesPerDay":{"type":"number","description":"Mean on/off cycles per day over the last 7 comparable days","example":5},"avgStandbyPower":{"type":"number","description":"Mean power (W) drawn while OFF — the learned standby signature","example":0.6},"recentStandbyPower":{"type":"number","description":"Mean power (W) drawn while OFF over the last 7 days","example":0.7},"spikeCountRecent":{"type":"number","description":"Hours above 3σ in the last 7 comparable days","example":2},"spikeCountPrior":{"type":"number","description":"Hours above 3σ in the 7 comparable days before that","example":1},"dailyAvgSlopeRecent":{"type":"number","description":"Slope of daily usage over the recent window (kWh/day per day)","example":0.03},"dailyAvgSlopeWindow":{"type":"number","description":"Slope of daily usage across the reference window (kWh/day per day)","example":0.01},"powerFactorSlope":{"type":"number","description":"Slope of daily mean power factor (PF per day). Negative = drifting down.","example":-0.0008}},"required":["dayType","calculatedAt","windowDays","healthScore","hourlyAvgUsage","hourlyStdDev","dailyAvgUsage","dailyStdDev","peakHourAvg","offPeakHourAvg","avgPowerFactor","avgCurrent","avgVoltage","avgPower","avgCyclesPerDay","avgActiveHoursPerDay","monitoringEnabled","sampleDays","recentHourlyAvgUsage","recentHourlyStdDev","recentActiveHoursPerDay","recentCyclesPerDay","avgStandbyPower","recentStandbyPower","spikeCountRecent","spikeCountPrior","dailyAvgSlopeRecent","dailyAvgSlopeWindow","powerFactorSlope"]},"DeviceDetailResponseDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"Device ID"},"weekday":{"description":"Weekday baseline","allOf":[{"$ref":"#/components/schemas/DeviceBaselineDataDto"}]},"weekend":{"description":"Weekend baseline","allOf":[{"$ref":"#/components/schemas/DeviceBaselineDataDto"}]},"anomalies":{"description":"All anomalies for this device","type":"array","items":{"$ref":"#/components/schemas/DeviceAnomalyResponseDto"}}},"required":["deviceId","anomalies"]},"UpdateAnomalyStatusDto":{"type":"object","properties":{"status":{"description":"New status for the anomaly","allOf":[{"$ref":"#/components/schemas/AnomalyStatus"}]},"notes":{"type":"string","description":"Notes about what happened or actions taken","example":"Checked on-site — compressor wear confirmed. Scheduled for replacement next week.","maxLength":1000}},"required":["status"]},"BulkMonitoringRequestDto":{"type":"object","properties":{"deviceIds":{"description":"Device IDs to enable or disable monitoring for","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}}},"required":["deviceIds"]},"BulkResetBaselineResponseDto":{"type":"object","properties":{"affected":{"type":"number","description":"Number of devices reset","example":3},"deviceIds":{"description":"Device IDs that were successfully reset","type":"array","items":{"type":"string"}},"baselineResetAt":{"format":"date-time","type":"string","description":"Timestamp the reset was triggered"},"weekdayRecalibratingUntil":{"format":"date-time","type":"string","description":"Weekday anomaly detection resumes after this date (28 days from reset)"},"weekendRecalibratingUntil":{"format":"date-time","type":"string","description":"Weekend anomaly detection resumes after this date (70 days from reset)"},"message":{"type":"string","description":"Human-readable confirmation message"}},"required":["affected","deviceIds","baselineResetAt","weekdayRecalibratingUntil","weekendRecalibratingUntil","message"]},"ResetHealthScoreRequestDto":{"type":"object","properties":{"deviceIds":{"description":"Device IDs to reset. Omit to reset every PLUG in the system — the usual case after a run of false positives.","example":["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012"],"type":"array","items":{"type":"string"}},"anomalyTypes":{"type":"array","description":"Only close open anomalies of these types. Omit to close all of them.","items":{"$ref":"#/components/schemas/AnomalyType"}}}},"BulkResetHealthScoreResponseDto":{"type":"object","properties":{"affected":{"type":"number","description":"Number of devices rescored","example":12},"deviceIds":{"description":"Device IDs that were rescored","type":"array","items":{"type":"string"}},"anomaliesClosed":{"type":"number","description":"Number of open anomalies marked as false positives","example":43},"message":{"type":"string","description":"Human-readable confirmation message"}},"required":["affected","deviceIds","anomaliesClosed","message"]},"BulkMonitoringResponseDto":{"type":"object","properties":{"affected":{"type":"number","description":"Number of devices affected","example":3},"deviceIds":{"description":"Device IDs that were successfully updated","type":"array","items":{"type":"string"}},"message":{"type":"string","description":"Human-readable confirmation message","example":"Monitoring enabled for 3 device(s)."}},"required":["affected","deviceIds","message"]},"MlTrainingResponseDto":{"type":"object","properties":{"samplesUsed":{"type":"number","description":"Number of baseline feature vectors submitted for training","example":128},"trained":{"type":"boolean","description":"Whether a model was actually fitted. False when the compute service was unreachable, or when the batch fell below its per-category minimum — a submitted batch is not a trained model.","example":true},"categoriesTrained":{"type":"object","description":"Samples fitted per plug category, keyed by category name","example":{"AIRCONDITIONER":70,"DESKTOP":58}},"message":{"type":"string","description":"Human-readable outcome"}},"required":["samplesUsed","trained","message"]},"SupervisedMlTrainingResponseDto":{"type":"object","properties":{"samplesUsed":{"type":"number","description":"Triaged anomalies that carried a feature snapshot and could be used as labels","example":64},"confirmedFaults":{"type":"number","description":"Of those, how many were RESOLVED — i.e. confirmed real faults","example":27},"falsePositives":{"type":"number","description":"Of those, how many were dismissed as FALSE_POSITIVE","example":37},"trained":{"type":"boolean","description":"Whether the classifier was fitted. False when there were too few labels, only one class was present, or the compute service was unreachable.","example":true},"cvRocAuc":{"type":"number","description":"Out-of-sample ROC AUC, measured by cross validation before the final fit. Absent when the minority class could not support at least two folds.","example":0.88},"featureImportances":{"type":"object","description":"Relative contribution of each feature, keyed by feature name","example":{"usage_ratio":0.21,"std_dev_ratio":0.18}},"message":{"type":"string","description":"Human-readable outcome"}},"required":["samplesUsed","confirmedFaults","falsePositives","trained","message"]},"ConsumerOrderLocation":{"type":"string","enum":["Central","North","Northeast","East","West"],"description":"Location of the customer"},"ConsumerOrderHousingType":{"type":"string","enum":["HDB","Condominium","Landed","Commercial","Other"],"description":"Type of housing"},"ConsumerOrderDevice":{"type":"string","enum":["smart-tv","water-cooler","air-purifier","printer","aircon","washing-machine","home-coffee-machine"],"description":"Device type identifier"},"ConsumerSelectedDeviceDto":{"type":"object","properties":{"deviceType":{"description":"Device type identifier","example":"aircon","allOf":[{"$ref":"#/components/schemas/ConsumerOrderDevice"}]},"qty":{"type":"number","description":"Number of units of this device","example":2,"minimum":1}},"required":["deviceType","qty"]},"CreateConsumerOrderDto":{"type":"object","properties":{"firstName":{"type":"string","description":"First name of the customer","example":"Jane"},"lastName":{"type":"string","description":"Last name of the customer","example":"Doe"},"mobile":{"type":"string","description":"Mobile number of the customer","example":"+6591234567"},"email":{"type":"string","description":"Email address of the customer","example":"jane.doe@example.com"},"location":{"description":"Location of the customer","example":"Central","allOf":[{"$ref":"#/components/schemas/ConsumerOrderLocation"}]},"housingType":{"description":"Type of housing","example":"HDB","allOf":[{"$ref":"#/components/schemas/ConsumerOrderHousingType"}]},"selectedDevices":{"description":"High-energy appliances the customer wants to monitor, with quantities","type":"array","items":{"$ref":"#/components/schemas/ConsumerSelectedDeviceDto"}}},"required":["firstName","lastName","mobile","email","location","housingType","selectedDevices"]},"CreateConsumerOrderResponseDto":{"type":"object","properties":{"message":{"type":"string","description":"Confirmation message","example":"Order submitted successfully."}},"required":["message"]},"SetSceneDto":{"type":"object","properties":{"sceneId":{"type":"string","description":"Stable id of the scene to activate (see GET /exhibit/scenes).","example":"day"},"setBy":{"type":"string","description":"Optional label for who triggered the change (audit only).","example":"ipad-control"}},"required":["sceneId"]},"SceneStateResponseDto":{"type":"object","properties":{"sceneId":{"type":"string","example":"day"},"label":{"type":"string","example":"Day"},"updatedAt":{"type":"string","description":"ISO timestamp of when this scene became active.","example":"2026-06-10T09:30:00.000Z"}},"required":["sceneId","label","updatedAt"]},"ExhibitDeviceKind":{"type":"string","enum":["ahu","light","other"]},"ExhibitDeviceTelemetryDto":{"type":"object","properties":{"deviceId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","example":"L1 FCU"},"deviceNumber":{"type":"number","example":1660},"kind":{"allOf":[{"$ref":"#/components/schemas/ExhibitDeviceKind"}]},"measurementType":{"allOf":[{"$ref":"#/components/schemas/McbMeasurementType"}]},"on":{"type":"boolean","description":"True when the circuit is energised."},"onStatus":{"allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]},"locked":{"type":"boolean","description":"Locked devices reject toggles upstream."},"level":{"type":"number","description":"Storey, parsed from the zone name (\"Level 3\" → 3).","example":3},"roomName":{"type":"string","example":"Executive Office"},"zoneName":{"type":"string","example":"Level 3"},"ratedCurrent":{"type":"number","example":16},"ratedVoltage":{"type":"number","example":230},"realTimePower":{"type":"number","description":"Real-time power (W)"},"realTimeVoltage":{"type":"number","description":"Real-time voltage (V)"},"realTimeCurrent":{"type":"number","description":"Real-time current (A)"},"powerFactor":{"type":"number","description":"Power factor (0-1)"},"totalEnergyUsage":{"type":"number","description":"Lifetime accumulated energy (kWh), not a daily figure."},"lastSeen":{"type":"string","description":"When the device last reported.","example":"2026-09-23T09:30:00.000Z"}},"required":["deviceId","name","kind","on","onStatus"]},"ExhibitTelemetryResponseDto":{"type":"object","properties":{"readAt":{"type":"string","description":"When this snapshot was read from upstream.","example":"2026-09-23T09:30:00.000Z"},"devices":{"type":"array","items":{"$ref":"#/components/schemas/ExhibitDeviceTelemetryDto"}}},"required":["readAt","devices"]},"ExhibitTelemetryPointDto":{"type":"object","properties":{"at":{"type":"string","example":"2026-09-23T09:30:00.000Z"},"power":{"type":"number","description":"Power (W)"},"voltage":{"type":"number","description":"Voltage (V)"},"current":{"type":"number","description":"Current (A)"}},"required":["at"]},"ExhibitDeviceHistoryDto":{"type":"object","properties":{"deviceId":{"type":"string","example":"69b397dccc20f4f967dc6a3c"},"points":{"description":"Upstream realtime readings, oldest first.","type":"array","items":{"$ref":"#/components/schemas/ExhibitTelemetryPointDto"}}},"required":["deviceId","points"]},"ExhibitTelemetryHistoryResponseDto":{"type":"object","properties":{"from":{"type":"string","example":"2026-09-23T08:30:00.000Z"},"till":{"type":"string","example":"2026-09-23T09:30:00.000Z"},"devices":{"type":"array","items":{"$ref":"#/components/schemas/ExhibitDeviceHistoryDto"}}},"required":["from","till","devices"]},"ExhibitEnvironmentPointDto":{"type":"object","properties":{"at":{"type":"string","example":"2026-09-23T09:30:00.000Z"},"temperature":{"type":"number","description":"Ambient temperature (°C)"},"humidity":{"type":"number","description":"Ambient humidity (%)"}},"required":["at"]},"ExhibitEnvironmentSensorDto":{"type":"object","properties":{"deviceId":{"type":"string","example":"6ab39c93aa9bfba2b2ffd46d"},"name":{"type":"string","example":"Building Temp & Humidity Sensor"},"points":{"description":"Upstream realtime readings, oldest first.","type":"array","items":{"$ref":"#/components/schemas/ExhibitEnvironmentPointDto"}}},"required":["deviceId","name","points"]},"ExhibitEnvironmentResponseDto":{"type":"object","properties":{"from":{"type":"string","example":"2026-09-23T08:30:00.000Z"},"till":{"type":"string","example":"2026-09-23T09:30:00.000Z"},"sensors":{"type":"array","items":{"$ref":"#/components/schemas/ExhibitEnvironmentSensorDto"}}},"required":["from","till","sensors"]},"ToggleExhibitDeviceDto":{"type":"object","properties":{"deviceId":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"onStatus":{"allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}},"required":["deviceId","onStatus"]},"BulkToggleExhibitDevicesDto":{"type":"object","properties":{"deviceIds":{"maxItems":50,"example":["64b7f1e6f9c9d4e5b8a9f7c3","64b7f1e6f9c9d4e5b8a9f7c4"],"type":"array","items":{"type":"string"}},"onStatus":{"allOf":[{"$ref":"#/components/schemas/DeviceStatus"}]}},"required":["deviceIds","onStatus"]},"ControlAirconSmartTempDto":{"type":"object","properties":{"value":{"type":"number"},"aggressiveness":{"type":"number","description":"Proportional gain (0–1). 0 = no compensation, 1 = full compensation. Default: 0.5.","example":0.5,"minimum":0,"maximum":1}}},"ControlExternalDeviceDaikinAirconDto":{"type":"object","properties":{"value":{"type":"number"},"externalDeviceIds":{"description":"IDs of the external devices to control","example":["64b7f1e6f9c9d4e5b8a9f7c1","64b7f1e6f9c9d4e5b8a9f7c2"],"type":"array","items":{"type":"string"}},"power":{"type":"boolean","description":"Power the unit on (true) or off (false)","example":true},"mode":{"description":"AC mode (1=cool, 2=heat, 3=fan, 4=auto, 5=dry)","example":1,"allOf":[{"$ref":"#/components/schemas/DaikinMode"}]},"fan":{"description":"Fan rate (1=low, 2=high, 3=middle, 4=auto)","example":4,"allOf":[{"$ref":"#/components/schemas/DaikinFanRate"}]},"setpoint":{"type":"number","description":"Setpoint temperature in °C (cool 18–32, heat 14–28; rejected in auto mode)","example":23,"minimum":14,"maximum":32},"lockPower":{"type":"boolean","description":"Lock (true) or unlock (false) the wired remote power button","example":false},"lockMode":{"type":"boolean","description":"Lock (true) or unlock (false) the wired remote mode selector","example":false},"lockSetpoint":{"type":"boolean","description":"Lock (true) or unlock (false) the wired remote setpoint adjust","example":false},"forcedOff":{"type":"boolean","description":"Forced system stop (true) or clear (false) — port-master units only (ac 0/64/128/192). Units stay OFF after clear.","example":false},"filterReset":{"type":"boolean","description":"Reset the dirty-filter sign","example":false},"forcedThermoOff":{"type":"boolean","description":"Force the thermostat off (true) or clear (false)","example":false},"energySaving":{"type":"boolean","description":"Enable (true) or clear (false) energy-saving operation","example":false}},"required":["externalDeviceIds"]},"CreateActionDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"ID of the device to perform the action on","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"actionType":{"description":"Type of action to perform","allOf":[{"$ref":"#/components/schemas/ActionType"}]},"controlDto":{"description":"Control payload. Required for CONTROL_AIRCON, AIRCON_SMART_TEMP, and CONTROL_EXTERNAL_DEVICE_DAIKIN_AIRCON. Shape is ControlAirconDto for CONTROL_AIRCON; ControlAirconSmartTempDto (value + aggressiveness) for AIRCON_SMART_TEMP; ControlExternalDeviceDaikinAirconDto (externalDeviceIds + Daikin fields) for CONTROL_EXTERNAL_DEVICE_DAIKIN_AIRCON.","oneOf":[{"$ref":"#/components/schemas/ControlAirconDto"},{"$ref":"#/components/schemas/ControlAirconSmartTempDto"},{"$ref":"#/components/schemas/ControlExternalDeviceDaikinAirconDto"}]}},"required":["deviceId","actionType"]},"SmartModeDeviceDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"deviceName":{"type":"string","example":"Living Room AC"},"deviceType":{"allOf":[{"$ref":"#/components/schemas/DeviceType"}]}},"required":["_id","deviceType"]},"ControlDto":{"type":"object","properties":{"value":{"type":"number"}}},"SmartModeCreatorDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","example":"John Doe"},"email":{"type":"string","example":"john@example.com"},"role":{"allOf":[{"$ref":"#/components/schemas/Role"}]}},"required":["_id","name","email","role"]},"NodePositionDto":{"type":"object","properties":{"x":{"type":"number","description":"X coordinate on the canvas","example":200},"y":{"type":"number","description":"Y coordinate on the canvas","example":100}},"required":["x","y"]},"GetActionDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"system":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"device":{"$ref":"#/components/schemas/SmartModeDeviceDto"},"actionType":{"allOf":[{"$ref":"#/components/schemas/ActionType"}]},"controlDto":{"description":"Control payload. Shape depends on actionType: ControlAirconDto for CONTROL_AIRCON; ControlAirconSmartTempDto for AIRCON_SMART_TEMP.","allOf":[{"$ref":"#/components/schemas/ControlDto"}]},"createdBy":{"$ref":"#/components/schemas/SmartModeCreatorDto"},"position":{"$ref":"#/components/schemas/NodePositionDto"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","system","actionType","createdAt","updatedAt"]},"GetActionsResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of actions matching the query","type":"array","items":{"$ref":"#/components/schemas/GetActionDto"}}},"required":["total","page","size","totalPages","data"]},"UpdateActionDto":{"type":"object","properties":{"deviceId":{"type":"string","description":"ID of the device to perform the action on","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"actionType":{"description":"Type of action to perform","allOf":[{"$ref":"#/components/schemas/ActionType"}]},"controlDto":{"description":"Control payload. Shape is ControlAirconDto for CONTROL_AIRCON; ControlAirconSmartTempDto (value + aggressiveness) for AIRCON_SMART_TEMP; ControlExternalDeviceDaikinAirconDto (externalDeviceIds + Daikin fields) for CONTROL_EXTERNAL_DEVICE_DAIKIN_AIRCON.","oneOf":[{"$ref":"#/components/schemas/ControlAirconDto"},{"$ref":"#/components/schemas/ControlAirconSmartTempDto"},{"$ref":"#/components/schemas/ControlExternalDeviceDaikinAirconDto"}]},"position":{"description":"Canvas position of this node","allOf":[{"$ref":"#/components/schemas/NodePositionDto"}]}}},"TriggerDayTimeRangeDto":{"type":"object","properties":{"day":{"description":"Day of the week this time range applies to","example":"monday","allOf":[{"$ref":"#/components/schemas/DayOfWeek"}]},"startTime":{"type":"string","description":"Start time in HH:mm format (inclusive)","example":"08:00"},"endTime":{"type":"string","description":"End time in HH:mm format (exclusive). Must be after startTime — overnight ranges are not supported.","example":"18:00"}},"required":["day","startTime","endTime"]},"CreateTriggerDto":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name for this trigger","example":"Server Room Overheat Alert"},"deviceId":{"type":"string","description":"ID of the device whose metric will be monitored. Required for device metrics; omit for weather metrics.","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"metric":{"description":"Metric to evaluate","allOf":[{"$ref":"#/components/schemas/TriggerMetric"}]},"operator":{"description":"Comparison operator","allOf":[{"$ref":"#/components/schemas/TriggerOperator"}]},"thresholdValue":{"type":"number","description":"Threshold value to compare the metric against (e.g. 30 for \"temperature > 30\")","example":30},"actions":{"description":"IDs of actions to execute when the condition is met","example":["64b7f1e6f9c9d4e5b8a9f7c3"],"type":"array","items":{"type":"string"}},"latitude":{"type":"number","description":"Latitude of the location to check weather for. Required when metric is a weather metric; ignored otherwise.","example":1.3521},"longitude":{"type":"number","description":"Longitude of the location to check weather for. Required when metric is a weather metric; ignored otherwise.","example":103.8198},"delayMinutes":{"type":"number","description":"Minutes the condition must be sustained before actions fire. Omit for immediate fire.","example":5},"intervalMinutes":{"type":"number","description":"Minutes between repeated action firings while the condition remains true. Absent means fire on every evaluation while the condition is met.","example":60,"minimum":1,"maximum":1440},"dayTimeRanges":{"description":"Time windows when this trigger is allowed to fire. If omitted or empty, the trigger fires at any time.","type":"array","items":{"$ref":"#/components/schemas/TriggerDayTimeRangeDto"}}},"required":["name","metric","operator","thresholdValue","actions"]},"GetTriggerDto":{"type":"object","properties":{"_id":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"system":{"type":"string","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"name":{"type":"string","description":"Human-readable name for this trigger","example":"Server Room Overheat Alert"},"device":{"$ref":"#/components/schemas/SmartModeDeviceDto"},"metric":{"allOf":[{"$ref":"#/components/schemas/TriggerMetric"}]},"operator":{"allOf":[{"$ref":"#/components/schemas/TriggerOperator"}]},"thresholdValue":{"type":"number","description":"Threshold value the metric is compared against","example":30},"actions":{"description":"Actions executed when the condition is met","type":"array","items":{"$ref":"#/components/schemas/GetActionDto"}},"latitude":{"type":"number","description":"Latitude of the weather location. Present only on weather triggers.","example":1.3521},"longitude":{"type":"number","description":"Longitude of the weather location. Present only on weather triggers.","example":103.8198},"delayMinutes":{"type":"number","description":"Minutes the condition must be sustained before actions fire. Absent means fire immediately.","example":5},"intervalMinutes":{"type":"number","description":"Minutes between repeated action firings while the condition remains true. Absent means fire only once per condition met.","example":60},"dayTimeRanges":{"description":"Time windows when this trigger is allowed to fire. Empty array means no restriction.","type":"array","items":{"$ref":"#/components/schemas/TriggerDayTimeRangeDto"}},"createdBy":{"$ref":"#/components/schemas/SmartModeCreatorDto"},"position":{"$ref":"#/components/schemas/NodePositionDto"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["_id","system","name","metric","operator","thresholdValue","actions","createdAt","updatedAt"]},"CreateTriggerWithActionDto":{"type":"object","properties":{"name":{"type":"string","description":"Name for the trigger","example":"Bedroom Fan on → Living Room AC on"},"triggerDeviceId":{"type":"string","description":"Device whose toggle state is monitored as the trigger condition","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"metric":{"allOf":[{"$ref":"#/components/schemas/TriggerMetric"}]},"operator":{"allOf":[{"$ref":"#/components/schemas/TriggerOperator"}]},"thresholdValue":{"type":"number","description":"Threshold value compared against the metric. Use 0 for IS_TRUE/IS_FALSE operators.","example":0},"actionDeviceId":{"type":"string","description":"Device to control when the trigger fires","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"actionType":{"allOf":[{"$ref":"#/components/schemas/ActionType"}]}},"required":["name","triggerDeviceId","metric","operator","thresholdValue","actionDeviceId","actionType"]},"GetTriggerWithActionResponseDto":{"type":"object","properties":{"trigger":{"$ref":"#/components/schemas/GetTriggerDto"},"action":{"$ref":"#/components/schemas/GetActionDto"}},"required":["trigger","action"]},"GetTriggersResponseDto":{"type":"object","properties":{"total":{"type":"number","description":"Total count of items matching filters","example":150},"page":{"type":"number","description":"Current page number","minimum":1,"example":1},"size":{"type":"number","description":"Items per page","minimum":1,"example":50},"totalPages":{"type":"number","description":"Total number of pages","minimum":0,"example":3},"data":{"description":"Array of triggers matching the query","type":"array","items":{"$ref":"#/components/schemas/GetTriggerDto"}}},"required":["total","page","size","totalPages","data"]},"UpdateTriggerDto":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name for this trigger","example":"Server Room Overheat Alert"},"deviceId":{"type":"string","description":"ID of the device whose metric will be monitored","example":"64b7f1e6f9c9d4e5b8a9f7c3"},"metric":{"description":"Metric to evaluate","allOf":[{"$ref":"#/components/schemas/TriggerMetric"}]},"operator":{"description":"Comparison operator","allOf":[{"$ref":"#/components/schemas/TriggerOperator"}]},"thresholdValue":{"type":"number","description":"Threshold value to compare the metric against","example":30},"actions":{"description":"IDs of actions to execute when the condition is met","type":"array","items":{"type":"string"}},"latitude":{"type":"number","description":"Latitude of the location to check weather for. Only applicable to weather metrics.","example":1.3521},"longitude":{"type":"number","description":"Longitude of the location to check weather for. Only applicable to weather metrics.","example":103.8198},"delayMinutes":{"type":"number","description":"Minutes the condition must be sustained before actions fire. Omit to leave unchanged.","example":5},"intervalMinutes":{"type":"number","description":"Minutes between repeated action firings while the condition remains true. Absent means fire on every evaluation while the condition is met.","example":60,"minimum":0,"maximum":1440},"position":{"description":"Canvas position of this node","allOf":[{"$ref":"#/components/schemas/NodePositionDto"}]},"dayTimeRanges":{"description":"Time windows when this trigger is allowed to fire. Pass an empty array to remove all restrictions.","type":"array","items":{"$ref":"#/components/schemas/TriggerDayTimeRangeDto"}}}}}}}