Skip to main content

Device AI Predictions - Quick Reference Guide

Overview

Adding AI prediction support for Device (water meter) time-series data to the existing TSAI-based prediction system.

Full Design: See device_ai_predictions_design.md


Key Changes Summary

1. ASP.NET API (C# .NET Core)

File: dwd-api-aspnet/DwdApiAspNet/Controllers/DeviceController.cs

New Endpoints:

GET /api/Device/{deviceId}/tag/{tagId}/data
- Returns time-series data for a device tag
- Optional: startTime, endTime filters
- Returns: [{ deviceTagId, unixTime, value }]

GET /api/Device/{deviceId}/tag/{tagId}
- Returns tag metadata
- Returns: { id, name, tagType, unit, deviceId }

2. FastAPI (Python)

File: dwd-api-fastapi/main.py

New Models:

class DeviceTagData(BaseModel):
deviceTagId: int
unixTime: int
value: float

class DeviceTagInfo(BaseModel):
id: int
name: str
tagType: str
unit: Optional[str]
deviceId: int

New Endpoint:

GET /device/{device_id}/tag/{tag_id}/graph-data
- Fetches data from ASP.NET API
- Applies TSAI predictions
- Parameters:
* base_url: ASP.NET API URL
* include_forecast: bool (default: true)
* forecast_percentage: float (0.1-2.0)
* startTime, endTime: optional filters

File: dwd-api-fastapi/config.py

New Config:

DEVICE_CONFIG = {
"supported_tag_types": [
"monthly_consumption",
"quarterly_consumption",
"yearly_consumption"
],
"min_data_points": 3,
"default_forecast_percentage": 0.5
}

3. Frontend (React)

File: dwd-frontend-react/src/components/DeviceChart.jsx

Changes:

  • Add showPredictions prop
  • Fetch from FastAPI when predictions enabled
  • Fetch from ASP.NET API when predictions disabled
  • Style forecast data differently (dashed line, opacity 0.7)

File: dwd-frontend-react/src/components/EsriCustomPopup.jsx

Changes:

  • Add PredictionButton to Device chart section
  • Pass showPredictions prop to DeviceChart
  • Use existing handlePredictionToggle handler

File: dwd-frontend-react/src/constants.jsx

New Constant:

export const FASTAPI_ENDPOINTS = {
// ... existing endpoints
GetDeviceTagGraphData: '/device/{device_id}/tag/{tag_id}/graph-data'
};

Implementation Steps

Step 1: ASP.NET Backend (2-3 hours)

  1. Open DeviceController.cs
  2. Add GetDeviceTagData method
  3. Add GetDeviceTagInfo method
  4. Test with Postman/API testing tool
  5. Verify data format matches design

Step 2: FastAPI Backend (3-4 hours)

  1. Open main.py
  2. Add Pydantic models (DeviceTagData, DeviceTagInfo)
  3. Add get_device_tag_graph_data endpoint
  4. Open config.py, add DEVICE_CONFIG
  5. Test endpoint with sample data
  6. Verify predictions are generated correctly

Step 3: Frontend Updates (3-4 hours)

  1. Open DeviceChart.jsx
    • Add showPredictions prop
    • Add conditional fetching logic
    • Add forecast data styling
  2. Open EsriCustomPopup.jsx
    • Add PredictionButton to Device section
    • Pass showPredictions to DeviceChart
  3. Open constants.jsx
    • Add GetDeviceTagGraphData endpoint
  4. Test in browser with real device data

Step 4: Testing & Polish (2-3 hours)

  1. End-to-end testing
  2. Error handling verification
  3. UI/UX polish
  4. Performance testing with large datasets
  5. Documentation updates

Code Snippets

ASP.NET Endpoint Template

[HttpGet("{deviceId}/tag/{tagId}/data")]
public async Task<ActionResult<IEnumerable<object>>> GetDeviceTagData(
int deviceId, int tagId,
[FromQuery] long? startTime, [FromQuery] long? endTime)
{
var query = _context.DeviceTagData
.Where(d => d.DeviceTagId == tagId);

if (startTime.HasValue)
query = query.Where(d => d.UnixTime >= startTime.Value);
if (endTime.HasValue)
query = query.Where(d => d.UnixTime <= endTime.Value);

var data = await query
.OrderBy(d => d.UnixTime)
.Select(d => new {
deviceTagId = d.DeviceTagId,
unixTime = d.UnixTime,
value = d.Value
})
.ToListAsync();

return Ok(data);
}

FastAPI Endpoint Template

@app.get("/device/{device_id}/tag/{tag_id}/graph-data")
async def get_device_tag_graph_data(
device_id: int,
tag_id: int,
base_url: str = Query(...),
include_forecast: bool = Query(default=True),
forecast_percentage: float = Query(default=1.0)):

# Fetch from ASP.NET API
url = f"{base_url}Device/{device_id}/tag/{tag_id}/data"
response = await client.get(url)
tag_data = response.json()

# Transform to prediction format
transformed = [
{"simUnixTime": item["unixTime"], "value": item["value"]}
for item in tag_data
]

# Add predictions
if include_forecast:
transformed = add_forecast_data(transformed, forecast_percentage)

# Return with forecast metadata
return {
"device_id": device_id,
"tag_id": tag_id,
"data": transformed,
"forecast_info": {...}
}

React DeviceChart Update

// In DeviceChart.jsx
const DeviceChart = ({ deviceId, leftTag, rightTag, showPredictions = false }) => {
useEffect(() => {
const fetchData = async () => {
const url = showPredictions
? `${FASTAPI_BASE_URL}device/${deviceId}/tag/${leftTag.id}/graph-data`
: `${API_BASE_URL}Device/${deviceId}/tag/${leftTag.id}/data`;

const params = new URLSearchParams();
if (showPredictions) {
params.append('base_url', API_BASE_URL);
params.append('include_forecast', 'true');
params.append('forecast_percentage', '1.0');
}

const response = await fetch(`${url}?${params}`);
const data = await response.json();

// Process and render...
};

fetchData();
}, [deviceId, leftTag, rightTag, showPredictions]);

// ... rest of component
};

React EsriCustomPopup Update

// In EsriCustomPopup.jsx, around the Device chart section
{selectedDisplay === 1 && entityType === DEVICE && (
<Box>
<Box sx={{ display: 'flex', gap: 2, mb: 2 }}>
<DeviceTagDropdown ... />
<DeviceTagDropdown ... />

{/* ADD THIS: */}
{(selectedLeftDeviceTag?.id || selectedRightDeviceTag?.id) && (
<PredictionButton
onClick={handlePredictionToggle}
showPredictions={showPredictions}
loading={predictionLoading}
/>
)}
</Box>

<DeviceChart
deviceId={entityId}
leftTag={selectedLeftDeviceTag}
rightTag={selectedRightDeviceTag}
showPredictions={showPredictions} {/* ADD THIS */}
/>
</Box>
)}

Testing Checklist

Backend Testing

  • ASP.NET endpoint returns data for valid device/tag
  • ASP.NET endpoint returns 404 for invalid IDs
  • FastAPI endpoint returns predictions
  • Predictions match expected format
  • Error handling works correctly

Frontend Testing

  • Prediction button appears for device charts
  • Clicking button toggles predictions on/off
  • Chart shows historical data correctly
  • Chart shows forecast data with different styling
  • Loading states work correctly
  • Error states are handled gracefully

Integration Testing

  • Full flow: Frontend → FastAPI → ASP.NET → Database
  • Test with monthly consumption data
  • Test with quarterly consumption data
  • Test with small datasets (< 10 points)
  • Test with large datasets (> 100 points)

Data Flow Diagram

User clicks "AI Predictions" button

Frontend: showPredictions = true

Frontend: DeviceChart re-renders

Frontend: Fetches from FastAPI
GET /device/123/tag/456/graph-data?base_url=...&include_forecast=true

FastAPI: Receives request

FastAPI: Fetches raw data from ASP.NET API
GET {base_url}/Device/123/tag/456/data

ASP.NET: Queries DeviceTagDatum table

ASP.NET: Returns [{ unixTime, value }, ...]

FastAPI: Transforms to prediction format
[{ simUnixTime, value }, ...]

FastAPI: Calls add_forecast_data()

FastAPI: TSAI model generates predictions

FastAPI: Adds isForecast: true to predictions

FastAPI: Returns combined data
{ data: [...historical, ...forecasted], forecast_info: {...} }

Frontend: Receives data

Frontend: Separates historical vs. forecast data

Frontend: Renders chart with styled predictions
- Historical: solid line
- Forecast: dashed line, 70% opacity

User sees predictions in chart

Troubleshooting

Issue: Predictions not showing

Check:

  1. Is showPredictions prop being passed correctly?
  2. Is FastAPI endpoint returning data?
  3. Is isForecast flag set on predicted data?
  4. Check browser console for errors

Issue: 404 from ASP.NET API

Check:

  1. Does device with that ID exist?
  2. Does tag with that ID exist?
  3. Does tag belong to that device?

Issue: Poor prediction quality

Check:

  1. How many data points are available? (Need minimum 3)
  2. Is data too sparse? (< 10 points)
  3. Consider using simpler fallback model
  4. Check for data anomalies/outliers

Issue: Slow performance

Check:

  1. How many data points being fetched?
  2. Consider adding date range filters
  3. Check database indexing on UnixTime
  4. Consider caching predictions

API Examples

Example 1: Get Device Tag Data (ASP.NET)

GET http://localhost:5000/api/Device/123/tag/456/data

Response:
[
{ "deviceTagId": 456, "unixTime": 1698278400, "value": 1234.56 },
{ "deviceTagId": 456, "unixTime": 1700956800, "value": 1150.23 },
{ "deviceTagId": 456, "unixTime": 1703635200, "value": 1210.89 }
]

Example 2: Get Device Tag Data with Predictions (FastAPI)

GET http://localhost:8000/device/123/tag/456/graph-data?base_url=http://localhost:5000/api/&include_forecast=true&forecast_percentage=0.5

Response:
{
"device_id": 123,
"tag_id": 456,
"include_forecast": true,
"data": [
{ "deviceTagId": 456, "unixTime": 1698278400, "value": 1234.56, "isForecast": false },
{ "deviceTagId": 456, "unixTime": 1700956800, "value": 1150.23, "isForecast": false },
{ "deviceTagId": 456, "unixTime": 1703635200, "value": 1210.89, "isForecast": false },
{ "deviceTagId": 456, "unixTime": 1706313600, "value": 1185.34, "isForecast": true }
],
"forecast_info": {
"forecast_percentage": 0.5,
"forecast_points_added": 1,
"description": "Forecast adds 50% additional data points using TSAI predictions"
}
}

Key Differences: Device vs. Model Data

AspectModel Data (Tanks, Pipes)Device Data (Meters)
GranularityHourly/Sub-hourlyMonthly/Quarterly/Yearly
Data Points100s-1000s per day12-48 per year
PatternsDiurnal cycles, operationalSeasonal, billing cycles
Prediction HorizonHours to daysMonths to quarters
Data VolumeHighLow
SparsityDenseSparse

Implication: Device predictions may use simpler models (linear regression) more often due to data sparsity.


Next Steps

  1. Review this guide and full design document
  2. Set up development environment
  3. Start with ASP.NET backend (easiest)
  4. Move to FastAPI (reuses existing logic)
  5. Finish with frontend (visual changes)
  6. Test thoroughly
  7. Deploy and monitor

Estimated Total Time: 10-14 hours


Support & Resources

  • Full Design: device_ai_predictions_design.md
  • Current Prediction Code: dwd-api-fastapi/main.py (lines 532-662)
  • Example Chart: dwd-frontend-react/src/components/ForecastChart.jsx
  • Prediction Button: dwd-frontend-react/src/components/PredictionButton.jsx
  • Config: dwd-api-fastapi/config.py