Overview
Hint engine rules works similarly to Pre-Processing Functions ✔️, it allows you to fine-tune PackOS automatic data analysis for your needs. By defining a set of functions, you can teach PackOS how to spot interesting patterns in observed data and send you and your crew notifications about them.
Each function defines:
Name
Trigger - when the function will be executed, you can select an explicit time period like ‘end of hour’ or ‘end of day’. Or run the function when an order or shift ends. The list of currently available triggers (can be extended in the future):
Every 10 minutes
End of hour
After order
After shift
At midnight - (defined by factory timezone)
At end of day - (hour defined by factory setup and timezone)
At the end of week - (end of day, before the configured ‘start of week’)
At the end of month - (end of day, last day of month)
Script - the body of your hint function, allowed statements and examples are described below
Test execution box - allows you to execute the script and inspect the result without generating actual hints.
Store parameters - when selected, all parameters generated by the script will be persisted in the database
Add parameter - let’s you inject/overwrite custom parameters, just for this run
Script
The body of the script should be a C# method, following the syntax and conventions defined by this language. We recommend the official Microsoft documentation:
https://learn.microsoft.com/en-us/dotnet/csharp/In the script, the following variables are available:
DateTime Now
- current time
Library
- set of predefined functions which are available out of the box, constantly extended and tuned by Ilabo team. Described below.
IOrderProvider Orders
- provider of orders
public interface IOrderProvider { // returns started and completed orders in the specified time period public Task<ICollection<Order>> GetExecutedOrders(Guid lineId, DateTime from, DateTime to); } public record Order( Guid LineId, string Status, DateTime StartDate, DateTime? EndDate);
IAssetProvider Assets
- provider of factory assets
public interface IAssetProvider { // returns all lines in the factory Task<ICollection<Element>> GetLines(); // returns all machines in the factory Task<ICollection<Element>> GetMachines(); } public record Element( Guid Id, Guid? ParentId, string Code, string Name, string Path, string Type);
IProductionProvider Production
- provider of production data
public interface IProductionProvider { // Returns bucket with aggregated production, waste and time loss classes Task<BucketInfo> GetBucket(string elementName, DateTime from, DateTime to); // Returns bucket with aggregated production, waste and time loss classes Task<BucketInfo> GetBucket(Guid elementId, DateTime from, DateTime to); // Returns aggregated production and waste in multiple time periods Task<ICollection<Production>> Get(ICollection<ProductionRequest> requests); // Returns aggregated production and waste in selected time period Task<ICollection<Production>> Get(ICollection<Guid> elementIds, string? unit, DateTime from, DateTime to); // Returns base units for selected elements (lines/machines) Task<Dictionary<Guid, string>> GetUnits(ICollection<Guid> elementIds); // Returns metadata about elements (lines/machines) Task<ICollection<ElementStatus>> GetStatus(ICollection<Guid> elementIds); } public record BucketInfo( Guid ElementId, string Unit, double Done, double Wasted, double Planned, DateTime From, DateTime To, TimeSpan QualityLoss, TimeSpan PerformanceLoss, TimeSpan AvailabilityLoss, TimeSpan UtilizationLoss, TimeSpan TotalTime, bool IsUnitConversionComplete); public record Production( Guid ElementId, DateTime From, DateTime To, string Unit, double Done, double Wasted, bool IsUnitConversionComplete); public record ElementStatus( Guid ElementId, bool IsBaseAvailability, bool IsBaseQuantity, bool IsDummy, bool IsAlive, string Kind, string Name, string Path, DateTime KeepAlive);
IWorkLogProvider WorkLogs
- provider for data about machine states
public interface IWorkLogProvider { // returns aggregated summary of work states in the selected time period Task<IEnumerable<AggregatedWorkLog>> GetAggregated(string elementName, DateTime from, DateTime to); // returns aggregated summary of work states in the selected time period, divided by different "groupBy" time periods // groupBy is an array of tokens from the list: // 'hour', 'day', 'week', 'month', 'shift', 'order', 'product', 'shiftInstance' Task<IEnumerable<AggregatedWorkLog>> GetSplitBy(string elementName, DateTime from, DateTime to, params string[] groupBy); // returns definitions of all defined problems (for all lines or machines) Task<IEnumerable<WorkEvent>> GetEventDefinitions(); // returns a definition of a particular problem Task<WorkEvent?> GetEventDefinition(Guid eventId); } public record WorkEvent( Guid ElementId, Guid WorkEventId, string Name); public record AggregatedWorkLog( DateTime? From, // only if effective time split available DateTime? To,// only if effective time split available Guid ElementId, Guid WorkEventId, string WorkStateName, Guid? RootElementId, Guid? RootWorkEventId, string? RootWorkStateName, TimeSpan TotalDuration, TimeSpan MinorStopDuration, TimeSpan WorkDuration, TimeSpan UtilizationLossDuration, TimeSpan AvailabilityLossDuration, TimeSpan PerformanceLossDuration, TimeSpan QualityLossDuration, int TotalCount, int MinorStopCount);
IParameterStore ParameterStore
- service which allows requesting and persisting custom parameters
public interface IParameterStore { // Get value of a custom parameter Parameter Get(string key); // Save multiple key/values of parameters Task Store(IEnumerable<Parameter> parameter); // Add temporarily a parameter to the store (won't be peristed) void Attach(IReadOnlyCollection<Parameter> additionalParameters); } public class Parameter { /** * Key of the parameter * Accessible from any job in this plant */ public string Key { get; init; } /** * Value of the parameter */ public string? Value { get; set; } = null; /** * How long will the parameter live in the store * Value equal or lower to 0 will be never removed */ public TimeSpan TimeToLive { get; set; } = TimeSpan.Zero; public Parameter(string key, string value) { Key = key; Value = value; } }
You can use all of the above variables/providers/services to request data of interest, make calculations and generate hints/warnings for your crew.
The function should return a new object of class HintFunctionOutput
containing a list of Hints
. Each Hint
is defined as:
public class Hint { // Line code or Line/Machine path public string Element { get; set; } = null!; // Title of the hint public string Title { get; set; } = string.Empty; // Description of the hint public string Description { get; set; } = string.Empty; // How long will the hint be displayed to the user public TimeSpan TimeToLive { get; set; } = TimeSpan.FromHours(1); }
Examples
Request work history for the 'Line1' and create a hint with a simple count
var result = new HintFunctionOutput(); var from = DateTime.UtcNow.AddHours(-1); var to = DateTime.UtcNow; var elementName = "ILA/OBS/Line1"; var workLogs = await WorkLogs.Get(element, from, to); result.Hints.Add(new Hint() { Element = elementName, Title = "Number of worklogs", Description = $"There were {workLogs.Count()} workLogs in from {from:G} to {to:G}" }); return result;
Use pre-defined library function to generate CounterMismatch hints:
var output = new HintFunctionOutput(); var hints = await Library.CounterMismatch.Run(TimeSpan.FromHours(1), threshold: 20); Library.CounterMismatch.CreateHints(output, hints); return output;
Library functions
The following hints are pre-defined in the library, and can be easily executed:
DowntimeFrequencyIncrease
- Detect an increasing number of downtimes of specific type
Task Build(TimeSpan timeSpan, string? elementPath = null, int percentile = 95)
Calculates and stores as a parameter the ‘usual number of downtimes’ for each observed downtime type in the time period defined as ‘timeSpan’.
In essence, ‘usual number' is calculated by aggregating the number of downtimes of each type for each hour, creating a histogram, and retrieving 95th percentile.
When executing theBuild
command, you can choose to run it only for a specific element by providingelementPath
or selecting a differentpercentile
cutoff for the algorithm.Task<ICollection<IncreasedDowntimeFrequency>> Run(string? elementPath = null)
Creates a list of
IncreasedDowntimeFrequency
objects, each describing an interesting instance of increased downtime frequency. You can filter or aggregate the result, use it to generate new hints, or calculate other parameters. Each entry contains the following details:public record IncreasedDowntimeFrequency( string ElementName, string ElementPath, Guid ElementId, Guid WorkEventId, string WorkEventName, DateTime From, DateTime To, int Count, int CutOff);
Note that the
Run
function can only be executed after theBuild
method pre-calculating statistics. We recommend to separate this process into 2 functions:
-Build
should be executed once a week/ once a month.
-Run
can be executed every hour/after a shift etc.void CreateHints(HintFunctionOutput output, ICollection<IncreasedDowntimeFrequency> increasedFrequency)
Will add new hints to the
output
using a default formatter (in english)
CounterMismatch
- Detect inconsistent production count for two different machines of the same line
Task<ICollection<MachineCounterMismatch>> Run( TimeSpan timeSpan, double threshold = 20, // acceptable threshold, 20% difference by default string? linePath = null)
Analyse production count in the defined by
timeSpan
time period. Production output of each machine is compared to the BaseQuantity machine. Units are converted if necessary. If the difference between counts exceeds the allowedthreshold
. An objectMachineCounterMismatch
with details is returned:public record MachineCounterMismatch( Guid ElementId, string ElementPath, string ElementName, double TotalCounter, Guid BaseQuantityId, string BaseQuantityName, string BaseQuantityPath, double BaseQuantityCounter, string Unit);
void CreateHints(HintFunctionOutput output, ICollection<MachineCounterMismatch> hints)
Will add new hints to the
output
using a default formatter (in english)
ProductionWithoutOrder
- Detect when the line was producing without any running order
// Increasing threshold reduces sensitivity of the hint // More production difference between order production and raw line production is acceptable Task<ICollection<ProductionWithoutOrderModel>> Run( double threshold = 0, TimeSpan? checkDuration = null, TimeSpan? bucketHourOffset = null)
Analyse the production and orders in the provided
checkDuration
. This period of time is analyse in the context of orders and production. If there is some production in this period of time which is not part of any order - a new instance ofProductionWithoutOrderModel
is raised with the following details:public record ProductionWithoutOrderModel( DateTime From, DateTime To, Guid ElementId, string ElementPath, string ElementName, double TotalCount, double TotalCountFromOrders, string Unit);
void CreateHints(HintFunctionOutput output, ICollection<ProductionWithoutOrderModel> hints)
Will add new hints to the
output
using a default formatter (in english)