Recommendations
Recommendation Messages
Recommendations in Kelvin are a method to package one or more actions into a single Recommendation, which is then displayed in the Kelvin UI for Operations to review.
The allows Operations to see and decide on the actions before they are implemented. In short this puts the Operations as the man-in-the-middle between the automation programs and the Assets.
Currently the only action available is Control Changes.
Allow automation for some Recommendations
By including the closed_loop parameter in the app.yaml, you enable Operations to determine which Assets will be automatically accepted (closed loop) and which require manual approval (open loop) prior to implementation.
This approach prevents Operations from being overwhelmed with Recommendations that do not require review or manual approval.
You can see a full example of open/closed loop setup in the Event Detection code in our Github repository. Link at the top of this page.
The Recommendation Object supports the following attributes :
| Attribute | Required | Description |
|---|---|---|
resource |
required | The KRNAsset that this Recommendation is meant for. |
type |
required | The Recommendation type (String). (e.g. speed_increase, speed_decrease, etc) |
expiration_date |
optional | Absolute datetime or a timedelta (from now) when the Control Change will expire. |
description |
optional | Detailed description for the Recommendation. |
confidence |
optional | Confidence of the recommendation (from 1 to 4). |
control_changes |
required | List of ControlChange Objects associated with the recommendation. |
metadata |
optional | Metadata for the recommendation. |
auto_accepted |
optional | Sets the Recommendation as auto accepted (Default is False). |
Evidences
When creating Recommendations, it is useful to embed the data used in the calculations for creating the new value in the recommendation.
This data is intended for;
- Operations : They can view the data when deciding whether to accept or reject the recommendation.
- Data Scientists : They can use the data to correlate with the confidence level reported by Operations to improve their machine learning models.
Info
Evidence can only be embedded into Recommendation if used in a Kelvin SmartApp™.
You can not create Recommendations with evidences using the Kelvin API or the Kelvin API Client (Python)
There are a number of different types of evidences that can be embedded with the Recommendation;
Bar Chart
This evidence is for creating bar charts.

This is how you can add this evidence into your Recommendation.
from kelvin.application import KelvinApp
from kelvin.message.evidences import BarChart
from kelvin.krn import KRNAsset
from datetime import datetime
async def main() -> None:
app = KelvinApp()
await app.connect()
evidences = [
BarChart(
title="Sample Bar Chart Title",
timestamp=datetime.now(),
x_axis={
"title": "X-Axis Title",
"categories": ["Category A", "Category B", "Category C", "Category D"]
},
y_axis={
"title": "Y-Axis Title",
"min": 0
},
series=[
{"name": "Series 1", "data": [5, 10, 15, 20]},
{"name": "Series 2", "data": [7, 14, 21, 28]}
]
)
]
recommendation = Recommendation(
resource=KRNAsset('pcp_51'),
type="decrease_speed",
control_changes=[],
evidences=evidences,
)
await app.publish(recommendation)
if __name__ == "__main__":
asyncio.run(main())
Line Chart
This evidence example is for creating line charts.
There are four types of line charts you can create;
- Linear
- Date Time
- Category
- Logarithmic

This is how you can add this evidence into your Recommendation.
from kelvin.application import KelvinApp
from kelvin.message.evidences import LineChart
from kelvin.krn import KRNAsset
from datetime import datetime
async def main() -> None:
app = KelvinApp()
await app.connect()
evidences = [
LineChart(
title="Sample Chart Title",
timestamp=datetime.now(),
x_axis={
"type":"linear", # | 'linear' | 'datetime' | 'category' | 'logarithmic';
"categories": ["Category 1", "Category 2", "Category 3"],
"title": "X-Axis Title"
},
y_axis={"title": "Y-Axis Title"},
series=[
{"name": "Series 1", "data": [1, 2, 3, 4, 5]},
{"name": "Series 2", "data": [[1, 2], [2, 3], [3, 5], [4, 7]]}
]
)
]
recommendation = Recommendation(
resource=KRNAsset('pcp_51'),
type="decrease_speed",
control_changes=[],
evidences=evidences,
)
await app.publish(recommendation)
if __name__ == "__main__":
asyncio.run(main())
Dynacard
This evidence example is for creating line charts.
There are four types of line charts you can create;
- Linear
- Date Time
- Category
- Logarithmic

This is how you can add this evidence into your Recommendation.
from kelvin.application import KelvinApp
from kelvin.message.evidences import Dynacard
from kelvin.krn import KRNAsset
from datetime import datetime
async def main() -> None:
app = KelvinApp()
await app.connect()
evidences = [
Dynacard(
title="Sample Chart Title",
timestamp=datetime.now(),
xAxis={
"title": "X-Axis Title"
},
yAxis={
"title": "Y-Axis Title"
},
series=[
{"name": "Series 1", "data": [1, 2, 3, 4, 5]},
{"name": "Series 2", "data": [[1, 2], [2, 3], [3, 5], [4, 7]]}
]
)
]
recommendation = Recommendation(
resource=KRNAsset('pcp_51'),
type="decrease_speed",
control_changes=[],
evidences=evidences,
)
await app.publish(recommendation)
if __name__ == "__main__":
asyncio.run(main())
HighCharts
This evidence is for creating any type of chart using the powerful High Charts format.

This is how you can add this evidence into your Recommendation.
from kelvin.application import KelvinApp
from kelvin.message.evidences import Chart
from kelvin.krn import KRNAsset
from datetime import datetime
async def main() -> None:
app = KelvinApp()
await app.connect()
evidences = [
Chart(
title="Sample Chart Title",
timestamp=datetime.now(),
... # Content here will depend on the type of High Chart you choose to display.
)
]
recommendation = Recommendation(
resource=KRNAsset('pcp_51'),
type="decrease_speed",
control_changes=[],
evidences=evidences,
)
await app.publish(recommendation)
if __name__ == "__main__":
asyncio.run(main())
Image
This evidence is for showing images. This is particular useful for computer vision related Recommendations.

Note
The image must be either from the Kelvin File Storage or a publicly available link.
This is how you can add this evidence into your Recommendation.
from kelvin.application import KelvinApp
from kelvin.message.evidences import Image
from kelvin.krn import KRNAsset
from datetime import datetime
async def main() -> None:
app = KelvinApp()
await app.connect()
evidences = [
Image(
title="My Image",
description="This is the image or evidence description.",
url="https://www.example.com/image.jpg",
)
]
recommendation = Recommendation(
resource=KRNAsset('pcp_51'),
type="decrease_speed",
control_changes=[],
evidences=evidences,
)
await app.publish(recommendation)
if __name__ == "__main__":
asyncio.run(main())
Markdown
This evidence is for showing text in markdown format.

Note
The image must be either from the Kelvin File Storage or a publicly available link.
This is how you can add this evidence into your Recommendation.
from kelvin.application import KelvinApp
from kelvin.message.evidences import Markdown
from kelvin.krn import KRNAsset
from datetime import datetime
async def main() -> None:
app = KelvinApp()
await app.connect()
evidences = [
Markdown(
title="My Markdown",
markdown="""
# Evidence 1
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
# Evidence 2
...
""" # (Multi line) String
)
]
recommendation = Recommendation(
resource=KRNAsset('pcp_51'),
type="decrease_speed",
control_changes=[],
evidences=evidences,
)
await app.publish(recommendation)
if __name__ == "__main__":
asyncio.run(main())
IFrame
This evidence is for showing any web content in an iFrame.

This is how you can add this evidence into your Recommendation.
from kelvin.application import KelvinApp
from kelvin.message.evidences import IFrame
from kelvin.krn import KRNAsset
from datetime import datetime
async def main() -> None:
app = KelvinApp()
await app.connect()
evidences = [
IFrame(
title="My IFrame",
url="https://www.example.com/content/",
)
]
recommendation = Recommendation(
resource=KRNAsset('pcp_51'),
type="decrease_speed",
control_changes=[],
evidences=evidences,
)
await app.publish(recommendation)
if __name__ == "__main__":
asyncio.run(main())
Examples
Basic Usage
Here is a minimal Recommendation.
from datetime import timedelta
from kelvin.application import KelvinApp
from kelvin.message import ControlChange, Recommendation
from kelvin.krn import KRNAssetDataStream, KRNAsset
(...)
# Create a Control Change
control_change = ControlChange(
resource=KRNAssetDataStream("my-motor-asset", "motor_speed_set_point"),
payload=1000,
expiration_date=timedelta(minutes=5)
)
# Create and Publish a Recommendation with one Control Change package
await app.publish(
Recommendation(
resource=KRNAsset("my-motor-asset"),
type="decrease_speed",
control_changes=[control_change]
)
)
Multiple Control Changes
from datetime import timedelta
from kelvin.application import KelvinApp
from kelvin.message import ControlChange, Recommendation
from kelvin.krn import KRNAssetDataStream, KRNAsset
(...)
# Create a Control Change
control_change_01 = ControlChange(
resource=KRNAssetDataStream("my-motor-asset_01", "motor_speed_set_point"),
payload=1000,
expiration_date=timedelta(minutes=5)
)
control_change_02 = ControlChange(
resource=KRNAssetDataStream("my-motor-asset_02", "motor_speed_set_point"),
payload=1000,
expiration_date=timedelta(minutes=5)
)
control_change_03 = ControlChange(
resource=KRNAssetDataStream("valve_01", "position_set_point"),
payload=1000,
expiration_date=timedelta(minutes=5)
)
# Create and Publish a Recommendation with one Control Change package
await app.publish(
Recommendation(
resource=KRNAsset("my-motor-asset"),
type="decrease_speed",
control_changes=[control_change_01, control_change_02, control_change_03]
)
)
Typical ML Usage
When you have a machine learning model producing the recommended control changes, then you can store additional data produced by the ML output
from datetime import timedelta
from kelvin.application import KelvinApp
from kelvin.message import ControlChange, Recommendation
from kelvin.krn import KRNAssetDataStream, KRNAsset
# Normally your ML mode predictions go here
(...)
# Create a Control Change
control_change = ControlChange(
resource=KRNAssetDataStream("my-motor-asset", "motor_speed_set_point"),
payload=1000,
expiration_date=timedelta(minutes=5)
)
# Create and Publish a Recommendation with one Control Change package
# Add also the ML-specific Data
await app.publish(
Recommendation(
resource=KRNAsset("my-motor-asset"),
type="decrease_speed",
control_changes=[control_change],
metadata={
"predicted_speed": 2.5,
"confidence": 0.87,
"input_features": {"current_speed": 5, "load": 4},
"timestamp": "2024-11-18T12:00:00Z",
"model_version": "1.2.0"
}
)
)
Incorporating Evidences
There are a number of types of evidences you can embed into the Recommendation.
Note
Each Recommendation can have an unlimited amount of evidence added.
- Bar Charts
- Line Charts
- Dynacards
- Any type of HighCharts
- Images
- Markdown
- IFrames
You can see full examples here.
