Skip to main content

Device AI Predictions - Architecture Comparison

This document provides a side-by-side comparison of the existing AI prediction implementation (for hydraulic model entities) and the proposed implementation for Device data.


High-Level Comparison

ComponentModel Entities (Current)Device Entities (Proposed)
Entity TypesTank, Reservoir, Junction, Pipe, Pump, ValveDevice (Water Meter)
Data SourceHydraulic simulation resultsPhysical meter readings
Data GranularitySub-hourly to hourlyMonthly to yearly
Data VolumeHigh (1000s of points)Low (10s of points)
Prediction ModelTSAI InceptionTimePlusTSAI InceptionTimePlus (same)
Fallback ModelScikit-learn Linear RegressionScikit-learn Linear Regression (same)
Frontend ComponentForecastChart, HydrQualChartDeviceChart

Architecture Comparison

Current: Model Entity Predictions

┌──────────────────────────────────────────────────────────────┐
│ Frontend (React) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ EsriCustomPopup │ │ ForecastChart │ │
│ │ HydrQualChart │ │ │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ └─────────┬──────────┘ │
└─────────────────────┼────────────────────────────────────────┘


GET /{node_type}/{node_id}/graph-data
?base_url={url}
&scenario_id={id}
&hydraulics_metric={metric}
&quality_species={species}
&include_forecast=true
&forecast_percentage=1.0

┌─────────────────────┼────────────────────────────────────────┐
│ FastAPI Service │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ get_node_graph_data() │ │
│ │ ├─ get_node_hydraulics_generic() │ │
│ │ ├─ get_node_quality_generic() │ │
│ │ └─ add_forecast_data() │ │
│ │ └─ predict_with_tsai() │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────┼────────────────────────────────────────┘


GET /api/{NodeType}/{nodeId}/Hydraulics?scenarioId={id}
GET /api/{NodeType}/{nodeId}/Quality?scenarioId={id}&species={name}

┌─────────────────────┼────────────────────────────────────────┐
│ ASP.NET API │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ TankController, ReservoirController, etc. │ │
│ │ ├─ GetHydraulics() │ │
│ │ └─ GetQuality() │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────┼────────────────────────────────────────┘


NodeHydraulicsDatum, NodeQualityDatum tables

Proposed: Device Entity Predictions

┌──────────────────────────────────────────────────────────────┐
│ Frontend (React) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ EsriCustomPopup │ │ DeviceChart │ │
│ │ (with Device │ │ (enhanced) │ │
│ │ support) │ │ │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ └─────────┬──────────┘ │
└─────────────────────┼────────────────────────────────────────┘


GET /device/{device_id}/tag/{tag_id}/graph-data
?base_url={url}
&include_forecast=true
&forecast_percentage=1.0
&startTime={unix}
&endTime={unix}

┌─────────────────────┼────────────────────────────────────────┐
│ FastAPI Service (NEW) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ get_device_tag_graph_data() [NEW] │ │
│ │ ├─ Fetch from ASP.NET Device endpoint │ │
│ │ ├─ Transform data format │ │
│ │ └─ add_forecast_data() [REUSED] │ │
│ │ └─ predict_with_tsai() [REUSED] │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────┼────────────────────────────────────────┘


GET /api/Device/{deviceId}/tag/{tagId}/data
?startTime={unix}
&endTime={unix}

┌─────────────────────┼────────────────────────────────────────┐
│ ASP.NET API (NEW) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ DeviceController [ENHANCED] │ │
│ │ └─ GetDeviceTagData() [NEW] │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────┼────────────────────────────────────────┘


DeviceTagDatum table

Data Model Comparison

Model Entity Data

NodeHydraulicsDatum:

{
Id: string
SimUnixTime: long
NodeId: string
ScenarioId: int
Head: double?
Demand: double?
Pressure: double?
Flow: double?
// ... multiple metrics in one record
}

NodeQualityDatum:

{
Id: string
SimUnixTime: long
NodeId: string
ScenarioId: int
Species: string
Quality: double
}

Device Entity Data

DeviceTagDatum:

{
DeviceTagId: int (PK)
UnixTime: long (PK)
Value: double
// Simple: just one value per timestamp
}

DeviceTag (metadata):

{
Id: int
Name: string // "Consumption", "Usage"
TagType: string // "monthly_consumption"
Unit: string // "cubic feet"
DeviceId: int
}

Key Differences:

  • Model data: Multiple metrics per record, scenario-based
  • Device data: Single value per record, tag-based
  • Model data: Tied to simulation scenarios
  • Device data: Real-world measurements, no scenarios

API Endpoint Comparison

Model Entities (Current)

FastAPI

GET /{node_type}/{node_id}/graph-data

Parameters:
- node_type: "tank" | "reservoir" | "junction" | "pipe" | "pump" | "valve"
- node_id: string
- base_url: string
- scenario_id: int
- hydraulics_metric: string (optional)
- quality_species: string (optional)
- include_forecast: bool
- forecast_percentage: float
- start_time: int (optional)
- end_time: int (optional)

Response:
{
"node_type": "tank",
"node_id": "T-1",
"scenario_id": 1,
"hydraulics_metric": "Head",
"quality_species": "Chlorine",
"hydraulics_data": [
{ "simUnixTime": 1234, "head": 123.45, ... }
],
"quality_data": [
{ "simUnixTime": 1234, "species": "Chlorine", "quality": 0.8 }
],
"forecast_info": { ... }
}

ASP.NET

GET /api/{NodeType}/{nodeId}/Hydraulics

Parameters:
- scenarioId: int
- startTime: long (optional)
- endTime: long (optional)
- decimalPrecision: int

Response:
[
{ "id": "...", "simUnixTime": 1234, "head": 123.45, "demand": 50.2, ... }
]

Device Entities (Proposed)

FastAPI (NEW)

GET /device/{device_id}/tag/{tag_id}/graph-data

Parameters:
- device_id: int
- tag_id: int
- base_url: string
- include_forecast: bool
- forecast_percentage: float
- start_time: int (optional)
- end_time: int (optional)

Response:
{
"device_id": 123,
"tag_id": 456,
"include_forecast": true,
"data": [
{ "deviceTagId": 456, "unixTime": 1234, "value": 1234.56, "isForecast": false },
{ "deviceTagId": 456, "unixTime": 2345, "value": 1250.30, "isForecast": true }
],
"forecast_info": { ... }
}

ASP.NET (NEW)

GET /api/Device/{deviceId}/tag/{tagId}/data

Parameters:
- startTime: long (optional)
- endTime: long (optional)

Response:
[
{ "deviceTagId": 456, "unixTime": 1234, "value": 1234.56 },
{ "deviceTagId": 456, "unixTime": 2345, "value": 1150.23 }
]

Key Differences:

  • Model: Separate endpoints for hydraulics and quality, combined response
  • Device: Single endpoint for tag data, simpler structure
  • Model: Scenario-based filtering
  • Device: No scenario concept (real-world data)
  • Model: Multiple metrics per timestamp
  • Device: Single value per timestamp

Frontend Component Comparison

Model Entities

ForecastChart.jsx (Dedicated prediction chart):

<ForecastChart
id="T-1"
type="Tank"
species="Chlorine"
hydraulicsMetric="Head"
/>
  • Always fetches from FastAPI
  • Always includes predictions
  • Dedicated to showing forecasts

HydrQualChart.jsx (Standard chart):

<HydrQualChart
id="T-1"
type="Tank"
species="Chlorine"
hydraulicsMetric="Head"
/>
  • Fetches from ASP.NET API
  • No predictions
  • Standard data visualization

Device Entities

DeviceChart.jsx (Hybrid chart):

<DeviceChart
deviceId={123}
leftTag={{ id: 456, name: "Consumption", unit: "cf" }}
rightTag={{ id: 789, name: "Usage", unit: "cf" }}
showPredictions={true} // Toggle predictions on/off
/>
  • Conditionally fetches from FastAPI or ASP.NET
  • Supports dual-axis (left/right tags)
  • Single component handles both modes

Key Differences:

  • Model: Separate components for predictions vs. standard
  • Device: Single component with toggle
  • Model: Single metric or quality per chart
  • Device: Can show two tags simultaneously (dual-axis)

Prediction Pipeline Comparison

Shared Components (Used by Both)

Both Model and Device predictions use the same core prediction logic:

# Shared by both entity types
def add_forecast_data(data: List[dict], forecast_percentage: float) -> List[dict]:
"""Add forecast data using time series predictions."""
# 1. Calculate forecast points
# 2. Identify numeric fields
# 3. Generate predictions for each field
# 4. Create forecast records with isForecast flag
# 5. Return combined data

def predict_with_tsai(data: List[dict], target_field: str,
forecast_points: int) -> List[float]:
"""Use TSAI InceptionTimePlus for predictions."""
# 1. Prepare time series data
# 2. Train TSAI model
# 3. Generate predictions
# 4. Fallback to scikit-learn if needed

def predict_with_scikit_learn(data: List[dict], target_field: str,
forecast_points: int) -> List[float]:
"""Fallback using Linear Regression."""
# 1. Prepare features
# 2. Train linear model
# 3. Generate predictions

Data Transformation Differences

Model Entities:

# Input from ASP.NET API
{
"id": "...",
"simUnixTime": 1234567890,
"head": 123.45,
"demand": 50.2,
"pressure": 85.3
}

# Already in correct format for prediction pipeline
# Multiple fields predicted simultaneously

Device Entities:

# Input from ASP.NET API
{
"deviceTagId": 456,
"unixTime": 1234567890,
"value": 1234.56
}

# Transform to prediction format
{
"simUnixTime": 1234567890, # Rename unixTime -> simUnixTime
"value": 1234.56 # Keep value field
}

# Single field (value) predicted

Configuration Comparison

Model Entities

# config.py
PREDICTION_CONFIG = {
"min_data_points": 5,
"window_size_ratio": 0.2,
"min_window_size": 10,
"max_window_size": 30,
"batch_sizes": 32,
"training_epochs": 3,
"learning_rate": 0.001
}

FALLBACK_CONFIG = {
"min_data_points": 3,
"context_window_size": 10,
"default_interval_seconds": 3600 # 1 hour
}

NODE_TYPE_MAPPING = {
"tank": "Tank",
"reservoir": "Reservoir",
"junction": "Junction",
"pipe": "Pipe",
"pump": "Pump",
"valve": "Valve"
}

Device Entities (Proposed Addition)

# config.py (NEW)
DEVICE_CONFIG = {
"supported_tag_types": [
"monthly_consumption",
"quarterly_consumption",
"yearly_consumption",
"daily_flow",
"hourly_flow"
],
"min_data_points": 3, # Lower threshold for sparse data
"default_forecast_percentage": 0.5,
"default_interval_seconds": 2592000 # 30 days (monthly)
}

# Can override PREDICTION_CONFIG for devices if needed
DEVICE_PREDICTION_CONFIG = {
"min_data_points": 3, # Lower for monthly data
"window_size_ratio": 0.3, # Larger window for seasonal patterns
"min_window_size": 3,
"max_window_size": 12, # ~1 year for monthly data
"training_epochs": 2 # Fewer epochs for smaller datasets
}

User Experience Comparison

Model Entities

  1. User clicks on Tank/Pipe/etc in map
  2. Popup opens with entity details
  3. User clicks "Graph" tab
  4. Dropdowns appear: "Hydraulics Metric" and "Quality Species"
  5. AI Prediction button appears (green button with brain icon)
  6. Chart shows standard data initially
  7. User clicks "AI Predictions" button
  8. Button turns orange, text changes to "Hide Predictions"
  9. Chart updates with forecast data (may take 1-2 seconds)
  10. Predictions shown as continuation of line with annotation

Device Entities (Proposed)

  1. User clicks on Device (water meter) in map
  2. Popup opens with device details
  3. User clicks "Graph" tab
  4. Dropdowns appear: "Left Axis Tag" and "Right Axis Tag"
  5. AI Prediction button appears (green button with brain icon) ✓ Same
  6. Chart shows standard data initially
  7. User clicks "AI Predictions" button
  8. Button turns orange, text changes to "Hide Predictions" ✓ Same
  9. Chart updates with forecast data (may take 1-2 seconds) ✓ Same
  10. Predictions shown with dashed lines, 70% opacity

Key Similarities:

  • Same prediction button component
  • Same toggle behavior
  • Same visual feedback (orange when active)
  • Same loading states

Key Differences:

  • Device: Two tags simultaneously (dual-axis)
  • Device: Predictions styled differently (dashed vs. solid)
  • Device: No quality/hydraulics distinction (just tags)

Testing Comparison

Model Entities Tests

Unit Tests:

  • Test prediction pipeline with synthetic data
  • Test fallback mechanisms
  • Test time series window generation

Integration Tests:

  • Test FastAPI → ASP.NET → Database flow
  • Test with different node types
  • Test with various scenario IDs
  • Test with quality and hydraulics data

UI Tests:

  • Test prediction button toggle
  • Test chart rendering with predictions
  • Test annotation display
  • Test multiple node types

Device Entities Tests (Proposed)

Unit Tests:

  • Test prediction pipeline with sparse data ✓ Same algorithm
  • Test with different tag types (monthly/quarterly/yearly)
  • Test data transformation (unixTime ↔ simUnixTime)

Integration Tests:

  • Test FastAPI → ASP.NET → Database flow ✓ Similar pattern
  • Test with different device IDs
  • Test with different tag types
  • Test with date range filters

UI Tests:

  • Test prediction button toggle ✓ Same button
  • Test dual-axis chart with predictions
  • Test dashed line styling for forecasts
  • Test with one vs. two tags

Performance Comparison

Model Entities

Typical Dataset:

  • 1000-5000 data points per metric
  • Sub-hourly granularity
  • Multiple metrics per request

Performance Characteristics:

  • Training time: 2-5 seconds
  • Memory usage: Moderate (32MB+)
  • Prediction time: < 1 second

Device Entities (Expected)

Typical Dataset:

  • 12-48 data points per tag (monthly/quarterly)
  • Monthly to yearly granularity
  • Single value per request

Expected Performance:

  • Training time: < 1 second (much less data)
  • Memory usage: Low (< 10MB)
  • Prediction time: < 0.5 seconds
  • Likely to use fallback model more often due to data sparsity

Optimization Opportunities:

  • Device predictions could be cached longer (data changes infrequently)
  • Batch predictions for multiple devices
  • Pre-compute predictions for common date ranges

Summary

AspectModel EntitiesDevice EntitiesReusable?
Prediction AlgorithmTSAI + Scikit-learnTSAI + Scikit-learn✅ 100%
API PatternGeneric node endpointDevice-specific endpoint✅ Pattern only
Data FormatMulti-metric recordsSingle-value records⚠️ Needs transform
Frontend ButtonPredictionButtonPredictionButton✅ 100%
Chart ComponentForecastChart/HydrQualChartDeviceChart⚠️ Needs enhancement
ConfigurationPREDICTION_CONFIGDEVICE_CONFIG⚠️ Additional config
UI/UX FlowClick → Toggle → PredictClick → Toggle → Predict✅ 100%

Reusability Score: ~70-80%

New Code Required: ~20-30%


Implementation Effort Comparison

Model Entities (Original Implementation)

  • Effort: ~40-60 hours
  • Scope: Full TSAI pipeline, multiple node types, frontend integration
  • Complexity: High (new feature)

Device Entities (Proposed Addition)

  • Effort: 10-14 hours (see design doc)
  • Scope: Extend existing pipeline, single entity type, enhance one component
  • Complexity: Low to Medium (extension of existing)

Efficiency Gain: ~75% reduction in effort due to reusable components


Conclusion

The proposed Device AI predictions implementation leverages ~70-80% of the existing infrastructure:

Fully Reusable:

  • Prediction algorithms (TSAI, scikit-learn)
  • Prediction button component
  • UI/UX patterns
  • Configuration patterns
  • Error handling

⚠️ Partially Reusable:

  • API endpoint structure (same pattern, different route)
  • Chart components (enhance existing)
  • Data transformation (simple mapping)

New Components:

  • ASP.NET Device tag endpoint
  • FastAPI Device prediction endpoint
  • Device-specific configuration

This high degree of reusability makes the implementation efficient and maintainable, with an estimated effort of only 10-14 hours vs. 40-60 hours for a from-scratch implementation.