Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Anomaly Detection outputs

Code Block
languagejson
{
  "table_type": "UnsTags",
  "data": [
    { "Name": "<AssetPath>/ML/AnomalyScore", "DataType": "Double", "Description": "Anomaly score (0=normal, higher=anomalous)" },
    { "Name": "<AssetPath>/ML/IsAnomaly", "DataType": "Boolean", "Description": "True when anomaly detected" },
    { "Name": "<AssetPath>/ML/LastPrediction", "DataType": "DateTime", "Description": "Timestamp of last prediction" }
  ]
}

Forecasting outputs

language
Code Block
json
{
  "table_type": "UnsTags",
  "data": [
    { "Name": "<AssetPath>/ML/Forecast", "DataType": "Double", "Description": "Forecasted value" },
    { "Name": "<AssetPath>/ML/ForecastLower", "DataType": "Double", "Description": "Lower confidence bound" },
    { "Name": "<AssetPath>/ML/ForecastUpper", "DataType": "Double", "Description": "Upper confidence bound" },
    { "Name": "<AssetPath>/ML/LastPrediction", "DataType": "DateTime", "Description": "Timestamp of last prediction" }
  ]
}

Regression outputs

Code Block
languagejson
{
  "table_type": "UnsTags",
  "data": [
    { "Name": "<AssetPath>/ML/PredictedValue", "DataType": "Double", "Description": "Model predicted value" },
    { "Name": "<AssetPath>/ML/LastPrediction", "DataType": "DateTime", "Description": "Timestamp of last prediction" }
  ]
}

Binary Classification outputs

Code Block
languagejsonc#
{
  "table_type": "UnsTags",
  "data": [
    { "Name": "<AssetPath>/ML/PredictedLabel", "DataType": "Boolean", "Description": "Predicted outcome (true/false)" },
    { "Name": "<AssetPath>/ML/Probability", "DataType": "Double", "Description": "Prediction probability (0-1)" },
    { "Name": "<AssetPath>/ML/LastPrediction", "DataType": "DateTime", "Description": "Timestamp of last prediction" }
  ]
}

...

Every ML Script Class follows this structure — always include persistence and LoadModel. No stripped-down versions.

Code Block
languagecsharpc#
// 1. Data classes — define input/output schemas for ML.NET
public class SensorData
{
    public float Value { get; set; }
}

public class PredictionResult
{
    // Fields vary by ML task (see task-specific examples below)
}

// 2. Static fields — MLContext and model persist across calls
private static MLContext mlContext = new MLContext(seed: 0);
private static ITransformer model;
private static IDataView lastTrainingDataView;
private static bool modelTrained = false;

// 3. Training buffer — collects data until enough for training
private static List<SensorData> trainingBuffer = new List<SensorData>();
private const int MinTrainingSize = 100;  // adjust per task

// 4. Model path — persisted to solution execution folder
private static readonly string ModelPath = Path.Combine(@Info.GetExecutionPath(), "<ClassName>.mlnet");

// 5. Public entry method — called from Expression or Task
public void Predict(double inputValue)
{
    trainingBuffer.Add(new SensorData { Value = (float)inputValue });

    if (!modelTrained && trainingBuffer.Count >= MinTrainingSize)
        TrainModel();

    if (modelTrained)
        RunPrediction(inputValue);
}

// 6. LoadModel — called from ServerStartup to reload persisted model
public void LoadModel()
{
    if (File.Exists(ModelPath))
    {
        model = mlContext.Model.Load(ModelPath, out _);
        modelTrained = true;
    }
}

// 7. TrainModel — build, fit, and persist the ML pipeline
private void TrainModel()
{
    lastTrainingDataView = mlContext.Data.LoadFromEnumerable(trainingBuffer);
    // pipeline.Fit() call here — see task-specific examples below
    model = pipeline.Fit(lastTrainingDataView);
    modelTrained = true;
    SaveModel();
}

// 8. SaveModel — persist to disk after training
private void SaveModel()
{
    mlContext.Model.Save(model, lastTrainingDataView.Schema, ModelPath);
}

// 9. RunPrediction — transform input and write to output tags
private void RunPrediction(double inputValue) { /* ... */ }

...

Always use the @Tag. prefix to read or write tag values:

csharp
Code Block
language
// Read from a tag
double temp = @Tag.Plant/Reactor1/Temperature.Value;

// Write to a tag
@Tag.Plant/Reactor1/ML/AnomalyScore.Value = score;
@Tag.Plant/Reactor1/ML/IsAnomaly.Value = true;
@Tag.Plant/Reactor1/ML/LastPrediction.Value = DateTime.Now;

...

Write the complete class with write_objects. The AI generates the full C# code based on the ML task chosen in Step 0.

json
Code Block
language
{
  "table_type": "ScriptsClasses",
  "data": [
    {
      "Name": "<ClassName>",
      "Code": "CSharp",
      "Domain": "Server",
      "ClassContent": "Methods",
      "NamespaceDeclarations": "Microsoft.ML;Microsoft.ML.Data;Microsoft.ML.Transforms;Microsoft.ML.Transforms.TimeSeries;Microsoft.ML.Transforms.Text;Microsoft.ML.Trainers;Microsoft.ML.TimeSeries",
      "Contents": "<AI-generated C# code following the class structure pattern>"
    }
  ]
}

...

SSA Spike Detection pipeline
Code Block
languagecsharpc#
var pipeline = mlContext.Transforms.DetectSpikeBySsa(
    outputColumnName: "Prediction",
    inputColumnName: nameof(SensorData.Value),
    confidence: 95.0,
    pvalueHistoryLength: 10,
    trainingWindowSize: 100,
    seasonalityWindowSize: 10);

...

SSA Change Point Detection pipeline
Code Block
languagecsharpc#
var pipeline = mlContext.Transforms.DetectChangePointBySsa(
    outputColumnName: "Prediction",
    inputColumnName: nameof(SensorData.Value),
    confidence: 95.0,
    changeHistoryLength: 10,
    trainingWindowSize: 100,
    seasonalityWindowSize: 10);

...

Full class example — Anomaly Detection (Spike)
Code Block
languagecsharpc#
public class SensorData
{
    public float Value { get; set; }
}

public class SpikePrediction
{
    [VectorType(3)]
    public double[] Prediction { get; set; }
}

private static MLContext mlContext = new MLContext(seed: 0);
private static ITransformer model;
private static IDataView lastTrainingDataView;
private static bool modelTrained = false;
private static List<SensorData> trainingBuffer = new List<SensorData>();
private const int MinTrainingSize = 100;
private static readonly string ModelPath = Path.Combine(@Info.GetExecutionPath(), "<ClassName>.mlnet");

public void Predict(double inputValue)
{
    trainingBuffer.Add(new SensorData { Value = (float)inputValue });

    if (!modelTrained && trainingBuffer.Count >= MinTrainingSize)
        TrainModel();

    if (modelTrained)
        RunPrediction();
}

public void LoadModel()
{
    if (File.Exists(ModelPath))
    {
        model = mlContext.Model.Load(ModelPath, out _);
        modelTrained = true;
    }
}

private void TrainModel()
{
    lastTrainingDataView = mlContext.Data.LoadFromEnumerable(trainingBuffer);
    var pipeline = mlContext.Transforms.DetectSpikeBySsa(
        outputColumnName: "Prediction",
        inputColumnName: nameof(SensorData.Value),
        confidence: 95.0,
        pvalueHistoryLength: 10,
        trainingWindowSize: 100,
        seasonalityWindowSize: 10);

    model = pipeline.Fit(lastTrainingDataView);
    modelTrained = true;
    SaveModel();
}

private void SaveModel()
{
    mlContext.Model.Save(model, lastTrainingDataView.Schema, ModelPath);
}

private void RunPrediction()
{
    var dataView = mlContext.Data.LoadFromEnumerable(trainingBuffer);
    var transformed = model.Transform(dataView);
    var predictions = mlContext.Data.CreateEnumerable<SpikePrediction>(transformed, reuseRowObject: false).ToList();
    var latest = predictions.Last();

    @Tag.<AssetPath>/ML/IsAnomaly.Value = latest.Prediction[0] == 1;
    @Tag.<AssetPath>/ML/AnomalyScore.Value = latest.Prediction[1];
    @Tag.<AssetPath>/ML/LastPrediction.Value = DateTime.Now;
}

...

Full class example — Binary Classification
Code Block
languagecsharp
public class ProcessData
{
    public float Feature1 { get; set; }  // e.g., Vibration
    public float Feature2 { get; set; }  // e.g., Temperature
    public float Feature3 { get; set; }  // e.g., Current
    public bool Label { get; set; }      // e.g., DidFault (true/false)
}

public class ClassificationPrediction
{
    public bool PredictedLabel { get; set; }
    public float Score { get; set; }
    public float Probability { get; set; }
}

private static MLContext mlContext = new MLContext(seed: 0);
private static ITransformer model;
private static IDataView lastTrainingDataView;
private static PredictionEngine<ProcessData, ClassificationPrediction> predictionEngine;
private static bool modelTrained = false;
private static List<ProcessData> trainingBuffer = new List<ProcessData>();
private const int MinTrainingSize = 200;
private static readonly string ModelPath = Path.Combine(@Info.GetExecutionPath(), "<ClassName>.mlnet");

public void Predict(double input1, double input2, double input3, bool label)
{
    trainingBuffer.Add(new ProcessData
    {
        Feature1 = (float)input1,
        Feature2 = (float)input2,
        Feature3 = (float)input3,
        Label = label
    });

    if (!modelTrained && trainingBuffer.Count >= MinTrainingSize)
        TrainModel();

    if (modelTrained)
        RunPrediction(input1, input2, input3);
}

public void LoadModel()
{
    if (File.Exists(ModelPath))
    {
        model = mlContext.Model.Load(ModelPath, out _);
        predictionEngine = mlContext.Model.CreatePredictionEngine<ProcessData, ClassificationPrediction>(model);
        modelTrained = true;
    }
}

private void TrainModel()
{
    lastTrainingDataView = mlContext.Data.LoadFromEnumerable(trainingBuffer);
    var pipeline = mlContext.Transforms.Concatenate("Features",
            nameof(ProcessData.Feature1),
            nameof(ProcessData.Feature2),
            nameof(ProcessData.Feature3))
        .Append(mlContext.BinaryClassification.Trainers.FastTree(
            labelColumnName: "Label",
            featureColumnName: "Features"));

    model = pipeline.Fit(lastTrainingDataView);
    predictionEngine = mlContext.Model.CreatePredictionEngine<ProcessData, ClassificationPrediction>(model);
    modelTrained = true;
    SaveModel();
}

private void SaveModel()
{
    mlContext.Model.Save(model, lastTrainingDataView.Schema, ModelPath);
}

private void RunPrediction(double input1, double input2, double input3)
{
    var input = new ProcessData
    {
        Feature1 = (float)input1,
        Feature2 = (float)input2,
        Feature3 = (float)input3
    };
    var result = predictionEngine.Predict(input);

    @Tag.<AssetPath>/ML/PredictedLabel.Value = result.PredictedLabel;
    @Tag.<AssetPath>/ML/Probability.Value = (double)result.Probability;
    @Tag.<AssetPath>/ML/LastPrediction.Value = DateTime.Now;
}

...

Code Block
get_table_schema('ScriptsExpressions')


json
Code Block
language
{
  "table_type": "ScriptsExpressions",
  "data": [
    {
      "Name": "ML_Predict_<SensorName>",
      "ObjectName": "",
      "Expression": "@Script.Class.<ClassName>.Predict(@Tag.<AssetPath>.<Member>)",
      "Execution": "OnChange",
      "Trigger": "<AssetPath>"
    }
  ]
}

...