AVEVA Predictive Analytics External Algorithm Developer Guide
AVEVA Predictive Analytics External Algorithm Developer Guide
Developer Guide
aveva.com
© 2015-2023 AVEVA Group Limited or its subsidiaries. All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any
means, mechanical, photocopying, recording, or otherwise, without the prior written permission of AVEVA
Group Limited. No liability is assumed with respect to the use of the information contained herein.
Although precaution has been taken in the preparation of this documentation, AVEVA assumes no responsibility
for errors or omissions. The information in this documentation is subject to change without notice and does not
represent a commitment on the part of AVEVA. The software described in this documentation is furnished under
a license agreement. This software may be used or copied only in accordance with the terms of such license
agreement. AVEVA, the AVEVA logo and logotype, OSIsoft, the OSIsoft logo and logotype, ArchestrA, Avantis,
Citect, DYNSIM, eDNA, EYESIM, InBatch, InduSoft, InStep, IntelaTrac, InTouch, Managed PI, OASyS, OSIsoft
Advanced Services, OSIsoft Cloud Services, OSIsoft Connected Services, OSIsoft EDS, PIPEPHASE, PI ACE, PI
Advanced Computing Engine, PI AF SDK, PI API, PI Asset Framework, PI Audit Viewer, PI Builder, PI Cloud
Connect, PI Connectors, PI Data Archive, PI DataLink, PI DataLink Server, PI Developers Club, PI Integrator for
Business Analytics, PI Interfaces, PI JDBC Driver, PI Manual Logger, PI Notifications, PI ODBC Driver, PI OLEDB
Enterprise, PI OLEDB Provider, PI OPC DA Server, PI OPC HDA Server, PI ProcessBook, PI SDK, PI Server, PI Square,
PI System, PI System Access, PI Vision, PI Visualization Suite, PI Web API, PI WebParts, PI Web Services, PRiSM,
PRO/II, PROVISION, ROMeo, RLINK, RtReports, SIM4ME, SimCentral, SimSci, Skelta, SmartGlance, Spiral Software,
WindowMaker, WindowViewer, and Wonderware are trademarks of AVEVA and/or its subsidiaries. All other
brands may be trademarks of their respective owners.
U.S. GOVERNMENT RIGHTS
Use, duplication or disclosure by the U.S. Government is subject to restrictions set forth in the license agreement
with AVEVA Group Limited or its subsidiaries and as provided in DFARS 227.7202, DFARS 252.227-7013, FAR
12-212, FAR 52.227-19, or their successors, as applicable.
AVEVA Third Party Software Notices and Licenses: https://www.aveva.com/en/legal/third-party-software-
license/
Publication date: Wednesday, July 26, 2023
Publication ID: 1254092
Contact information
AVEVA Group Limited
High Cross
Madingley Road
Cambridge
CB3 0HB. UK
https://sw.aveva.com/
For information on how to contact sales and customer training, see https://sw.aveva.com/contact.
For information on how to contact technical support, see https://sw.aveva.com/support.
To access the AVEVA Knowledge and Support center, visit https://softwaresupport.aveva.com.
Chapter 1 Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Create external algorithms. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Use endpoints to test your algorithm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Chapter 2 Overview. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Overview of data flow. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Overview of roles and responsibilities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Chapter 5 Add your external algorithm to your AVEVA Predictive Analytics system. 40
External algorithp Permissions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Connect to an external algorithm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Introduction
Overview
Endpoint structure
After you design your algorithm, test it by providing the following REST endpoints and methods:
• api/ExternalAlgorithm/creation
• api/ExternalAlgorithm/generation
• api/ExternalAlgorithm/deletion
Each endpoint requires a POST method and a HEAD method, which tests the connection between AVEVA
Predictive Analytics Server and your algorithm. The creation endpoint also has a PATCH method for updating
operational profile IDs.
Note: Your endpoints do not need to be named creation, generation and deletion. This guide uses these names
for clarity, but you can name the endpoints however you want.
Security considerations
Each request packet can contain an optional "secret" field to help prevent DDoS attacks. The secret creates a
"handshake" between AVEVA Predictive Analytics and the external algorithm server. If the connection is
configured in the Client application to include the secret, but no secret is sent in the request packet, the external
algorithm will return an error code.
When using secure sockets layer (that is, https:), the external algorithm server must have a valid certificate. If
not, the AVEVA Predictive Analytics HTML client returns an error and the expected action fails. If required, you
can bypass this safety check when designing algorithms. An operational profile using an external algorithm with
this bypass will ignore invalid certificates and continue without an error. Algorithms designed with http: do not
use a certificate.
To maintain the security of your system, it is strongly recommended that, for each external algorithm, you
• Use https.
• Do not bypass the certificates.
• Use a secret.
Creation endpoint
The creation endpoint requires a POST, a PATCH, and a HEAD method.
POST
This initializes training data used by a new operational profile or updates training data for an existing operational
profile.
Request Body
• Secret: string
• Profile ID: string
• Training Data Set: struct
• Start Time: DateTime
• End Time: DateTime
• Data Frequency Seconds: int
Must be between 1 and 999,999.
• Training Data: Array of Struct
• Timestamp: DateTime
• Is Excluded: Boolean
• Sensor Record Training Data: Array of Struct
• Sensor Name: string
• Sensor Value: double
• Sensor Quality: enum
Good = 0
Error = 3
Questionable = 4
The following is a sample JSON request body:
{
"Secret": "secret"
"ProfileId": "23b877bb-8411-4843-998d-87f957cb37fd",
"TrainingDataSet": {
"StartTime": "2022-08-04T17:09:00Z",
"EndTime": "2022-08-04T18:09:00Z",
"DataFrequencySeconds": 1800,
"TrainingData": [
{
"Timestamp": "2022-08-04T17:09:00Z",
"IsExcluded": false,
"SensorRecordTrainingData": [
{
"SensorName": "CALC.-5TO5",
"Value": 0.94,
"Quality": 0
},
{
"SensorName": "CALC.CALC0016",
"Value": 7.12027345,
"Quality": 0
}
]
},
{
"Timestamp": "2022-08-04T17:39:00Z",
"IsExcluded": false,
"SensorRecordTrainingData": [
{
"SensorName": "CALC.-5TO5",
"Value": -3.02,
"Quality": 0
},
{
"SensorName": "CALC.CALC0016",
"Value": 1.20303964,
"Quality": 0
}
]
},
{
"Timestamp": "2022-08-04T18:09:00Z",
"IsExcluded": false,
"SensorRecordTrainingData": [
{
"SensorName": "CALC.-5TO5",
"Value": 3.91,
"Quality": 0
},
{
"SensorName": "CALC.CALC0016",
"Value": 0.0827051607,
"Quality": 0
}
]
}
]
}
}
Response
• Returns:
HTML OK (status 200) if successful
HTML Bad Request (status 400) if not successful
• No body
PATCH
This method replaces an operational profile's temporary ID (GUID) with a proper ID in the Central Database.
New operational profiles can be configured and existing profiles can be retrained before their projects are saved.
However, when a profile uses an external algorithm, it requires an ID to allow Data Playback to function. So,
Predictive Analytics creates a temporary ID for profiles that use external algorithms while they are being
retrained and before their projects are saved.
The Patch method replaces that temporary ID with a permanent one so the project and profile can be stored
properly in the Central Database.
Request Body
• Secret: string
• Old ID: string
• New ID: string
The following is a sample JSON request body:
{
"Secret": "secret"
"OldId": "95f25678-05e4-40b1-9b68-892a36d00a1c",
"NewId": "5267"
}
Response
• Returns:
HTML OK (status 200) if successful
HTML Bad Request (status 400) if not successful
• No body
HEAD
• This allows Predictive Analytics to validate the server/endpoint.
• No request parameters or body.
Response should always be HTML OK (status 200) with no body.
Generation endpoint
The generation endpoint requires a POST and a HEAD method.
Generation is required for generating predicted values based on each point's current timestamp value.
POST
This generates predicted values for an external algorithm operational profile using the current timestamp values
of each point.
Request Body
• Secret: string
• Profile ID: string (same ID as used in creation[POST])
• Current Sensor Data: Array of Struct
SensorName":"CALC.-5TO5",
"SensorTimestamp": "2022-08-26T15:46:29-05:00",
"Value": -3.02,
"Quality": 1
},
{
SensorName":"CALC.CALC0016",
"SensorTimestamp": "2022-08-26T15:46:25-05:00",
"Value": 1.7239906,
"Quality": 1
}
]
}
Response
• Returns:
HTML OK (status 200) if successful
HTML Bad Request (status 400) if not successful
• Return body (if status 200)
• Overall Generation Timestamp: DateTime
• Generation Response Data: Array of Struct
• Sensor Generation Timestamp: DateTime
• Sensor Predicted Value: double
• Sensor Predicted Upper Value: double (nullable)
• Sensor Predicted Lower Value: double (nullable)
The following is a sample JSON response body:
{
"requestTimestamp": "2022-08-26T15:50:42.5112295-05:00",
"data":[
{
"timestamp": "2022-08-26T15:50:42.5076124-05:00",
"predictedValue": -3.07,
"upperValue": null,
"lowerValue": -3.17
},
{
"timestamp": "2022-08-26T15:50:42.5076182-05:00",
"predictedValue": 14.487136450000001,
"upperValue": null,
"lowerValue": 14.387136450000002
}
]
}
HEAD
• This allows Predictive Analytics to validate the server/endpoint.
• No request parameters or body.
Response should always be HTML OK (status 200) with no body.
Deletion endpoint
The deletion endpoint requires a POST and a HEAD method.
Deletion is required for retraining or removing operational profiles and for maintaining database integrity.
POST
This deletes an external algorithm profile from the external algorithm server.
Note By using POST instead of DELETE, you can send a request body.
Request Body
• Secret: string
• Profile ID: string (same ID as used in creation[POST])
• Response:
• Returns:
HTML OK (status 200) if successful
HTML Bad Request (status 400) if not successful
• No body
The following is a sample JSON request body:
{
"Secret": "secret"
"ProfileId": "5267"
}
HEAD
Questionable = 4
}
Return value:
public class GenerateResponseDto
{
public DateTime RequestTimestamp {get;}
public List<GenerateResponseData> Data {get;}
}
public class GenerateResponseData
{
public DateTime Timestamp {get;}
public double PredictedValue {get;}
public double? UpperValue {get;}
public double? LowerValue {get;}
}
}
The result should look like this:
var pointTrainingData =
trainingData.SensorRecordTrainingData[pointIndex];
var trainingPointName = pointTrainingData.SensorName;
var generatePointName = generateInfo.SensorData[pointIndex].SensorName;
if (trainingPointName != generatePointName)
{
ExternalAlgorithm.Log($"Training Point Name {trainingPointName} is not
the same as Generation Point Name {generatePointName}!
{Environment.NewLine}");
}
sb.Append($"\tPoint Name: {pointTrainingData.SensorName}
{Environment.NewLine}");
var timestamp = DateTime.UtcNow;
var trainingValue = pointTrainingData.Value;
var sensorValue = generateInfo.SensorData[pointIndex].Value;
var prediction = trainingValue + sensorValue;
var upperValue = prediction + 0.1;
var lowerValue = prediction - 0.1;
sb.Append($"\t\tTimestamp: {timestamp}{Environment.NewLine}");
sb.Append($"\t\tTraining Value: {trainingValue}{Environment.NewLine}");
sb.Append($"\t\tSensor Value: {sensorValue}{Environment.NewLine}");
sb.Append($"\t\tPredicted Value: {prediction}{Environment.NewLine}");
sb.Append($"\t\tUpper Value: {upperValue}{Environment.NewLine}");
sb.Append($"\t\tLower Value: {lowerValue}{Environment.NewLine}
{Environment.NewLine}");
generateResponse.Add(new GenerateResponseData(timestamp, prediction,
upperValue, lowerValue));
}
this.trainingDataSetsIndexByProfileId[generateInfo.ProfileId] =
trainingDataSetIndex;
ExternalAlgorithm.Log(sb.ToString());
return new GenerateResponseDto(DateTime.UtcNow, generateResponse);
}
var outString = $"{DateTime.UtcNow} - Profile {generateInfo.ProfileId} not found.";
ExternalAlgorithm.Log(outString);
return null;
}
public bool DeleteProfile(DeletionRequestDto deleteInfo)
{
var profileId = deleteInfo.ProfileId;
var profileFound = this.trainingDataSetsByProfileId.Remove(profileId);
var outString = profileFound ? $"{DateTime.UtcNow} - Profile {profileId}
deleted." : $"{DateTime.UtcNow} - Profile {profileId} not found.";
ExternalAlgorithm.Log(outString + Environment.NewLine);
this.trainingDataSetsIndexByProfileId.Remove(profileId);
this.DisplayProfileIds();
return profileFound;
}
private static void Log(string logMessage)
{
Console.WriteLine(logMessage);
}
}
Code:
ExternalAlgorithmController.cs file contracts:
using ExternalAlgorithmTestServer.Dtos;
using ExternalAlgorithmTestServer.Interfaces;
using Microsoft.AspNetCore.Mvc;
// For more information on enabling Web API for empty projects, visit https://
go.microsoft.com/fwlink/?LinkID=397860
namespace ExternalAlgorithmTestServer.Controllers
{
[Route("api/[controller]")]
[ApiController]
[DisableRequestSizeLimit]
public class ExternalAlgorithmController : ControllerBase
{
private readonly IExternalAlgorithm externalAlgorithmProcessing;
public ExternalAlgorithmController(IExternalAlgorithm externalAlgorithmProcessing)
{
this.externalAlgorithmProcessing = externalAlgorithmProcessing;
}
[HttpPost("creation")]
public IActionResult Create(CreateRequestDto createInfo)
{
var success = this.externalAlgorithmProcessing.InitializeProfile(createInfo);
return success ? this.Ok() : this.BadRequest();
}
[HttpPatch("creation")]
public IActionResult UpdateProfileId(IdPatchRequestDto patchInfo)
{
var success = this.externalAlgorithmProcessing.UpdateProfileId(patchInfo);
return success ? this.Ok() : this.BadRequest();
}
[HttpHead("creation")]
public IActionResult Create()
{
return this.Ok();
}
[HttpPost("generation")]
public ActionResult<GenerateResponseDto> Generate(GenerateRequestDto generateInfo)
{
var prediction =
this.externalAlgorithmProcessing.GetProfilePrediction(generateInfo);
return prediction == null ? this.BadRequest() : prediction;
}
[HttpHead("generation")]
public IActionResult Generate()
{
return this.Ok();
}
[HttpPost("deletion")]
public IActionResult Delete(DeletionRequestDto deleteInfo)
{
var success = this.externalAlgorithmProcessing.DeleteProfile(deleteInfo);
return success ? this.Ok() : this.BadRequest();
}
[HttpHead("deletion")]
public IActionResult Delete()
{
return this.Ok();
}
}
}
Code:
using ExternalAlgorithmTestServer.Interfaces;
using ExternalAlgorithmTestServer.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IExternalAlgorithm, ExternalAlgorithm>();
var app = builder.Build();
app.MapControllers();
app.Run();
Python example
This Web application was built on Flask (Micro Web Framework).
The application:
1. Receives values passed from the AVEVA Predictive Analytics CVT
2. Calculates a predicted value for the timestamp
3. Calculates an optional upper and lower predicted range for the timestamp
Prerequisites
Python version 3 or higher
scipy==1.9.2
sklearn==0.0
threadpoolctl==3.1.0
waitress==2.1.2
Werkzeug==2.2.2
self.upper_bound = []
self.lower_bound = []
def get_json(self):
now = datetime.now().isoformat()
resp = {}
resp['requestTimestamp'] = now
resp['data'] = []
for v in self.values:
resp['data'].append({
'timestamp' : now,
'predictedValue' : v,
'upperValue' : v*1.1,
'lowerValue' : v*0.9
})
# print(resp)
return jsonify(resp)
<body>
<h1>External Algorithm Manager</h1>
</body>
</html>
if request.method == 'POST':
if not secret:
return jsonify({'error' : 'Unauthenticated'}), 401
id=data.get('ProfileId')
if not id:
return jsonify({'error' : 'Bad Request'}), 400
training_dataset = data.get('TrainingDataSet')
# start_time = training_dataset.get('StartTime')
# end_time = training_dataset.get('EndTime')
# frequency = training_dataset.get('DataFrequencySeconds')
training_data = training_dataset.get('TrainingData')
np_data_array = prepare_training_data_set(training_data)
kmeans = KMeans(n_clusters=200, random_state=0).fit(np_data_array)
model_ = get_model(models, id)
if model_ == None:
model_ = PredictionModel(id)
model_.save_model(kmeans)
models.append(model_)
else:
model_.add_model(kmeans)
pickle = jsonpickle.encode(kmeans)
create_model_file(id, pickle)
return jsonify({'status' : 'ok'})
return jsonify({'error' : 'Bad Request'}), 400
@bp.route('/kmeans/predict', methods=['HEAD', 'POST'])
def predict():
if request.method == 'HEAD':
return jsonify({'status' : 'ok'})
data = request.get_json()
secret = data.get('Secret')
if request.method == 'POST':
if not secret:
return jsonify({'error' : 'Unauthenticated'}), 401
sensor_data = data.get('SensorData')
nparray = prepare_prediction_data_set(sensor_data)
id = data.get('ProfileId')
cc = model_.model.cluster_centers_
nparray = np.reshape(nparray, (1,-1))
Y = model_.model.predict(nparray)
outputs = cc[Y[0]]
outputs_list = outputs.tolist()
predict = Prediction(outputs_list)
return predict.get_json()
def prepare_training_data_set(json_data):
if len(json_data) > 0:
arr = []
for _data in json_data:
__data = _data.get('SensorRecordTrainingData')
l = []
for _d in __data:
l.append(_d.get('Value'))
arr.append(l)
nparray = [np.array(_arr) for _arr in arr]
return nparray
return []
def prepare_prediction_data_set(json_data):
if len(json_data) > 0:
arr = []
for _data in json_data:
val = _data.get('Value')
arr.append(val)
nparray = [np.array(_arr) for _arr in arr]
return nparray
return []
def check_model_exist(id):
return os.path.exists(get_abs_path(id))
def remove_model_file(id):
os.remove(get_abs_path(id))
15. To make the algorithm available to your operational profiles, select Enabled.
Note: If you disable an external algorithm that is assigned to a deployed operational profile, AVEVA
Predictive Analytics undeploys the profile.
16. Select OK.
4. Select OK.
The project now uses the external algorithm to process the operational profile.