<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.onqpr.com/pa/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=MarHink</id>
	<title>QPR ProcessAnalyzer Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.onqpr.com/pa/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=MarHink"/>
	<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php/Special:Contributions/MarHink"/>
	<updated>2026-06-06T01:22:55Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.39.1</generator>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_Objects_in_Expression_Language&amp;diff=28445</id>
		<title>QPR ProcessAnalyzer Objects in Expression Language</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_Objects_in_Expression_Language&amp;diff=28445"/>
		<updated>2026-06-02T13:18:15Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Script */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Filter==&lt;br /&gt;
Filters contain a set of filter rules used to filter cases and events in models. Filters are objects located in the models. Filters are owned by the creator user, and when a filter publish mode is private, only the creator can use it.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Filter properties&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||CreatedBy (User)&lt;br /&gt;
||Returns the user who created the filter.&lt;br /&gt;
|-&lt;br /&gt;
||CreatedDate (DateTime)&lt;br /&gt;
||Returns date when the filter created date.&lt;br /&gt;
|-&lt;br /&gt;
||Description (String)&lt;br /&gt;
||Returns description of the filter.&lt;br /&gt;
|-&lt;br /&gt;
||Id (Integer)&lt;br /&gt;
||Returns id of the filter.&lt;br /&gt;
|-&lt;br /&gt;
||LastModifiedBy (User)&lt;br /&gt;
||Returns user who modified the filter.&lt;br /&gt;
|-&lt;br /&gt;
||LastModifiedDate (DateTime)&lt;br /&gt;
||Returns date when the filter last modified.&lt;br /&gt;
|-&lt;br /&gt;
||Model&lt;br /&gt;
||Returns model where the filter belongs to.&lt;br /&gt;
|-&lt;br /&gt;
||ModelId (Integer)&lt;br /&gt;
||Returns model where the filter belongs to.&lt;br /&gt;
|-&lt;br /&gt;
||Name (String)&lt;br /&gt;
||Returns the name of the filter.&lt;br /&gt;
|-&lt;br /&gt;
||Project&lt;br /&gt;
||Returns project where the filter belongs to.&lt;br /&gt;
|-&lt;br /&gt;
||ProjectId (Integer)&lt;br /&gt;
||Returns project id where the filter belongs to.&lt;br /&gt;
|-&lt;br /&gt;
||PublishMode (String)&lt;br /&gt;
||Returns publish mode of the filter, one of the following: &#039;&#039;&#039;Private&#039;&#039;&#039;, &#039;&#039;&#039;Public&#039;&#039;&#039;, or &#039;&#039;&#039;Default&#039;&#039;&#039;.&lt;br /&gt;
|-&lt;br /&gt;
||Rules (Dictionary)&lt;br /&gt;
||Returns a dictionary containing the filter rules in the filter.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Filter functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||DeletePermanently&lt;br /&gt;
||(none)&lt;br /&gt;
||&lt;br /&gt;
Deletes the filter permanently. To delete own filters, the &#039;&#039;&#039;Filtering&#039;&#039;&#039; permission is needed, and to delete any filters the &#039;&#039;&#039;ManageViews&#039;&#039;&#039; permission is needed.&lt;br /&gt;
|-&lt;br /&gt;
||Modify&lt;br /&gt;
||Dictionary&lt;br /&gt;
||&lt;br /&gt;
Changes the filter properties. The input parameter is a dictionary containing the filter properties to change:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039; (String): Name of the filter.&lt;br /&gt;
* &#039;&#039;&#039;Description&#039;&#039;&#039; (String): Description of the filter.&lt;br /&gt;
* &#039;&#039;&#039;PublishMode&#039;&#039;&#039; (String): Privacy model of the filter rule. One of the following: &#039;&#039;Private&#039;&#039;, &#039;&#039;Public&#039;&#039;, or &#039;&#039;Default&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Rules&#039;&#039;&#039; (Dictionary): Filter rules as Dictionary ([[Filtering_in_QPR_ProcessAnalyzer_Queries|more information]]).&lt;br /&gt;
* &#039;&#039;&#039;ModelId&#039;&#039;&#039; (Integer): Id of the model where the filter is located. Changing this will move the filter into a different model.&lt;br /&gt;
&lt;br /&gt;
The function returns the updated filter object. Requires &#039;&#039;Filtering&#039;&#039; or &#039;&#039;ManageViews&#039;&#039; permission for the project depending on the owner of the filter.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FilterById(1)&lt;br /&gt;
	.Modify(#{&lt;br /&gt;
		&amp;quot;Name&amp;quot;: &amp;quot;My filter&amp;quot;,&lt;br /&gt;
		&amp;quot;Description&amp;quot;: &amp;quot;My description&amp;quot;,&lt;br /&gt;
		&amp;quot;PublishMode&amp;quot;: &amp;quot;Public&amp;quot;,&lt;br /&gt;
		&amp;quot;Rules&amp;quot;: #{&lt;br /&gt;
			&amp;quot;Items&amp;quot;: [&lt;br /&gt;
				#{&lt;br /&gt;
					&amp;quot;Type&amp;quot;: &amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
					&amp;quot;Items&amp;quot;: [&lt;br /&gt;
						#{&lt;br /&gt;
							&amp;quot;Type&amp;quot;: &amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
							&amp;quot;Attribute&amp;quot;: &amp;quot;Account Manager&amp;quot;,&lt;br /&gt;
							&amp;quot;StringifiedValues&amp;quot;: [&lt;br /&gt;
								&amp;quot;0Mary Wilson&amp;quot;&lt;br /&gt;
							]&lt;br /&gt;
						}&lt;br /&gt;
					]&lt;br /&gt;
				}&lt;br /&gt;
			]&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Function to get filter id:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||FilterById&lt;br /&gt;
||&lt;br /&gt;
* Filter id (Integer)&lt;br /&gt;
||&lt;br /&gt;
Returns Filter object corresponding to the provided filter id.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Model==&lt;br /&gt;
Notes:&lt;br /&gt;
* For in-memory models that are offline, the object counts represent the situation when the model was last time online (loaded into the memory). &#039;&#039;null&#039;&#039; is returned if the model has never been loaded into the memory.&lt;br /&gt;
* If [[Case_Level_Permissions|Case permissions]] are used for the model, and user doesn&#039;t have &#039;&#039;&#039;GenericWrite&#039;&#039;&#039; permission for the model, &#039;&#039;null&#039;&#039; is returned for data security reasons. Users that have the &#039;&#039;&#039;GenericWrite&#039;&#039;&#039; permission, see null when the model is offline, and when online, they see counts where the case level permissions settings are applied.&lt;br /&gt;
* Properties &#039;&#039;CaseAttributes&#039;&#039;, &#039;&#039;EventAttributes&#039;&#039; and &#039;&#039;Eventlog&#039;&#039; work only for the in-memory models and they require the model to be loaded into the memory. If the model is not in the memory, it is loaded when these properties is used. Other model properties down require the model to be in the memory.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Model properties&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||AllFilters (Filter*)&lt;br /&gt;
||Returns an array of all [[#Filter|filters]] in the model the user has access to. In addition to the &#039;&#039;Filters&#039;&#039; property, &#039;&#039;AllFilters&#039;&#039; also returns private filters of other users. The &#039;&#039;ManageViews&#039;&#039; permission is required to use this property - otherwise error is given.&lt;br /&gt;
|-&lt;br /&gt;
||Calendars (BusinessCalendar*)&lt;br /&gt;
||&lt;br /&gt;
Returns all [[Business_Calendar|business calendars]] stored to the Model as an array. Returns an empty array, if there are no business calendars stored to the model. Note: UI allows to set only one business calendar for a Model.&lt;br /&gt;
|-&lt;br /&gt;
||CaseAttributes (AttributeType*)&lt;br /&gt;
||[[#AttributeType|CaseAttributes]] in the model returned in the alphabetical order. Using this property requires that the model is loaded in the memory. If the model is not in the memory, it&#039;s loaded when this property is used.&lt;br /&gt;
|-&lt;br /&gt;
||CasesDatatable (Datatable)&lt;br /&gt;
||Returns the Datatable the model uses as a datasource for cases. Returns &#039;&#039;null&#039;&#039; if the cases Datatable is not defined or if model uses other than the Datatable datasource.&lt;br /&gt;
|-&lt;br /&gt;
||Configuration (Dictionary)&lt;br /&gt;
||Returns the Model configuration as dictionary. Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ModelById(123).Configuration.DataSource.Events.DataTableName&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||ConfigurationJson (String)&lt;br /&gt;
||Returns the Model configuration as JSON string.&lt;br /&gt;
|-&lt;br /&gt;
||CreatedBy (User)&lt;br /&gt;
||User who created the model.&lt;br /&gt;
|-&lt;br /&gt;
||CreatedDate (DateTime)&lt;br /&gt;
||Timestamp when the model was created.&lt;br /&gt;
|-&lt;br /&gt;
||DefaultCalendar (BusinessCalendar)&lt;br /&gt;
||Returns the default [[Business_Calendar|business calendar]] of the Model. Returns &#039;&#039;null&#039;&#039;, if there are no calendars in the Model or no calendar has been set as a default calendar. Note: UI allows to set only one business calendar for a Model, which is also the default calendar.&lt;br /&gt;
|-&lt;br /&gt;
||DefaultFilter (Filter)&lt;br /&gt;
||Default filter of the model. Returns &#039;&#039;null&#039;&#039; if the model does not have a default filter.&lt;br /&gt;
|-&lt;br /&gt;
||DefaultFilterId (Integer)&lt;br /&gt;
||Default filter id of the model. Returns &#039;&#039;null&#039;&#039; if the model does not have a default filter.&lt;br /&gt;
|-&lt;br /&gt;
||Description (String)&lt;br /&gt;
||Model description. The model description may contain line breaks.&lt;br /&gt;
|-&lt;br /&gt;
||DeletedDate (DateTime)&lt;br /&gt;
||Timestamp when Model was deleted (moved to the recycle bin).&lt;br /&gt;
|-&lt;br /&gt;
||DeletedBy (User)&lt;br /&gt;
||User how deleted the Model.&lt;br /&gt;
|-&lt;br /&gt;
||Diagrams (Diagram*)&lt;br /&gt;
||Returns an array of all [[Diagram_in_Expression_Language|diagrams]] in the model.&lt;br /&gt;
|-&lt;br /&gt;
||EstimatedMemory (Integer)&lt;br /&gt;
||Returns an estimation of how much memory in bytes the model requires.&lt;br /&gt;
|-&lt;br /&gt;
||EventsDatatable (Datatable)&lt;br /&gt;
||Returns the Datatable the model uses as a datasource for events. Returns &#039;&#039;null&#039;&#039; if the events Datatable is not defined or if model uses other than the Datatable datasource.&lt;br /&gt;
|-&lt;br /&gt;
||EventAttributes (AttributeType*)&lt;br /&gt;
||[[#AttributeType|EventAttributes]] in the model returned in the alphabetical order. Using this property requires that the model is loaded in the memory. If the model is not in the memory, it&#039;s loaded when this property is used.&lt;br /&gt;
|-&lt;br /&gt;
||EventLog (EventLog)&lt;br /&gt;
||EventLog containing the entire model (i.e. event log where no filters have been applied). Using this property requires that the model is loaded in the memory. If the model is not in the memory, it&#039;s loaded when this property is used.&lt;br /&gt;
|-&lt;br /&gt;
||Filters (Filter*)&lt;br /&gt;
||Returns an array of all public [[#Filter|filters]], the default filter (if any) and the user&#039;s own private filters in the model. Note that the other users&#039;s private filters are not returned even for administrators.&lt;br /&gt;
|-&lt;br /&gt;
||Id (Integer)&lt;br /&gt;
||Model Id. Model Id is generated by QPR ProcessAnalyzer when the model is created.&lt;br /&gt;
|-&lt;br /&gt;
||IsValidInMemoryModel (boolean)&lt;br /&gt;
||Returns &#039;&#039;true&#039;&#039; if all the following conditions are met:&lt;br /&gt;
* CheckModelValidity function doesn&#039;t return any issues (because invalid models are assumed to be Snowflake models).&lt;br /&gt;
* Model is not an object-centric model.&lt;br /&gt;
* Data source of the model is &#039;&#039;ODBC&#039;&#039; or &#039;&#039;Expression&#039;&#039;, or the referred datatable has &#039;&#039;DataSourceType&#039;&#039; either &#039;&#039;Local&#039;&#039; or &#039;&#039;SqlServer&#039;&#039;.&lt;br /&gt;
|-&lt;br /&gt;
||LastModifiedBy (User)&lt;br /&gt;
||User who last time modified the model properties. Note that datatables containing the eventlog data are separate objects having similar fields to track the last modification and last data import.&lt;br /&gt;
|-&lt;br /&gt;
||LastModifiedDate (DateTime)&lt;br /&gt;
||Timestamp when the model was modified the last time.&lt;br /&gt;
|-&lt;br /&gt;
||Name (String)&lt;br /&gt;
||Model name.&lt;br /&gt;
|-&lt;br /&gt;
||NCache (Integer)&lt;br /&gt;
||Number of objects related to the model when the model is loaded into the memory.&lt;br /&gt;
|-&lt;br /&gt;
||NCaseAttributes (Integer)&lt;br /&gt;
||Number of [[#AttributeType|CaseAttributes]] in model. Works only for in-memory models.&lt;br /&gt;
|-&lt;br /&gt;
||NCases (Integer)&lt;br /&gt;
||Number of [[#Case|Cases]] in the model. Works only for in-memory models.&lt;br /&gt;
|-&lt;br /&gt;
||NEventAttributes (Integer)&lt;br /&gt;
||Number of [[#AttributeType|EventAttributes]] in model. Works only for in-memory models.&lt;br /&gt;
|-&lt;br /&gt;
||NEvents (Integer)&lt;br /&gt;
||Number of [[#Event|Events]] in model. Works only for in-memory models.&lt;br /&gt;
|-&lt;br /&gt;
||NEventTypes (Integer)&lt;br /&gt;
||Number of [[#EventType|EventTypes]] in the model. Works only for in-memory models.&lt;br /&gt;
|-&lt;br /&gt;
||Project (Project)&lt;br /&gt;
||[[#Project|Project]] where the model belongs to.&lt;br /&gt;
|-&lt;br /&gt;
||ProjectId (Integer)&lt;br /&gt;
||[[#Project|Project]] id where the model belongs to.&lt;br /&gt;
|-&lt;br /&gt;
||Status (String)&lt;br /&gt;
||&lt;br /&gt;
Memory availability status of the model. There are the following statuses:&lt;br /&gt;
* &#039;&#039;&#039;Loading&#039;&#039;&#039;: The model is currently loading into the memory. When the loading is ready, the status changes to &#039;&#039;online&#039;&#039;. If the loading fails, the status changes to &#039;&#039;offline&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Offline&#039;&#039;&#039;: The model is currently not loaded into the memory. The model needs to be loaded into the memory, so that analyses can be calculated from the model (occurs automatically when an analysis is requested).&lt;br /&gt;
* &#039;&#039;&#039;Online&#039;&#039;&#039;: The model is in the memory and ready for analysis calculation. If the model is dropped from the memory, its status changes to &#039;&#039;offline&#039;&#039;.&lt;br /&gt;
|-&lt;br /&gt;
||UsedDatatables (Datatable*)&lt;br /&gt;
||Returns all datatables the model uses as a datasource.&lt;br /&gt;
&lt;br /&gt;
Example: List datatables used by a model:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
StringJoin(&amp;quot;, &amp;quot;, OrderByValue(ModelById(1).UsedDataTables.Name))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Model functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||CalendarByName (BusinessCalendar)&lt;br /&gt;
||&lt;br /&gt;
name (String)&lt;br /&gt;
||&lt;br /&gt;
Returns a [[Business_Calendar|business calendar]] stored to the Model by the name of the calendar. Business calendars can be stored to models in the model properties. Returns &#039;&#039;null&#039;&#039;, if a calendar with the provided name is not stored to the model.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ModelById(123).CalendarByName(&amp;quot;MyCalendar&amp;quot;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||CreateDiagram (Diagram)&lt;br /&gt;
||Parameters dictionary&lt;br /&gt;
||&lt;br /&gt;
Creates a [[Diagram_in_Expression_Language|diagram]] to the model. Parameters is a dictionary containing diagram properties. Following properties are available:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039; (string): Diagram name that distinguishes diagrams in a model.&lt;br /&gt;
* &#039;&#039;&#039;Description&#039;&#039;&#039; (string): Diagram description text.&lt;br /&gt;
* &#039;&#039;&#039;Content&#039;&#039;&#039; (dictionary): Diagram content as dictionary.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ModelById(1)&lt;br /&gt;
  .CreateDiagram(#{&lt;br /&gt;
    &amp;quot;Name&amp;quot;: &amp;quot;My diagram&amp;quot;,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: &amp;quot;This is my new diagram&amp;quot;,&lt;br /&gt;
    &amp;quot;Content&amp;quot;: #{ ... },&lt;br /&gt;
  })&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||CreateFilter (Filter)&lt;br /&gt;
||Parameters dictionary&lt;br /&gt;
||Creates a filter to a model. Requires &#039;&#039;GenericWrite&#039;&#039; permission for the project and global &#039;&#039;CreateModel&#039;&#039; permission. If a filter with that name already exists in the model, an exception is thrown.&lt;br /&gt;
The parameters dictionary may have the following properties:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039;: Name of the filter. This property is mandatory.&lt;br /&gt;
* &#039;&#039;&#039;Description&#039;&#039;&#039;: Description of the filter. This property is optional.&lt;br /&gt;
* &#039;&#039;&#039;Rules&#039;&#039;&#039;: Filter rules for the filter defined as a dictionary according to the [[Filtering_in_QPR_ProcessAnalyzer_Queries|filter json format]]. This property is mandatory.&lt;br /&gt;
* &#039;&#039;&#039;PublishMode&#039;&#039;&#039;: Publish mode of the filter which is one of the following: &#039;&#039;Private&#039;&#039;, &#039;&#039;Public&#039;&#039; or &#039;&#039;Default&#039;&#039;. This property is optional, and the default value is &#039;&#039;Private&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let newFilter = modelById(1).CreateFilter(#{    &lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My Filter&amp;quot;,&lt;br /&gt;
  &amp;quot;Rules&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [#{&lt;br /&gt;
      &amp;quot;Type&amp;quot;: &amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
      &amp;quot;Items&amp;quot;: [#{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
        &amp;quot;Attribute&amp;quot;: &amp;quot;Account Manager&amp;quot;,&lt;br /&gt;
        &amp;quot;StringifiedValues&amp;quot;: [ &amp;quot;0Robert Miller&amp;quot; ]&lt;br /&gt;
      }]&lt;br /&gt;
    }]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;PublishMode&amp;quot;: &amp;quot;Public&amp;quot;&lt;br /&gt;
});&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||DeletePermanently&lt;br /&gt;
||(none)&lt;br /&gt;
||Deletes the Model permanently. The model doesn&#039;t need to be in the recycle bin to be able to delete it permanently.&lt;br /&gt;
|-&lt;br /&gt;
||Modify (Model)&lt;br /&gt;
||Dictionary&lt;br /&gt;
||&lt;br /&gt;
Modifies model properties. The parameter is a dictionary containing the properties to be changed. Following properties can be changed: &#039;&#039;Name&#039;&#039;, &#039;&#039;Description&#039;&#039;, &#039;&#039;ProjectId&#039;&#039;, and &#039;&#039;Configuration&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The function returns the updated model object. Requires the &#039;&#039;GenericWrite&#039;&#039; permission for the project and the global &#039;&#039;CreateModel&#039;&#039; permission.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ModelById(1)&lt;br /&gt;
	.Modify(#{&lt;br /&gt;
		&amp;quot;Name&amp;quot;: &amp;quot;My model&amp;quot;,&lt;br /&gt;
		&amp;quot;Description&amp;quot;: &amp;quot;My description&amp;quot;,&lt;br /&gt;
		&amp;quot;ProjectId&amp;quot;: 2,&lt;br /&gt;
		&amp;quot;Configuration&amp;quot;: #{&lt;br /&gt;
			&amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
				&amp;quot;Cases&amp;quot;: #{&lt;br /&gt;
					&amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
					&amp;quot;DataTableName&amp;quot;: &amp;quot;My cases datatable&amp;quot;,&lt;br /&gt;
					&amp;quot;Columns&amp;quot;: #{&lt;br /&gt;
						&amp;quot;CaseId&amp;quot;: &amp;quot;Case Name&amp;quot;&lt;br /&gt;
					}&lt;br /&gt;
				},&lt;br /&gt;
				&amp;quot;Events&amp;quot;: #{&lt;br /&gt;
					&amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
					&amp;quot;DataTableName&amp;quot;: &amp;quot;My events datatable&amp;quot;,&lt;br /&gt;
					&amp;quot;Columns&amp;quot;: #{&lt;br /&gt;
						&amp;quot;CaseId&amp;quot;: &amp;quot;Case Name&amp;quot;,&lt;br /&gt;
						&amp;quot;EventType&amp;quot;: &amp;quot;Event Type&amp;quot;,&lt;br /&gt;
						&amp;quot;Timestamp&amp;quot;: &amp;quot;Start Time&amp;quot;&lt;br /&gt;
					}&lt;br /&gt;
				}&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||ResetModelCache&lt;br /&gt;
||(none)&lt;br /&gt;
||&lt;br /&gt;
Synchronously clears all cached model data. For a Snowflake model, deletes all cache tables related to the model from Snowflake. For an in-memory model, drops the model from the memory and also drops all other model related caches from the memory. &lt;br /&gt;
|-&lt;br /&gt;
||ResetPreprocessings&lt;br /&gt;
||(none)&lt;br /&gt;
||&lt;br /&gt;
Removes all cached items related to the Model, e.g. preprocessings and calculation results. In practice, the Model is reset to a state where it was right after the model was loaded into memory.&lt;br /&gt;
|-&lt;br /&gt;
||Restore&lt;br /&gt;
||(none)&lt;br /&gt;
||Restores the Model from the recycle bin back to the original location.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;ToSqlDataFrame&amp;quot;&amp;gt;ToSqlDataFrame&amp;lt;/span&amp;gt;&lt;br /&gt;
||In-memory dataframe&lt;br /&gt;
||Converts an in-memory dataframe to an SQL dataframe. In practice, an SQL query is created from the in-memory dataframe and the query is executed in the datasource so that the data is available in the datasource for further SQL operations. This function is intended only to small amounts of data which is less than 16384 rows.&lt;br /&gt;
&lt;br /&gt;
Example: Select matching cases from events data using in-memory dataframe:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let model = ModelById(1);&lt;br /&gt;
let dfEvents = model.EventsDatatable.SqlDataFrame;&lt;br /&gt;
let inMemoryDf = ToDataFrame(&lt;br /&gt;
  [[&amp;quot;1&amp;quot;], [&amp;quot;2&amp;quot;], [&amp;quot;3&amp;quot;]],&lt;br /&gt;
  [#{&amp;quot;Name&amp;quot;: &amp;quot;id&amp;quot;, &amp;quot;DataType&amp;quot;: &amp;quot;String&amp;quot;}]&lt;br /&gt;
);&lt;br /&gt;
model.ToSqlDataFrame(inMemoryDf)&lt;br /&gt;
  .Join(dfEvents, [&amp;quot;id&amp;quot;: &amp;quot;CaseId&amp;quot;])&lt;br /&gt;
  .SelectDistinct([&amp;quot;CaseId&amp;quot;])&lt;br /&gt;
  .Collect();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;TriggerNotifications&amp;quot;&amp;gt;TriggerNotifications&amp;lt;/span&amp;gt; (Boolean)&lt;br /&gt;
||Notification names (String*)&lt;br /&gt;
||Triggers the given notifications for the Model. Notifications are given by their names. Triggering means that the configured rules are run and notification emails are sent as defined by the rules. If the notification names parameter is not provided, all notifications in the Model are triggered.&lt;br /&gt;
&lt;br /&gt;
The function return &#039;&#039;true&#039;&#039; if any notification were triggered, otherwise &#039;&#039;false&#039;&#039;.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ModelById(123).TriggerNotifications([&amp;quot;Notification 1&amp;quot;, &amp;quot;Notification 2&amp;quot;]);&lt;br /&gt;
Triggers notifications Notification 1 and Notification 2 in model id 123.&lt;br /&gt;
&lt;br /&gt;
ModelById(123).TriggerNotifications();&lt;br /&gt;
Triggers all notifications in model id 123.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;CheckModelValidity&amp;quot;&amp;gt;CheckModelValidity&amp;lt;/span&amp;gt; (Object array)&lt;br /&gt;
||CheckData field in dictionary&lt;br /&gt;
||Checks the model validity and returns found issues. The returned data is an array of objects where each object represents one validity error and contains the following properties:&lt;br /&gt;
* &#039;&#039;&#039;IssueType&#039;&#039;&#039; (String): Specifies the issue type.&lt;br /&gt;
* &#039;&#039;&#039;ContextType&#039;&#039;&#039; (String): Context in which the issue was found, and it can be &#039;&#039;&#039;EventDataSource&#039;&#039;&#039;, &#039;&#039;&#039;CaseDataSource&#039;&#039;&#039;, &#039;&#039;&#039;OcelDataSource&#039;&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Details&#039;&#039;&#039; (Dictionary): Additional details which depend on the type of the issue.&lt;br /&gt;
&lt;br /&gt;
There are two types of checks available (based on whether the &#039;&#039;&#039;CheckData&#039;&#039;&#039; parameter is defined):&lt;br /&gt;
* &#039;&#039;Lightweight check&#039;&#039;: The check is based on only the configuration data stored in QPR ProcessAnalyzer. This check is very quick and does not require running queries in datasource (e.g., in Snowflake).&lt;br /&gt;
* &#039;&#039;Full check&#039;&#039;: The check is comprehensive and it&#039;s able to detect any validity issues the model may have. The full check requires running queries to the actual data which makes the check slower, and in case of Snowflake, it uses the Snowflake warehouse to run the queries.&lt;br /&gt;
&lt;br /&gt;
The lightweight check is performed automatically by the [[QPR_ProcessAnalyzer_Project_Workspace|Workspace]], so if there are any validity issues that the lightweight check can detect, the Workspace notifies about them immediately. If there are any problems with the model calculation results, it might be a good idea to run the full validity check to confirm whether the problems are due to the model being invalid.&lt;br /&gt;
&lt;br /&gt;
Example: Lightweight check:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ToJson(ModelById(1).CheckModelValidity())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Full check:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ToJson(ModelById(1).CheckModelValidity(#{ &amp;quot;CheckData&amp;quot;: true }))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||CortexAgentsQuery&lt;br /&gt;
||&lt;br /&gt;
||Creates a Snowflake Cortex semantic model (see &#039;&#039;GetSemanticModel&#039;&#039; function) for the process mining model and makes a natural language query on it using Snowflake Cortex Agents. More information: https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents. &lt;br /&gt;
&lt;br /&gt;
There are the following parameters:&lt;br /&gt;
# &#039;&#039;&#039;Parameters&#039;&#039;&#039;: Dictionary parameters given to the Cortex Agents REST API query (https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-rest-api). There is a special handling for the following parameters:&lt;br /&gt;
#* &#039;&#039;&#039;model&#039;&#039;&#039;: If not defined, uses the default Cortex Agents model name configured into the database, or if that is not defined, uses &amp;quot;llama3.1-70b&amp;quot;.&lt;br /&gt;
#* &#039;&#039;&#039;_tools&#039;&#039;&#039;: Additional tool_spec of type &amp;quot;cortex_analyst_text_to_sql&amp;quot; will be added to this value with a reference to the generated semantic model.&lt;br /&gt;
#* &#039;&#039;&#039;_tool_resources&#039;&#039;&#039;: Generated semantic model is added as an additional resource.&lt;br /&gt;
# &#039;&#039;&#039;Filter configuration&#039;&#039;&#039;: Can be a string containing filter JSON or a dictionary containing the filter configuration. The semantic model is created for the filtered eventlog. If not defined, the entire model eventlog will be used.&lt;br /&gt;
# &#039;&#039;&#039;Event column role nappings&#039;&#039;&#039;: Mappings to apply for event columns. If not defined, default column mappings are used.&lt;br /&gt;
# &#039;&#039;&#039;Case column role mappings&#039;&#039;&#039;: Mappings to apply for case columns. If not defined, default column mappings are used.&lt;br /&gt;
&lt;br /&gt;
The function returns a dictionary with the following keys:&lt;br /&gt;
# &#039;&#039;&#039;Response&#039;&#039;&#039;: Actual response as a dictionary returned by the Cortex Agents.&lt;br /&gt;
# &#039;&#039;&#039;Response items&#039;&#039;&#039;: Contains processed response consisting of an array of objects having the following properties:&lt;br /&gt;
#* &#039;&#039;&#039;Text&#039;&#039;&#039;: Textual response.&lt;br /&gt;
#* &#039;&#039;&#039;Sql&#039;&#039;&#039;: Response SQL query string. Not mandatory.&lt;br /&gt;
#* &#039;&#039;&#039;SqlDataFrame&#039;&#039;&#039;: SqlDataFrame created for the SQL query in the Sql property. Only present if Sql is present.&lt;br /&gt;
|-&lt;br /&gt;
||GetSemanticModel&lt;br /&gt;
||&lt;br /&gt;
||Creates a Snowflake Cortex Analyst semantic model for the process mining model and returns it as a dictionary. More information: https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst/semantic-model-spec.&lt;br /&gt;
&lt;br /&gt;
There are the following parameters:&lt;br /&gt;
# &#039;&#039;&#039;Filter configuration&#039;&#039;&#039;: Can be a string containing filter JSON or a dictionary containing the filter. The semantic model is created for the filtered eventlog. If not defined, the entire model eventlog will be used.&lt;br /&gt;
# &#039;&#039;&#039;Event column role mappings&#039;&#039;&#039;: Mappings to apply for event columns. If not defined, default column mappings are used.&lt;br /&gt;
# &#039;&#039;&#039;Case column role mappings&#039;&#039;&#039;: Mappings to apply for case columns. If not defined, default column mappings are used.&lt;br /&gt;
&lt;br /&gt;
Examples: Returns a semantic model without any filtering applied.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ModelById(1).GetSemanticModel();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Function to get Model by model id:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||ModelById&lt;br /&gt;
||&lt;br /&gt;
* Model id (Integer)&lt;br /&gt;
||&lt;br /&gt;
Returns [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Model|Model]] object corresponding to the provided model id.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Object-centric model==&lt;br /&gt;
Object-centric models additionally have the following properties and functions.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Object-Centric model properties&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||IsOcelModel (boolean)&lt;br /&gt;
||Returns &#039;&#039;true&#039;&#039; when the model is an OCEL model.&lt;br /&gt;
|-&lt;br /&gt;
||OcelEvents (Datatable)&lt;br /&gt;
||Datatable containing event data for the OCEL model. Value &#039;&#039;null&#039;&#039; is returned if event datatable has not been configured for the model. Throws an unsupported operation exception if the model is not an OCEL model.&lt;br /&gt;
|-&lt;br /&gt;
||OcelEventToObject (Datatable)&lt;br /&gt;
||Datatable containing event-to-object relations data for the OCEL model. Value &#039;&#039;null&#039;&#039; is returned if event-to-object relation datatable has not been configured for the model. Throws an unsupported operation exception if the model is not an OCEL model.&lt;br /&gt;
|-&lt;br /&gt;
||OcelEventTypes (Dictionary)&lt;br /&gt;
||Returns a dictionary containing event type names as keys and the datatables holding event data for that event type in this OCEL model as value. An empty array is returned if event types datatable has not been configured for the model. Throws an unsupported operation exception if the model is not an OCEL model.&lt;br /&gt;
&lt;br /&gt;
Example: Get datatable for &amp;quot;Create order&amp;quot; events:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ModelById(1).OcelEventTypes.Get(&amp;quot;Create order&amp;quot;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||OcelObjects (Datatable)&lt;br /&gt;
||Datatable containing objects data for the OCEL model. Value &#039;&#039;null&#039;&#039; is returned if object datatable has not been configured for the model. Throws an unsupported operation exception if the model is not an OCEL model.&lt;br /&gt;
|-&lt;br /&gt;
||OcelObjectToObject (Datatable)&lt;br /&gt;
||Datatable containing object-to-object relations data for the OCEL model. Value &#039;&#039;null&#039;&#039; is returned if object-to-object relation datatable has not been configured for the model. Throws an unsupported operation exception if the model is not an OCEL model.&lt;br /&gt;
|-&lt;br /&gt;
||OcelObjectTypes (Dictionary)&lt;br /&gt;
||Returns a dictionary containing object type names as keys and the datatables holding data for that object type in this OCEL model as value. An empty array is returned if object types have not been configured for this model. Throws an unsupported operation exception if the model is not an OCEL model.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Object-centric model functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||OcelEventType&lt;br /&gt;
||Event type name (String)&lt;br /&gt;
||&lt;br /&gt;
Datatable containing event type attributes of given event type in this OCEL model. Value &#039;&#039;null&#039;&#039; is returned if a datatable is not configured for this model for given event type. Throws an unsupported operation exception if the model is not an OCEL model.&lt;br /&gt;
&lt;br /&gt;
Example: Get datatable for &amp;quot;Create order&amp;quot; events:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ModelById(1).OcelEventType(&amp;quot;Create order&amp;quot;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||OcelObjectType&lt;br /&gt;
||Object type name (String)&lt;br /&gt;
||Datatable containing object type attributes of given object type in this OCEL model. Value &#039;&#039;null&#039;&#039; is returned if a datatable is not configured for this model for given object type. Throws an unsupported operation exception if the model is not an OCEL model.&lt;br /&gt;
|-&lt;br /&gt;
||OcelObjectTypeConfiguration&lt;br /&gt;
||Object type name (String)&lt;br /&gt;
||Returns a matching configuration object with the following properties:&amp;lt;br&amp;gt;&lt;br /&gt;
# Datatable: name of the datatable&lt;br /&gt;
# Unit: unit label for object type items&lt;br /&gt;
&#039;&#039;Null&#039;&#039; is returned if the given object type is not found in the model configuration.  &lt;br /&gt;
If the object type name is not specified or &#039;&#039;null&#039;&#039;, the function returns a dictionary containing configurations for all defined object types.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Project ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Project properties&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||CreatedBy (User)&lt;br /&gt;
||User who created the Project.&lt;br /&gt;
|-&lt;br /&gt;
||CreatedDate (DateTime)&lt;br /&gt;
||Timestamp when the Project was created.&lt;br /&gt;
|-&lt;br /&gt;
||Configuration (Dictionary)&lt;br /&gt;
||Project settings as Dictionary object. See example in &#039;&#039;ConfigurationJson&#039;&#039; property.&lt;br /&gt;
|-&lt;br /&gt;
||ConfigurationJson (String)&lt;br /&gt;
||Project settings as json string.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;DefaultLocationInDataSource&amp;quot;: {&lt;br /&gt;
    &amp;quot;Database&amp;quot;: &amp;quot;MyDatabase&amp;quot;,&lt;br /&gt;
    &amp;quot;Schema&amp;quot;: &amp;quot;MySchema&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;ConnectionStringKeys&amp;quot;: {&lt;br /&gt;
    &amp;quot;Snowflake&amp;quot;: &amp;quot;MyKey1&amp;quot;,&lt;br /&gt;
    &amp;quot;SqlServer&amp;quot;: &amp;quot;MyKey2&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||Dashboards (Dashboard*)&lt;br /&gt;
||Returns all [[Dashboard_in_Expression_Language|dashboards]] in the project.&lt;br /&gt;
|-&lt;br /&gt;
||Datatables (Datatable*)&lt;br /&gt;
||Returns all Datatables in the project.&lt;br /&gt;
|-&lt;br /&gt;
||DeletedDate (DateTime)&lt;br /&gt;
||Timestamp when the Project was deleted (moved to the recycle bin).&lt;br /&gt;
|-&lt;br /&gt;
||Description (String)&lt;br /&gt;
||Project description. The project description may contain line breaks.&lt;br /&gt;
|-&lt;br /&gt;
||DeletedBy (User)&lt;br /&gt;
||User who deleted the Project (moved to the recycle bin).&lt;br /&gt;
|-&lt;br /&gt;
||Id (Integer)&lt;br /&gt;
||Id of the Project.&lt;br /&gt;
|-&lt;br /&gt;
||LastModifiedBy (User)&lt;br /&gt;
||User who last modified the Project.&lt;br /&gt;
|-&lt;br /&gt;
||LastModifiedDate (DateTime)&lt;br /&gt;
||Timestamp when the Project was last modified (refers to the project name, description and parent, not the contents of the project).&lt;br /&gt;
|-&lt;br /&gt;
||Name (String)&lt;br /&gt;
||Name of the Project.&lt;br /&gt;
|-&lt;br /&gt;
||Models (Model*)&lt;br /&gt;
||Models that are in the Project.&lt;br /&gt;
|-&lt;br /&gt;
||Parent (Project)&lt;br /&gt;
||Parent project, i.e. a Project where the Project is located in the hierarchy of Projects. Returns &#039;&#039;null&#039;&#039; for root level Projects. Throws an error if user doesn&#039;t have access to the parent project.&lt;br /&gt;
|-&lt;br /&gt;
||ParentProjectId (Integer)&lt;br /&gt;
||Parent project id. Returns &#039;&#039;null&#039;&#039; for root level Projects. The parent project id is returned even if user doesn&#039;t have access to the parent project.&lt;br /&gt;
|-&lt;br /&gt;
||Scripts (Script*)&lt;br /&gt;
||Scripts that are in the Project.&lt;br /&gt;
|-&lt;br /&gt;
||Secrets (Dictionary*)&lt;br /&gt;
||Returns array of all [[Storing_Secrets_for_Scripts|secrets]] in the project as Dictionary with following properties:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039; (string): Name of the secret.&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039; (string): Type of the secret which is one of the following: &amp;quot;odbc&amp;quot;, &amp;quot;sap&amp;quot;, &amp;quot;salesforce&amp;quot;.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Project functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||CreateDashboard (Dashboard)&lt;br /&gt;
||Parameters dictionary&lt;br /&gt;
||Creates a dashboard to the project. &#039;&#039;EditDashboards&#039;&#039; permission to the project is required. The parameter is dictionary with following supported dashboard properties:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039; (String): Name of the dashboard.&lt;br /&gt;
* &#039;&#039;&#039;Identifier&#039;&#039;&#039; (String): Identifier of the dashboard.&lt;br /&gt;
* &#039;&#039;&#039;Description&#039;&#039;&#039; (String): Description of the dashboard.&lt;br /&gt;
* &#039;&#039;&#039;Content&#039;&#039;&#039; (Dictionary): Content of the dashboard.&lt;br /&gt;
&lt;br /&gt;
Example: Create empty dashboard.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1)&lt;br /&gt;
  .CreateDashboard(#{&lt;br /&gt;
    &amp;quot;Name&amp;quot;: &amp;quot;My dashboard&amp;quot;,&lt;br /&gt;
    &amp;quot;Identifier&amp;quot;: &amp;quot;MyDashboard&amp;quot;&lt;br /&gt;
  });&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Create dashboard with a chart.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1)&lt;br /&gt;
  .CreateDashboard(#{&lt;br /&gt;
    &amp;quot;Name&amp;quot;: &amp;quot;My dashboard&amp;quot;,&lt;br /&gt;
    &amp;quot;Content&amp;quot;: #{&lt;br /&gt;
      &amp;quot;version&amp;quot;: 4,&lt;br /&gt;
      &amp;quot;typeName&amp;quot;: &amp;quot;View&amp;quot;,&lt;br /&gt;
      &amp;quot;name&amp;quot;: &amp;quot;My dashboard&amp;quot;,&lt;br /&gt;
      &amp;quot;subElements&amp;quot;: [&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;position&amp;quot;: #{&lt;br /&gt;
            &amp;quot;x&amp;quot;: 0,&lt;br /&gt;
            &amp;quot;y&amp;quot;: 0,&lt;br /&gt;
            &amp;quot;width&amp;quot;: 0.5,&lt;br /&gt;
            &amp;quot;height&amp;quot;: 0.5,&lt;br /&gt;
            &amp;quot;zOrder&amp;quot;: 0&lt;br /&gt;
          },&lt;br /&gt;
          &amp;quot;element&amp;quot;: #{&lt;br /&gt;
            &amp;quot;typeName&amp;quot;: &amp;quot;Chart&amp;quot;,&lt;br /&gt;
            &amp;quot;configuration&amp;quot;: #{&lt;br /&gt;
              &amp;quot;root&amp;quot;: #{&lt;br /&gt;
                &amp;quot;expressionType&amp;quot;: &amp;quot;Cases&amp;quot;,&lt;br /&gt;
                &amp;quot;expressionParameters&amp;quot;: #{}&lt;br /&gt;
              },&lt;br /&gt;
              &amp;quot;measures&amp;quot;: [#{&lt;br /&gt;
                &amp;quot;expressionType&amp;quot;: &amp;quot;Count&amp;quot;,&lt;br /&gt;
                &amp;quot;expressionParameters&amp;quot;: #{}&lt;br /&gt;
              }]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;CreateDatatable&amp;quot;&amp;gt;CreateDatatable&amp;lt;/span&amp;gt; (Datatable)&lt;br /&gt;
||&lt;br /&gt;
* Parameters dictionary&lt;br /&gt;
||Creates datatable to the project. After creation, there are no columns or rows in the datatable. The function returns the created datatable entity. Following properties can be set for the datatable:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039; (string): Name of the datatable. This parameter is mandatory.&lt;br /&gt;
* &#039;&#039;&#039;Description&#039;&#039;&#039; (string): Description for the datatable. This parameter is optional.&lt;br /&gt;
* &#039;&#039;&#039;NameInDataSource&#039;&#039;&#039; (string): Table name in the datasource (e.g., in Snowflake) which this datatable is linked to. This parameter is optional.&lt;br /&gt;
* &#039;&#039;&#039;SchemaNameInDataSource&#039;&#039;&#039; (string): Schema name in the datasource (e.g., in Snowflake) which this datatable is linked to. This parameter is optional.&lt;br /&gt;
* &#039;&#039;&#039;DatabaseNameInDataSource&#039;&#039;&#039; (string): Database name in the datasource (e.g., in Snowflake) which this datatable is linked to. This parameter is optional.&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039; (string): Defines where the data for the datatable is located. Available values are &#039;&#039;&#039;Snowflake&#039;&#039;&#039;, &#039;&#039;&#039;SqlServer&#039;&#039;&#039;, and &#039;&#039;&#039;Local&#039;&#039;&#039;.  This parameter is optional and default value is defined by the [[PA_Configuration_database_table#General_Settings|DefaultDataSource]] setting.&lt;br /&gt;
* &#039;&#039;&#039;Connection&#039;&#039;&#039;: Connection object for the datatable. This parameter is optional.&lt;br /&gt;
&lt;br /&gt;
Example: Create a new datatable:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1).CreateDatatable(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My datatable&amp;quot;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Create Snowflake datatable linked to a custom table:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1).CreateDatatable(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My datatable&amp;quot;,&lt;br /&gt;
  &amp;quot;Description&amp;quot;: &amp;quot;My description&amp;quot;,&lt;br /&gt;
  &amp;quot;NameInDataSource&amp;quot;: &amp;quot;MyTable&amp;quot;,&lt;br /&gt;
  &amp;quot;SchemaNameInDataSource&amp;quot;: &amp;quot;MySchema&amp;quot;,&lt;br /&gt;
  &amp;quot;DatabaseNameInDataSource&amp;quot;: &amp;quot;MyDatabase&amp;quot;,&lt;br /&gt;
  &amp;quot;Type&amp;quot;: &amp;quot;Snowflake&amp;quot;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Create Snowflake datatable where connection string is stored as a [[Storing_Secrets_for_Scripts|secret]]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1).CreateDatatable(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My datatable&amp;quot;,&lt;br /&gt;
  &amp;quot;Type&amp;quot;: &amp;quot;Snowflake&amp;quot;,&lt;br /&gt;
  &amp;quot;Connection&amp;quot;: ProjectById(1).CreateSnowflakeConnection(#{ &amp;quot;OdbcConnectionStringKey&amp;quot;: &amp;quot;MyKey&amp;quot; })&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||CreateMeaConnection&lt;br /&gt;
||&lt;br /&gt;
||&lt;br /&gt;
Creates an object representing connection to QPR MEA (QPR Suite) Web Service. The function parameter is a dictionary containing the property &#039;&#039;&#039;ConnectionStringKey&#039;&#039;&#039; which defines the MEA connection string secret name in the same project. When the connection object has been created, MEA Web Service [[QPR_MEA_Integration|queries and other operations]] can be executed using it.&lt;br /&gt;
&lt;br /&gt;
Example: create connection:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let connection = ProjectByName(&amp;quot;MyProject&amp;quot;).CreateMeaConnection( #{&amp;quot;ConnectionStringKey&amp;quot;: &amp;quot;MyMeaConnection&amp;quot;} );&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: use connection:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let results = connection.QueryObjects(&amp;quot;[PG.785401983.683494101]&amp;quot;, &amp;quot;name, typename&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||CreateModel (Model)&lt;br /&gt;
||&lt;br /&gt;
* Parameters dictionary&lt;br /&gt;
||Creates a model to a project. Requires &#039;&#039;GenericWrite&#039;&#039; permission for the Project and global &#039;&#039;CreateModel&#039;&#039; permission. If a model with that name already exists, an exception is thrown.&lt;br /&gt;
&lt;br /&gt;
Parameters dictionary has the following properties:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039;: Name of the model. This property is mandatory.&lt;br /&gt;
* &#039;&#039;&#039;Description&#039;&#039;&#039;: Description of the model. This property is optional.&lt;br /&gt;
* &#039;&#039;&#039;Configuration&#039;&#039;&#039;: Configuration dictionary for the model. This property is technically optional, but a working model requires at least datasource settings to be defined.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1).CreateModel(#{    &lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My model&amp;quot;,&lt;br /&gt;
  &amp;quot;Description&amp;quot;: &amp;quot;My description&amp;quot;,&lt;br /&gt;
  &amp;quot;Configuration&amp;quot;: #{&lt;br /&gt;
    &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: #{&lt;br /&gt;
        &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
        &amp;quot;DataTableName&amp;quot;: &amp;quot;My cases datatable&amp;quot;,&lt;br /&gt;
        &amp;quot;Columns&amp;quot;: #{&lt;br /&gt;
          &amp;quot;CaseId&amp;quot;: &amp;quot;Case Name&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
      },&lt;br /&gt;
      &amp;quot;Events&amp;quot;: #{&lt;br /&gt;
        &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
        &amp;quot;DataTableName&amp;quot;: &amp;quot;My events datatable&amp;quot;,&lt;br /&gt;
        &amp;quot;Columns&amp;quot;: #{&lt;br /&gt;
           &amp;quot;CaseId&amp;quot;: &amp;quot;Case Name&amp;quot;,&lt;br /&gt;
           &amp;quot;EventType&amp;quot;: &amp;quot;Event Type&amp;quot;,&lt;br /&gt;
           &amp;quot;Timestamp&amp;quot;: &amp;quot;Start Time&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;CreateProject&amp;quot;&amp;gt;CreateProject&amp;lt;/span&amp;gt; (Project)&lt;br /&gt;
||&lt;br /&gt;
* Parameters dictionary&lt;br /&gt;
||Create a project as a sub-project of the context project. Returns the created project. Requires the &#039;&#039;ManageProject&#039;&#039; permission.&lt;br /&gt;
&lt;br /&gt;
Parameters dictionary has the following properties:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039; (string): Name of the project. This property is required.&lt;br /&gt;
* &#039;&#039;&#039;Description&#039;&#039;&#039; (string): Description of the project.&lt;br /&gt;
* &#039;&#039;&#039;ParentProjectId&#039;&#039;&#039; (integer): Id of the parent project where the new project is created. This parameter is usually not needed because the parent project is the context project. The CreateProject function is also available in the generic context where the &#039;&#039;ParentProjectId&#039;&#039; parameter is needed to create sub-projects.&lt;br /&gt;
* &#039;&#039;&#039;DatabaseNameInDataSource&#039;&#039;&#039; (string): Snowflake database the project is linked to. Data for the datatables in this project will be located in this database.&lt;br /&gt;
* &#039;&#039;&#039;SchemaNameInDataSource&#039;&#039;&#039; (string): Snowflake schema the project is linked to. Data for the datatables in this project will be located in this schema. If the schema is defined, also the &#039;&#039;DatabaseNameInDataSource&#039;&#039; needs to be defined.&lt;br /&gt;
* &#039;&#039;&#039;SnowflakeConnectionStringKey&#039;&#039;&#039; (string): Snowflake connection string key to be used for the datatables in this project. &lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1).CreateProject(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My project&amp;quot;, &lt;br /&gt;
  &amp;quot;Description&amp;quot;: &amp;quot;My description&amp;quot;, &lt;br /&gt;
  &amp;quot;DatabaseNameInDataSource&amp;quot;: &amp;quot;My database&amp;quot;, &lt;br /&gt;
  &amp;quot;SchemaNameInDataSource&amp;quot;: &amp;quot;My schema&amp;quot; &lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||CreateScript (Script)&lt;br /&gt;
||Parameters dictionary&lt;br /&gt;
||Creates a new script to the project. The parameter is a dictionary containing the following script properties:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039; (String): Name of the script. This is a mandatory parameter. Note that it&#039;s not possible to create multiple scripts with the same name in the same project.&lt;br /&gt;
* &#039;&#039;&#039;Description&#039;&#039;&#039; (String): Optional description text of the script.&lt;br /&gt;
* &#039;&#039;&#039;Language&#039;&#039;&#039; (String): Language of the script. Options: &#039;&#039;Expression&#039;&#039; (default), or &#039;&#039;SQL&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Code&#039;&#039;&#039; (String): Script code.&lt;br /&gt;
* &#039;&#039;&#039;Configuration&#039;&#039;&#039; (Dictionary): Script json configuration.&lt;br /&gt;
&lt;br /&gt;
The function returns the created script object. Using the function requires &#039;&#039;ManageScripts&#039;&#039; permission for the project, and additionally &#039;&#039;RunScripts&#039;&#039; for SQL scripts.&lt;br /&gt;
&lt;br /&gt;
Example: Create an expression script:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1).CreateScript(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My expression script&amp;quot;,&lt;br /&gt;
  &amp;quot;Code&amp;quot;: &amp;quot;WriteLog(\&amp;quot;Hello world!\&amp;quot;);&amp;quot;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Create an SQL script:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1).CreateScript(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My SQL script&amp;quot;,&lt;br /&gt;
  &amp;quot;Description&amp;quot;: &amp;quot;This is an example SQL script...&amp;quot;,&lt;br /&gt;
  &amp;quot;Language&amp;quot;: &amp;quot;SQL&amp;quot;,&lt;br /&gt;
  &amp;quot;Code&amp;quot;: &amp;quot;SELECT * FROM table;&amp;quot;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||DatatableByName (Datatable)&lt;br /&gt;
||Datatable name (String)&lt;br /&gt;
||&lt;br /&gt;
Returns Datatable by its name located in the project. Returns null, if Datatable with that name does not exist in the project.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(123).DatatableByName(&amp;quot;MyDatatable1&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Get datatable by name, and create it if it doesn&#039;t exist:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let project = ProjectById(123);&lt;br /&gt;
let datatableName = &amp;quot;MyDatatable1&amp;quot;;&lt;br /&gt;
let datatable = project.DatatableByName(datatableName);&lt;br /&gt;
if (datatable == null) {&lt;br /&gt;
  datatable = project.CreateDatatable(datatableName);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||DeletePermanently&lt;br /&gt;
||(none)&lt;br /&gt;
||Deletes the Project permanently. Note that the Project doesn&#039;t need to be in the recycle bin to be able to delete it permanently.&lt;br /&gt;
|-&lt;br /&gt;
||Export (String)&lt;br /&gt;
||(none)&lt;br /&gt;
||Exports the project and its content to a json string. The json format is described in [[Projects Export File Format]].&lt;br /&gt;
&lt;br /&gt;
Example: Export project id 1 to json data:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1).Export();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||Import&lt;br /&gt;
||JSON string&lt;br /&gt;
||Creates one or several projects from a json string. The project(s) are created as child projects of the context project. The json format is described in [[Projects Export File Format]]. The function returns the created project objects. In case the project names already exist, the import operation automatically adjusts the names to be unique.&lt;br /&gt;
&lt;br /&gt;
Example: Create project from json data (as child of project id 1):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let jsonData = `{ &amp;quot;Projects&amp;quot;: [ { &amp;quot;Name&amp;quot;: &amp;quot;My project&amp;quot; } ] }`;&lt;br /&gt;
let result = ProjectById(1).Import(jsonData);&lt;br /&gt;
let createdProjectId = result[&amp;quot;Projects&amp;quot;][0].Id;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||Restore&lt;br /&gt;
||(none)&lt;br /&gt;
||Restores the Project from the recycle bin back to the original location.&lt;br /&gt;
|-&lt;br /&gt;
||ModelByName (Model)&lt;br /&gt;
||Model name (String)&lt;br /&gt;
||&lt;br /&gt;
Returns Model by its name located in the project. Returns null, if Model with that name does not exist in the project.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(123).ModelByName(&amp;quot;My Model 1&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;ModifyProject&amp;quot;&amp;gt;Modify&amp;lt;/span&amp;gt; (Project)&lt;br /&gt;
||Dictionary of settings to change&lt;br /&gt;
||&lt;br /&gt;
Change project settings. Following settings are supported:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039; (String): Name of the project.&lt;br /&gt;
* &#039;&#039;&#039;Description&#039;&#039;&#039; (String): Description text of the project.&lt;br /&gt;
* &#039;&#039;&#039;ParentProjectId&#039;&#039;&#039; (Integer): Parent project id. Changing this effectively moves the project into different parent project.&lt;br /&gt;
* &#039;&#039;&#039;DatabaseNameInDataSource&#039;&#039;&#039;: Name of the Snowflake database where the project&#039;s datatables are located. The database needs to exist in the same Snowflake account configured in the Snowflake connection string. When defining this setting, define also the &#039;&#039;SchemaNameInDataSource&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;SchemaNameInDataSource&#039;&#039;&#039;: Name of the Snowflake schema where the project&#039;s datatables are located. The schema needs to exist in the same Snowflake account configured in the Snowflake connection string. When defining this setting, define also the &#039;&#039;DatabaseNameInDataSource&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;SnowflakeConnectionStringKey&#039;&#039;&#039; (String): Snowflake connection string key for the project. Snowflake datatables in the project will use connection string behind this key (unless specified by the datatatable).&lt;br /&gt;
* &#039;&#039;&#039;SqlServerConnectionStringKey&#039;&#039;&#039; (String): SQL Server connection string key. SQL Server datatables in the project will use connection string behind this key (unless specified by the datatatable).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;ManageProject&#039;&#039; permission is needed to change project properties.&lt;br /&gt;
&lt;br /&gt;
Example: Change project name and move project into other parent project:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1)&lt;br /&gt;
  .Modify(#{&lt;br /&gt;
    &amp;quot;Name&amp;quot;: &amp;quot;Project 1&amp;quot;&lt;br /&gt;
    &amp;quot;ParentProjectId&amp;quot;: 2&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Set Snowflake connection string key for the project:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1)&lt;br /&gt;
  .Modify(#{&lt;br /&gt;
    &amp;quot;SnowflakeConnectionStringKey&amp;quot;: &amp;quot;MyKey1&amp;quot;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||ScriptByName (Script)&lt;br /&gt;
||Script name (String)&lt;br /&gt;
||&lt;br /&gt;
Returns Script by its name located in the project. Returns null, if Script with that name does not exist in the project.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(123).ScriptByName(&amp;quot;MyScript1&amp;quot;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;SetSecret&amp;quot;&amp;gt;SetSecret&amp;lt;/span&amp;gt;&lt;br /&gt;
||&lt;br /&gt;
# Secret type (string)&lt;br /&gt;
# Secret name (string)&lt;br /&gt;
# Secret value (string)&lt;br /&gt;
||Sets or adds a [[Storing_Secrets_for_Scripts|secret]] for the project. Setting the secret value to &#039;&#039;null&#039;&#039; removes the secret. There can be several secrets with the same name in the same project if the type of the secret is different. Setting secrets requires the project specific &#039;&#039;ManageProject&#039;&#039; permission.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
# &#039;&#039;&#039;Type&#039;&#039;&#039; (string): Secret type which is one of the following:&lt;br /&gt;
#* &amp;quot;externaldatatableconnection&amp;quot;: Snowflake ODBC connection string for datatables.&lt;br /&gt;
#* &amp;quot;odbc&amp;quot;: ODBC connection string (e.g., to extract data in scripts, or load an in-memory model).&lt;br /&gt;
#* &amp;quot;oledb&amp;quot;: OleDB connection string (e.g., to extract data in scripts, or load an in-memory model).&lt;br /&gt;
#* &amp;quot;sap&amp;quot;: SAP password.&lt;br /&gt;
#* &amp;quot;salesforce&amp;quot;: Salesforce password.&lt;br /&gt;
#* &amp;quot;sql&amp;quot;: SQL Server connection string.&lt;br /&gt;
#* &amp;quot;qprmea&amp;quot;: QPR MEA connection string.&lt;br /&gt;
# &#039;&#039;&#039;Name&#039;&#039;&#039; (string): Secret name, used to refer to the secret in the commands.&lt;br /&gt;
# &#039;&#039;&#039;Value&#039;&#039;&#039; (string): Secret value which contains the confidential information.&lt;br /&gt;
&lt;br /&gt;
Example: Set SAP password:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1).SetSecret(&amp;quot;sap&amp;quot;, &amp;quot;MySapPassword&amp;quot;, &amp;quot;I l0ve 5AP!&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Remove SAP password:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ProjectById(1).SetSecret(&amp;quot;sap&amp;quot;, &amp;quot;MySapPassword&amp;quot;, null);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Functions to get project:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||ProjectById&lt;br /&gt;
||Project id (Integer)&lt;br /&gt;
||&lt;br /&gt;
Returns project object corresponding to the provided project id.&lt;br /&gt;
|-&lt;br /&gt;
||ProjectByName&lt;br /&gt;
||Project name (String)&lt;br /&gt;
||&lt;br /&gt;
Returns project object by given project name. If there is no such project or user doesn&#039;t have access to it, &#039;&#039;null&#039;&#039; value is returned. If there are multiple projects with the same name, one of them is returned.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let project = ProjectByName(&amp;quot;My Project&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Functions to create project:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;CreateProject&amp;quot;&amp;gt;CreateProject&amp;lt;/span&amp;gt; (Project)&lt;br /&gt;
||Parameters dictionary&lt;br /&gt;
||Create a project. This is a similar function as the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#CreateProject|CreateProject function]] in the project context. This function in the generic context is needed to create root-level projects (which don&#039;t have parent project).&lt;br /&gt;
&lt;br /&gt;
Example: create a root project:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CreateProject(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My project&amp;quot;&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: create a sub-project:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CreateProject(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My project&amp;quot;&lt;br /&gt;
  &amp;quot;ParentProjectId&amp;quot;: 1&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;Import&amp;quot;&amp;gt;Import&amp;lt;/span&amp;gt; (Project*)&lt;br /&gt;
||JSON string&lt;br /&gt;
||Creates one or several projects from a json string. The project(s) are created as the root level projects. The json format is described in [[Projects Export File Format]]. The function returns the created project objects. In case the project names already exist, the import operation automatically adjusts the names to be unique.&lt;br /&gt;
&lt;br /&gt;
Example: Create a root level project from json data:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let jsonData = `{ &amp;quot;Projects&amp;quot;: [ { &amp;quot;Name&amp;quot;: &amp;quot;My project&amp;quot; } ] }`;&lt;br /&gt;
let result = Import(jsonData);&lt;br /&gt;
let createdProjectId = result[&amp;quot;Projects&amp;quot;][0].Id;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Script ==&lt;br /&gt;
Scripts are entities that contain executable code, that can be run. Usually scripts contains ETL routines but also other kind of tasks are possible.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Script properties&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||Code (String)&lt;br /&gt;
||Script code.&lt;br /&gt;
|-&lt;br /&gt;
||Configuration (Dictionary)&lt;br /&gt;
||Script&#039;s configuration.&lt;br /&gt;
|-&lt;br /&gt;
||CreatedBy (User)&lt;br /&gt;
||User who created the Script.&lt;br /&gt;
|-&lt;br /&gt;
||CreatedDate (DateTime)&lt;br /&gt;
||Timestamp when the Script was created.&lt;br /&gt;
|-&lt;br /&gt;
||CurrentRunStart (DateTime)&lt;br /&gt;
||Timestamp of the current run start. Null if the script is currently not running.&lt;br /&gt;
|-&lt;br /&gt;
||Description (String)&lt;br /&gt;
||Description of the Script.&lt;br /&gt;
|-&lt;br /&gt;
||Id (Integer)&lt;br /&gt;
||Id of the Script.&lt;br /&gt;
|-&lt;br /&gt;
||Language (String)&lt;br /&gt;
||Either of the following scripting language: &#039;&#039;&#039;Expression&#039;&#039;&#039; or &#039;&#039;&#039;SQL&#039;&#039;&#039;. When language is Expression, the script is run as an expression script, and when language is SQL, the script is run as an SQL script (using the sandbox database).&lt;br /&gt;
|-&lt;br /&gt;
||LastModifiedBy (User)&lt;br /&gt;
||User who last modified the Script.&lt;br /&gt;
|-&lt;br /&gt;
||LastModifiedDate (DateTime)&lt;br /&gt;
||Timestamp when the Script was last modified.&lt;br /&gt;
|-&lt;br /&gt;
||LastRunEnd (DateTime)&lt;br /&gt;
||Timestamp of the last completed script run end (either successful completion or failure). Null if the Script hasn&#039;t been run yet.&lt;br /&gt;
|-&lt;br /&gt;
||LastRunResult (String)&lt;br /&gt;
||Result of the last run. Options are:&lt;br /&gt;
* &#039;&#039;&#039;Completed&#039;&#039;&#039;: The last run was completed successfully.&lt;br /&gt;
* &#039;&#039;&#039;Failed&#039;&#039;&#039;: An error occurred during the last run, so likely the script did not complete as intended.&lt;br /&gt;
* &#039;&#039;&#039;Aborted&#039;&#039;&#039;: Script run was manually stopped prematurely by a user, so the script did not proceeded in the end.&lt;br /&gt;
&lt;br /&gt;
Null if the Script hasn&#039;t been run yet.&lt;br /&gt;
|-&lt;br /&gt;
||LastRunStart (DateTime)&lt;br /&gt;
||Timestamp of the last completed script run start time. Null if the Script hasn&#039;t been run yet.&lt;br /&gt;
|-&lt;br /&gt;
|McpPrimitiveType (String)&lt;br /&gt;
|Returns one of the following values depending on which type of MCP primitive the script supports:&lt;br /&gt;
&lt;br /&gt;
* Tool&lt;br /&gt;
* Prompt&lt;br /&gt;
* ResourceNull value means that the script does not support any MCP primitive type.&lt;br /&gt;
|-&lt;br /&gt;
||Name (String)&lt;br /&gt;
||Name of the Script.&lt;br /&gt;
|-&lt;br /&gt;
||OperationId (Integer)&lt;br /&gt;
||Id of the operation which runs the Script. Null if the script is currently not running.&lt;br /&gt;
|-&lt;br /&gt;
||Project (Project)&lt;br /&gt;
||Project where the Script is located. Null if the script is in the global context.&lt;br /&gt;
|-&lt;br /&gt;
||ProjectId (Integer)&lt;br /&gt;
||Id of the project where the Script is located. Null if the script is in the global context.&lt;br /&gt;
|-&lt;br /&gt;
||Status (String)&lt;br /&gt;
||Current status of the script. Options are:&lt;br /&gt;
* &#039;&#039;&#039;Ready&#039;&#039;&#039;: Script is not running. In this status, the script can be started (changing the status to &#039;&#039;Running&#039;&#039;).&lt;br /&gt;
* &#039;&#039;&#039;Running&#039;&#039;&#039;: Script is running. In this status, the script can be stopped (changing the status to &#039;&#039;Stopping&#039;&#039;). Calling stop just requests a script to stop, and the actual stopping occurs some time later.&lt;br /&gt;
* &#039;&#039;&#039;Stopping&#039;&#039;&#039;: Script has been requested to be stopped, but it&#039;s still running. In this status, neither start nor stop can be called for the script. When the script eventually stops, its status changes to &#039;&#039;Ready&#039;&#039;.&lt;br /&gt;
|-&lt;br /&gt;
||CanBeUsedAsMcpTool (Boolean)&lt;br /&gt;
||A boolean value determining whether this script can be used as a MCP tool or not.&lt;br /&gt;
|-&lt;br /&gt;
||CanBeUsedAsMcpPrompt (Boolean)&lt;br /&gt;
||A boolean value determining whether this script can be used as a MCP prompt or not.&lt;br /&gt;
|-&lt;br /&gt;
||CanBeUsedAsMcpResource (Boolean)&lt;br /&gt;
||A boolean value determining whether this script can be used as a MCP resource or not.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Script functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||DeletePermanently&lt;br /&gt;
||(none)&lt;br /&gt;
||&lt;br /&gt;
Deletes the script permanently. To delete an expression script, &#039;&#039;&#039;ManageScripts&#039;&#039;&#039; permission for the project is needed, and to delete an SQL script, global &#039;&#039;&#039;RunScripts&#039;&#039;&#039; permission and &#039;&#039;&#039;ManageScripts&#039;&#039;&#039; permission for the project is needed.&lt;br /&gt;
|-&lt;br /&gt;
||Modify&lt;br /&gt;
||Parameters dictionary&lt;br /&gt;
||Modifies properties of a script. The parameter is a dictionary of properties to change:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039; (String): Name of the script.&lt;br /&gt;
* &#039;&#039;&#039;Description&#039;&#039;&#039; (String): Description text of the script.&lt;br /&gt;
* &#039;&#039;&#039;Language&#039;&#039;&#039; (String): Language of the script, either &#039;&#039;Expression&#039;&#039; or &#039;&#039;SQL&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Code&#039;&#039;&#039; (String): Script code.&lt;br /&gt;
* &#039;&#039;&#039;ProjectId&#039;&#039;&#039; (Integer): Id of the project where the script is located. Changing this property moves the script into a different project.&lt;br /&gt;
* &#039;&#039;&#039;Configuration&#039;&#039;&#039; (Dictionary): Script json configuration.&lt;br /&gt;
&lt;br /&gt;
Requires &#039;&#039;ManageScripts&#039;&#039; permissions in the project. Additionally, requires &#039;&#039;RunScripts&#039;&#039; permission for SQL scripts.&lt;br /&gt;
&lt;br /&gt;
Example: Rename script:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ScriptById(1).Modify(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;Updated script&amp;quot;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Change multiple properties in the same call:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ScriptById(1).Modify(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;Updated script&amp;quot;,&lt;br /&gt;
  &amp;quot;Description&amp;quot;: &amp;quot;Updated description&amp;quot;,&lt;br /&gt;
  &amp;quot;Language&amp;quot;: &amp;quot;SQL&amp;quot;,&lt;br /&gt;
  &amp;quot;Code&amp;quot;: &amp;quot;SELECT 1&amp;quot;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Move script to a different project:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ScriptById(1).Modify(#{&lt;br /&gt;
  &amp;quot;ProjectId&amp;quot;: ProjectByName(&amp;quot;My project&amp;quot;).Id&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||Run (Object)&lt;br /&gt;
||Dictionary of parameters&lt;br /&gt;
||&lt;br /&gt;
Runs the script using the provided parameters. The parameters are available in the script as variables (see the example). Any type of variables can be passed to the script. Note that if the script assumes certain variables, but that they are not passed to the script, the script run will throw an error. &lt;br /&gt;
&lt;br /&gt;
For SQL scripts, the passed parameters are available in the script as variables in format &#039;&#039;&#039;@_parameter_&amp;lt;ParameterName&amp;gt;&#039;&#039;&#039; where &amp;lt;ParameterName&amp;gt; is the name of the parameter, e.g. &#039;&#039;parameter_myParameter1&#039;&#039;. Only string type of parameters can be used, so any other type of data in parameter values is converted into strings.&lt;br /&gt;
&lt;br /&gt;
The return value of the script is returned by the Run function. Expression scripts return a value with the &#039;&#039;return&#039;&#039; statement or alternatively the result of the last line of the script is the return value. If the script does not return any value, the Run function returns &#039;&#039;_empty&#039;&#039;. For SQL scripts, the return value is the last dataset produced by the script (returned as a DataFrame) (SQL scripts might create several datasets using the &#039;&#039;--#ShowReport&#039;&#039; command or the &#039;&#039;Show&#039;&#039; parameter).&lt;br /&gt;
&lt;br /&gt;
When a script is called using the Run function, the called script status does not change, because it&#039;s the parent script that is &#039;&#039;Running&#039;&#039;. Also the called script log is not filled, but instead the logging goes to the calling script.&lt;br /&gt;
&lt;br /&gt;
It&#039;s possible to call a script using the Run function several times simultaneously.&lt;br /&gt;
&lt;br /&gt;
If there is an error when running the called script, the Run function throws the error to the calling script.&lt;br /&gt;
&lt;br /&gt;
Scripts are run in the script entity context, so for example the following properties are available:&lt;br /&gt;
* Id: Script id&lt;br /&gt;
* Name: Script name&lt;br /&gt;
* Project.Id: Project id where the script is located&lt;br /&gt;
* Project.Name: Name of the project where the script is located&lt;br /&gt;
&lt;br /&gt;
Example: Following script (id 123) raises a specified number to a specified power:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
return Pow(numberToRaise, exponent);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script can be called as follows (returning 16):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let runResult = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;numberToRaise&amp;quot;: 4,&lt;br /&gt;
  &amp;quot;exponent&amp;quot;: 2&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;Start&amp;quot;&amp;gt;Start&amp;lt;/span&amp;gt;&lt;br /&gt;
||Dictionary of parameters&lt;br /&gt;
||Starts the script. The function call doesn&#039;t wait for the script run to complete (i.e., asynchronous behavior) which is same as starting the script in the [[Managing_Scripts#Starting_Script|Workspace]].&lt;br /&gt;
&lt;br /&gt;
Parameters to the script can be provided as a dictionary of name-value pairs (which is not possible when script is started in the Workspace). Return value is the script run id (integer) if the script was started. If the script is already running, return value is &#039;&#039;null&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Example: Start script (without parameters) and store the script run id:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let runId = ScriptById(1).Start();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Start script with passing parameters:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ScriptById(1).Start(#{&lt;br /&gt;
  &amp;quot;variable1&amp;quot;: &amp;quot;val1&amp;quot;,&lt;br /&gt;
  &amp;quot;variable2&amp;quot;: 5&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||Stop&lt;br /&gt;
||&lt;br /&gt;
||Stops the script. The operation doesn&#039;t wait for the stopping to complete (i.e., asynchronous behavior) which is same as stopping the script in the [[Managing_Scripts#Stopping_Script|Workspace]]. Depending on the operation that the script is performing, the stopping might take some time.&lt;br /&gt;
&lt;br /&gt;
Return value is the script run id (integer) if the script was running. If the script isn&#039;t running, the return value is &#039;&#039;null&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let runId = ScriptById(1).Stop();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Function to get a script by the script id:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||ScriptById&lt;br /&gt;
||&lt;br /&gt;
* Script id (Integer)&lt;br /&gt;
||&lt;br /&gt;
Returns Script object corresponding to the given script id. If script with the given id doesn&#039;t exist or user doesn&#039;t have permissions to it, an error is given.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== User/Group ==&lt;br /&gt;
User objects represents users and user groups. Note that some properties can only be used for users and some for groups.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;User/group properties&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||CreatedBy (User)&lt;br /&gt;
||Returns the user who created the user.&lt;br /&gt;
|-&lt;br /&gt;
||CreatedDate (DateTime)&lt;br /&gt;
||Returns the date when the user was created.&lt;br /&gt;
|-&lt;br /&gt;
||DefaultDashboard (String)&lt;br /&gt;
||Returns the configured [[User_Settings#Starting_dashboard|starting dashboard]] (&amp;quot;default dashboard&amp;quot;) identifier for a group (for users, the starting dashboard cannot be configured). The starting dashboard is automatically opened when a user logs in.&lt;br /&gt;
|-&lt;br /&gt;
||Description (String)&lt;br /&gt;
||Description of the user.&lt;br /&gt;
|-&lt;br /&gt;
||EffectiveDefaultDashboard (String)&lt;br /&gt;
||Returns the [[User_Settings#Starting_dashboard|starting dashboard]] of a user. Value &#039;&#039;null&#039;&#039; means that the user doesn&#039;t have a starting dashboard. The starting dashboard comes from the user&#039;s groups. If multiple of user&#039;s groups have the starting dashboard defined, the user&#039;s starting dashboard will be taken from a group having alphabetically the first name.&lt;br /&gt;
|-&lt;br /&gt;
||Email (String)&lt;br /&gt;
||Email address of the user.&lt;br /&gt;
|-&lt;br /&gt;
||FullName (String)&lt;br /&gt;
||Full name of the user or group name.&lt;br /&gt;
|-&lt;br /&gt;
||GlobalPermissions (String*)&lt;br /&gt;
||Array of global [[Roles and Permissions#Global_and_Project_Roles|permissions]] of the user. Global permissions come from the global roles assigned to the user and groups that the user belongs to. Note that to get the effective permissions for certain objects, also project specific permissions need to be taken into account.&lt;br /&gt;
|-&lt;br /&gt;
||GroupMemberNames (String*)&lt;br /&gt;
||Array of names of members of a user group. This property is available for groups.&lt;br /&gt;
|-&lt;br /&gt;
||GroupMembers (User*)&lt;br /&gt;
||Array of members of a user group. This property is available for groups.&lt;br /&gt;
|-&lt;br /&gt;
||GroupNames (String*)&lt;br /&gt;
||Array of names of user groups the user belongs to. This property is available for users.&lt;br /&gt;
|-&lt;br /&gt;
||Groups (User*)&lt;br /&gt;
||Array of user groups the user belongs to. This property is available for users.&lt;br /&gt;
|-&lt;br /&gt;
||HasPassword (Boolean)&lt;br /&gt;
||Returns true if user has a password defined in QPR ProcessAnalyzer and thus user can authenticate using the password. If user doesn&#039;t have a password, the SAML authentication is the only way to log in. &#039;&#039;ManageUsers&#039;&#039; permission is needed to access this property for other users.&lt;br /&gt;
|-&lt;br /&gt;
||Id (Integer)&lt;br /&gt;
||Id of the user, which is unique for every user.&lt;br /&gt;
|-&lt;br /&gt;
||IsActive (Boolean)&lt;br /&gt;
||Returns true only if the user is active (not disabled).&lt;br /&gt;
|-&lt;br /&gt;
||IsGroup (Boolean)&lt;br /&gt;
||Returns true if the user is a user group.&lt;br /&gt;
|-&lt;br /&gt;
||IsLocked (Boolean)&lt;br /&gt;
||Returns true if user account is currently [[User_Session_Management#Preventing_password_guessing_attacks|locked]]. &#039;&#039;ManageUsers&#039;&#039; permission is needed to access this property.&lt;br /&gt;
|-&lt;br /&gt;
||LastLockedDate (DateTime)&lt;br /&gt;
||Returns date when user account was locked the last time. Returns &#039;&#039;null&#039;&#039; if the user account has never been locked. &#039;&#039;ManageUsers&#039;&#039; permission is needed to access this property.&lt;br /&gt;
|-&lt;br /&gt;
||LastLoginDate (DateTime)&lt;br /&gt;
||Returns date when the user made last successful login. &#039;&#039;ManageUsers&#039;&#039; permission is needed to access this property for other users.&lt;br /&gt;
|-&lt;br /&gt;
||LastModifiedBy (User)&lt;br /&gt;
||Returns the user who last modified this user.&lt;br /&gt;
|-&lt;br /&gt;
||LastModifiedDate (DateTime)&lt;br /&gt;
||Returns the date when the user was last modified.&lt;br /&gt;
|-&lt;br /&gt;
||Name (String)&lt;br /&gt;
||Login name of the user or group.&lt;br /&gt;
|-&lt;br /&gt;
||Roles (Object**)&lt;br /&gt;
||&lt;br /&gt;
Returns all roles of the user (both global and project roles) as a nested array structure.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ToJson(Users.Where(name == &amp;quot;qpr&amp;quot;).Roles)&lt;br /&gt;
Returns (for example):&lt;br /&gt;
[&lt;br /&gt;
  [{&amp;quot;calcId&amp;quot;: &amp;quot;Project:1&amp;quot;}, &amp;quot;Administrator&amp;quot;],&lt;br /&gt;
  [{&amp;quot;calcId&amp;quot;: &amp;quot;Project:2&amp;quot;}, &amp;quot;Analyzer&amp;quot;],&lt;br /&gt;
  [{&amp;quot;calcId&amp;quot;: &amp;quot;Project:3&amp;quot;}, &amp;quot;Viewer&amp;quot;],&lt;br /&gt;
  [null, &amp;quot;RunScripts&amp;quot;],&lt;br /&gt;
  [null, &amp;quot;Administrator&amp;quot;]&lt;br /&gt;
]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;User/group functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||EffectivePermissionsFor (String Array)&lt;br /&gt;
||&lt;br /&gt;
* Project to get permissions&lt;br /&gt;
||&lt;br /&gt;
Returns effective (actual) permission of the user to the given project. Project is given as a [[#Project|project object]] (not as a project id). Effective permissions determine the actual permissions that the user has, i.e. a combination of all permissions assigned to the user and groups the user belong to, including both project specific and global roles.&lt;br /&gt;
&lt;br /&gt;
Permissions for the EffectivePermissionsFor function are as follows:&lt;br /&gt;
* All users can query their own permissions&lt;br /&gt;
* To get permissions for any user, the user needs to have [[Roles_and_Permissions|ManageUsers permission]].&lt;br /&gt;
&lt;br /&gt;
Note that &#039;&#039;inactive&#039;&#039; users don&#039;t have any effective permissions, so the EffectivePermissionsFor function does not return any permissions for those users.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
UserById(1).EffectivePermissionsFor(ModelById(2).Project)&lt;br /&gt;
Returns (for example): [&amp;quot;EditDashboards&amp;quot;, &amp;quot;Filtering&amp;quot;, &amp;quot;GenericRead&amp;quot;]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||GetAttribute&lt;br /&gt;
||&lt;br /&gt;
||&lt;br /&gt;
Returns user attribute value by given attribute name and optionally by the model/project/dashboard context. Supported data types are String, Integer, Float, and DateTime. To store more complex data types, data can be converted into json and stored as string. If the attribute doesn&#039;t exist, null is returned.&lt;br /&gt;
&lt;br /&gt;
For example, if using dashboard as context, the attributes are effectively bound to each user and each dashboard separately. Thus, there can be several attributes with the same name as long as the dashboard is different.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
# &#039;&#039;&#039;Attribute name&#039;&#039;&#039; (String): Name of the attribute.&lt;br /&gt;
# &#039;&#039;&#039;Attribute context&#039;&#039;&#039; (Project/Model/Dashboard): Optional context object the attribute is linked to.&lt;br /&gt;
&lt;br /&gt;
Users have permissions to get attributes for themselves, and also (administrator) users with global &#039;&#039;ManageUsers&#039;&#039; permission can get attributes for all users. In addition, if using the context object, the &#039;&#039;GenericRead&#039;&#039; permission is required for the context object.&lt;br /&gt;
&lt;br /&gt;
Example: Get user attribute MyDataValue for myself:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CurrentUser&lt;br /&gt;
  .GetAttribute(&amp;quot;MyDataValue&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Get user attribute MyDataValue for user John:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Users&lt;br /&gt;
  .Where(Name==&amp;quot;John&amp;quot;)&lt;br /&gt;
  .GetAttribute(&amp;quot;MyDataValue&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Get user attribute MyDataValue for user 1 related to dashboard id 1:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
UserById(1)&lt;br /&gt;
  .GetAttribute(&amp;quot;MyDataValue&amp;quot;, DashboardById(1));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
||SetAttribute&lt;br /&gt;
||&lt;br /&gt;
||&lt;br /&gt;
Sets user attribute value for given attribute name and optionally for the model/project/dashboard context. Supported data types are String, Integer, Float, and DateTime. To store more complex data types, data can be converted into json and stored as string. If setting value &#039;&#039;null&#039;&#039;, the user attribute is removed. Required permissions are same as in the GetAttribute function.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
# &#039;&#039;&#039;Attribute name&#039;&#039;&#039; (String): Name of the attribute.&lt;br /&gt;
# &#039;&#039;&#039;Attribute value&#039;&#039;&#039; (String/Integer/Float/DateTime): Attribute value to be stored.&lt;br /&gt;
# &#039;&#039;&#039;Attribute context&#039;&#039;&#039; (Project/Model/Dashboard): Optional context object the attribute value is linked to.&lt;br /&gt;
&lt;br /&gt;
Example: Set user attribute MyDataValue for myself:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CurrentUser&lt;br /&gt;
  .SetAttribute(&amp;quot;MyDataValue &amp;quot;, &amp;quot;value&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Set value 123 as user attribute MyDataValue for user John:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Users&lt;br /&gt;
  .Where(Name==&amp;quot;John&amp;quot;)&lt;br /&gt;
  .SetAttribute(&amp;quot;MyDataValue&amp;quot;, 123);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Set current time as user attribute MyDataValue for user 1 related to dashboard id 1:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
UserById(1)&lt;br /&gt;
  .GetAttribute(&amp;quot;MyDataValue&amp;quot;, Now, DashboardById(1));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Function to get User by user id:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Functions&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||UserById (User)&lt;br /&gt;
||&lt;br /&gt;
* User id (Integer)&lt;br /&gt;
||&lt;br /&gt;
Returns User object that has the provided user id. Also groups can be queried with this function. Returns an access denied error if the user with given id does not exist or the current user does not have access to it.&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28400</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28400"/>
		<updated>2026-05-25T08:30:40Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* OAuth 2.0 Authentication */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Configuring MCP Server Overview ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API key. Both the authentication methods can be configured at the same time to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication, set the [[PA_Configuration_database_table#:~:text=BuiltInOAuthServerConfiguration|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default. Also, ensure the value of [[PA Configuration database table#SAML 2.0 Federated Authentication Settings|ServiceProviderLocation]] has been set (NOTE: SAML does not have to be enabled).&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
Some clients use the [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint: /.well-known/openid-configuration. To get this endpoint working when QPR ProcessAnalyzer is hosted in Windows in IIS, the following setup is required:&lt;br /&gt;
&lt;br /&gt;
Add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - oauth-protected-resource&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/oauth-protected-resource$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                 url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/.well-known/oauth-protected-resource/qprpa/api/mcp&amp;quot;&lt;br /&gt;
                 redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - oauth-authorization-server&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/oauth-authorization-server$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                 url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/oauth-authorization-server&amp;quot;&lt;br /&gt;
                 redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
== Testing MCP Client Connection ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table.&lt;br /&gt;
* &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_CONFIGURATION database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. This is often automatically done by MCP Client software.&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
* Connect to Server: &lt;br /&gt;
** In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., https://your-pa-server/qprpa/api/mcp)&lt;br /&gt;
** Select Streamable HTTP as the transport type.&lt;br /&gt;
** Select &amp;quot;Via Proxy&amp;quot; as connection type.&lt;br /&gt;
* Authenticate: &lt;br /&gt;
** If using API Key-based authentication, configure &amp;quot;X-Mcp-Api-Key&amp;quot; as additional custom header with the value specified in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting of the PA server.&lt;br /&gt;
** If using OAuth, set one of the accepted audience values to Authentication/OAuth 2.0 Flow/Client ID (or leave it empty if DCR is enabled).&lt;br /&gt;
*** Open Auth Settings&lt;br /&gt;
*** Click &amp;quot;Quick OAuth Flow&amp;quot;-button.&lt;br /&gt;
**** Perform OAuth authorization using the built-in OAuth provider.&lt;br /&gt;
**** Provided that the authentication has been configured correctly, a successful authentication message should be shown.&lt;br /&gt;
** Click Connect.&lt;br /&gt;
* List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool created for a parameterless QPR ProcessAnalyzer script having id 216, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
  &amp;quot;params&amp;quot;: {&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Script-216&amp;quot;,&lt;br /&gt;
    &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
    &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
      &amp;quot;progressToken&amp;quot;: 0&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Connecting_to_QPR_ProcessAnalyzer_MCP_Server&amp;diff=28328</id>
		<title>Connecting to QPR ProcessAnalyzer MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Connecting_to_QPR_ProcessAnalyzer_MCP_Server&amp;diff=28328"/>
		<updated>2026-05-13T10:24:26Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Step 1: Create API Integration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer provides a standard [[QPR_ProcessAnalyzer_as_MCP_Server|Model Context Protocol (MCP) server]] endpoint that AI clients can connect to access process mining data and analyses. The primary authentication method is &#039;&#039;&#039;OAuth 2.0&#039;&#039;&#039;, and secondarily an &#039;&#039;&#039;API key&#039;&#039;&#039; can be used for simpler setups. Before configuring any client, ensure that QPRProcessAnalyzer has been configured as the MCP server and the desired authentication methods are enabled.&lt;br /&gt;
&lt;br /&gt;
== Claude Desktop ==&lt;br /&gt;
&lt;br /&gt;
Claude Desktop is Anthropic&#039;s desktop GUI application. It supports remote MCP servers through &#039;&#039;&#039;Custom Connectors&#039;&#039;&#039; and local servers through JSON configuration.&lt;br /&gt;
&lt;br /&gt;
=== Method 1: Custom Connectors / OAuth ===&lt;br /&gt;
&lt;br /&gt;
For a remote QPR ProcessAnalyzer MCP server with OAuth, use the Custom Connectors feature:&lt;br /&gt;
&lt;br /&gt;
# Open Claude Desktop.&lt;br /&gt;
# Click the &#039;&#039;&#039;+&#039;&#039;&#039; button at the bottom of the chat area.&lt;br /&gt;
# Select &#039;&#039;&#039;Connectors&#039;&#039;&#039; → &#039;&#039;&#039;Manage Connectors&#039;&#039;&#039;.&lt;br /&gt;
# Click &#039;&#039;&#039;Add custom connector&#039;&#039;&#039;.&lt;br /&gt;
# Enter the QPR ProcessAnalyzer MCP server URL: &amp;lt;code&amp;gt;https://your-pa-server.com/qprpa/api/mcp&amp;lt;/code&amp;gt;&lt;br /&gt;
# Claude Desktop will initiate the OAuth flow. Sign in to QPR ProcessAnalyzer and authorize access.&lt;br /&gt;
# Once connected, the QPR ProcessAnalyzer tools will be available in your conversations.&lt;br /&gt;
&lt;br /&gt;
Note: Custom Connectors are available on &#039;&#039;&#039;Pro&#039;&#039;&#039;, &#039;&#039;&#039;Max&#039;&#039;&#039;, &#039;&#039;&#039;Team&#039;&#039;&#039;, and &#039;&#039;&#039;Enterprise&#039;&#039;&#039; plans. Remote servers use Streamable HTTP transport with OAuth.&lt;br /&gt;
&lt;br /&gt;
=== Method 2: JSON Configuration with API Key ===&lt;br /&gt;
&lt;br /&gt;
For API key authentication, edit the &amp;lt;code&amp;gt;claude_desktop_config.json&amp;lt;/code&amp;gt; file directly.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;File location:&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;macOS:&#039;&#039;&#039; &amp;lt;code&amp;gt;~/Library/Application Support/Claude/claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;Windows:&#039;&#039;&#039; &amp;lt;code&amp;gt;%APPDATA%\Claude\claude_desktop_config.json&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Shortcut:&#039;&#039;&#039; In Claude Desktop, go to &#039;&#039;&#039;Settings&#039;&#039;&#039; → &#039;&#039;&#039;Developer&#039;&#039;&#039; → &#039;&#039;&#039;Edit Config&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Configuration:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;mcpServers&amp;quot;: {&lt;br /&gt;
    &amp;quot;qprpa-server&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;http&amp;quot;,&lt;br /&gt;
      &amp;quot;url&amp;quot;: &amp;quot;https://your-pa-server.com/qprpa/api/mcp&amp;quot;,&lt;br /&gt;
      &amp;quot;headers&amp;quot;: {&lt;br /&gt;
        &amp;quot;X-Mcp-Api-Key&amp;quot;: &amp;quot;&amp;lt;your API key&amp;gt;&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After saving, &#039;&#039;&#039;completely quit&#039;&#039;&#039; Claude Desktop (not just close the window) and restart it. Look for the MCP server indicator in the bottom toolbar of the conversation area to confirm the connection.&lt;br /&gt;
&lt;br /&gt;
=== Verification ===&lt;br /&gt;
&lt;br /&gt;
* Click the &#039;&#039;&#039;hammer icon&#039;&#039;&#039; (🔨) or the &#039;&#039;&#039;+&#039;&#039;&#039; button → &#039;&#039;&#039;Connectors&#039;&#039;&#039; to see available tools.&lt;br /&gt;
* Go to &#039;&#039;&#039;Settings&#039;&#039;&#039; → &#039;&#039;&#039;Developer&#039;&#039;&#039; → &#039;&#039;&#039;Logs&#039;&#039;&#039; for error messages if the server does not appear.&lt;br /&gt;
&lt;br /&gt;
== Claude Code ==&lt;br /&gt;
&lt;br /&gt;
Claude Code is Anthropic&#039;s CLI-based AI coding agent. It has built-in support for connecting to remote MCP servers with OAuth authentication.&lt;br /&gt;
&lt;br /&gt;
=== Method 1: OAuth Authentication ===&lt;br /&gt;
&lt;br /&gt;
Claude Code supports &#039;&#039;&#039;OAuth 2.1&#039;&#039;&#039; natively for remote MCP servers. When connecting, it automatically discovers OAuth metadata, launches a browser-based authorization flow, and securely stores tokens in the system keychain.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Add the server via CLI:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
claude mcp add --transport http \&lt;br /&gt;
  --client-id &amp;quot;YOUR_QPRPA_CLIENT_ID&amp;quot; \&lt;br /&gt;
  --client-secret &amp;quot;YOUR_QPRPA_CLIENT_SECRET&amp;quot; \&lt;br /&gt;
  --callback-port 8080 \&lt;br /&gt;
  qprpa-server https://your-pa-server.com/qprpa/api/mcp&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Parameters:&#039;&#039;&#039;&lt;br /&gt;
* &amp;lt;code&amp;gt;--transport http&amp;lt;/code&amp;gt; — Use Streamable HTTP transport (recommended over deprecated SSE).&lt;br /&gt;
* &amp;lt;code&amp;gt;--client-id&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;--client-secret&amp;lt;/code&amp;gt; — Pre-configured OAuth credentials from QPR ProcessAnalyzer. If the server supports Dynamic Client Registration (DCR), these can be omitted.&lt;br /&gt;
* &amp;lt;code&amp;gt;--callback-port&amp;lt;/code&amp;gt; — Fixes the OAuth callback port to match a pre-registered redirect URI of the form &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;http://localhost:8080/callback&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complete the OAuth flow:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
# Run &amp;lt;code&amp;gt;/mcp&amp;lt;/code&amp;gt; inside your Claude Code session to initiate the connection.&lt;br /&gt;
# A browser window will open to the QPR ProcessAnalyzer authorization page.&lt;br /&gt;
# Sign in and grant the requested permissions.&lt;br /&gt;
# Claude Code exchanges the authorization code for tokens and stores them securely.&lt;br /&gt;
&lt;br /&gt;
Tip: If QPR ProcessAnalyzer uses a non-standard OAuth metadata endpoint, you can specify it explicitly with the &amp;lt;code&amp;gt;--authServerMetadataUrl&amp;lt;/code&amp;gt; flag. By default, Claude Code checks &amp;lt;code&amp;gt;/.well-known/oauth-protected-resource&amp;lt;/code&amp;gt; (RFC 9728) then falls back to &amp;lt;code&amp;gt;/.well-known/oauth-authorization-server&amp;lt;/code&amp;gt; (RFC 8414).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Alternatively, add via JSON configuration:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
claude mcp add-json qprpa-server &#039;{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;http&amp;quot;,&lt;br /&gt;
  &amp;quot;url&amp;quot;: &amp;quot;https://your-pa-server.com/qprpa/api/mcp&amp;quot;,&lt;br /&gt;
  &amp;quot;oauth&amp;quot;: {&lt;br /&gt;
    &amp;quot;clientId&amp;quot;: &amp;quot;YOUR_QPRPA_CLIENT_ID&amp;quot;,&lt;br /&gt;
    &amp;quot;clientSecret&amp;quot;: &amp;quot;YOUR_QPRPA_CLIENT_SECRET&amp;quot;,&lt;br /&gt;
    &amp;quot;callbackPort&amp;quot;: 8080&lt;br /&gt;
  }&lt;br /&gt;
}&#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Method 2: API Key Authentication ===&lt;br /&gt;
&lt;br /&gt;
If using an API key instead of OAuth, configure the server with a static &amp;lt;code&amp;gt;Authorization&amp;lt;/code&amp;gt; header:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
claude mcp add-json qprpa-server &#039;{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;http&amp;quot;,&lt;br /&gt;
  &amp;quot;url&amp;quot;: &amp;quot;https://your-pa-server.com/qprpa/api/mcp&amp;quot;,&lt;br /&gt;
  &amp;quot;headers&amp;quot;: {&lt;br /&gt;
    &amp;quot;X-Mcp-Api-Key&amp;quot;: &amp;quot;&amp;lt;your API key&amp;gt;&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Verification ===&lt;br /&gt;
&lt;br /&gt;
To verify the connection is working:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
claude mcp get qprpa-server&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or use the &amp;lt;code&amp;gt;/mcp&amp;lt;/code&amp;gt; slash command inside an active Claude Code session to see the connected server and its available tools.&lt;br /&gt;
&lt;br /&gt;
=== Scopes ===&lt;br /&gt;
&lt;br /&gt;
Claude Code supports three configuration scopes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;local&#039;&#039;&#039; (default) — Available only to you in the current project.&lt;br /&gt;
* &#039;&#039;&#039;project&#039;&#039;&#039; — Shared with everyone in the project via &amp;lt;code&amp;gt;.mcp.json&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;user&#039;&#039;&#039; — Available to you across all projects.&lt;br /&gt;
&lt;br /&gt;
Add &amp;lt;code&amp;gt;-s project&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;-s user&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;claude mcp add&amp;lt;/code&amp;gt; command to change the scope.&lt;br /&gt;
&lt;br /&gt;
== Microsoft Copilot (via Copilot Studio) ==&lt;br /&gt;
&lt;br /&gt;
Microsoft Copilot can connect to QPR ProcessAnalyzer as an MCP server through &#039;&#039;&#039;Microsoft Copilot Studio&#039;&#039;&#039;. Copilot Studio supports the &#039;&#039;&#039;Streamable HTTP&#039;&#039;&#039; transport type and both &#039;&#039;&#039;OAuth 2.0&#039;&#039;&#039; and &#039;&#039;&#039;API key&#039;&#039;&#039; authentication.&lt;br /&gt;
&lt;br /&gt;
=== Method 1: MCP Onboarding Wizard ===&lt;br /&gt;
&lt;br /&gt;
The simplest approach uses the built-in MCP onboarding wizard in Copilot Studio:&lt;br /&gt;
&lt;br /&gt;
# Open your agent in &#039;&#039;&#039;Microsoft Copilot Studio&#039;&#039;&#039;.&lt;br /&gt;
# Ensure &#039;&#039;&#039;generative orchestration&#039;&#039;&#039; is enabled for your agent.&lt;br /&gt;
# Navigate to the &#039;&#039;&#039;Tools&#039;&#039;&#039; page.&lt;br /&gt;
# Select &#039;&#039;&#039;Add a tool&#039;&#039;&#039; → &#039;&#039;&#039;New tool&#039;&#039;&#039; → &#039;&#039;&#039;Model Context Protocol&#039;&#039;&#039;.&lt;br /&gt;
# The MCP onboarding wizard appears. Fill in the required fields:&lt;br /&gt;
#* &#039;&#039;&#039;Server name:&#039;&#039;&#039; &amp;lt;code&amp;gt;QPR ProcessAnalyzer&amp;lt;/code&amp;gt;&lt;br /&gt;
#* &#039;&#039;&#039;Server description:&#039;&#039;&#039; &amp;lt;code&amp;gt;QPR ProcessAnalyzer process mining MCP server&amp;lt;/code&amp;gt;&lt;br /&gt;
#* &#039;&#039;&#039;Server URL:&#039;&#039;&#039; &amp;lt;code&amp;gt;https://your-pa-server.com/qprpa/api/mcp&amp;lt;/code&amp;gt;&lt;br /&gt;
# Configure authentication (see below).&lt;br /&gt;
# Select &#039;&#039;&#039;Next&#039;&#039;&#039; and create a connection.&lt;br /&gt;
# Select &#039;&#039;&#039;Add and configure&#039;&#039;&#039; to finalize.&lt;br /&gt;
&lt;br /&gt;
=== Authentication Configuration ===&lt;br /&gt;
&lt;br /&gt;
==== OAuth 2.0 ====&lt;br /&gt;
&lt;br /&gt;
When using OAuth 2.0 with Copilot Studio:&lt;br /&gt;
&lt;br /&gt;
# During the MCP wizard setup, select &#039;&#039;&#039;OAuth 2.0&#039;&#039;&#039; as the authentication type.&lt;br /&gt;
# Provide the credentials from your QPR ProcessAnalyzer identity provider:&lt;br /&gt;
#* &#039;&#039;&#039;Client ID&#039;&#039;&#039;&lt;br /&gt;
#* &#039;&#039;&#039;Client Secret&#039;&#039;&#039;&lt;br /&gt;
#* &#039;&#039;&#039;Authorization URL&#039;&#039;&#039;&lt;br /&gt;
#* &#039;&#039;&#039;Token URL&#039;&#039;&#039;&lt;br /&gt;
#* &#039;&#039;&#039;Scopes&#039;&#039;&#039; (e.g., &amp;lt;code&amp;gt;openid profile api://your-app-id/mcp&amp;lt;/code&amp;gt;)&lt;br /&gt;
# After the tool is created, &#039;&#039;&#039;copy the Redirect URL&#039;&#039;&#039; provided by Copilot Studio.&lt;br /&gt;
# Register this redirect URL in your QPR ProcessAnalyzer OAuth / identity provider configuration (e.g., add it to the allowed redirect URIs in your app registration).&lt;br /&gt;
# Return to Copilot Studio, create a new connection, and complete the sign-in flow.&lt;br /&gt;
&lt;br /&gt;
==== API Key ====&lt;br /&gt;
&lt;br /&gt;
For API key authentication:&lt;br /&gt;
&lt;br /&gt;
# During setup, select &#039;&#039;&#039;API Key&#039;&#039;&#039; as the authentication type.&lt;br /&gt;
# Enter the QPR ProcessAnalyzer API key when prompted.&lt;br /&gt;
&lt;br /&gt;
=== Method 2: Custom Connector via Power Apps ===&lt;br /&gt;
&lt;br /&gt;
For more advanced control, create a Custom Connector:&lt;br /&gt;
&lt;br /&gt;
# Go to the &#039;&#039;&#039;Power Apps&#039;&#039;&#039; or &#039;&#039;&#039;Power Automate&#039;&#039;&#039; portal.&lt;br /&gt;
# Select &#039;&#039;&#039;Custom connectors&#039;&#039;&#039; → &#039;&#039;&#039;New custom connector&#039;&#039;&#039; → &#039;&#039;&#039;Import OpenAPI file&#039;&#039;&#039;.&lt;br /&gt;
# Create an OpenAPI specification pointing to the QPR ProcessAnalyzer MCP endpoint:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;yaml&amp;quot;&amp;gt;&lt;br /&gt;
swagger: &#039;2.0&#039;&lt;br /&gt;
info:&lt;br /&gt;
  title: QPR ProcessAnalyzer&lt;br /&gt;
  description: QPR ProcessAnalyzer MCP Server&lt;br /&gt;
  version: 1.0.0&lt;br /&gt;
host: your-qprpa-instance.example.com&lt;br /&gt;
basePath: /&lt;br /&gt;
schemes:&lt;br /&gt;
  - https&lt;br /&gt;
paths:&lt;br /&gt;
  /mcp:&lt;br /&gt;
    post:&lt;br /&gt;
      summary: QPR ProcessAnalyzer MCP Server&lt;br /&gt;
      x-ms-agentic-protocol: mcp-streamable-1.0&lt;br /&gt;
      operationId: InvokeMCP&lt;br /&gt;
      responses:&lt;br /&gt;
        &#039;200&#039;:&lt;br /&gt;
          description: Success&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Import the file and continue through the wizard.&lt;br /&gt;
# On the &#039;&#039;&#039;Security&#039;&#039;&#039; tab, configure OAuth 2.0 with the QPR ProcessAnalyzer credentials (Client ID, Client Secret, Authorization URL, Token URL).&lt;br /&gt;
# Select &#039;&#039;&#039;Update Connector&#039;&#039;&#039; to save.&lt;br /&gt;
# In &#039;&#039;&#039;Copilot Studio&#039;&#039;&#039;, go to &#039;&#039;&#039;Tools&#039;&#039;&#039; → &#039;&#039;&#039;Add a tool&#039;&#039;&#039; → &#039;&#039;&#039;Model Context Protocol&#039;&#039;&#039; and select your custom connector.&lt;br /&gt;
&lt;br /&gt;
=== Managing Tools ===&lt;br /&gt;
&lt;br /&gt;
Once connected, the QPR ProcessAnalyzer tools automatically appear under the MCP server&#039;s tool listing in Copilot Studio. You can selectively enable or disable individual tools by toggling the &#039;&#039;&#039;Allow all&#039;&#039;&#039; switch off and configuring individual tool toggles.&lt;br /&gt;
&lt;br /&gt;
== ChatGPT ==&lt;br /&gt;
&lt;br /&gt;
ChatGPT supports remote MCP servers through its &#039;&#039;&#039;Apps &amp;amp; Connectors&#039;&#039;&#039; interface (formerly called &amp;quot;Connectors&amp;quot;). MCP support requires &#039;&#039;&#039;Developer Mode&#039;&#039;&#039; for full tool access in chat.&lt;br /&gt;
&lt;br /&gt;
=== Prerequisites ===&lt;br /&gt;
&lt;br /&gt;
* A &#039;&#039;&#039;ChatGPT Pro&#039;&#039;&#039;, &#039;&#039;&#039;Plus&#039;&#039;&#039;, &#039;&#039;&#039;Team&#039;&#039;&#039;, &#039;&#039;&#039;Enterprise&#039;&#039;&#039;, or &#039;&#039;&#039;Education&#039;&#039;&#039; subscription.&lt;br /&gt;
* &#039;&#039;&#039;Developer Mode&#039;&#039;&#039; enabled (for full read/write tool access in Chat mode).&lt;br /&gt;
* The QPR ProcessAnalyzer MCP server must be publicly accessible (ChatGPT cannot connect to localhost).&lt;br /&gt;
&lt;br /&gt;
=== Adding QPR ProcessAnalyzer as an MCP Server ===&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Enable Developer Mode ====&lt;br /&gt;
&lt;br /&gt;
# Open &#039;&#039;&#039;ChatGPT&#039;&#039;&#039; (web or desktop app).&lt;br /&gt;
# Go to &#039;&#039;&#039;Settings&#039;&#039;&#039; → &#039;&#039;&#039;Connectors&#039;&#039;&#039; (or &#039;&#039;&#039;Apps &amp;amp; Connectors&#039;&#039;&#039;) → &#039;&#039;&#039;Advanced&#039;&#039;&#039;.&lt;br /&gt;
# Enable &#039;&#039;&#039;Developer Mode&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Add the MCP Server ====&lt;br /&gt;
&lt;br /&gt;
# In ChatGPT Settings, go to &#039;&#039;&#039;Apps &amp;amp; Connectors&#039;&#039;&#039;.&lt;br /&gt;
# Select &#039;&#039;&#039;Add connector&#039;&#039;&#039; or &#039;&#039;&#039;Add app&#039;&#039;&#039;.&lt;br /&gt;
# Enter the QPR ProcessAnalyzer MCP server URL: &amp;lt;code&amp;gt;https://your-pa-server.com/qprpa/api/mcp&amp;lt;/code&amp;gt;&lt;br /&gt;
# If using &#039;&#039;&#039;OAuth&#039;&#039;&#039;: ChatGPT will present the OAuth authorization flow. Sign in to QPR ProcessAnalyzer and grant permissions. OpenAI recommends using OAuth with dynamic client registration.&lt;br /&gt;
# If using &#039;&#039;&#039;API Key&#039;&#039;&#039;: Enter the server URL with the API key appended as a query parameter or configure it as directed by your QPR ProcessAnalyzer admin.&lt;br /&gt;
# The connector will be verified and added to your workspace.&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Use in Chat ====&lt;br /&gt;
&lt;br /&gt;
# In a new or existing chat, open the &#039;&#039;&#039;Developer Mode&#039;&#039;&#039; menu.&lt;br /&gt;
# Enable the QPR ProcessAnalyzer connector for the current session.&lt;br /&gt;
# Ask ChatGPT to use QPR ProcessAnalyzer tools, e.g., &#039;&#039;&amp;quot;Show me the process mining analysis for the Q1 purchase-to-pay process.&amp;quot;&#039;&#039;&lt;br /&gt;
# ChatGPT will call the appropriate MCP tools and display results. Write operations will prompt for user confirmation.&lt;br /&gt;
&lt;br /&gt;
=== Using with the API ===&lt;br /&gt;
&lt;br /&gt;
For programmatic access via the OpenAI API, configure the MCP server in your API call:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;model&amp;quot;: &amp;quot;gpt-4o&amp;quot;,&lt;br /&gt;
  &amp;quot;messages&amp;quot;: [...],&lt;br /&gt;
  &amp;quot;mcp_servers&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;url&amp;quot;,&lt;br /&gt;
      &amp;quot;url&amp;quot;: &amp;quot;https://your-pa-server.com/qprpa/api/mcp&amp;quot;,&lt;br /&gt;
      &amp;quot;name&amp;quot;: &amp;quot;qprpa-server&amp;quot;,&lt;br /&gt;
      &amp;quot;authorization_token&amp;quot;: &amp;quot;YOUR_OAUTH_ACCESS_TOKEN&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note|When using the API, you must handle the OAuth flow yourself and provide a valid access token. The API does not perform interactive OAuth flows.&lt;br /&gt;
&lt;br /&gt;
=== Workspace Publishing ===&lt;br /&gt;
&lt;br /&gt;
Enterprise, Education, and Team administrators can publish the QPR ProcessAnalyzer connector across the workspace so all users can access it without individual setup.&lt;br /&gt;
&lt;br /&gt;
== VSCode GitHub Copilot ==&lt;br /&gt;
GitHub Copilot in Visual Studio Code supports connecting to QPR ProcessAnalyzer MCP server. Note: Agent mode is required. MCP tools are invisible in Ask or Edit mode. Open Copilot Chat, click the mode dropdown, and select &amp;quot;Agent.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== Configuration File Locations ===&lt;br /&gt;
You can manually configure the MCP server by editing the &amp;lt;code&amp;gt;mcp.json&amp;lt;/code&amp;gt; file. There are two locations for this file: &#039;&#039;&#039;Workspace&#039;&#039;&#039; — create or open &amp;lt;code&amp;gt;.vscode/mcp.json&amp;lt;/code&amp;gt; in your project (include this file in source control to share MCP server configurations with your team); &#039;&#039;&#039;User profile&#039;&#039;&#039; — run the &amp;lt;code&amp;gt;MCP: Open User Configuration&amp;lt;/code&amp;gt; command to open the &amp;lt;code&amp;gt;mcp.json&amp;lt;/code&amp;gt; file in your user profile folder.&lt;br /&gt;
&lt;br /&gt;
=== Method 1: OAuth Authentication ===&lt;br /&gt;
VS Code GitHub Copilot supports OAuth authentication for remote MCP servers. If you are using a remote server with OAuth authentication, in the &amp;lt;code&amp;gt;mcp.json&amp;lt;/code&amp;gt; file, click &#039;&#039;&#039;Auth&#039;&#039;&#039; from the CodeLens above the server to authenticate to the server. A pop-up or new window will appear, allowing you to authenticate with your account.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Step 1: Add the MCP server configuration&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Create or edit &amp;lt;code&amp;gt;.vscode/mcp.json&amp;lt;/code&amp;gt; in your workspace (or use the user-level configuration):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;servers&amp;quot;: {&lt;br /&gt;
    &amp;quot;qpr-processanalyzer&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;http&amp;quot;,&lt;br /&gt;
      &amp;quot;url&amp;quot;: &amp;quot;https://your-pa-server.com/qprpa/api/mcp&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When using OAuth, VSCode will automatically discover the server&#039;s OAuth metadata and initiate the browser-based authentication flow.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Step 2: Start the server and authenticate&#039;&#039;&#039;&lt;br /&gt;
# A &amp;quot;Start&amp;quot; button will appear in your &amp;lt;code&amp;gt;.vscode/mcp.json&amp;lt;/code&amp;gt; file, at the top of the list of servers. Click the &amp;quot;Start&amp;quot; button to start the MCP servers. This will trigger the input dialog and discover the server tools, which are then stored for later sessions.&lt;br /&gt;
# Click the &#039;&#039;&#039;Auth&#039;&#039;&#039; CodeLens link above the server entry in &amp;lt;code&amp;gt;mcp.json&amp;lt;/code&amp;gt; to initiate the OAuth flow.&lt;br /&gt;
# Sign in to QPR ProcessAnalyzer in the browser window and grant the requested permissions.&lt;br /&gt;
# VS Code securely stores the OAuth tokens for subsequent sessions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Step 3: Use in Copilot Chat&#039;&#039;&#039;&lt;br /&gt;
# Open Copilot Chat by clicking the icon in the title bar of Visual Studio Code. In the Copilot Chat box, select &#039;&#039;&#039;Agent&#039;&#039;&#039; from the popup menu.&lt;br /&gt;
# To view your list of available MCP servers, click the tools icon in the top left corner of the chat box. This will open the MCP server list, where you can see all the MCP servers and associated tools that are currently available in your Visual Studio Code instance.&lt;br /&gt;
# Ask Copilot to use QPR ProcessAnalyzer tools, e.g., &#039;&#039;&amp;quot;Analyze the purchase-to-pay process for Q1 using QPR ProcessAnalyzer.&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Method 2: API Key Authentication ===&lt;br /&gt;
For API key authentication, the API key can be configured to the mcp.json file as shown by the example below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;servers&amp;quot;: {&lt;br /&gt;
    &amp;quot;qpr-processanalyzer&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;http&amp;quot;,&lt;br /&gt;
      &amp;quot;url&amp;quot;: &amp;quot;https://your-pa-server.com/qprpa/api/mcp&amp;quot;,&lt;br /&gt;
      &amp;quot;headers&amp;quot;: {&lt;br /&gt;
        &amp;quot;X-Mcp-Api-Key&amp;quot;: &amp;quot;&amp;lt;your API key&amp;gt;&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Snowflake Cortex Agents and Snowflake Intelligence ==&lt;br /&gt;
Snowflake [https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents Cortex Agents] and [https://docs.snowflake.com/en/user-guide/snowflake-cortex/snowflake-intelligence Snowflake Intelligence] can connect to QPR ProcessAnalyzer as an external MCP server using Snowflake&#039;s MCP Connectors feature. To setup the connection, you can follow the instruction below or check out the Snowflake documentation (https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-mcp-connectors#custom-mcp-connectors).&lt;br /&gt;
&lt;br /&gt;
=== Setup with OAuth Dynamic Client Registration (DCR) ===&lt;br /&gt;
&lt;br /&gt;
==== Step 1: Create API Integration ====&lt;br /&gt;
Run the following SQL in Snowflake to create an API integration with full OAuth configuration:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sql&amp;quot;&amp;gt;&lt;br /&gt;
CREATE API INTEGRATION qpr_processanalyzer_mcp_api&lt;br /&gt;
  API_PROVIDER = external_mcp&lt;br /&gt;
  API_ALLOWED_PREFIXES = ( &#039;https://your-pa-server.com/&#039; )&lt;br /&gt;
  API_USER_AUTHENTICATION = (&lt;br /&gt;
     TYPE = OAUTH_DYNAMIC_CLIENT&lt;br /&gt;
     OAUTH_RESOURCE_URL = &#039;https://your-pa-server.com/builtin-oauth&#039;&lt;br /&gt;
  )&lt;br /&gt;
  ENABLED = TRUE;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 2: Create External MCP Server ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sql&amp;quot;&amp;gt;&lt;br /&gt;
CREATE EXTERNAL MCP SERVER qpr_processanalyzer_mcp&lt;br /&gt;
  WITH DISPLAY_NAME = &#039;QPR ProcessAnalyzer&#039;&lt;br /&gt;
  URL = &#039;https://your-pa-server.com/qprpa/api/mcp&#039;&lt;br /&gt;
  API_INTEGRATION = qpr_processanalyzer_mcp_api;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Step 3: Add MCP Connector to Agent ====&lt;br /&gt;
Using the Snowsight UI:&lt;br /&gt;
# Sign in to Snowsight.&lt;br /&gt;
# In the navigation menu, select &#039;&#039;&#039;AI &amp;amp; ML&#039;&#039;&#039; → &#039;&#039;&#039;Agents&#039;&#039;&#039;.&lt;br /&gt;
# Select your agent from the list.&lt;br /&gt;
# Select &#039;&#039;&#039;MCP Connectors&#039;&#039;&#039;.&lt;br /&gt;
# From the list of &#039;&#039;&#039;Available Connectors&#039;&#039;&#039;, select &#039;&#039;&#039;QPR ProcessAnalyzer&#039;&#039;&#039;.&lt;br /&gt;
# Review the connector details and select &#039;&#039;&#039;Add to agent&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Using in Snowflake Intelligence ===&lt;br /&gt;
Snowflake Intelligence users can connect to QPR ProcessAnalyzer through the Snowflake Intelligence interface:&lt;br /&gt;
&lt;br /&gt;
# Navigate to the Snowflake Intelligence interface.&lt;br /&gt;
# Open the sources panel and select &#039;&#039;&#039;Connectors&#039;&#039;&#039;.&lt;br /&gt;
# Select &#039;&#039;&#039;Connect&#039;&#039;&#039; next to the &#039;&#039;&#039;QPR ProcessAnalyzer&#039;&#039;&#039; connector.&lt;br /&gt;
# You will be redirected to the QPR ProcessAnalyzer authentication page to approve the connection.&lt;br /&gt;
# After authentication, the connector appears as &#039;&#039;&#039;Connected&#039;&#039;&#039; in the sources list. You can now interact with the agent to access process mining data from QPR ProcessAnalyzer.&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28197</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28197"/>
		<updated>2026-04-27T12:08:38Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Testing MCP tools in QPR ProcessAnalyzer */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Overview of MCP Server Functionality ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
== Configuring the MCP Server ==&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API key. Both the authentication methods can be configured at the same time to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication, set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
Some clients use the [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint: /.well-known/openid-configuration. To get this endpoint working when QPR ProcessAnalyzer is hosted in Windows in IIS, the following setup is required:&lt;br /&gt;
&lt;br /&gt;
Add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
== Creating MCP Tools ==&lt;br /&gt;
MCP tools are implemented using [[Managing_Scripts|scripts]] written in QPR ProcessAnalyzer expression language. A script will be added as the MCP tool when the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox is enabled in the [[Managing_Scripts#Script_Properties|Script Properties]]. Only system administrator users can enable the MCP tool checkbox. Also, scripts that are MCP tools, only system administrators can modify them because any changes to the MCP interface is restricted to system administrators for security reasons.&lt;br /&gt;
&lt;br /&gt;
To enable a script as an MCP Tool:&lt;br /&gt;
# Log in to QPR ProcessAnalyzer as a System Administrator.&lt;br /&gt;
# Open or create the script.&lt;br /&gt;
# For the script to be used as an MCP tool, give it a name. This name is then used as the MCP tool name. It is important to ensure that every script intended for MCP use has a non-empty, valid name, as clients may fail to discover tools with invalid names.&lt;br /&gt;
# Open the properties of the script.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;MCP&#039;&#039;&#039; tab and select the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;Description&#039;&#039;&#039; tab and provide a description. The description is added to the context of the client.&lt;br /&gt;
&lt;br /&gt;
=== MCP Tool Configuration ===&lt;br /&gt;
Every script can define its own &#039;&#039;&#039;McpTool&#039;&#039;&#039; configuration as JSON. Supported values are same as [https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool here], except for the &#039;&#039;&#039;name&#039;&#039;&#039; which is always generated automatically and can&#039;t be overridden. Defining the McpTool configuration will override any default configurations generated for scripts automatically by QPR ProcessAnalyzer. For example, specifying the value for &amp;quot;title&amp;quot; here will override the default functionality of using script&#039;s name as title of the tool.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following JSON configures the MCP tool with a customized title and description, as well as some additional [https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.Core/Protocol/ToolAnnotations.cs MCP tool annotations]:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;title&amp;quot;: &amp;quot;Example MCP tool title&amp;quot;,&lt;br /&gt;
    &amp;quot;description&amp;quot;: &amp;quot;Example MCP tool description&amp;quot;,&lt;br /&gt;
    &amp;quot;annotations&amp;quot;: {&lt;br /&gt;
        &amp;quot;destructiveHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;idempotentHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;openWorldHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;readOnlyHint&amp;quot;: false&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Defining Input Parameters ===&lt;br /&gt;
MCP tool input parameters are defined using a JSON schema following the [https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage Model Context Protocol&#039;s JSON Schema]. Each supported input parameter is listed under &#039;&#039;&#039;properties&#039;&#039;&#039;. By default, script don&#039;t enforce any schema.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following schema defines a script with five different types of parameters:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
    &amp;quot;stringParameter&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;String parameter&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
	&amp;quot;numberParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Number parameter&amp;quot;&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;booleanParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Boolean parameter&amp;quot;&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;arrayParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Array parameter&amp;quot;,&lt;br /&gt;
	  &amp;quot;nullable&amp;quot;: true,&lt;br /&gt;
	  &amp;quot;items&amp;quot;: {&lt;br /&gt;
		  &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;&lt;br /&gt;
	  }&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;objectParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Object parameter&amp;quot;,&lt;br /&gt;
	  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
	    &amp;quot;inner&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;}&lt;br /&gt;
	  }&lt;br /&gt;
	}&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
After this, the tool can be called, for example, with the following set of parameters:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;stringParameter&amp;quot;: &amp;quot;hello&amp;quot;,&lt;br /&gt;
  &amp;quot;numberParameter&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;booleanParameter&amp;quot;: true,&lt;br /&gt;
  &amp;quot;arrayParameter&amp;quot;: [ 1, 2, 3 ],&lt;br /&gt;
  &amp;quot;objectParameter&amp;quot;: { &amp;quot;inner&amp;quot;: &amp;quot;test&amp;quot; }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Defining Structured Tool Output ===&lt;br /&gt;
Structured tool output is described using a JSON schema following the [https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage Model Context Protocol&#039;s JSON Schema]. By default, scripts don&#039;t enforce any schema and the result is considered to be just text.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following example configures the script&#039;s return value to contain an object having the &amp;quot;models&amp;quot; property with information about each model:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
    &amp;quot;string&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;String value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;number&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Number value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;boolean&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Boolean value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;array&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Array value&amp;quot;,&lt;br /&gt;
      &amp;quot;nullable&amp;quot;: true,&lt;br /&gt;
      &amp;quot;items&amp;quot;: {&lt;br /&gt;
        &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;object&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Object value&amp;quot;,&lt;br /&gt;
      &amp;quot;properties&amp;quot;: {&lt;br /&gt;
        &amp;quot;inner&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;}&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== Testing MCP tools in QPR ProcessAnalyzer ==&lt;br /&gt;
When developing MCP tools, it might be useful try test them in QPR ProcessAnalyzer before publishing as MCP tools. Scripts (used as MCP tools) can be called in the [[Navigation_Menu#Expression_Designer|Expression Designer]]. The following example expression calls a script with some parameters and shows the return value as json:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let returnValue = ScriptById(1).Run(#{&lt;br /&gt;
  &amp;quot;stringParameter&amp;quot;: &amp;quot;hello&amp;quot;,&lt;br /&gt;
  &amp;quot;numberParameter&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;booleanParameter&amp;quot;: true,&lt;br /&gt;
  &amp;quot;arrayParameter&amp;quot;: [1, 2, 3]&lt;br /&gt;
  &amp;quot;objectParameter&amp;quot;: {&lt;br /&gt;
    &amp;quot;inner&amp;quot;: &amp;quot;test&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
return ToJson(returnValue);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Now, if script having id 1 has the following script source code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;string&amp;quot;: stringParameter,&lt;br /&gt;
  &amp;quot;number&amp;quot;: numberParameter,&lt;br /&gt;
  &amp;quot;boolean&amp;quot;: booleanParameter,&lt;br /&gt;
  &amp;quot;array&amp;quot;: arrayParameter,&lt;br /&gt;
  &amp;quot;object&amp;quot;: objectParameter&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;, and is configured as specified in the previous chapter, the result of expression will be the following valid JSON object as string:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;string&amp;quot;: &amp;quot;hello&amp;quot;,&lt;br /&gt;
  &amp;quot;number&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;boolean&amp;quot;: true,&lt;br /&gt;
  &amp;quot;array&amp;quot;: [1,2,3],&lt;br /&gt;
  &amp;quot;object&amp;quot;: {&amp;quot;inner&amp;quot;: &amp;quot;text&amp;quot;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Connecting as an MCP Client ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table.&lt;br /&gt;
* &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_CONFIGURATION database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. This is often automatically done by MCP Client software.&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
* Connect to Server: &lt;br /&gt;
** In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., https://your-pa-server/qprpa/api/mcp)&lt;br /&gt;
** Select Streamable HTTP as the transport type.&lt;br /&gt;
** Select &amp;quot;Via Proxy&amp;quot; as connection type.&lt;br /&gt;
* Authenticate: &lt;br /&gt;
** If using API Key-based authentication, configure &amp;quot;X-Mcp-Api-Key&amp;quot; as additional custom header with the value specified in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting of the PA server.&lt;br /&gt;
** If using OAuth, set one of the accepted audience values to Authentication/OAuth 2.0 Flow/Client ID (or leave it empty if DCR is enabled).&lt;br /&gt;
*** Open Auth Settings&lt;br /&gt;
*** Click &amp;quot;Quick OAuth Flow&amp;quot;-button.&lt;br /&gt;
**** Perform OAuth authorization using the built-in OAuth provider.&lt;br /&gt;
**** Provided that the authentication has been configured correctly, a successful authentication message should be shown.&lt;br /&gt;
** Click Connect.&lt;br /&gt;
* List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool created for a parameterless QPR ProcessAnalyzer script having id 216, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
  &amp;quot;params&amp;quot;: {&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Script-216&amp;quot;,&lt;br /&gt;
    &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
    &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
      &amp;quot;progressToken&amp;quot;: 0&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28196</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28196"/>
		<updated>2026-04-27T12:06:10Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Testing MCP tools in QPR ProcessAnalyzer */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Overview of MCP Server Functionality ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
== Configuring the MCP Server ==&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API key. Both the authentication methods can be configured at the same time to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication, set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
Some clients use the [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint: /.well-known/openid-configuration. To get this endpoint working when QPR ProcessAnalyzer is hosted in Windows in IIS, the following setup is required:&lt;br /&gt;
&lt;br /&gt;
Add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
== Creating MCP Tools ==&lt;br /&gt;
MCP tools are implemented using [[Managing_Scripts|scripts]] written in QPR ProcessAnalyzer expression language. A script will be added as the MCP tool when the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox is enabled in the [[Managing_Scripts#Script_Properties|Script Properties]]. Only system administrator users can enable the MCP tool checkbox. Also, scripts that are MCP tools, only system administrators can modify them because any changes to the MCP interface is restricted to system administrators for security reasons.&lt;br /&gt;
&lt;br /&gt;
To enable a script as an MCP Tool:&lt;br /&gt;
# Log in to QPR ProcessAnalyzer as a System Administrator.&lt;br /&gt;
# Open or create the script.&lt;br /&gt;
# For the script to be used as an MCP tool, give it a name. This name is then used as the MCP tool name. It is important to ensure that every script intended for MCP use has a non-empty, valid name, as clients may fail to discover tools with invalid names.&lt;br /&gt;
# Open the properties of the script.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;MCP&#039;&#039;&#039; tab and select the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;Description&#039;&#039;&#039; tab and provide a description. The description is added to the context of the client.&lt;br /&gt;
&lt;br /&gt;
=== MCP Tool Configuration ===&lt;br /&gt;
Every script can define its own &#039;&#039;&#039;McpTool&#039;&#039;&#039; configuration as JSON. Supported values are same as [https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool here], except for the &#039;&#039;&#039;name&#039;&#039;&#039; which is always generated automatically and can&#039;t be overridden. Defining the McpTool configuration will override any default configurations generated for scripts automatically by QPR ProcessAnalyzer. For example, specifying the value for &amp;quot;title&amp;quot; here will override the default functionality of using script&#039;s name as title of the tool.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following JSON configures the MCP tool with a customized title and description, as well as some additional [https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.Core/Protocol/ToolAnnotations.cs MCP tool annotations]:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;title&amp;quot;: &amp;quot;Example MCP tool title&amp;quot;,&lt;br /&gt;
    &amp;quot;description&amp;quot;: &amp;quot;Example MCP tool description&amp;quot;,&lt;br /&gt;
    &amp;quot;annotations&amp;quot;: {&lt;br /&gt;
        &amp;quot;destructiveHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;idempotentHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;openWorldHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;readOnlyHint&amp;quot;: false&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Defining Input Parameters ===&lt;br /&gt;
MCP tool input parameters are defined using a JSON schema following the [https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage Model Context Protocol&#039;s JSON Schema]. Each supported input parameter is listed under &#039;&#039;&#039;properties&#039;&#039;&#039;. By default, script don&#039;t enforce any schema.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following schema defines a script with five different types of parameters:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
    &amp;quot;stringParameter&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;String parameter&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
	&amp;quot;numberParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Number parameter&amp;quot;&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;booleanParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Boolean parameter&amp;quot;&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;arrayParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Array parameter&amp;quot;,&lt;br /&gt;
	  &amp;quot;nullable&amp;quot;: true,&lt;br /&gt;
	  &amp;quot;items&amp;quot;: {&lt;br /&gt;
		  &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;&lt;br /&gt;
	  }&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;objectParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Object parameter&amp;quot;,&lt;br /&gt;
	  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
	    &amp;quot;inner&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;}&lt;br /&gt;
	  }&lt;br /&gt;
	}&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
After this, the tool can be called, for example, with the following set of parameters:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;stringParameter&amp;quot;: &amp;quot;hello&amp;quot;,&lt;br /&gt;
  &amp;quot;numberParameter&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;booleanParameter&amp;quot;: true,&lt;br /&gt;
  &amp;quot;arrayParameter&amp;quot;: [ 1, 2, 3 ],&lt;br /&gt;
  &amp;quot;objectParameter&amp;quot;: { &amp;quot;inner&amp;quot;: &amp;quot;test&amp;quot; }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Defining Structured Tool Output ===&lt;br /&gt;
Structured tool output is described using a JSON schema following the [https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage Model Context Protocol&#039;s JSON Schema]. By default, scripts don&#039;t enforce any schema and the result is considered to be just text.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following example configures the script&#039;s return value to contain an object having the &amp;quot;models&amp;quot; property with information about each model:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
    &amp;quot;string&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;String value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;number&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Number value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;boolean&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Boolean value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;array&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Array value&amp;quot;,&lt;br /&gt;
      &amp;quot;nullable&amp;quot;: true,&lt;br /&gt;
      &amp;quot;items&amp;quot;: {&lt;br /&gt;
        &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;object&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Object value&amp;quot;,&lt;br /&gt;
      &amp;quot;properties&amp;quot;: {&lt;br /&gt;
        &amp;quot;inner&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;}&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== Testing MCP tools in QPR ProcessAnalyzer ==&lt;br /&gt;
When developing MCP tools, it might be useful try test them in QPR ProcessAnalyzer before publishing as MCP tools. Scripts (used as MCP tools) can be called in the [[Navigation_Menu#Expression_Designer|Expression Designer]]. The following example expression calls a script with some parameters and shows the return value as json:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let returnValue = ScriptById(1).Run(#{&lt;br /&gt;
  &amp;quot;stringParameter&amp;quot;: &amp;quot;hello&amp;quot;,&lt;br /&gt;
  &amp;quot;numberParameter&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;booleanParameter&amp;quot;: true,&lt;br /&gt;
  &amp;quot;arrayParameter&amp;quot;: [1, 2, 3]&lt;br /&gt;
  &amp;quot;objectParameter&amp;quot;: {&lt;br /&gt;
    &amp;quot;inner&amp;quot;: &amp;quot;test&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
return ToJson(returnValue);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Now, if script having id 1 has the following script source code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;string&amp;quot;: stringParameter,&lt;br /&gt;
  &amp;quot;number&amp;quot;: numberParameter,&lt;br /&gt;
  &amp;quot;boolean&amp;quot;: booleanParameter,&lt;br /&gt;
  &amp;quot;array&amp;quot;: arrayParameter,&lt;br /&gt;
  &amp;quot;object&amp;quot;: objectParameter&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;, and is configured as specified in the previous chapter, the result of the script/tool call will be the following valid JSON object:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;string&amp;quot;: &amp;quot;hello&amp;quot;,&lt;br /&gt;
  &amp;quot;number&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;boolean&amp;quot;: true,&lt;br /&gt;
  &amp;quot;array&amp;quot;: [1,2,3],&lt;br /&gt;
  &amp;quot;object&amp;quot;: {&amp;quot;inner&amp;quot;: &amp;quot;text&amp;quot;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Connecting as an MCP Client ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table.&lt;br /&gt;
* &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_CONFIGURATION database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. This is often automatically done by MCP Client software.&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
* Connect to Server: &lt;br /&gt;
** In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., https://your-pa-server/qprpa/api/mcp)&lt;br /&gt;
** Select Streamable HTTP as the transport type.&lt;br /&gt;
** Select &amp;quot;Via Proxy&amp;quot; as connection type.&lt;br /&gt;
* Authenticate: &lt;br /&gt;
** If using API Key-based authentication, configure &amp;quot;X-Mcp-Api-Key&amp;quot; as additional custom header with the value specified in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting of the PA server.&lt;br /&gt;
** If using OAuth, set one of the accepted audience values to Authentication/OAuth 2.0 Flow/Client ID (or leave it empty if DCR is enabled).&lt;br /&gt;
*** Open Auth Settings&lt;br /&gt;
*** Click &amp;quot;Quick OAuth Flow&amp;quot;-button.&lt;br /&gt;
**** Perform OAuth authorization using the built-in OAuth provider.&lt;br /&gt;
**** Provided that the authentication has been configured correctly, a successful authentication message should be shown.&lt;br /&gt;
** Click Connect.&lt;br /&gt;
* List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool created for a parameterless QPR ProcessAnalyzer script having id 216, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
  &amp;quot;params&amp;quot;: {&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Script-216&amp;quot;,&lt;br /&gt;
    &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
    &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
      &amp;quot;progressToken&amp;quot;: 0&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28195</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28195"/>
		<updated>2026-04-27T12:01:56Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* MCP Tool Configuration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Overview of MCP Server Functionality ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
== Configuring the MCP Server ==&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API key. Both the authentication methods can be configured at the same time to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication, set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
Some clients use the [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint: /.well-known/openid-configuration. To get this endpoint working when QPR ProcessAnalyzer is hosted in Windows in IIS, the following setup is required:&lt;br /&gt;
&lt;br /&gt;
Add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
== Creating MCP Tools ==&lt;br /&gt;
MCP tools are implemented using [[Managing_Scripts|scripts]] written in QPR ProcessAnalyzer expression language. A script will be added as the MCP tool when the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox is enabled in the [[Managing_Scripts#Script_Properties|Script Properties]]. Only system administrator users can enable the MCP tool checkbox. Also, scripts that are MCP tools, only system administrators can modify them because any changes to the MCP interface is restricted to system administrators for security reasons.&lt;br /&gt;
&lt;br /&gt;
To enable a script as an MCP Tool:&lt;br /&gt;
# Log in to QPR ProcessAnalyzer as a System Administrator.&lt;br /&gt;
# Open or create the script.&lt;br /&gt;
# For the script to be used as an MCP tool, give it a name. This name is then used as the MCP tool name. It is important to ensure that every script intended for MCP use has a non-empty, valid name, as clients may fail to discover tools with invalid names.&lt;br /&gt;
# Open the properties of the script.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;MCP&#039;&#039;&#039; tab and select the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;Description&#039;&#039;&#039; tab and provide a description. The description is added to the context of the client.&lt;br /&gt;
&lt;br /&gt;
=== MCP Tool Configuration ===&lt;br /&gt;
Every script can define its own &#039;&#039;&#039;McpTool&#039;&#039;&#039; configuration as JSON. Supported values are same as [https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool here], except for the &#039;&#039;&#039;name&#039;&#039;&#039; which is always generated automatically and can&#039;t be overridden. Defining the McpTool configuration will override any default configurations generated for scripts automatically by QPR ProcessAnalyzer. For example, specifying the value for &amp;quot;title&amp;quot; here will override the default functionality of using script&#039;s name as title of the tool.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following JSON configures the MCP tool with a customized title and description, as well as some additional [https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.Core/Protocol/ToolAnnotations.cs MCP tool annotations]:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;title&amp;quot;: &amp;quot;Example MCP tool title&amp;quot;,&lt;br /&gt;
    &amp;quot;description&amp;quot;: &amp;quot;Example MCP tool description&amp;quot;,&lt;br /&gt;
    &amp;quot;annotations&amp;quot;: {&lt;br /&gt;
        &amp;quot;destructiveHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;idempotentHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;openWorldHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;readOnlyHint&amp;quot;: false&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Defining Input Parameters ===&lt;br /&gt;
MCP tool input parameters are defined using a JSON schema following the [https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage Model Context Protocol&#039;s JSON Schema]. Each supported input parameter is listed under &#039;&#039;&#039;properties&#039;&#039;&#039;. By default, script don&#039;t enforce any schema.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following schema defines a script with five different types of parameters:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
    &amp;quot;stringParameter&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;String parameter&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
	&amp;quot;numberParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Number parameter&amp;quot;&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;booleanParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Boolean parameter&amp;quot;&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;arrayParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Array parameter&amp;quot;,&lt;br /&gt;
	  &amp;quot;nullable&amp;quot;: true,&lt;br /&gt;
	  &amp;quot;items&amp;quot;: {&lt;br /&gt;
		  &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;&lt;br /&gt;
	  }&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;objectParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Object parameter&amp;quot;,&lt;br /&gt;
	  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
	    &amp;quot;inner&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;}&lt;br /&gt;
	  }&lt;br /&gt;
	}&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
After this, the tool can be called, for example, with the following set of parameters:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;stringParameter&amp;quot;: &amp;quot;hello&amp;quot;,&lt;br /&gt;
  &amp;quot;numberParameter&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;booleanParameter&amp;quot;: true,&lt;br /&gt;
  &amp;quot;arrayParameter&amp;quot;: [ 1, 2, 3 ],&lt;br /&gt;
  &amp;quot;objectParameter&amp;quot;: { &amp;quot;inner&amp;quot;: &amp;quot;test&amp;quot; }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Defining Structured Tool Output ===&lt;br /&gt;
Structured tool output is described using a JSON schema following the [https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage Model Context Protocol&#039;s JSON Schema]. By default, scripts don&#039;t enforce any schema and the result is considered to be just text.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following example configures the script&#039;s return value to contain an object having the &amp;quot;models&amp;quot; property with information about each model:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
    &amp;quot;string&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;String value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;number&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Number value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;boolean&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Boolean value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;array&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Array value&amp;quot;,&lt;br /&gt;
      &amp;quot;nullable&amp;quot;: true,&lt;br /&gt;
      &amp;quot;items&amp;quot;: {&lt;br /&gt;
        &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;object&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Object value&amp;quot;,&lt;br /&gt;
      &amp;quot;properties&amp;quot;: {&lt;br /&gt;
        &amp;quot;inner&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;}&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Now, if script has the following script source code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;string&amp;quot;: stringParameter,&lt;br /&gt;
  &amp;quot;number&amp;quot;: numberParameter,&lt;br /&gt;
  &amp;quot;boolean&amp;quot;: booleanParameter,&lt;br /&gt;
  &amp;quot;array&amp;quot;: arrayParameter,&lt;br /&gt;
  &amp;quot;object&amp;quot;: objectParameter&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;, and if this script is called with parameters shown in the previous chapter, the result of the tool call will be the following valid JSON object:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;string&amp;quot;: &amp;quot;hello&amp;quot;,&lt;br /&gt;
  &amp;quot;number&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;boolean&amp;quot;: true,&lt;br /&gt;
  &amp;quot;array&amp;quot;: [1,2,3],&lt;br /&gt;
  &amp;quot;object&amp;quot;: {&amp;quot;inner&amp;quot;: &amp;quot;text&amp;quot;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing MCP tools in QPR ProcessAnalyzer ==&lt;br /&gt;
When developing MCP tools, it might be useful try test them in QPR ProcessAnalyzer before publishing as MCP tools. Scripts (used as MCP tools) can be called in the [[Navigation_Menu#Expression_Designer|Expression Designer]]. The following example expression calls a script with some parameters and shows the return value as json:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let returnValue = ScriptById(1).Run(#{&lt;br /&gt;
  &amp;quot;stringParameter&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;numberParameter&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;booleanParameter&amp;quot;: true,&lt;br /&gt;
  &amp;quot;arrayParameter&amp;quot;: [1, 2, 3]&lt;br /&gt;
  &amp;quot;objectParameter&amp;quot;: {&lt;br /&gt;
    &amp;quot;inner&amp;quot;: &amp;quot;value&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
return ToJson(returnValue);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Connecting as an MCP Client ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table.&lt;br /&gt;
* &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_CONFIGURATION database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. This is often automatically done by MCP Client software.&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
* Connect to Server: &lt;br /&gt;
** In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., https://your-pa-server/qprpa/api/mcp)&lt;br /&gt;
** Select Streamable HTTP as the transport type.&lt;br /&gt;
** Select &amp;quot;Via Proxy&amp;quot; as connection type.&lt;br /&gt;
* Authenticate: &lt;br /&gt;
** If using API Key-based authentication, configure &amp;quot;X-Mcp-Api-Key&amp;quot; as additional custom header with the value specified in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting of the PA server.&lt;br /&gt;
** If using OAuth, set one of the accepted audience values to Authentication/OAuth 2.0 Flow/Client ID (or leave it empty if DCR is enabled).&lt;br /&gt;
*** Open Auth Settings&lt;br /&gt;
*** Click &amp;quot;Quick OAuth Flow&amp;quot;-button.&lt;br /&gt;
**** Perform OAuth authorization using the built-in OAuth provider.&lt;br /&gt;
**** Provided that the authentication has been configured correctly, a successful authentication message should be shown.&lt;br /&gt;
** Click Connect.&lt;br /&gt;
* List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool created for a parameterless QPR ProcessAnalyzer script having id 216, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
  &amp;quot;params&amp;quot;: {&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Script-216&amp;quot;,&lt;br /&gt;
    &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
    &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
      &amp;quot;progressToken&amp;quot;: 0&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28194</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28194"/>
		<updated>2026-04-27T12:00:39Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Testing MCP tools in QPR ProcessAnalyzer */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Overview of MCP Server Functionality ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
== Configuring the MCP Server ==&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API key. Both the authentication methods can be configured at the same time to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication, set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
Some clients use the [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint: /.well-known/openid-configuration. To get this endpoint working when QPR ProcessAnalyzer is hosted in Windows in IIS, the following setup is required:&lt;br /&gt;
&lt;br /&gt;
Add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
== Creating MCP Tools ==&lt;br /&gt;
MCP tools are implemented using [[Managing_Scripts|scripts]] written in QPR ProcessAnalyzer expression language. A script will be added as the MCP tool when the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox is enabled in the [[Managing_Scripts#Script_Properties|Script Properties]]. Only system administrator users can enable the MCP tool checkbox. Also, scripts that are MCP tools, only system administrators can modify them because any changes to the MCP interface is restricted to system administrators for security reasons.&lt;br /&gt;
&lt;br /&gt;
To enable a script as an MCP Tool:&lt;br /&gt;
# Log in to QPR ProcessAnalyzer as a System Administrator.&lt;br /&gt;
# Open or create the script.&lt;br /&gt;
# For the script to be used as an MCP tool, give it a name. This name is then used as the MCP tool name. It is important to ensure that every script intended for MCP use has a non-empty, valid name, as clients may fail to discover tools with invalid names.&lt;br /&gt;
# Open the properties of the script.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;MCP&#039;&#039;&#039; tab and select the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;Description&#039;&#039;&#039; tab and provide a description. The description is added to the context of the client.&lt;br /&gt;
&lt;br /&gt;
=== MCP Tool Configuration ===&lt;br /&gt;
Every script can define its own &#039;&#039;&#039;McpTool&#039;&#039;&#039; configuration as JSON. Supported values are same as [https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool here], except for the &#039;&#039;&#039;name&#039;&#039;&#039; which is always generated automatically and can&#039;t be overridden. Defining the McpTool configuration will override any default configurations generated for scripts automatically by QPR ProcessAnalyzer. For example, specifying the value for &amp;quot;title&amp;quot; here will override the default functionality of using script&#039;s name as title of the tool.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following JSON configures the MCP tool with a customized title and description, as well as some additional [https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.Core/Protocol/ToolAnnotations.cs MCP tool annotations]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;title&amp;quot;: &amp;quot;Example MCP tool title&amp;quot;,&lt;br /&gt;
    &amp;quot;description&amp;quot;: &amp;quot;Example MCP tool description&amp;quot;,&lt;br /&gt;
    &amp;quot;annotations&amp;quot;: {&lt;br /&gt;
        &amp;quot;destructiveHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;idempotentHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;openWorldHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;readOnlyHint&amp;quot;: false&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Defining Input Parameters ===&lt;br /&gt;
MCP tool input parameters are defined using a JSON schema following the [https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage Model Context Protocol&#039;s JSON Schema]. Each supported input parameter is listed under &#039;&#039;&#039;properties&#039;&#039;&#039;. By default, script don&#039;t enforce any schema.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following schema defines a script with five different types of parameters:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
    &amp;quot;stringParameter&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;String parameter&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
	&amp;quot;numberParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Number parameter&amp;quot;&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;booleanParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Boolean parameter&amp;quot;&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;arrayParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Array parameter&amp;quot;,&lt;br /&gt;
	  &amp;quot;nullable&amp;quot;: true,&lt;br /&gt;
	  &amp;quot;items&amp;quot;: {&lt;br /&gt;
		  &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;&lt;br /&gt;
	  }&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;objectParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Object parameter&amp;quot;,&lt;br /&gt;
	  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
	    &amp;quot;inner&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;}&lt;br /&gt;
	  }&lt;br /&gt;
	}&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
After this, the tool can be called, for example, with the following set of parameters:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;stringParameter&amp;quot;: &amp;quot;hello&amp;quot;,&lt;br /&gt;
  &amp;quot;numberParameter&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;booleanParameter&amp;quot;: true,&lt;br /&gt;
  &amp;quot;arrayParameter&amp;quot;: [ 1, 2, 3 ],&lt;br /&gt;
  &amp;quot;objectParameter&amp;quot;: { &amp;quot;inner&amp;quot;: &amp;quot;test&amp;quot; }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Defining Structured Tool Output ===&lt;br /&gt;
Structured tool output is described using a JSON schema following the [https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage Model Context Protocol&#039;s JSON Schema]. By default, scripts don&#039;t enforce any schema and the result is considered to be just text.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following example configures the script&#039;s return value to contain an object having the &amp;quot;models&amp;quot; property with information about each model:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
    &amp;quot;string&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;String value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;number&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Number value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;boolean&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Boolean value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;array&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Array value&amp;quot;,&lt;br /&gt;
      &amp;quot;nullable&amp;quot;: true,&lt;br /&gt;
      &amp;quot;items&amp;quot;: {&lt;br /&gt;
        &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;object&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Object value&amp;quot;,&lt;br /&gt;
      &amp;quot;properties&amp;quot;: {&lt;br /&gt;
        &amp;quot;inner&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;}&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Now, if script has the following script source code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;string&amp;quot;: stringParameter,&lt;br /&gt;
  &amp;quot;number&amp;quot;: numberParameter,&lt;br /&gt;
  &amp;quot;boolean&amp;quot;: booleanParameter,&lt;br /&gt;
  &amp;quot;array&amp;quot;: arrayParameter,&lt;br /&gt;
  &amp;quot;object&amp;quot;: objectParameter&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;, and if this script is called with parameters shown in the previous chapter, the result of the tool call will be the following valid JSON object:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;string&amp;quot;: &amp;quot;hello&amp;quot;,&lt;br /&gt;
  &amp;quot;number&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;boolean&amp;quot;: true,&lt;br /&gt;
  &amp;quot;array&amp;quot;: [1,2,3],&lt;br /&gt;
  &amp;quot;object&amp;quot;: {&amp;quot;inner&amp;quot;: &amp;quot;text&amp;quot;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing MCP tools in QPR ProcessAnalyzer ==&lt;br /&gt;
When developing MCP tools, it might be useful try test them in QPR ProcessAnalyzer before publishing as MCP tools. Scripts (used as MCP tools) can be called in the [[Navigation_Menu#Expression_Designer|Expression Designer]]. The following example expression calls a script with some parameters and shows the return value as json:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let returnValue = ScriptById(1).Run(#{&lt;br /&gt;
  &amp;quot;stringParameter&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;numberParameter&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;booleanParameter&amp;quot;: true,&lt;br /&gt;
  &amp;quot;arrayParameter&amp;quot;: [1, 2, 3]&lt;br /&gt;
  &amp;quot;objectParameter&amp;quot;: {&lt;br /&gt;
    &amp;quot;inner&amp;quot;: &amp;quot;value&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
return ToJson(returnValue);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Connecting as an MCP Client ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table.&lt;br /&gt;
* &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_CONFIGURATION database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. This is often automatically done by MCP Client software.&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
* Connect to Server: &lt;br /&gt;
** In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., https://your-pa-server/qprpa/api/mcp)&lt;br /&gt;
** Select Streamable HTTP as the transport type.&lt;br /&gt;
** Select &amp;quot;Via Proxy&amp;quot; as connection type.&lt;br /&gt;
* Authenticate: &lt;br /&gt;
** If using API Key-based authentication, configure &amp;quot;X-Mcp-Api-Key&amp;quot; as additional custom header with the value specified in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting of the PA server.&lt;br /&gt;
** If using OAuth, set one of the accepted audience values to Authentication/OAuth 2.0 Flow/Client ID (or leave it empty if DCR is enabled).&lt;br /&gt;
*** Open Auth Settings&lt;br /&gt;
*** Click &amp;quot;Quick OAuth Flow&amp;quot;-button.&lt;br /&gt;
**** Perform OAuth authorization using the built-in OAuth provider.&lt;br /&gt;
**** Provided that the authentication has been configured correctly, a successful authentication message should be shown.&lt;br /&gt;
** Click Connect.&lt;br /&gt;
* List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool created for a parameterless QPR ProcessAnalyzer script having id 216, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
  &amp;quot;params&amp;quot;: {&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Script-216&amp;quot;,&lt;br /&gt;
    &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
    &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
      &amp;quot;progressToken&amp;quot;: 0&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28193</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28193"/>
		<updated>2026-04-27T11:49:04Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Defining Structured Tool Output */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Overview of MCP Server Functionality ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
== Configuring the MCP Server ==&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API key. Both the authentication methods can be configured at the same time to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication, set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
Some clients use the [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint: /.well-known/openid-configuration. To get this endpoint working when QPR ProcessAnalyzer is hosted in Windows in IIS, the following setup is required:&lt;br /&gt;
&lt;br /&gt;
Add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
== Creating MCP Tools ==&lt;br /&gt;
MCP tools are implemented using [[Managing_Scripts|scripts]] written in QPR ProcessAnalyzer expression language. A script will be added as the MCP tool when the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox is enabled in the [[Managing_Scripts#Script_Properties|Script Properties]]. Only system administrator users can enable the MCP tool checkbox. Also, scripts that are MCP tools, only system administrators can modify them because any changes to the MCP interface is restricted to system administrators for security reasons.&lt;br /&gt;
&lt;br /&gt;
To enable a script as an MCP Tool:&lt;br /&gt;
# Log in to QPR ProcessAnalyzer as a System Administrator.&lt;br /&gt;
# Open or create the script.&lt;br /&gt;
# For the script to be used as an MCP tool, give it a name. This name is then used as the MCP tool name. It is important to ensure that every script intended for MCP use has a non-empty, valid name, as clients may fail to discover tools with invalid names.&lt;br /&gt;
# Open the properties of the script.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;MCP&#039;&#039;&#039; tab and select the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;Description&#039;&#039;&#039; tab and provide a description. The description is added to the context of the client.&lt;br /&gt;
&lt;br /&gt;
=== MCP Tool Configuration ===&lt;br /&gt;
Every script can define its own &#039;&#039;&#039;McpTool&#039;&#039;&#039; configuration as JSON. Supported values are same as [https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool here], except for the &#039;&#039;&#039;name&#039;&#039;&#039; which is always generated automatically and can&#039;t be overridden. Defining the McpTool configuration will override any default configurations generated for scripts automatically by QPR ProcessAnalyzer. For example, specifying the value for &amp;quot;title&amp;quot; here will override the default functionality of using script&#039;s name as title of the tool.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following JSON configures the MCP tool with a customized title and description, as well as some additional [https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.Core/Protocol/ToolAnnotations.cs MCP tool annotations]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;title&amp;quot;: &amp;quot;Example MCP tool title&amp;quot;,&lt;br /&gt;
    &amp;quot;description&amp;quot;: &amp;quot;Example MCP tool description&amp;quot;,&lt;br /&gt;
    &amp;quot;annotations&amp;quot;: {&lt;br /&gt;
        &amp;quot;destructiveHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;idempotentHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;openWorldHint&amp;quot;: true,&lt;br /&gt;
        &amp;quot;readOnlyHint&amp;quot;: false&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Defining Input Parameters ===&lt;br /&gt;
MCP tool input parameters are defined using a JSON schema following the [https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage Model Context Protocol&#039;s JSON Schema]. Each supported input parameter is listed under &#039;&#039;&#039;properties&#039;&#039;&#039;. By default, script don&#039;t enforce any schema.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following schema defines a script with five different types of parameters:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
    &amp;quot;stringParameter&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;String parameter&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
	&amp;quot;numberParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Number parameter&amp;quot;&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;booleanParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Boolean parameter&amp;quot;&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;arrayParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Array parameter&amp;quot;,&lt;br /&gt;
	  &amp;quot;nullable&amp;quot;: true,&lt;br /&gt;
	  &amp;quot;items&amp;quot;: {&lt;br /&gt;
		  &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;&lt;br /&gt;
	  }&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;objectParameter&amp;quot;: {&lt;br /&gt;
	  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
	  &amp;quot;description&amp;quot;: &amp;quot;Object parameter&amp;quot;,&lt;br /&gt;
	  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
	    &amp;quot;inner&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;}&lt;br /&gt;
	  }&lt;br /&gt;
	}&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
After this, the tool can be called, for example, with the following set of parameters:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;stringParameter&amp;quot;: &amp;quot;bar&amp;quot;,&lt;br /&gt;
  &amp;quot;numberParameter&amp;quot;: 123.456,&lt;br /&gt;
  &amp;quot;booleanParameter&amp;quot;: true,&lt;br /&gt;
  &amp;quot;arrayParameter&amp;quot;: [ 1, 2, 3 ],&lt;br /&gt;
  &amp;quot;objectParameter&amp;quot;: { &amp;quot;inner&amp;quot;: &amp;quot;test&amp;quot; }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Defining Structured Tool Output ===&lt;br /&gt;
Structured tool output is described using a JSON schema following the [https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage Model Context Protocol&#039;s JSON Schema]. By default, scripts don&#039;t enforce any schema and the result is considered to be just text.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Example&#039;&#039;&#039;: The following example configures the script&#039;s return value to contain an object having the &amp;quot;models&amp;quot; property with information about each model:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
  &amp;quot;properties&amp;quot;: {&lt;br /&gt;
    &amp;quot;string&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;String value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;number&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;number&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Number value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;boolean&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;boolean&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Boolean value&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;array&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;array&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Array value&amp;quot;,&lt;br /&gt;
      &amp;quot;nullable&amp;quot;: true,&lt;br /&gt;
      &amp;quot;items&amp;quot;: {&lt;br /&gt;
        &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;object&amp;quot;: {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&lt;br /&gt;
      &amp;quot;description&amp;quot;: &amp;quot;Object value&amp;quot;,&lt;br /&gt;
      &amp;quot;properties&amp;quot;: {&lt;br /&gt;
        &amp;quot;inner&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;}&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing MCP tools in QPR ProcessAnalyzer ==&lt;br /&gt;
When developing MCP tools, it might be useful try test them in QPR ProcessAnalyzer before publishing as MCP tools. Scripts (used as MCP tools) can be called in the [[Navigation_Menu#Expression_Designer|Expression Designer]]. The following example expression calls a script with some parameters and shows the return value as json:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let returnValue = ScriptById(1).Run(#{&lt;br /&gt;
  &amp;quot;string_param&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;integer_param&amp;quot;: 123,&lt;br /&gt;
  &amp;quot;boolean_param&amp;quot;: false,&lt;br /&gt;
  &amp;quot;array_param&amp;quot;: [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;]&lt;br /&gt;
});&lt;br /&gt;
return ToJson(returnValue);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Connecting as an MCP Client ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table.&lt;br /&gt;
* &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_CONFIGURATION database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. This is often automatically done by MCP Client software.&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
* Connect to Server: &lt;br /&gt;
** In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., https://your-pa-server/qprpa/api/mcp)&lt;br /&gt;
** Select Streamable HTTP as the transport type.&lt;br /&gt;
** Select &amp;quot;Via Proxy&amp;quot; as connection type.&lt;br /&gt;
* Authenticate: &lt;br /&gt;
** If using API Key-based authentication, configure &amp;quot;X-Mcp-Api-Key&amp;quot; as additional custom header with the value specified in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting of the PA server.&lt;br /&gt;
** If using OAuth, set one of the accepted audience values to Authentication/OAuth 2.0 Flow/Client ID (or leave it empty if DCR is enabled).&lt;br /&gt;
*** Open Auth Settings&lt;br /&gt;
*** Click &amp;quot;Quick OAuth Flow&amp;quot;-button.&lt;br /&gt;
**** Perform OAuth authorization using the built-in OAuth provider.&lt;br /&gt;
**** Provided that the authentication has been configured correctly, a successful authentication message should be shown.&lt;br /&gt;
** Click Connect.&lt;br /&gt;
* List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool created for a parameterless QPR ProcessAnalyzer script having id 216, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
  &amp;quot;params&amp;quot;: {&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Script-216&amp;quot;,&lt;br /&gt;
    &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
    &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
      &amp;quot;progressToken&amp;quot;: 0&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28082</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28082"/>
		<updated>2026-04-09T14:21:36Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Example: Using MCP Inspector */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Overview of MCP Server Functionality ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
== Configuring the MCP Server ==&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API Keys. It&#039;s also possible to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table as &#039;&#039;&#039;null&#039;&#039;&#039;.&lt;br /&gt;
* Set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
When QPR ProcessAnalyzer is hosted in IIS, some clients (for example [https://modelcontextprotocol.io/docs/tools/inspector MCP Inspector]) may expect [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint:&amp;lt;br&amp;gt;&lt;br /&gt;
/.well-known/openid-configuration&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To make this work, add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&amp;lt;br&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
=== Oauth 2.0 and API Key Authentication ===&lt;br /&gt;
This method combines both per-user authentication and static API key authentication. To use both OAuth 2.0 and API key Authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client using API key authentication must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
* Set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_Configuration database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
== Implementing MCP Tools with Scripts ==&lt;br /&gt;
Any QPR ProcessAnalyzer script can be turned into an MCP tool. This is controlled in the [[Managing_Scripts#Script_Properties|Script Properties]].&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To enable a script as an MCP Tool:&lt;br /&gt;
# Log in to QPR ProcessAnalyzer as a System Administrator.&lt;br /&gt;
# For the script to be used as an MCP tool, give it a name. This name is then used as the MCP tool name. It is important to ensure that every script intended for MCP use has a non-empty, valid name, as clients may fail to discover tools with invalid names.&lt;br /&gt;
# Open the properties of the script.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;MCP&#039;&#039;&#039; tab and select the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;Description&#039;&#039;&#039; tab and provide a description. The description is added to the context of the client.&lt;br /&gt;
&lt;br /&gt;
== Connecting as an MCP Client ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table.&lt;br /&gt;
* &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_CONFIGURATION database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. This is often automatically done by MCP Client software.&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
* Connect to Server: &lt;br /&gt;
** In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., https://your-pa-server/qprpa/api/mcp)&lt;br /&gt;
** Select Streamable HTTP as the transport type.&lt;br /&gt;
** Select &amp;quot;Via Proxy&amp;quot; as connection type.&lt;br /&gt;
* Authenticate: &lt;br /&gt;
** If using API Key-based authentication, configure &amp;quot;X-Mcp-Api-Key&amp;quot; as additional custom header with the value specified in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting of the PA server.&lt;br /&gt;
** If using OAuth, set one of the accepted audience values to Authentication/OAuth 2.0 Flow/Client ID (or leave it empty if DCR is enabled).&lt;br /&gt;
*** Open Auth Settings&lt;br /&gt;
*** Click &amp;quot;Quick OAuth Flow&amp;quot;-button.&lt;br /&gt;
**** Perform OAuth authorization using the built-in OAuth provider.&lt;br /&gt;
**** Provided that the authentication has been configured correctly, a successful authentication message should be shown.&lt;br /&gt;
** Click Connect.&lt;br /&gt;
* List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool created for a parameterless QPR ProcessAnalyzer script having id 216, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
  &amp;quot;params&amp;quot;: {&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Script-216&amp;quot;,&lt;br /&gt;
    &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
    &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
      &amp;quot;progressToken&amp;quot;: 0&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category: QPR ProcessAnalyzer]]&lt;br /&gt;
[[Category: MCP]]&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28081</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28081"/>
		<updated>2026-04-09T14:05:35Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Connecting as an MCP Client */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Overview of MCP Server Functionality ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
== Configuring the MCP Server ==&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API Keys. It&#039;s also possible to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table as &#039;&#039;&#039;null&#039;&#039;&#039;.&lt;br /&gt;
* Set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
When QPR ProcessAnalyzer is hosted in IIS, some clients (for example [https://modelcontextprotocol.io/docs/tools/inspector MCP Inspector]) may expect [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint:&amp;lt;br&amp;gt;&lt;br /&gt;
/.well-known/openid-configuration&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To make this work, add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&amp;lt;br&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
=== Oauth 2.0 and API Key Authentication ===&lt;br /&gt;
This method combines both per-user authentication and static API key authentication. To use both OAuth 2.0 and API key Authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client using API key authentication must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
* Set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_Configuration database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
== Implementing MCP Tools with Scripts ==&lt;br /&gt;
Any QPR ProcessAnalyzer script can be turned into an MCP tool. This is controlled in the [[Managing_Scripts#Script_Properties|Script Properties]].&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To enable a script as an MCP Tool:&lt;br /&gt;
# Log in to QPR ProcessAnalyzer as a System Administrator.&lt;br /&gt;
# For the script to be used as an MCP tool, give it a name. This name is then used as the MCP tool name. It is important to ensure that every script intended for MCP use has a non-empty, valid name, as clients may fail to discover tools with invalid names.&lt;br /&gt;
# Open the properties of the script.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;MCP&#039;&#039;&#039; tab and select the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;Description&#039;&#039;&#039; tab and provide a description. The description is added to the context of the client.&lt;br /&gt;
&lt;br /&gt;
== Connecting as an MCP Client ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table.&lt;br /&gt;
* &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_CONFIGURATION database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. This is often automatically done by MCP Client software.&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
* Connect to Server: &lt;br /&gt;
** In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., https://your-pa-server/qprpa/api/mcp)&lt;br /&gt;
** Select Streamable HTTP as the transport type.&lt;br /&gt;
** Select &amp;quot;Via Proxy&amp;quot; as connection type.&lt;br /&gt;
* Authenticate: &lt;br /&gt;
** If using API Key-based authentication, configure &amp;quot;X-Mcp-Api-Key&amp;quot; as additional custom header with the value specified in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting of the PA server.&lt;br /&gt;
** If using OAuth, set one of the accepted audience values to Authentication/OAuth 2.0 Flow/Client ID (or leave it empty if DCR is enabled).&lt;br /&gt;
*** Open Auth Settings&lt;br /&gt;
*** Click &amp;quot;Quick OAuth Flow&amp;quot;-button.&lt;br /&gt;
**** If authentication has been configured correctly, a successful authentication message should be shown.&lt;br /&gt;
** Click Connect.&lt;br /&gt;
* List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool created for a parameterless QPR ProcessAnalyzer script having id 216, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
  &amp;quot;params&amp;quot;: {&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Script-216&amp;quot;,&lt;br /&gt;
    &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
    &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
      &amp;quot;progressToken&amp;quot;: 0&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category: QPR ProcessAnalyzer]]&lt;br /&gt;
[[Category: MCP]]&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28080</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28080"/>
		<updated>2026-04-09T13:39:34Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Connecting as an MCP Client */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Overview of MCP Server Functionality ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
== Configuring the MCP Server ==&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API Keys. It&#039;s also possible to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table as &#039;&#039;&#039;null&#039;&#039;&#039;.&lt;br /&gt;
* Set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
When QPR ProcessAnalyzer is hosted in IIS, some clients (for example [https://modelcontextprotocol.io/docs/tools/inspector MCP Inspector]) may expect [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint:&amp;lt;br&amp;gt;&lt;br /&gt;
/.well-known/openid-configuration&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To make this work, add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&amp;lt;br&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
=== Oauth 2.0 and API Key Authentication ===&lt;br /&gt;
This method combines both per-user authentication and static API key authentication. To use both OAuth 2.0 and API key Authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client using API key authentication must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
* Set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_Configuration database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
== Implementing MCP Tools with Scripts ==&lt;br /&gt;
Any QPR ProcessAnalyzer script can be turned into an MCP tool. This is controlled in the [[Managing_Scripts#Script_Properties|Script Properties]].&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To enable a script as an MCP Tool:&lt;br /&gt;
# Log in to QPR ProcessAnalyzer as a System Administrator.&lt;br /&gt;
# For the script to be used as an MCP tool, give it a name. This name is then used as the MCP tool name. It is important to ensure that every script intended for MCP use has a non-empty, valid name, as clients may fail to discover tools with invalid names.&lt;br /&gt;
# Open the properties of the script.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;MCP&#039;&#039;&#039; tab and select the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;Description&#039;&#039;&#039; tab and provide a description. The description is added to the context of the client.&lt;br /&gt;
&lt;br /&gt;
== Connecting as an MCP Client ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table.&lt;br /&gt;
* &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_Configuration database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. This is often automatically done by MCP Client software.&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
Connect to Server: In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., http://your-pa-server/qprpa/api/mcp) and select streamable-http as the transport type.&amp;lt;br&amp;gt;&lt;br /&gt;
Authenticate: Configure the necessary authentication header (e.g., X-Mcp-Api-Key).&amp;lt;br&amp;gt;&lt;br /&gt;
List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool named GetCurrentTime, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
    &amp;quot;params&amp;quot;: {&lt;br /&gt;
        &amp;quot;name&amp;quot;: &amp;quot;GetCurrentTime&amp;quot;,&lt;br /&gt;
        &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
        &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
            &amp;quot;progressToken&amp;quot;: 2&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;jsonrpc&amp;quot;: &amp;quot;2.0&amp;quot;,&lt;br /&gt;
    &amp;quot;id&amp;quot;: 2&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category: QPR ProcessAnalyzer]]&lt;br /&gt;
[[Category: MCP]]&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=PA_Configuration_database_table&amp;diff=28079</id>
		<title>PA Configuration database table</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=PA_Configuration_database_table&amp;diff=28079"/>
		<updated>2026-04-09T13:36:47Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* OAuth 2.0 Authentication Settings */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer database has a configuration table &#039;&#039;&#039;PA_Configuration&#039;&#039;&#039; containing settings listed in the tables below. You need &#039;&#039;&#039;SQL Server Management Studio&#039;&#039;&#039; to edit the settings in the configuration table. QPR ProcessAnalyzer Server needs to be restarted (e.g. IIS application pool recycled) for the changes to take effect.&lt;br /&gt;
&lt;br /&gt;
For boolean values, &#039;&#039;true&#039;&#039; and &#039;&#039;1&#039;&#039; are valid values for yes, and &#039;&#039;false&#039;&#039; and &#039;&#039;0&#039;&#039; are valid for no.&lt;br /&gt;
&lt;br /&gt;
== General Settings ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Default&amp;amp;nbsp;value	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||DefaultDataSource&lt;br /&gt;
||&lt;br /&gt;
||Datasource where datatables data is stored when datatables are created by a script when the datasource is not explicitly specified in the script. Options are &#039;&#039;&#039;Snowflake&#039;&#039;&#039; and &#039;&#039;&#039;SqlServer&#039;&#039;&#039;. Value &#039;&#039;snowflake&#039;&#039; can be used when the &#039;&#039;SnowflakeConnectionString&#039;&#039; setting is defined, and value &#039;&#039;sqlserver&#039;&#039; can be used when the setting &#039;&#039;SqlServerConnectionString&#039;&#039; is configured. The setting can be changed without affecting the existing datatables, as the setting only affect new datatables. When this setting is empty, datatables are created in the metadata database.&lt;br /&gt;
|-&lt;br /&gt;
||SnowflakeConnectionString&lt;br /&gt;
||&lt;br /&gt;
||ODBC connection string for the Snowflake account. This setting is needed to make analytics calculations in the Snowflake. More information how to configure the [[Snowflake_Connection_Configuration#Set_Snowflake_ODBC_connection|Snowflake connection string]]. The Snowflake ODBC driver also needs to be installed in the machine running the QPR ProcessAnalyzer Server. When this setting has been configured, users can create Snowflake stored datatables and models using Snowflake calculation.&lt;br /&gt;
&lt;br /&gt;
When running QPR ProcessAnalyzer in the Snowpark Container Services, leave the SnowflakeConnectionString empty because then the connection string is created automatically using the method provided by the Snowpark Container Services.&lt;br /&gt;
|-&lt;br /&gt;
||SqlServerConnectionString&lt;br /&gt;
||&lt;br /&gt;
||Connection string for the SQL Server database containing the datatables data. It&#039;s recommended to use a separate database, but it&#039;s also possible to connect to the same database as the configuration data. If this setting is not configured, local datatables cannot be created (SQL Server stored). Existing datatables located in the configuration datatabase still work even if this setting has not be configured. Note that the connection uses ADO.Net (not ODBC), so the connection string is similar to the configuration database ([[Server_settings_in_appsettings.json|appsettings.json]] file).&lt;br /&gt;
|-&lt;br /&gt;
||DefaultColorPalette&lt;br /&gt;
||&lt;br /&gt;
||Charts color palette used globally in the environment. Defined as a json array of strings encoded with RGB hex (with or without alpha). Note that when a color palette in a chart has been changed, the chart starts using a chart-specific color palette, and the global color palette doesn&#039;t affect those charts. &lt;br /&gt;
&lt;br /&gt;
Example: [&amp;quot;#1F77B4&amp;quot;, &amp;quot;#FF7F0E&amp;quot;, &amp;quot;#2CA02C&amp;quot;, &amp;quot;#D62728&amp;quot;, &amp;quot;#9467BD&amp;quot;, &amp;quot;#8C564B&amp;quot;, &amp;quot;#E377C2&amp;quot;, &amp;quot;#7F7F7F&amp;quot;, &amp;quot;#BCBD22&amp;quot;, &amp;quot;#17BECF&amp;quot;]&lt;br /&gt;
|-&lt;br /&gt;
||OpenAIAPIKey&lt;br /&gt;
||&lt;br /&gt;
||API key for the OpenAI API (https://platform.openai.com/docs/api-reference). It needs to be configured to use the [[AI_Assistant_for_QPR_ProcessAnalyzer|AI Assistant]] and [[Generic_Functions_in_QPR_ProcessAnalyzer#OpenAIChatCompletion|OpenAIChatCompletion]] function.&lt;br /&gt;
|-&lt;br /&gt;
||OpenAIDefaultModelName&lt;br /&gt;
||gpt-4o&lt;br /&gt;
||OpenAI large language model (LLM) to use for the [[AI_Assistant_for_QPR_ProcessAnalyzer|AI Assistant]] and [[Generic_Functions_in_QPR_ProcessAnalyzer#OpenAIChatCompletion|OpenAIChatCompletion]] function. If not defined, &#039;&#039;&#039;gpt-4o&#039;&#039;&#039; will be used. Note that only LLM&#039;s that support the function calling feature, are suitable for the AI Assistant. More information about OpenAI models: https://platform.openai.com/docs/models.&lt;br /&gt;
|-&lt;br /&gt;
||DefaultCortexAgentsModelName&lt;br /&gt;
||llama3.1-70b&lt;br /&gt;
||Specifies the default language model when using Snowflake Cortex Agents (https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-rest-api). If not defined, &#039;&#039;&#039;llama3.1-70b&#039;&#039;&#039; is used.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;QueryTimeout&amp;quot;&amp;gt;QueryTimeout&amp;lt;/span&amp;gt;&lt;br /&gt;
||300&lt;br /&gt;
||Timeout (in seconds) for requests made to /api/expression/query and /api/expression endpoints. When the timeout is exceeded, the query is stopped and a timeout error is returned. Purpose of the timeout is to protect the system against potentially too long running or even never-ending queries which might otherwise jam the system.&lt;br /&gt;
|-&lt;br /&gt;
||SessionIdleTimeout&lt;br /&gt;
||3600&lt;br /&gt;
||Idle user session expiration timeout in seconds. User session expires if the session hasn&#039;t been used after this amount of time.&lt;br /&gt;
|-&lt;br /&gt;
||SessionMaximumDuration&lt;br /&gt;
||86400&lt;br /&gt;
||Maximum duration for a user session in seconds. Even if a session is used so that the SessionIdleTimeout is not reached, the session is expired after this amount of time.&lt;br /&gt;
|-&lt;br /&gt;
|DatabaseId&lt;br /&gt;
|&lt;br /&gt;
||Unique identifier for the QPR ProcessAnalyzer environment. Any characters between a-z, A-Z, 0-9 and _ (underscore) can be used in the DatabaseId. If the DatabaseId is missing or set to null, the system will generate a new GUID during startup and use it as the DatabaseId. The DatabaseId can also be an empty string. If using several QPR ProcessAnalyzer environments, make sure each use a different DatabaseId. The DatabaseId is used as part of the table names in Snowflake and SQL Server (in the datatables database). Thus if the DatabaseId is changed, all tables in Snowflake and SQL Server named with qprpa_dt_&amp;lt;DatabaseId&amp;gt;_&amp;lt;DatatableId&amp;gt; need to be renamed.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;CacheOnlyPrimaryKeysForFilters&amp;quot;&amp;gt;CacheOnlyPrimaryKeysForFilters&amp;lt;/span&amp;gt;&lt;br /&gt;
||false&lt;br /&gt;
||Defines whether to include all columns in the Snowflake event cache filter tables (&#039;&#039;false&#039;&#039;), or only the primary key columns (&#039;&#039;true&#039;&#039;). When &#039;&#039;false&#039;&#039;, cache table creation is slower, but the analysis calculation is faster because the original event table is not used anymore. When &#039;&#039;false&#039;&#039;, also the cache tables require more storage space in Snowflake.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Localization Settings ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Default&amp;amp;nbsp;value	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||DefaultUiLanguage&lt;br /&gt;
||en_US&lt;br /&gt;
||Language code for the UI language that new user accounts get by default. Thus, a created user account has this language until the user changes her/his language. Also the login page is translated using this language when QPR ProcessAnalyzer is used for the first time in that web browser (when user has changed the language, it&#039;s remembered by the browser). This setting must be one of the supported language codes (xx_XX):&lt;br /&gt;
* English: &#039;&#039;&#039;en_US&#039;&#039;&#039;&lt;br /&gt;
* Finnish: &#039;&#039;&#039;fi_FI&#039;&#039;&#039;&lt;br /&gt;
* French: &#039;&#039;&#039;fr_FR&#039;&#039;&#039;&lt;br /&gt;
* German: &#039;&#039;&#039;de_DE&#039;&#039;&#039;&lt;br /&gt;
* Polish: &#039;&#039;&#039;pl_PL&#039;&#039;&#039;&lt;br /&gt;
* Portuguese: &#039;&#039;&#039;pt_BR&#039;&#039;&#039;&lt;br /&gt;
* Spanish: &#039;&#039;&#039;es_ES&#039;&#039;&#039;&lt;br /&gt;
* Swedish: &#039;&#039;&#039;sv_SE&#039;&#039;&#039;&lt;br /&gt;
* Ukrainian: &#039;&#039;&#039;uk_UK&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||DefaultDateFormat&lt;br /&gt;
||MM/dd/yyyy&lt;br /&gt;
||Default date format that new user accounts get by default. The date format does not contain the time part (e.g. hours, minutes and seconds). Defined using the .Net date format (https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings).&lt;br /&gt;
|-&lt;br /&gt;
||DefaultFirstDayOfWeek&lt;br /&gt;
||0&lt;br /&gt;
||Default first day of the week that new user accounts get by default. &#039;&#039;&#039;0&#039;&#039;&#039; is Sunday and &#039;&#039;&#039;1&#039;&#039;&#039; is Monday. This information is used by the UI when showing e.g. calendars.&lt;br /&gt;
|-&lt;br /&gt;
||DefaultUse12HourClock&lt;br /&gt;
||false&lt;br /&gt;
||Defines whether the 12-hour clock is used by default (instead of the 24-hour clock) for the new user accounts when showing time information in the UI. Defined as &#039;&#039;&#039;true&#039;&#039;&#039; or &#039;&#039;&#039;false&#039;&#039;&#039;. More information about the 12-hour clock: https://en.wikipedia.org/wiki/12-hour_clock.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ETL Scripts Settings ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Default&amp;amp;nbsp;value	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||AllowExternalDatasources&lt;br /&gt;
||true&lt;br /&gt;
||Can be used to disallow all connections to external datasources in the expression language and SQL scripts to improve security. Disallowed operations include ODBC, OLE DB, SQL Server (Ado.Net), SAP, Salesforce, and call web service. Note that this setting does not prevent the Snowflake processing. Regardless of this setting, QPR ScriptLauncher can be used to extract data from source systems.&lt;br /&gt;
|-&lt;br /&gt;
||SandboxDatabaseConnectionString&lt;br /&gt;
||&lt;br /&gt;
||Connection string to scripting sandbox database (ETL). If not defined, SQL-based ETL scripts cannot be run. Connection string for the scripting sandbox database is similar to the  [[Server_settings_in_appsettings.json|QPR ProcessAnalyzer database connection string]]. More information: [[Setting up Scripting Sandbox]].&lt;br /&gt;
|-&lt;br /&gt;
||AllowNonTemporaryETLTargetTable&lt;br /&gt;
||false&lt;br /&gt;
||Defined whether ETL scripts are allowed to create global temporary database tables (tables starting with ##). More information about temporary tables: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql?view=sql-server-ver15#temporary-tables.&lt;br /&gt;
|-&lt;br /&gt;
||DatabaseBulkCopyTimeout&lt;br /&gt;
||600&lt;br /&gt;
||Timeout used for data import operations to datatables.&lt;br /&gt;
|-&lt;br /&gt;
|SandboxDatabaseBulkCopyTimeout&lt;br /&gt;
||600&lt;br /&gt;
||Timeout used for data import operations to sandbox tables in the SQL scripts.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span style=&amp;quot;color:lightgrey;&amp;quot;&amp;gt;DatabaseBulkCopyBatchSize&amp;lt;/span&amp;gt;&lt;br /&gt;
||5000&lt;br /&gt;
||BulkCopyBatchSize given for QPR ProcessAnalyzer database SqlBulkCopy operations.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span style=&amp;quot;color:lightgrey;&amp;quot;&amp;gt;SandboxDatabaseBulkCopyBatchSize&amp;lt;/span&amp;gt;&lt;br /&gt;
||5000&lt;br /&gt;
||BulkCopyBatchSize given for sandbox SqlBulkCopy operations.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== In-memory Calculation Settings ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Default&amp;amp;nbsp;value	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||NumberOfParallelModelReaders&lt;br /&gt;
||4&lt;br /&gt;
||Models and datatable contents can be loaded with multiple simultaneous connections to the database to speed up the loading. This setting determines how many parallel loaders/readers at maximum (loaders are loading at the same time). For smaller models there are less parallel loaders than the defined limit: If there are less than 100000 rows in the table, there is only one loader. If there are less than 200000 rows in the table, there are only two loaders, and so on. &lt;br /&gt;
&lt;br /&gt;
The more there are parallel loaders, the more processor load and network bandwidth is consumed, and other operations in QPR ProcessAnalyzer might slow down. Note also that the performance optimum is achieved with a certain number of parallel loaders which differs between environment. Thus to achieve the best performance, data loading should be tested with different number of parallel loaders. Increasing number of parallel loaders beyond the optimum decreases the performance.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
||StartupModelLoadingMaxParallelism&lt;br /&gt;
||2&lt;br /&gt;
||Maximum number of QPR ProcessAnalyzer models that are loaded into memory simultaneously by the [[Automatic_Model_Loading_on_Server_Startup|Automatic Loading on Server Startup]]. If there are more models to be loaded on the server startup than this setting, loading for the rest of the models is started one by one when previous model loadings are completed. If this setting is not defined, &#039;&#039;&#039;2&#039;&#039;&#039; is used as a default value.&lt;br /&gt;
&lt;br /&gt;
Loading more models at the same time will speed up the whole model loading process, but on the other hand, it causes more load on the system, which affects the system responsiveness for users. Model loading consists of (1) transferring data from the datasource to QPR ProcessAnalyzer and (2) loaded data preprocessing into a model. The former uses mainly network bandwidth (if datasource is in a different server) and the latter uses mainly processor capacity in the QPR ProcessAnalyzer server. &lt;br /&gt;
&lt;br /&gt;
This setting affects only the model loading during the server startup and it doesn&#039;t restrict models loadings initiated by users.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== SAML 2.0 Federated Authentication Settings ==&lt;br /&gt;
Note that the SAMLMetadataUrl and ServiceProviderLocation are mandatory for the federated authentication to work. Having both ExternalOAuthServerConfiguration and SAML authentication configured at the same time is not supported.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||SAMLMetadataUrl&lt;br /&gt;
||&lt;br /&gt;
Metadata URL of the identity provider (IdP). Check that the metadata url can actually be opened using a web browser and is publicly available. The metadata is an XML document starting with &#039;&#039;&#039;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&#039;&#039;&#039; followed by an &#039;&#039;&#039;EntityDescriptor&#039;&#039;&#039; tag. The metadata URL might look &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;https://your.federated.identity.provider.com/saml/metadata&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;. This setting is mandatory for the SAML authentication to work.&lt;br /&gt;
|-&lt;br /&gt;
||ServiceProviderLocation&lt;br /&gt;
||&lt;br /&gt;
Specifies the QPR ProcessAnalyzer server location (the root path which contains e.g. the &#039;&#039;ui&#039;&#039; folder). It&#039;s used by the url to redirect back to QPR ProcessAnalyzer after a successful authentication from the identity provider. The setting is defined in the following form: &#039;&#039;&#039;https://&amp;lt;hostname&amp;gt;/qprpa&#039;&#039;&#039;, for example &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;https://customer.onqpr.com/qprpa&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;. Note that the actual redirect back url is &#039;&#039;&#039;https://&amp;lt;hostname&amp;gt;/qprpa/api/Saml2/Acs&#039;&#039;&#039; (/api/Saml2/Acs is automatically included to the url). This setting is mandatory for the SAML authentication to work. Note that if this reply url is configured the identity provider, it must match with the ServiceProviderLocation setting.&lt;br /&gt;
|-&lt;br /&gt;
||SAMLUserIdAttribute&lt;br /&gt;
||&lt;br /&gt;
Name of the SAML attribute in the assertion that will be used as the user&#039;s login name. If this field is not defined, the &#039;&#039;&#039;saml:Assertion&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;saml:Subject&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;saml:NameID&#039;&#039;&#039; attribute in the assertion is used. If this setting is given, one of the &#039;&#039;&#039;saml:Assertion&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;saml:AttributeStatement&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;saml:Attribute&#039;&#039;&#039; elements in the assertion is used (the &#039;&#039;&#039;Name&#039;&#039;&#039; attribute in the &#039;&#039;&#039;saml:Attribute&#039;&#039;&#039; element is used for matching). Please note that the saml:NameID element is different than the usual SAML attributes that are defined by the saml:Attribute elements. For example, if an email address is used as a user id, the value of the setting could be for example &#039;&#039;&amp;lt;nowiki&amp;gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress&amp;lt;/nowiki&amp;gt;&#039;&#039;.&lt;br /&gt;
|-&lt;br /&gt;
||SAMLGroupsAttribute&lt;br /&gt;
||Attribute name in SAML assertion that is mapped to user groups in QPR ProcessAnalyzer. The user group names are case sensitive. When a user logs in, the user is added to and removed from groups based on the information in the SAML assertion. If this setting is not configured, users are not added to or removed from groups automatically. Note that the user needs to login for the groups to be synchronized. If a group doesn&#039;t exist in QPR ProcessAnalyzer, that group is skipped.&lt;br /&gt;
&lt;br /&gt;
In the SAML assertion, attributes are in the saml:Assertion &amp;gt; saml:AttributeStatement &amp;gt; saml:Attribute elements (the Name attribute in the saml:Attribute element is used for matching).&lt;br /&gt;
|-&lt;br /&gt;
||SAMLEncryptionCertificate&lt;br /&gt;
||This setting defines a PFX formatted X.509 certificate (defined in RCF 1422) used to encrypt SAML assertions. The public key of the certificate is published in the service provider metadata, where the identity provider can read it and encrypt SAML assertions. QPR ProcessAnalyzer as the service provider uses the corresponding private key of the certificate to decrypt SAML assertions. The setting needs to be a PFX formatted certificate file that is base64 encoded and it doesn&#039;t contain the BEGIN CERTIFICATE etc. header or footer lines. This setting is needed only when using the SAML assertions encryption. Even though this setting is defined, the SAML assertions are not required to be encrypted. More information how to create the certificate file (https://stackoverflow.com/questions/16480846/x-509-private-public-key) and convert it to base64 (https://stackoverflow.com/questions/46959822/base-64-encoded-form-of-the-pfx-file).&lt;br /&gt;
|-&lt;br /&gt;
||SAMLSigningCertificate&lt;br /&gt;
||This setting defines a PFX formatted X.509 certificate (defined in RCF 1422) used to sign SAML authentication requests sent from QPR ProcessAnalyzer to the identity provider. The public key of the certificate is published in the service provider metadata, where the identity provider can read it, to verify the authenticity of the SAML requests. The setting needs to be a PFX formatted certificate file that is base64 encoded and it doesn&#039;t contain the BEGIN CERTIFICATE etc. header or footer lines. If this setting is not defined, the internal hard-coded signing certificate is used. More information how to create the certificate file (https://stackoverflow.com/questions/16480846/x-509-private-public-key) and convert it to base64 (https://stackoverflow.com/questions/46959822/base-64-encoded-form-of-the-pfx-file).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== OAuth 2.0 Authentication Settings ==&lt;br /&gt;
Having both ExternalOAuthServerConfiguration and SAML authentication configured at the same time is not supported.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||ExternalOAuthServerConfiguration&lt;br /&gt;
||Used to configure OAuth 2.0 compatible OAuth server settings for server that is used to authenticate the user signing in to ProcessAnalyzer. If not set, external OAuth server will not be used for authentication. If set, contains a string representation of a JSON object that supports the following properties:&lt;br /&gt;
* &#039;&#039;&#039;Authority&#039;&#039;&#039; (string): OAuth authority URL for authentication. E.g., https://accounts.google.com/&lt;br /&gt;
* &#039;&#039;&#039;Audience&#039;&#039;&#039; (string): Mandatory. OAuth audience/client ID for validating OAuth tokens.&lt;br /&gt;
* &#039;&#039;&#039;AuthorizeUrlOverride&#039;&#039;&#039; (string): Override URL for the OAuth authorization endpoint. If empty or not defined, the default URL from the authority&#039;s discovery document is used.&lt;br /&gt;
* &#039;&#039;&#039;TokenUrlOverride&#039;&#039;&#039; (string): Override URL for the OAuth token endpoint. If empty or not defined, the default URL from the authority&#039;s discovery document is used.&lt;br /&gt;
* &#039;&#039;&#039;UserInfoUrlOverride&#039;&#039;&#039; (string): Override URL to fetch user information from the OAuth provider. If empty or not defined, the default URL from the authority&#039;s discovery document is used. Should not be used if OpendID Connect is to be used as access token validation is skipped.&lt;br /&gt;
* &#039;&#039;&#039;ClientSecret&#039;&#039;&#039; (string): OAuth client secret for confidential client authentication. If configured, this value is sent as the client_secret parameter when exchanging authorization codes for tokens.&lt;br /&gt;
* &#039;&#039;&#039;Issuer&#039;&#039;&#039; (string): OAuth issuer for validating OAuth tokens. If empty, the authority URL&#039;s issuer is used.&lt;br /&gt;
* &#039;&#039;&#039;UserNameClaim&#039;&#039;&#039; (string): Name of the claim whose value is to be used as the name of the authenticated user. The default value is &amp;quot;preferred_username&amp;quot;.&lt;br /&gt;
* &#039;&#039;&#039;UserGroupsClaim&#039;&#039;&#039; (string): Name of the claim whose value is to be used as the names of the user groups the authenticated user belongs to. When a user logs in, the user is added to and removed from groups based on the information in the UserGroupsClaim. If this setting is not configured, users are not added to or removed from groups automatically. Note that the user needs to login for the groups to be synchronized. If a group doesn&#039;t exist in QPR ProcessAnalyzer, that group is skipped. The default value is empty, i.e. groups are not synchronized.&amp;lt;br&amp;gt;&lt;br /&gt;
Example configuration:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Authority&amp;quot;: &amp;quot;https://accounts.google.com/&amp;quot;,&lt;br /&gt;
  &amp;quot;Audience&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
  &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
  &amp;quot;UserNameClaim&amp;quot;: &amp;quot;name&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== SMTP Server Settings ==&lt;br /&gt;
SMTP server settings are needed for QPR ProcessAnalyzer to send email messages. Email sending is used by the [[Email_Notifications|notifications]] and the [[Generic_Functions_in_QPR_ProcessAnalyzer#SendEmail|SendEmail]] function in the expression language.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||SmtpServer&lt;br /&gt;
||DNS name, host name or IP address of the SMTP server. Mandatory setting for the email sending to work.&lt;br /&gt;
|-&lt;br /&gt;
||SmtpPort&lt;br /&gt;
||TCP port number of the SMTP server. If not defined, port 25 is used by default.&lt;br /&gt;
|-&lt;br /&gt;
||SmtpAuthenticationUsername&lt;br /&gt;
||User name for authenticating to the SMTP server. If not defined, no authentication is used to connect to the SMTP server.&lt;br /&gt;
|-&lt;br /&gt;
||SmtpFromAddress&lt;br /&gt;
||Email address where email messages sent by QPR ProcessAnalyzer appear to be coming from. This doesn&#039;t need to be a real email address, although the address used may affect email spam filters. The setting configured here is the default email address to use in following cases:&lt;br /&gt;
* &#039;&#039;From address&#039;&#039; is not set for the email notifications&lt;br /&gt;
* &#039;&#039;From&#039;&#039; parameter is not defined for the expression language &#039;&#039;SendEmail&#039;&#039; function&lt;br /&gt;
* &#039;&#039;EmailFrom&#039;&#039; parameter is not defined for the SQL Scripting SendEmail operation&lt;br /&gt;
|-&lt;br /&gt;
||SmtpAuthenticationPassword&lt;br /&gt;
||Password for authenticating to the SMTP server.&lt;br /&gt;
|-&lt;br /&gt;
||SmtpEnableSSL&lt;br /&gt;
||Use value &#039;&#039;&#039;True&#039;&#039;&#039; or &#039;&#039;&#039;False&#039;&#039;&#039; depending whether TLS connection to the SMTP server is used or not. If not defined, &#039;&#039;False&#039;&#039; is the default value.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== MCP Server Settings ==&lt;br /&gt;
MCP server settings are needed for QPR ProcessAnalyzer to act as an [[QPR_ProcessAnalyzer_as_MCP_Server|MCP server]].&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||McpServerConfiguration&lt;br /&gt;
||Used to configure the MCP server built-in to ProcessAnalyzer server. If not set, MCP server functionality is disabled and MCP clients can&#039;t access to this server using MCP. If set, contains a string representation of a JSON object that supports the following properties:&lt;br /&gt;
* &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; (string): If defined and not empty, defines the API key that can be used to connect to QPR ProcessAnalyzer MCP server without any other authentication. Default value is empty.&amp;lt;br&amp;gt;&lt;br /&gt;
Example value when using API Key authentication:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{ &amp;quot;McpApiKey&amp;quot;: &amp;quot;xnTqr@Hd87JcuCmQZbjUHfwD@&amp;quot; }&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Example value when using OAuth 2.0 authentication:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{ &amp;quot;McpApiKey&amp;quot;: null }&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that when using OAuth 2.0 authentication the BuiltInOAuthServerConfiguration (see below) needs to be defined with an AcceptedAudiences value other than the default.&lt;br /&gt;
|-&lt;br /&gt;
||BuiltInOAuthServerConfiguration&lt;br /&gt;
||Used to configure OAuth 2.0 compatible OAuth server settings for OAuth server built-in to ProcessAnalyzer server. If not set, built-in OAuth server functionality is disabled and clients can&#039;t connect to this server using OAuth. If set, contains a string representation of a JSON object that supports the following properties: &lt;br /&gt;
* &#039;&#039;&#039;Issuer&#039;&#039;&#039; (string): OAuth issuer, which identifies a trusted authorization server that authenticates users and issues OAuth 2.0 access tokens and JSON Web Tokens (JWTs). If not defined or empty, default value is used, which is of format: &amp;lt;QPR ProcessAnalyzer server&#039;s base URL&amp;gt;/builtin-oauth. For example: https://example.com/builtin-oauth. The default value is empty.&lt;br /&gt;
* &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; (array of strings): Array of strings that define all the accepted audiences this QPR ProcessAnalyzer server is serving. When authorizing user using OAuth, these values are matched with the audience-parameter (a.k.a. client id) of the authorization. Only requests with a value that matches a value in this array are accepted. If null, audience-parameters are not validated at all. Instead, all authorization requests will pass the audience validation check. This also enables [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. The default value is an empty array.&lt;br /&gt;
* &#039;&#039;&#039;SigningKey&#039;&#039;&#039; (string): Signing key for the built-in OAuth identity provider. If empty, generates a non-deterministic key based on the physical system where QPR ProcessAnalyzer is running. NOTE: Once QPR ProcessAnalyzer server is restarted, these non-deterministic keys no longer work. If defined, string must contain the key either in PEM ([https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsa.importfrompem?view=net-10.0 RFC 7468 PEM-encoded key]) or JSON ([https://datatracker.ietf.org/doc/html/rfc7517 RFC 7517]) format. The default value is empty.&lt;br /&gt;
* &#039;&#039;&#039;TokenLifetimeSeconds&#039;&#039;&#039; (integer): Token lifetime in seconds for the built-in OAuth identity provider. After access token created by built-in gets older than this lifetime, it becomes unusable and a new token has to be created. The default value is 3600.&lt;br /&gt;
* &#039;&#039;&#039;DisableExternalOAuthForwarding&#039;&#039;&#039; (boolean): Can be used to disable forwarding OAuth requests to any configured external OAuth authorization server or SAML identity provider. If set, a QPR ProcessAnalyzer&#039;s own login view functionality is always used when authorizing a user. The default value is false.&lt;br /&gt;
Example configuration:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Issuer&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
  &amp;quot;AcceptedAudiences&amp;quot;: [&amp;quot;qpr-processanalyzer&amp;quot;],&lt;br /&gt;
  &amp;quot;SigningKey&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
  &amp;quot;TokenLifetime&amp;quot;: 3600&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Readonly Information ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Description&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;span style=&amp;quot;color:lightgrey;&amp;quot;&amp;gt;DatabaseVersion&amp;lt;/span&amp;gt;&lt;br /&gt;
||Database schema version. It will be updated automatically when the newer version of QPR ProcessAnalyzer Server connects to the database and performs migration for the database schema.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span style=&amp;quot;color:lightgrey;&amp;quot;&amp;gt;InitializationScriptDatabaseVersion&amp;lt;/span&amp;gt;&lt;br /&gt;
||Database version that was when the database was initialized when the software was installed. Do not change this setting.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span style=&amp;quot;color:lightgrey;&amp;quot;&amp;gt;MinimumDatabaseVersion&amp;lt;/span&amp;gt;&lt;br /&gt;
||Minimum allowed database version for QPR ProcessAnalyzer Server connecting to the database. This is a legacy setting and it should not be used.&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28073</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28073"/>
		<updated>2026-04-09T12:13:58Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* OAuth 2.0 Authentication */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Overview of MCP Server Functionality ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
== Configuring the MCP Server ==&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API Keys. It&#039;s also possible to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_CONFIGURATION database table as &#039;&#039;&#039;null&#039;&#039;&#039;.&lt;br /&gt;
* Set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
When QPR ProcessAnalyzer is hosted in IIS, some clients (for example [https://modelcontextprotocol.io/docs/tools/inspector MCP Inspector]) may expect [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint:&amp;lt;br&amp;gt;&lt;br /&gt;
/.well-known/openid-configuration&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To make this work, add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&amp;lt;br&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
=== Oauth 2.0 and API Key Authentication ===&lt;br /&gt;
This method combines both per-user authentication and static API key authentication. To use both OAuth 2.0 and API key Authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client using API key authentication must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
* Set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_Configuration database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
== Implementing MCP Tools with Scripts ==&lt;br /&gt;
Any QPR ProcessAnalyzer script can be turned into an MCP tool. This is controlled in the [[Managing_Scripts#Script_Properties|Script Properties]].&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To enable a script as an MCP Tool:&lt;br /&gt;
# Log in to QPR ProcessAnalyzer as a System Administrator.&lt;br /&gt;
# For the script to be used as an MCP tool, give it a name. This name is then used as the MCP tool name. It is important to ensure that every script intended for MCP use has a non-empty, valid name, as clients may fail to discover tools with invalid names.&lt;br /&gt;
# Open the properties of the script.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;MCP&#039;&#039;&#039; tab and select the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;Description&#039;&#039;&#039; tab and provide a description. The description is added to the context of the client.&lt;br /&gt;
&lt;br /&gt;
== Connecting as an MCP Client ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table.&lt;br /&gt;
** &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_Configuration database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)].&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
Connect to Server: In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., http://your-pa-server/qprpa/api/mcp) and select streamable-http as the transport type.&amp;lt;br&amp;gt;&lt;br /&gt;
Authenticate: Configure the necessary authentication header (e.g., X-Mcp-Api-Key).&amp;lt;br&amp;gt;&lt;br /&gt;
List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool named GetCurrentTime, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
    &amp;quot;params&amp;quot;: {&lt;br /&gt;
        &amp;quot;name&amp;quot;: &amp;quot;GetCurrentTime&amp;quot;,&lt;br /&gt;
        &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
        &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
            &amp;quot;progressToken&amp;quot;: 2&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;jsonrpc&amp;quot;: &amp;quot;2.0&amp;quot;,&lt;br /&gt;
    &amp;quot;id&amp;quot;: 2&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category: QPR ProcessAnalyzer]]&lt;br /&gt;
[[Category: MCP]]&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28072</id>
		<title>QPR ProcessAnalyzer as MCP Server</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_as_MCP_Server&amp;diff=28072"/>
		<updated>2026-04-09T12:13:17Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Authentication Methods */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can act as a Model Context Protocol (MCP) server, allowing external tools and applications to interact with its analysis capabilities through script-based tools.&lt;br /&gt;
&lt;br /&gt;
== Overview of MCP Server Functionality ==&lt;br /&gt;
The Model Context Protocol (MCP) is a standard for communication between modeling tools. By acting as an MCP server, QPR ProcessAnalyzer exposes its functionalities, which are implemented as [[Managing_Scripts|scripts]], to any MCP-compliant client. This enables a wide range of integrations and automation possibilities.&lt;br /&gt;
&lt;br /&gt;
Key features:&lt;br /&gt;
* Script-based Tools: Any QPR ProcessAnalyzer script can be exposed as an MCP tool.&lt;br /&gt;
* Flexible Authentication: Supports both API Key and OAuth 2.0 for secure access.&lt;br /&gt;
* Standardized Communication: Uses the MCP standard for interoperability with clients like MCP Inspector and Visual Studio Code.&lt;br /&gt;
&lt;br /&gt;
== Configuring the MCP Server ==&lt;br /&gt;
To enable and configure the MCP server functionality in QPR ProcessAnalyzer, a system administrator needs to adjust the McpServerConfiguration setting in the [[PA_Configuration_database_table|PA Configuration Database Table]].&lt;br /&gt;
&lt;br /&gt;
== Authentication Methods ==&lt;br /&gt;
The supported methods to authenticate MCP clients are OAuth 2.0 or API Keys. It&#039;s also possible to use both.&lt;br /&gt;
&lt;br /&gt;
=== OAuth 2.0 Authentication ===&lt;br /&gt;
This method provides per-user authentication, allowing each MCP request to be authenticated against a specific QPR ProcessAnalyzer user. This is the recommended authentication method for MCP. To use OAuth 2.0 Authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table as &#039;&#039;&#039;null&#039;&#039;&#039;.&lt;br /&gt;
* Set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_CONFIGURATION database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
==== Setting up IIS to support OpenId Connect Discovery ====&lt;br /&gt;
When QPR ProcessAnalyzer is hosted in IIS, some clients (for example [https://modelcontextprotocol.io/docs/tools/inspector MCP Inspector]) may expect [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig OpenID Connect discovery metadata] to be available from the standard endpoint:&amp;lt;br&amp;gt;&lt;br /&gt;
/.well-known/openid-configuration&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To make this work, add an IIS rewrite rule and a CORS header at the IIS root-level &#039;&#039;&#039;web.config&#039;&#039;&#039;. Adding rewrite rules to IIS requires [https://www.iis.net/downloads/microsoft/url-rewrite URL Rewrite] to be installed on the system.&lt;br /&gt;
&lt;br /&gt;
: 1. Open the root-level IIS web.config.&lt;br /&gt;
: 2. Add the following configuration under &amp;lt;system.webServer&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;configuration&amp;gt;&lt;br /&gt;
  &amp;lt;system.webServer&amp;gt;&lt;br /&gt;
    &amp;lt;rewrite&amp;gt;&lt;br /&gt;
      &amp;lt;rules&amp;gt;&lt;br /&gt;
        &amp;lt;rule name=&amp;quot;Redirect OIDC Discovery - openid-configuration&amp;quot; stopProcessing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;match url=&amp;quot;^\.well-known/openid-configuration$&amp;quot; /&amp;gt;&lt;br /&gt;
          &amp;lt;action type=&amp;quot;Redirect&amp;quot;&lt;br /&gt;
                  url=&amp;quot;&amp;lt;QPR ProcessAnalyzer Path&amp;gt;/builtin-oauth/.well-known/openid-configuration&amp;quot;&lt;br /&gt;
                  redirectType=&amp;quot;Permanent&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;/rule&amp;gt;&lt;br /&gt;
      &amp;lt;/rules&amp;gt;&lt;br /&gt;
    &amp;lt;/rewrite&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
    &amp;lt;httpProtocol&amp;gt;&lt;br /&gt;
      &amp;lt;customHeaders&amp;gt;&lt;br /&gt;
        &amp;lt;add name=&amp;quot;Access-Control-Allow-Origin&amp;quot; value=&amp;quot;&amp;lt;Allowed CORS origins&amp;gt;&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/customHeaders&amp;gt;&lt;br /&gt;
    &amp;lt;/httpProtocol&amp;gt;&lt;br /&gt;
  &amp;lt;/system.webServer&amp;gt;&lt;br /&gt;
&amp;lt;/configuration&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
: 3. Replace placeholders:&lt;br /&gt;
:* &amp;lt;QPR ProcessAnalyzer Path&amp;gt;: IIS virtual path to the installed QPR ProcessAnalyzer application, for example qprpa.&lt;br /&gt;
:* &amp;lt;Allowed CORS origins&amp;gt;: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS CORS origin] origin(s) allowed to call discovery. Use specific origin(s) in production.&lt;br /&gt;
&lt;br /&gt;
You may also need to add the URL of the client accessing the MCP server to the CorsOrigins setting in the server&#039;s [[Server_settings_in_appsettings.json|appsettings.json]].&lt;br /&gt;
&lt;br /&gt;
After configuration, the following URL should return OpenID provider metadata:&amp;lt;br&amp;gt;&lt;br /&gt;
https://&amp;lt;your-host&amp;gt;/.well-known/openid-configuration&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
If the endpoint does not work, verify that the rewrite rule is in the IIS root-level web.config and that the target path resolves to .../builtin-oauth/.well-known/openid-configuration.&lt;br /&gt;
&lt;br /&gt;
=== API Key Authentication ===&lt;br /&gt;
This method uses a static, pre-shared key for all MCP requests. To use API key authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
&lt;br /&gt;
=== Oauth 2.0 and API Key Authentication ===&lt;br /&gt;
This method combines both per-user authentication and static API key authentication. To use both OAuth 2.0 and API key Authentication:&lt;br /&gt;
* Set the &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; value in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table. The MCP client using API key authentication must then include this key in the &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039; header of each request.&lt;br /&gt;
* Create a user named &amp;quot;MCP Client&amp;quot; in QPR ProcessAnalyzer. All the MCP server operations are performed in the context of &amp;quot;MCP Client&amp;quot; user, i.e. the user can&#039;t see or modify anything the &amp;quot;MCP Client&amp;quot; user can&#039;t see or modify in any other way.&lt;br /&gt;
* Set the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] value in the PA_Configuration database table with at least an &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; value other than the default.&lt;br /&gt;
&lt;br /&gt;
== Implementing MCP Tools with Scripts ==&lt;br /&gt;
Any QPR ProcessAnalyzer script can be turned into an MCP tool. This is controlled in the [[Managing_Scripts#Script_Properties|Script Properties]].&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To enable a script as an MCP Tool:&lt;br /&gt;
# Log in to QPR ProcessAnalyzer as a System Administrator.&lt;br /&gt;
# For the script to be used as an MCP tool, give it a name. This name is then used as the MCP tool name. It is important to ensure that every script intended for MCP use has a non-empty, valid name, as clients may fail to discover tools with invalid names.&lt;br /&gt;
# Open the properties of the script.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;MCP&#039;&#039;&#039; tab and select the &#039;&#039;&#039;MCP tool&#039;&#039;&#039; checkbox.&lt;br /&gt;
# Switch to the &#039;&#039;&#039;Description&#039;&#039;&#039; tab and provide a description. The description is added to the context of the client.&lt;br /&gt;
&lt;br /&gt;
== Connecting as an MCP Client ==&lt;br /&gt;
MCP clients can connect to the QPR ProcessAnalyzer server to discover and execute the available tools. The endpoint and connection details are:&lt;br /&gt;
* &#039;&#039;&#039;URL&#039;&#039;&#039;: The primary MCP endpoint is located at /api/mcp relative to the QPR ProcessAnalyzer server URL (e.g., https://your-pa-server.com/qprpa/api/mcp).&lt;br /&gt;
* &#039;&#039;&#039;Type&#039;&#039;&#039;: http.&lt;br /&gt;
* &#039;&#039;&#039;Headers&#039;&#039;&#039;: Clients must include the following headers in their requests:&lt;br /&gt;
** &#039;&#039;&#039;X-Mcp-Api-Key&#039;&#039;&#039;: Used when authenticating with API key. The API key that is defined in the [[PA_Configuration_database_table#MCP_Server_Settings|McpServerConfiguration]] setting in the PA_Configuration database table.&lt;br /&gt;
** &#039;&#039;&#039;Client ID&#039;&#039;&#039;: Used when authenticating with OAuth 2.0. If the AcceptedAudiences value in the [[PA_Configuration_database_table#MCP_Server_Settings|BuiltInOAuthServerConfiguration]] setting in the PA_Configuration database table is something else than null, the client ID has to match it. If the AcceptedAudiences value is null, the client ID must be created with [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)].&lt;br /&gt;
&lt;br /&gt;
=== Example: Using MCP Inspector ===&lt;br /&gt;
MCP Inspector is a standard client that can be used to test the connection and interact with the MCP tools.&lt;br /&gt;
&lt;br /&gt;
Connect to Server: In MCP Inspector, provide the server&#039;s MCP endpoint URL (e.g., http://your-pa-server/qprpa/api/mcp) and select streamable-http as the transport type.&amp;lt;br&amp;gt;&lt;br /&gt;
Authenticate: Configure the necessary authentication header (e.g., X-Mcp-Api-Key).&amp;lt;br&amp;gt;&lt;br /&gt;
List and Call Tools: Once connected, you can list the available tools and call them. For example, to call a tool named GetCurrentTime, the client would send a JSON-RPC request like the one below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;method&amp;quot;: &amp;quot;tools/call&amp;quot;,&lt;br /&gt;
    &amp;quot;params&amp;quot;: {&lt;br /&gt;
        &amp;quot;name&amp;quot;: &amp;quot;GetCurrentTime&amp;quot;,&lt;br /&gt;
        &amp;quot;arguments&amp;quot;: {},&lt;br /&gt;
        &amp;quot;_meta&amp;quot;: {&lt;br /&gt;
            &amp;quot;progressToken&amp;quot;: 2&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;jsonrpc&amp;quot;: &amp;quot;2.0&amp;quot;,&lt;br /&gt;
    &amp;quot;id&amp;quot;: 2&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category: QPR ProcessAnalyzer]]&lt;br /&gt;
[[Category: MCP]]&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=PA_Configuration_database_table&amp;diff=28071</id>
		<title>PA Configuration database table</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=PA_Configuration_database_table&amp;diff=28071"/>
		<updated>2026-04-09T12:07:26Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* MCP Server Settings */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer database has a configuration table &#039;&#039;&#039;PA_Configuration&#039;&#039;&#039; containing settings listed in the tables below. You need &#039;&#039;&#039;SQL Server Management Studio&#039;&#039;&#039; to edit the settings in the configuration table. QPR ProcessAnalyzer Server needs to be restarted (e.g. IIS application pool recycled) for the changes to take effect.&lt;br /&gt;
&lt;br /&gt;
For boolean values, &#039;&#039;true&#039;&#039; and &#039;&#039;1&#039;&#039; are valid values for yes, and &#039;&#039;false&#039;&#039; and &#039;&#039;0&#039;&#039; are valid for no.&lt;br /&gt;
&lt;br /&gt;
== General Settings ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Default&amp;amp;nbsp;value	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||DefaultDataSource&lt;br /&gt;
||&lt;br /&gt;
||Datasource where datatables data is stored when datatables are created by a script when the datasource is not explicitly specified in the script. Options are &#039;&#039;&#039;Snowflake&#039;&#039;&#039; and &#039;&#039;&#039;SqlServer&#039;&#039;&#039;. Value &#039;&#039;snowflake&#039;&#039; can be used when the &#039;&#039;SnowflakeConnectionString&#039;&#039; setting is defined, and value &#039;&#039;sqlserver&#039;&#039; can be used when the setting &#039;&#039;SqlServerConnectionString&#039;&#039; is configured. The setting can be changed without affecting the existing datatables, as the setting only affect new datatables. When this setting is empty, datatables are created in the metadata database.&lt;br /&gt;
|-&lt;br /&gt;
||SnowflakeConnectionString&lt;br /&gt;
||&lt;br /&gt;
||ODBC connection string for the Snowflake account. This setting is needed to make analytics calculations in the Snowflake. More information how to configure the [[Snowflake_Connection_Configuration#Set_Snowflake_ODBC_connection|Snowflake connection string]]. The Snowflake ODBC driver also needs to be installed in the machine running the QPR ProcessAnalyzer Server. When this setting has been configured, users can create Snowflake stored datatables and models using Snowflake calculation.&lt;br /&gt;
&lt;br /&gt;
When running QPR ProcessAnalyzer in the Snowpark Container Services, leave the SnowflakeConnectionString empty because then the connection string is created automatically using the method provided by the Snowpark Container Services.&lt;br /&gt;
|-&lt;br /&gt;
||SqlServerConnectionString&lt;br /&gt;
||&lt;br /&gt;
||Connection string for the SQL Server database containing the datatables data. It&#039;s recommended to use a separate database, but it&#039;s also possible to connect to the same database as the configuration data. If this setting is not configured, local datatables cannot be created (SQL Server stored). Existing datatables located in the configuration datatabase still work even if this setting has not be configured. Note that the connection uses ADO.Net (not ODBC), so the connection string is similar to the configuration database ([[Server_settings_in_appsettings.json|appsettings.json]] file).&lt;br /&gt;
|-&lt;br /&gt;
||DefaultColorPalette&lt;br /&gt;
||&lt;br /&gt;
||Charts color palette used globally in the environment. Defined as a json array of strings encoded with RGB hex (with or without alpha). Note that when a color palette in a chart has been changed, the chart starts using a chart-specific color palette, and the global color palette doesn&#039;t affect those charts. &lt;br /&gt;
&lt;br /&gt;
Example: [&amp;quot;#1F77B4&amp;quot;, &amp;quot;#FF7F0E&amp;quot;, &amp;quot;#2CA02C&amp;quot;, &amp;quot;#D62728&amp;quot;, &amp;quot;#9467BD&amp;quot;, &amp;quot;#8C564B&amp;quot;, &amp;quot;#E377C2&amp;quot;, &amp;quot;#7F7F7F&amp;quot;, &amp;quot;#BCBD22&amp;quot;, &amp;quot;#17BECF&amp;quot;]&lt;br /&gt;
|-&lt;br /&gt;
||OpenAIAPIKey&lt;br /&gt;
||&lt;br /&gt;
||API key for the OpenAI API (https://platform.openai.com/docs/api-reference). It needs to be configured to use the [[AI_Assistant_for_QPR_ProcessAnalyzer|AI Assistant]] and [[Generic_Functions_in_QPR_ProcessAnalyzer#OpenAIChatCompletion|OpenAIChatCompletion]] function.&lt;br /&gt;
|-&lt;br /&gt;
||OpenAIDefaultModelName&lt;br /&gt;
||gpt-4o&lt;br /&gt;
||OpenAI large language model (LLM) to use for the [[AI_Assistant_for_QPR_ProcessAnalyzer|AI Assistant]] and [[Generic_Functions_in_QPR_ProcessAnalyzer#OpenAIChatCompletion|OpenAIChatCompletion]] function. If not defined, &#039;&#039;&#039;gpt-4o&#039;&#039;&#039; will be used. Note that only LLM&#039;s that support the function calling feature, are suitable for the AI Assistant. More information about OpenAI models: https://platform.openai.com/docs/models.&lt;br /&gt;
|-&lt;br /&gt;
||DefaultCortexAgentsModelName&lt;br /&gt;
||llama3.1-70b&lt;br /&gt;
||Specifies the default language model when using Snowflake Cortex Agents (https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents-rest-api). If not defined, &#039;&#039;&#039;llama3.1-70b&#039;&#039;&#039; is used.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;QueryTimeout&amp;quot;&amp;gt;QueryTimeout&amp;lt;/span&amp;gt;&lt;br /&gt;
||300&lt;br /&gt;
||Timeout (in seconds) for requests made to /api/expression/query and /api/expression endpoints. When the timeout is exceeded, the query is stopped and a timeout error is returned. Purpose of the timeout is to protect the system against potentially too long running or even never-ending queries which might otherwise jam the system.&lt;br /&gt;
|-&lt;br /&gt;
||SessionIdleTimeout&lt;br /&gt;
||3600&lt;br /&gt;
||Idle user session expiration timeout in seconds. User session expires if the session hasn&#039;t been used after this amount of time.&lt;br /&gt;
|-&lt;br /&gt;
||SessionMaximumDuration&lt;br /&gt;
||86400&lt;br /&gt;
||Maximum duration for a user session in seconds. Even if a session is used so that the SessionIdleTimeout is not reached, the session is expired after this amount of time.&lt;br /&gt;
|-&lt;br /&gt;
|DatabaseId&lt;br /&gt;
|&lt;br /&gt;
||Unique identifier for the QPR ProcessAnalyzer environment. Any characters between a-z, A-Z, 0-9 and _ (underscore) can be used in the DatabaseId. If the DatabaseId is missing or set to null, the system will generate a new GUID during startup and use it as the DatabaseId. The DatabaseId can also be an empty string. If using several QPR ProcessAnalyzer environments, make sure each use a different DatabaseId. The DatabaseId is used as part of the table names in Snowflake and SQL Server (in the datatables database). Thus if the DatabaseId is changed, all tables in Snowflake and SQL Server named with qprpa_dt_&amp;lt;DatabaseId&amp;gt;_&amp;lt;DatatableId&amp;gt; need to be renamed.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span id=&amp;quot;CacheOnlyPrimaryKeysForFilters&amp;quot;&amp;gt;CacheOnlyPrimaryKeysForFilters&amp;lt;/span&amp;gt;&lt;br /&gt;
||false&lt;br /&gt;
||Defines whether to include all columns in the Snowflake event cache filter tables (&#039;&#039;false&#039;&#039;), or only the primary key columns (&#039;&#039;true&#039;&#039;). When &#039;&#039;false&#039;&#039;, cache table creation is slower, but the analysis calculation is faster because the original event table is not used anymore. When &#039;&#039;false&#039;&#039;, also the cache tables require more storage space in Snowflake.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Localization Settings ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Default&amp;amp;nbsp;value	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||DefaultUiLanguage&lt;br /&gt;
||en_US&lt;br /&gt;
||Language code for the UI language that new user accounts get by default. Thus, a created user account has this language until the user changes her/his language. Also the login page is translated using this language when QPR ProcessAnalyzer is used for the first time in that web browser (when user has changed the language, it&#039;s remembered by the browser). This setting must be one of the supported language codes (xx_XX):&lt;br /&gt;
* English: &#039;&#039;&#039;en_US&#039;&#039;&#039;&lt;br /&gt;
* Finnish: &#039;&#039;&#039;fi_FI&#039;&#039;&#039;&lt;br /&gt;
* French: &#039;&#039;&#039;fr_FR&#039;&#039;&#039;&lt;br /&gt;
* German: &#039;&#039;&#039;de_DE&#039;&#039;&#039;&lt;br /&gt;
* Polish: &#039;&#039;&#039;pl_PL&#039;&#039;&#039;&lt;br /&gt;
* Portuguese: &#039;&#039;&#039;pt_BR&#039;&#039;&#039;&lt;br /&gt;
* Spanish: &#039;&#039;&#039;es_ES&#039;&#039;&#039;&lt;br /&gt;
* Swedish: &#039;&#039;&#039;sv_SE&#039;&#039;&#039;&lt;br /&gt;
* Ukrainian: &#039;&#039;&#039;uk_UK&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||DefaultDateFormat&lt;br /&gt;
||MM/dd/yyyy&lt;br /&gt;
||Default date format that new user accounts get by default. The date format does not contain the time part (e.g. hours, minutes and seconds). Defined using the .Net date format (https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings).&lt;br /&gt;
|-&lt;br /&gt;
||DefaultFirstDayOfWeek&lt;br /&gt;
||0&lt;br /&gt;
||Default first day of the week that new user accounts get by default. &#039;&#039;&#039;0&#039;&#039;&#039; is Sunday and &#039;&#039;&#039;1&#039;&#039;&#039; is Monday. This information is used by the UI when showing e.g. calendars.&lt;br /&gt;
|-&lt;br /&gt;
||DefaultUse12HourClock&lt;br /&gt;
||false&lt;br /&gt;
||Defines whether the 12-hour clock is used by default (instead of the 24-hour clock) for the new user accounts when showing time information in the UI. Defined as &#039;&#039;&#039;true&#039;&#039;&#039; or &#039;&#039;&#039;false&#039;&#039;&#039;. More information about the 12-hour clock: https://en.wikipedia.org/wiki/12-hour_clock.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ETL Scripts Settings ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Default&amp;amp;nbsp;value	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||AllowExternalDatasources&lt;br /&gt;
||true&lt;br /&gt;
||Can be used to disallow all connections to external datasources in the expression language and SQL scripts to improve security. Disallowed operations include ODBC, OLE DB, SQL Server (Ado.Net), SAP, Salesforce, and call web service. Note that this setting does not prevent the Snowflake processing. Regardless of this setting, QPR ScriptLauncher can be used to extract data from source systems.&lt;br /&gt;
|-&lt;br /&gt;
||SandboxDatabaseConnectionString&lt;br /&gt;
||&lt;br /&gt;
||Connection string to scripting sandbox database (ETL). If not defined, SQL-based ETL scripts cannot be run. Connection string for the scripting sandbox database is similar to the  [[Server_settings_in_appsettings.json|QPR ProcessAnalyzer database connection string]]. More information: [[Setting up Scripting Sandbox]].&lt;br /&gt;
|-&lt;br /&gt;
||AllowNonTemporaryETLTargetTable&lt;br /&gt;
||false&lt;br /&gt;
||Defined whether ETL scripts are allowed to create global temporary database tables (tables starting with ##). More information about temporary tables: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql?view=sql-server-ver15#temporary-tables.&lt;br /&gt;
|-&lt;br /&gt;
||DatabaseBulkCopyTimeout&lt;br /&gt;
||600&lt;br /&gt;
||Timeout used for data import operations to datatables.&lt;br /&gt;
|-&lt;br /&gt;
|SandboxDatabaseBulkCopyTimeout&lt;br /&gt;
||600&lt;br /&gt;
||Timeout used for data import operations to sandbox tables in the SQL scripts.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span style=&amp;quot;color:lightgrey;&amp;quot;&amp;gt;DatabaseBulkCopyBatchSize&amp;lt;/span&amp;gt;&lt;br /&gt;
||5000&lt;br /&gt;
||BulkCopyBatchSize given for QPR ProcessAnalyzer database SqlBulkCopy operations.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span style=&amp;quot;color:lightgrey;&amp;quot;&amp;gt;SandboxDatabaseBulkCopyBatchSize&amp;lt;/span&amp;gt;&lt;br /&gt;
||5000&lt;br /&gt;
||BulkCopyBatchSize given for sandbox SqlBulkCopy operations.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== In-memory Calculation Settings ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Default&amp;amp;nbsp;value	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||NumberOfParallelModelReaders&lt;br /&gt;
||4&lt;br /&gt;
||Models and datatable contents can be loaded with multiple simultaneous connections to the database to speed up the loading. This setting determines how many parallel loaders/readers at maximum (loaders are loading at the same time). For smaller models there are less parallel loaders than the defined limit: If there are less than 100000 rows in the table, there is only one loader. If there are less than 200000 rows in the table, there are only two loaders, and so on. &lt;br /&gt;
&lt;br /&gt;
The more there are parallel loaders, the more processor load and network bandwidth is consumed, and other operations in QPR ProcessAnalyzer might slow down. Note also that the performance optimum is achieved with a certain number of parallel loaders which differs between environment. Thus to achieve the best performance, data loading should be tested with different number of parallel loaders. Increasing number of parallel loaders beyond the optimum decreases the performance.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
||StartupModelLoadingMaxParallelism&lt;br /&gt;
||2&lt;br /&gt;
||Maximum number of QPR ProcessAnalyzer models that are loaded into memory simultaneously by the [[Automatic_Model_Loading_on_Server_Startup|Automatic Loading on Server Startup]]. If there are more models to be loaded on the server startup than this setting, loading for the rest of the models is started one by one when previous model loadings are completed. If this setting is not defined, &#039;&#039;&#039;2&#039;&#039;&#039; is used as a default value.&lt;br /&gt;
&lt;br /&gt;
Loading more models at the same time will speed up the whole model loading process, but on the other hand, it causes more load on the system, which affects the system responsiveness for users. Model loading consists of (1) transferring data from the datasource to QPR ProcessAnalyzer and (2) loaded data preprocessing into a model. The former uses mainly network bandwidth (if datasource is in a different server) and the latter uses mainly processor capacity in the QPR ProcessAnalyzer server. &lt;br /&gt;
&lt;br /&gt;
This setting affects only the model loading during the server startup and it doesn&#039;t restrict models loadings initiated by users.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== SAML 2.0 Federated Authentication Settings ==&lt;br /&gt;
Note that the SAMLMetadataUrl and ServiceProviderLocation are mandatory for the federated authentication to work. Having both ExternalOAuthServerConfiguration and SAML authentication configured at the same time is not supported.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||SAMLMetadataUrl&lt;br /&gt;
||&lt;br /&gt;
Metadata URL of the identity provider (IdP). Check that the metadata url can actually be opened using a web browser and is publicly available. The metadata is an XML document starting with &#039;&#039;&#039;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&#039;&#039;&#039; followed by an &#039;&#039;&#039;EntityDescriptor&#039;&#039;&#039; tag. The metadata URL might look &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;https://your.federated.identity.provider.com/saml/metadata&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;. This setting is mandatory for the SAML authentication to work.&lt;br /&gt;
|-&lt;br /&gt;
||ServiceProviderLocation&lt;br /&gt;
||&lt;br /&gt;
Specifies the QPR ProcessAnalyzer server location (the root path which contains e.g. the &#039;&#039;ui&#039;&#039; folder). It&#039;s used by the url to redirect back to QPR ProcessAnalyzer after a successful authentication from the identity provider. The setting is defined in the following form: &#039;&#039;&#039;https://&amp;lt;hostname&amp;gt;/qprpa&#039;&#039;&#039;, for example &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;https://customer.onqpr.com/qprpa&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;. Note that the actual redirect back url is &#039;&#039;&#039;https://&amp;lt;hostname&amp;gt;/qprpa/api/Saml2/Acs&#039;&#039;&#039; (/api/Saml2/Acs is automatically included to the url). This setting is mandatory for the SAML authentication to work. Note that if this reply url is configured the identity provider, it must match with the ServiceProviderLocation setting.&lt;br /&gt;
|-&lt;br /&gt;
||SAMLUserIdAttribute&lt;br /&gt;
||&lt;br /&gt;
Name of the SAML attribute in the assertion that will be used as the user&#039;s login name. If this field is not defined, the &#039;&#039;&#039;saml:Assertion&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;saml:Subject&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;saml:NameID&#039;&#039;&#039; attribute in the assertion is used. If this setting is given, one of the &#039;&#039;&#039;saml:Assertion&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;saml:AttributeStatement&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;saml:Attribute&#039;&#039;&#039; elements in the assertion is used (the &#039;&#039;&#039;Name&#039;&#039;&#039; attribute in the &#039;&#039;&#039;saml:Attribute&#039;&#039;&#039; element is used for matching). Please note that the saml:NameID element is different than the usual SAML attributes that are defined by the saml:Attribute elements. For example, if an email address is used as a user id, the value of the setting could be for example &#039;&#039;&amp;lt;nowiki&amp;gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress&amp;lt;/nowiki&amp;gt;&#039;&#039;.&lt;br /&gt;
|-&lt;br /&gt;
||SAMLGroupsAttribute&lt;br /&gt;
||Attribute name in SAML assertion that is mapped to user groups in QPR ProcessAnalyzer. The user group names are case sensitive. When a user logs in, the user is added to and removed from groups based on the information in the SAML assertion. If this setting is not configured, users are not added to or removed from groups automatically. Note that the user needs to login for the groups to be synchronized. If a group doesn&#039;t exist in QPR ProcessAnalyzer, that group is skipped.&lt;br /&gt;
&lt;br /&gt;
In the SAML assertion, attributes are in the saml:Assertion &amp;gt; saml:AttributeStatement &amp;gt; saml:Attribute elements (the Name attribute in the saml:Attribute element is used for matching).&lt;br /&gt;
|-&lt;br /&gt;
||SAMLEncryptionCertificate&lt;br /&gt;
||This setting defines a PFX formatted X.509 certificate (defined in RCF 1422) used to encrypt SAML assertions. The public key of the certificate is published in the service provider metadata, where the identity provider can read it and encrypt SAML assertions. QPR ProcessAnalyzer as the service provider uses the corresponding private key of the certificate to decrypt SAML assertions. The setting needs to be a PFX formatted certificate file that is base64 encoded and it doesn&#039;t contain the BEGIN CERTIFICATE etc. header or footer lines. This setting is needed only when using the SAML assertions encryption. Even though this setting is defined, the SAML assertions are not required to be encrypted. More information how to create the certificate file (https://stackoverflow.com/questions/16480846/x-509-private-public-key) and convert it to base64 (https://stackoverflow.com/questions/46959822/base-64-encoded-form-of-the-pfx-file).&lt;br /&gt;
|-&lt;br /&gt;
||SAMLSigningCertificate&lt;br /&gt;
||This setting defines a PFX formatted X.509 certificate (defined in RCF 1422) used to sign SAML authentication requests sent from QPR ProcessAnalyzer to the identity provider. The public key of the certificate is published in the service provider metadata, where the identity provider can read it, to verify the authenticity of the SAML requests. The setting needs to be a PFX formatted certificate file that is base64 encoded and it doesn&#039;t contain the BEGIN CERTIFICATE etc. header or footer lines. If this setting is not defined, the internal hard-coded signing certificate is used. More information how to create the certificate file (https://stackoverflow.com/questions/16480846/x-509-private-public-key) and convert it to base64 (https://stackoverflow.com/questions/46959822/base-64-encoded-form-of-the-pfx-file).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== OAuth 2.0 Authentication Settings ==&lt;br /&gt;
Having both ExternalOAuthServerConfiguration and SAML authentication configured at the same time is not supported.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||ExternalOAuthServerConfiguration&lt;br /&gt;
||Used to configure OAuth 2.0 compatible OAuth server settings for server that is used to authenticate the user signing in to ProcessAnalyzer. If not set, external OAuth server will not be used for authentication. If set, contains a string representation of a JSON object that supports the following properties:&lt;br /&gt;
* &#039;&#039;&#039;Authority&#039;&#039;&#039; (string): OAuth authority URL for authentication. E.g., https://accounts.google.com/&lt;br /&gt;
* &#039;&#039;&#039;Audience&#039;&#039;&#039; (string): Mandatory. OAuth audience/client ID for validating OAuth tokens.&lt;br /&gt;
* &#039;&#039;&#039;AuthorizeUrlOverride&#039;&#039;&#039; (string): Override URL for the OAuth authorization endpoint. If empty or not defined, the default URL from the authority&#039;s discovery document is used.&lt;br /&gt;
* &#039;&#039;&#039;TokenUrlOverride&#039;&#039;&#039; (string): Override URL for the OAuth token endpoint. If empty or not defined, the default URL from the authority&#039;s discovery document is used.&lt;br /&gt;
* &#039;&#039;&#039;UserInfoUrlOverride&#039;&#039;&#039; (string): Override URL to fetch user information from the OAuth provider. If empty or not defined, the default URL from the authority&#039;s discovery document is used. Should not be used if OpendID Connect is to be used as access token validation is skipped.&lt;br /&gt;
* &#039;&#039;&#039;ClientSecret&#039;&#039;&#039; (string): OAuth client secret for confidential client authentication. If configured, this value is sent as the client_secret parameter when exchanging authorization codes for tokens.&lt;br /&gt;
* &#039;&#039;&#039;Issuer&#039;&#039;&#039; (string): OAuth issuer for validating OAuth tokens. If empty, the authority URL&#039;s issuer is used.&lt;br /&gt;
* &#039;&#039;&#039;UserNameClaim&#039;&#039;&#039; (string): Name of the claim whose value is to be used as the name of the authenticated user. The default value is &amp;quot;preferred_username&amp;quot;.&lt;br /&gt;
* &#039;&#039;&#039;UserGroupsClaim&#039;&#039;&#039; (string): Name of the claim whose value is to be used as the names of the user groups the authenticated user belongs to. When a user logs in, the user is added to and removed from groups based on the information in the UserGroupsClaim. If this setting is not configured, users are not added to or removed from groups automatically. Note that the user needs to login for the groups to be synchronized. If a group doesn&#039;t exist in QPR ProcessAnalyzer, that group is skipped. The default value is empty, i.e. groups are not synchronized.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== SMTP Server Settings ==&lt;br /&gt;
SMTP server settings are needed for QPR ProcessAnalyzer to send email messages. Email sending is used by the [[Email_Notifications|notifications]] and the [[Generic_Functions_in_QPR_ProcessAnalyzer#SendEmail|SendEmail]] function in the expression language.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||SmtpServer&lt;br /&gt;
||DNS name, host name or IP address of the SMTP server. Mandatory setting for the email sending to work.&lt;br /&gt;
|-&lt;br /&gt;
||SmtpPort&lt;br /&gt;
||TCP port number of the SMTP server. If not defined, port 25 is used by default.&lt;br /&gt;
|-&lt;br /&gt;
||SmtpAuthenticationUsername&lt;br /&gt;
||User name for authenticating to the SMTP server. If not defined, no authentication is used to connect to the SMTP server.&lt;br /&gt;
|-&lt;br /&gt;
||SmtpFromAddress&lt;br /&gt;
||Email address where email messages sent by QPR ProcessAnalyzer appear to be coming from. This doesn&#039;t need to be a real email address, although the address used may affect email spam filters. The setting configured here is the default email address to use in following cases:&lt;br /&gt;
* &#039;&#039;From address&#039;&#039; is not set for the email notifications&lt;br /&gt;
* &#039;&#039;From&#039;&#039; parameter is not defined for the expression language &#039;&#039;SendEmail&#039;&#039; function&lt;br /&gt;
* &#039;&#039;EmailFrom&#039;&#039; parameter is not defined for the SQL Scripting SendEmail operation&lt;br /&gt;
|-&lt;br /&gt;
||SmtpAuthenticationPassword&lt;br /&gt;
||Password for authenticating to the SMTP server.&lt;br /&gt;
|-&lt;br /&gt;
||SmtpEnableSSL&lt;br /&gt;
||Use value &#039;&#039;&#039;True&#039;&#039;&#039; or &#039;&#039;&#039;False&#039;&#039;&#039; depending whether TLS connection to the SMTP server is used or not. If not defined, &#039;&#039;False&#039;&#039; is the default value.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== MCP Server Settings ==&lt;br /&gt;
MCP server settings are needed for QPR ProcessAnalyzer to act as an [[QPR_ProcessAnalyzer_as_MCP_Server|MCP server]].&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Description&lt;br /&gt;
|-&lt;br /&gt;
||McpServerConfiguration&lt;br /&gt;
||Used to configure the MCP server built-in to ProcessAnalyzer server. If not set, MCP server functionality is disabled and MCP clients can&#039;t access to this server using MCP. If set, contains a string representation of a JSON object that supports the following properties:&lt;br /&gt;
* &#039;&#039;&#039;McpApiKey&#039;&#039;&#039; (string): If defined and not empty, defines the API key that can be used to connect to QPR ProcessAnalyzer MCP server without any other authentication. Default value is empty.&lt;br /&gt;
|-&lt;br /&gt;
||BuiltInOAuthServerConfiguration&lt;br /&gt;
||Used to configure OAuth 2.0 compatible OAuth server settings for OAuth server built-in to ProcessAnalyzer server. If not set, built-in OAuth server functionality is disabled and clients can&#039;t connect to this server using OAuth. If set, contains a string representation of a JSON object that supports the following properties: &lt;br /&gt;
* &#039;&#039;&#039;Issuer&#039;&#039;&#039; (string): OAuth issuer, which identifies a trusted authorization server that authenticates users and issues OAuth 2.0 access tokens and JSON Web Tokens (JWTs). If not defined or empty, default value is used, which is of format: &amp;lt;QPR ProcessAnalyzer server&#039;s base URL&amp;gt;/builtin-oauth. For example: https://example.com/builtin-oauth. The default value is empty.&lt;br /&gt;
* &#039;&#039;&#039;AcceptedAudiences&#039;&#039;&#039; (array of strings): Array of strings that define all the accepted audiences this QPR ProcessAnalyzer server is serving. When authorizing user using OAuth, these values are matched with the audience-parameter (a.k.a. client id) of the authorization. Only requests with a value that matches a value in this array are accepted. If null, audience-parameters are not validated at all. Instead, all authorization requests will pass the audience validation check. This also enables [https://datatracker.ietf.org/doc/html/rfc7591 Dynamic Client Registration Protocol (DCR)]. The default value is an empty array.&lt;br /&gt;
* &#039;&#039;&#039;SigningKey&#039;&#039;&#039; (string): Signing key for the built-in OAuth identity provider. If empty, generates a non-deterministic key based on the physical system where QPR ProcessAnalyzer is running. NOTE: Once QPR ProcessAnalyzer server is restarted, these non-deterministic keys no longer work. If defined, string must contain the key either in PEM ([https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsa.importfrompem?view=net-10.0 RFC 7468 PEM-encoded key]) or JSON ([https://datatracker.ietf.org/doc/html/rfc7517 RFC 7517]) format. The default value is empty.&lt;br /&gt;
* &#039;&#039;&#039;TokenLifetimeSeconds&#039;&#039;&#039; (integer): Token lifetime in seconds for the built-in OAuth identity provider. After access token created by built-in gets older than this lifetime, it becomes unusable and a new token has to be created. The default value is 3600.&lt;br /&gt;
* &#039;&#039;&#039;DisableExternalOAuthForwarding&#039;&#039;&#039; (boolean): Can be used to disable forwarding OAuth requests to any configured external OAuth authorization server or SAML identity provider. If set, a QPR ProcessAnalyzer&#039;s own login view functionality is always used when authorizing a user. The default value is false.&lt;br /&gt;
Example configuration:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Issuer&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
  &amp;quot;AcceptedAudiences&amp;quot;: [&amp;quot;qpr-processanalyzer&amp;quot;],&lt;br /&gt;
  &amp;quot;SigningKey&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
  &amp;quot;TokenLifetime&amp;quot;: 3600&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Readonly Information ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: left&amp;quot;&lt;br /&gt;
!Name	!!Description&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;span style=&amp;quot;color:lightgrey;&amp;quot;&amp;gt;DatabaseVersion&amp;lt;/span&amp;gt;&lt;br /&gt;
||Database schema version. It will be updated automatically when the newer version of QPR ProcessAnalyzer Server connects to the database and performs migration for the database schema.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span style=&amp;quot;color:lightgrey;&amp;quot;&amp;gt;InitializationScriptDatabaseVersion&amp;lt;/span&amp;gt;&lt;br /&gt;
||Database version that was when the database was initialized when the software was installed. Do not change this setting.&lt;br /&gt;
|-&lt;br /&gt;
||&amp;lt;span style=&amp;quot;color:lightgrey;&amp;quot;&amp;gt;MinimumDatabaseVersion&amp;lt;/span&amp;gt;&lt;br /&gt;
||Minimum allowed database version for QPR ProcessAnalyzer Server connecting to the database. This is a legacy setting and it should not be used.&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Create_Predicted_Eventlog&amp;diff=27626</id>
		<title>Create Predicted Eventlog</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Create_Predicted_Eventlog&amp;diff=27626"/>
		<updated>2026-01-20T14:15:37Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article has instructions how to install, configure and use eventlog predictions. The prediction creates a new model that contains the source model data and the predictions. It&#039;s able to predict case attributes for the generated new cases and event attributes for the predicted events. By default, predictions use [https://en.wikipedia.org/wiki/Transformer_(deep_learning) Transformer] neural network architecture. However, also [https://en.wikipedia.org/wiki/Long_short-term_memory LSTM] and [https://en.wikipedia.org/wiki/Gated_recurrent_unit GRU]-based architectures are supported.&lt;br /&gt;
&lt;br /&gt;
To distinguish the real (source data) and predicted events and cases, there are following attributes in the model:&lt;br /&gt;
* Event attribute &#039;&#039;&#039;Predicted&#039;&#039;&#039; denotes whether the event is from the source data (&#039;&#039;false&#039;&#039;) or whether it&#039;s predicted (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
* Case attribute &#039;&#039;&#039;Generated&#039;&#039;&#039; denotes whether the case is in the source data (&#039;&#039;false&#039;&#039;) or whether the prediction generated it as a new case (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for prediction ==&lt;br /&gt;
Following prerequisites need to be fulfilled to run the eventlog prediction:&lt;br /&gt;
* QPR ProcessAnalyzer 2024.8 or later in use&lt;br /&gt;
* Snowflake connection is configured&lt;br /&gt;
* Source models are stored to Snowflake&lt;br /&gt;
&lt;br /&gt;
== Install prediction to Snowflake ==&lt;br /&gt;
To install the eventlog prediction to Snowflake:&lt;br /&gt;
# Go to Snowflake, and create a Snowflake-managed stage with name &#039;&#039;&#039;DECISION_INTELLIGENCE&#039;&#039;&#039; to the same schema configured to QPR ProcessAnalyzer (in the Snowflake connection string). Use settings in the following image: [[File:Create_Snowflake_stage.png]]&lt;br /&gt;
# Open the created stage and upload the &#039;&#039;&#039;predict.pyz&#039;&#039;&#039; file into the stage (ask the file from your QPR representative).&lt;br /&gt;
# Create the following procedure to the same schema:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sql&amp;quot;&amp;gt;&lt;br /&gt;
CREATE OR REPLACE PROCEDURE QPRPA_SP_PREDICTION(&amp;quot;CONFIGURATION&amp;quot; OBJECT)&lt;br /&gt;
RETURNS OBJECT&lt;br /&gt;
LANGUAGE PYTHON&lt;br /&gt;
STRICT&lt;br /&gt;
RUNTIME_VERSION = &#039;3.11&#039;&lt;br /&gt;
PACKAGES = (&#039;nltk&#039;,&#039;numpy&#039;,&#039;networkx&#039;,&#039;pandas&#039;,&#039;scikit-learn&#039;,&#039;snowflake-snowpark-python&#039;,&#039;tensorflow==2.12.0&#039;,&#039;dill&#039;,&#039;psutil&#039;,&#039;prophet&#039;,&#039;holidays&#039;,&#039;python-kubernetes&#039;,&#039;docker-py&#039;,&#039;cryptography&#039;)&lt;br /&gt;
HANDLER = &#039;main&#039;&lt;br /&gt;
EXECUTE AS OWNER&lt;br /&gt;
AS &#039;&lt;br /&gt;
import sys&lt;br /&gt;
def main(session, parameters_in: dict) -&amp;gt; dict:&lt;br /&gt;
	session.file.get(&#039;&#039;@decision_intelligence/predict.pyz&#039;&#039;, &#039;&#039;/tmp&#039;&#039;)&lt;br /&gt;
	sys.path.append(&#039;&#039;/tmp/predict.pyz&#039;&#039;)&lt;br /&gt;
	import predict&lt;br /&gt;
	return predict.main(session, parameters_in)&lt;br /&gt;
&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Create prediction script in QPR ProcessAnalyzer ==&lt;br /&gt;
1. Create the following example expression script (e.g., with name &#039;&#039;&#039;Create prediction model&#039;&#039;&#039;):&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let completeCaseEventTypeNames = [&amp;quot;&amp;lt;event type name found only in complete cases&amp;gt;&amp;quot;, &amp;quot;&amp;lt;another event type name&amp;gt;&amp;quot;, &amp;quot;...&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
let eventTypeColumnName = sourceModel.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
_system.ML.GeneratePredictionModel(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My prediction model&amp;quot;,      // Name of the PA model to generate to the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,         // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,     // Target project to create the model into.&lt;br /&gt;
  &amp;quot;TrainingConfiguration&amp;quot;: #{         // Training parameters.&lt;br /&gt;
    &amp;quot;num_epochs_to_train&amp;quot;: 200&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;GenerationConfiguration&amp;quot;: #{       // Model generation parameters.&lt;br /&gt;
    &amp;quot;cases_to_generate&amp;quot;: 1000&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;TrainingDataFilter&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [&lt;br /&gt;
      #{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
        &amp;quot;Items&amp;quot;: [&lt;br /&gt;
          #{&lt;br /&gt;
            &amp;quot;Type&amp;quot;: &amp;quot;EventAttributeValue&amp;quot;,&lt;br /&gt;
            &amp;quot;Attribute&amp;quot;: eventTypeColumnName,&lt;br /&gt;
            &amp;quot;Values&amp;quot;: completeCaseEventTypeNames&lt;br /&gt;
          }&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;IncompleteCasesFilter&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [&lt;br /&gt;
      #{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;ExcludeCases&amp;quot;,&lt;br /&gt;
        &amp;quot;Items&amp;quot;: [&lt;br /&gt;
          #{&lt;br /&gt;
            &amp;quot;Type&amp;quot;: &amp;quot;EventAttributeValue&amp;quot;,&lt;br /&gt;
            &amp;quot;Attribute&amp;quot;: eventTypeColumnName,&lt;br /&gt;
            &amp;quot;Values&amp;quot;: completeCaseEventTypeNames&lt;br /&gt;
          }&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;RecreatePredictionModel&amp;quot;: true,    // Should a prediction model be overwritten if one already exists for this source model and target model name combination.&lt;br /&gt;
  &amp;quot;TrainingCaseSampleSize&amp;quot;: 10000     // Maximum number of cases to use from the source model (random sampled).&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;2. Configure prediction for the previously created script as instructed in the next chapter. At minimum, replace the tags listed below with some suitable values:&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;project name&amp;gt;&#039;&#039;&#039;: Name of the project in which the source model is located.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;model name&amp;gt;&#039;&#039;&#039;: Name of the model to be used as source model. This data in this source model will be used to train the prediction model so that it can generate new cases and continuations for incomplete existing cases.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;event type name found only in complete cases&amp;gt;&#039;&#039;&#039;: This example script has been hard-coded to determine whether a case is complete or incomplete based on the existence of this event type.&lt;br /&gt;
&lt;br /&gt;
== Configure prediction ==&lt;br /&gt;
Prediction script has the following settings in the GeneratePredictionModel call:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039;: Name of the QPR ProcessAnalyzer model that is created to the target project. The model will contain the source model content and the predictions.&lt;br /&gt;
* &#039;&#039;&#039;SourceModel&#039;&#039;&#039;: Source model for which the prediction is made. Model can be selected for example based on id with ModelById function or by name with ModelByName function.&lt;br /&gt;
* &#039;&#039;&#039;TargetProject&#039;&#039;&#039;: Target project to create the new model into.&lt;br /&gt;
* &#039;&#039;&#039;RecreatePredictionModel&#039;&#039;&#039;: When &#039;&#039;true&#039;&#039;, a new ML model is trained when the script is run. When &#039;&#039;false&#039;&#039;, the prediction is run using possibly pre-existing ML model. &lt;br /&gt;
* &#039;&#039;&#039;TrainingConfiguration&#039;&#039;&#039;: Training parameters.&lt;br /&gt;
** &#039;&#039;&#039;attributes&#039;&#039;&#039;: Attribute configurations (for more information, see the chapter below).&lt;br /&gt;
** &#039;&#039;&#039;generate_start_time_trend_images&#039;&#039;&#039;: If set to true, two images will be generated for each cross validated Prophet-parameter combination and also for the final selected parameters showing the results of plot and plot_components-functions. &lt;br /&gt;
*** The images will be generated into stage files with the following path names:&lt;br /&gt;
**** plot: @decision_intelligence_testing/{model_name}_st_RMSE={rmse_value or &amp;quot;final&amp;quot;}.png&lt;br /&gt;
****plot_components:  @decision_intelligence_testing/{model_name}_st_RMSE={rmse_value or &amp;quot;final&amp;quot;}_comp.png&lt;br /&gt;
***The default value is false.&lt;br /&gt;
** &#039;&#039;&#039;max_num_case_clusters&#039;&#039;&#039;: Set the maximum number of clusters to divide the case attribute values into.&lt;br /&gt;
*** The default value is 20.&lt;br /&gt;
** &#039;&#039;&#039;max_num_traces_in_training&#039;&#039;&#039;: Set the maximum number of traces used in training.&lt;br /&gt;
*** When training, every case of length N will be split into N traces (a.k.a. prefixes) (p_1, ..., p_N), where p_x contains first x events of the all events of the full case.&lt;br /&gt;
**** If there are more traces available than this configured value, cases to include will be random sampled so that the maximum is exceeded by at most one case.&lt;br /&gt;
**** If null, all the traces will be used, no matter what (may easily lead to running out of memory).&lt;br /&gt;
**** The default value is 100000.&lt;br /&gt;
** &#039;&#039;&#039;num_epochs_to_train&#039;&#039;&#039;: How many times the training set is used in training. The best performing model out of all the iterations will be selected.&lt;br /&gt;
*** The default value is 500.&lt;br /&gt;
** &#039;&#039;&#039;num_extra_years_to_reserve_in_created_model&#039;&#039;&#039;: Number of additional years after the year of the last timestamp in the training data to reserve to the capacity of the created ML model, allowing the model to  be able to predict timestamps in the range between the minimum timestamp year in the training data and the maximum timestamp year plus this value.&lt;br /&gt;
*** The default value is 20.&lt;br /&gt;
** &#039;&#039;&#039;reserve_extra_sequence_length&#039;&#039;&#039;: How many extra events to reserve space for in the ML model compared to the number of events the longest case in the training data has.&lt;br /&gt;
*** The default value is 5.&lt;br /&gt;
** &#039;&#039;&#039;samples_per_epoch&#039;&#039;&#039;: If not null, specifies (approximately) how many traces/prefixes will be used to represent one epoch of data in the training. The actual value used will be made divisible by batch_size using this formula:&lt;br /&gt;
***max(floor(samples_per_epoch / batch_size), 1) * batch_size&lt;br /&gt;
***If null, every epoch will use all the traces/prefixes in the training data.&lt;br /&gt;
***The default value is null&lt;br /&gt;
**&#039;&#039;&#039;validation_split&#039;&#039;&#039;: Percentage of traces/prefixes to use to evaluate the loss and any model metrics at the end of each epoch. The model will not be trained on this data.&lt;br /&gt;
***If 0, separate validation data will not be used. Instead, all the training data will be used also as validation data.&lt;br /&gt;
***The default value is 0.&lt;br /&gt;
* &#039;&#039;&#039;GenerationConfiguration&#039;&#039;&#039;: Event generation parameters. When null, no generation is done. For example, following parameters are supported:&lt;br /&gt;
** &#039;&#039;&#039;avoid_repeated_activities&#039;&#039;&#039;: Array of activity names that should occur at most once in any case. The probability of selecting any of the activities specified in this configuration more than once is set to be 0. &lt;br /&gt;
*** Empty array means that activity generation is not restricted by this setting at all. &lt;br /&gt;
*** null value means that there should not be any activities that can occur more than once (shortcut for specifying all the activity names).&lt;br /&gt;
*** The default value is an empty array.&lt;br /&gt;
** &#039;&#039;&#039;cases_to_generate&#039;&#039;&#039;: Maximum number cases to create. The number of created cases is further limited by the capabilities of the trained model and the &#039;&#039;case_generation_start_time&#039;&#039; and &#039;&#039;case_generation_end_time&#039;&#039; parameters.&lt;br /&gt;
*** The default value is such that the number of cases,  by itself, is not limited.&lt;br /&gt;
** &#039;&#039;&#039;case_generation_start_time&#039;&#039;&#039;: If defined, new cases will be generated after this timestamp (given as string in ISO datetime format). &lt;br /&gt;
*** If undefined, the latest start event timestamp used in the training data is used.&lt;br /&gt;
*** The default value is undefined.&lt;br /&gt;
** &#039;&#039;&#039;case_generation_end_time&#039;&#039;&#039;: If defined, new events and cases will not be generated after this timestamp (given as string in ISO datetime format). E.g., &amp;quot;2015-01-01T00:00:00&amp;quot;.&lt;br /&gt;
*** The default value is unlimited (only limit comes from the capacity of the trained model)&lt;br /&gt;
** &#039;&#039;&#039;generate_debug_event_attributes&#039;&#039;&#039;: &lt;br /&gt;
*** If true, additional columns will be added containing, e.g., probabilities of the selected activity and other activities.&lt;br /&gt;
*** The default value is false.&lt;br /&gt;
** &#039;&#039;&#039;max_num_events&#039;&#039;&#039;:&lt;br /&gt;
*** Specifies the maximum number of events to generate for any case.&lt;br /&gt;
*** If unspecified (=default), the value equals to &#039;&#039;&amp;lt;the maximum number of events in any case in the training data&amp;gt;&#039;&#039;+&#039;&#039;&amp;lt;the value of reserve_extra_sequence_length in training&amp;gt;&#039;&#039;.&lt;br /&gt;
** &#039;&#039;&#039;min_prediction_probability &#039;&#039;&#039;: &lt;br /&gt;
*** The minimum probability of any prediction. If the probability of a prediction is lower than this, it will never be picked. &lt;br /&gt;
*** The default value is 0.01.&lt;br /&gt;
** &#039;&#039;&#039;temperature&#039;&#039;&#039;: &lt;br /&gt;
*** If 0, the generated next activity will always be the one that is the most probable. &lt;br /&gt;
*** If 1, the generated next activity is purely based on the probabilities returned by the trained ML model. &lt;br /&gt;
*** This behavior is interpolated when using values between 0 and 1.&lt;br /&gt;
*** The default value is 0.9.&lt;br /&gt;
* &#039;&#039;&#039;TrainingDataFilter&#039;&#039;&#039;: [[Filtering_in_QPR_ProcessAnalyzer_Queries|Filter]] to select specific cases that are used to train the prediction model. This filter is required to train the model only using the completed cases. Uncompleted cases should not be used for the training, so the model doesn&#039;t incorrectly learn that cases should end like that.&lt;br /&gt;
* &#039;&#039;&#039;IncompleteCasesFilter&#039;&#039;&#039;: Optional [[Filtering_in_QPR_ProcessAnalyzer_Queries|filter]] to select which cases the prediction is made for. To improve performance of the prediction, it&#039;s recommended to include only the incomplete cases for which new events might appear, and skip the completed cases for which new events are not expected anymore.&lt;br /&gt;
* &#039;&#039;&#039;TrainingCaseSampleSize&#039;&#039;&#039;: Maximum number of cases to take from the source model (cases are selected randomly). Use a lower setting to speed up the ML model training. The greater the value, the more subtle phenomena the prediction can learn from the data.&lt;br /&gt;
&lt;br /&gt;
== Attribute configuration ==&lt;br /&gt;
Attribute configuration is used in &#039;&#039;&#039;TrainingConfiguration&#039;&#039;&#039; (see the chapter above) to configure which event- and case attributes should be used in prediction model and how they are used.&lt;br /&gt;
&lt;br /&gt;
The configuration is in the top level split into two sections: &amp;quot;event&amp;quot; and &amp;quot;case&amp;quot;. &amp;quot;Event&amp;quot; is used to configure event attributes, whereas &amp;quot;case&amp;quot; is used for case attributes.&lt;br /&gt;
&lt;br /&gt;
The next level supports one value: &amp;quot;input&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The next level after that, supports the following settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;categorical_groups&#039;&#039;&#039;: An array of categorical attribute group configuration objects used to define groups of attributes that will be bundled together in the trained model, either as separate input- or output features. Each attribute group will form its own input- or output vector used in the model training and generation.&lt;br /&gt;
** If null, only one group will be created with all the available categorical attributes included.&lt;br /&gt;
** The following settings are supported by these objects:&lt;br /&gt;
*** &#039;&#039;&#039;attributes&#039;&#039;&#039;: An array of attribute names.&lt;br /&gt;
**** If null, all the input attributes are to be included in this group.&lt;br /&gt;
*** &#039;&#039;&#039;max_num_clusters&#039;&#039;&#039;: The maximum number of clusters (input- or output vector feature values) to use to represent this group of attributes.&lt;br /&gt;
**** Default value: 20&lt;br /&gt;
**** NOTE: Clustering is used by default to convert a set of attribute values into an input- or output vector used by the prediction model.&lt;br /&gt;
*** &#039;&#039;&#039;ignore_values_threshold:&#039;&#039;&#039; The minimum percentage of objects having a specific attribute value in order for that attribute value to be taken into account as unique attribute value within this categorical group.&lt;br /&gt;
**** Depending on the context, the default value is any one of the following configurations:&lt;br /&gt;
***** ignore_values_threshold_for_case_attribute_values&lt;br /&gt;
****** Used when clustering case attributes when generating attribute values for generated new cases.&lt;br /&gt;
****** Default value is 0.01.&lt;br /&gt;
***** ignore_values_threshold_for_case_attributes&lt;br /&gt;
****** Used when clustering case attributes.&lt;br /&gt;
****** Default value is 0.1.&lt;br /&gt;
***** ignore_values_threshold_for_event_attributes&lt;br /&gt;
****** Used when clustering event attributes.&lt;br /&gt;
****** Default value is 0.1.&lt;br /&gt;
* &#039;&#039;&#039;columns&#039;&#039;&#039;: An array of attribute column configuration objects used to define columns in the input data that are to be used as event- or case attributes.&lt;br /&gt;
** If null, all the columns will be included as categorical attributes (except case id, event type (only for event) and timestamp (only for event) columns).&lt;br /&gt;
** The following settings are supported by these objects:&lt;br /&gt;
*** &#039;&#039;&#039;label&#039;&#039;&#039;: Column name.&lt;br /&gt;
*** &#039;&#039;&#039;type&#039;&#039;&#039;: Type of the column. Supported types are:&lt;br /&gt;
**** &#039;&#039;&#039;categorical&#039;&#039;&#039;: Values can take on one of a limited, and usually fixed, number of possible values.&lt;br /&gt;
**** &#039;&#039;&#039;numeric&#039;&#039;&#039;: Value is considered as a continuous numeric value.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
Use all event attributes as input for the prediction model. In addition, additional machine learning input vector for SAP_User-event data column supporting at most 10 unique values.&lt;br /&gt;
&lt;br /&gt;
In addition, for case attributes, only &amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;, &amp;quot;Account Manager&amp;quot; and &amp;quot;Customer Group&amp;quot; case data columns are used as categorical attributes and &amp;quot;Cost&amp;quot; as numeric attribute. Furthermore, the four categorical case attributes are grouped into three groups, each of which are used as its own input vector for the prediction model.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When generating, all event attributes will be included for generated events as columns. Generated cases will have only &amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;, &amp;quot;Account Manager&amp;quot;,  &amp;quot;Customer Group&amp;quot;, and &amp;quot;Cost&amp;quot; columns.&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;attributes&amp;quot;: #{&lt;br /&gt;
  &amp;quot;event&amp;quot;: #{&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;categorical_groups&amp;quot;: [&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;attributes&amp;quot;: None&lt;br /&gt;
        },&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;attributes&amp;quot;: [&amp;quot;SAP_User&amp;quot;],&lt;br /&gt;
          &amp;quot;max_num_clusters&amp;quot;: 10&lt;br /&gt;
        }&lt;br /&gt;
      ],&lt;br /&gt;
      &amp;quot;columns&amp;quot;: None&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;case&amp;quot;: #{&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;categorical_groups&amp;quot;: [#{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Account Manager&amp;quot;]&lt;br /&gt;
      }, #{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Customer Group&amp;quot;]&lt;br /&gt;
      }, #{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;]&lt;br /&gt;
      }],&lt;br /&gt;
      &amp;quot;columns&amp;quot;: [&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Region&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Product Group&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Account Manager&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Customer Group&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Cost&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;numeric&amp;quot; }&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Predicting case attribute values ==&lt;br /&gt;
QPR ProcessAnalyzer can also be used to, e.g.,  predict the final values of case attributes of running cases. The following script gives an example on how to perform this.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let caseAttributeToPredict = &amp;quot;&amp;lt;name of the case attribute&amp;gt;&amp;quot;;&lt;br /&gt;
let resultModelName = &amp;quot;&amp;lt;name of the model to be created/replaced&amp;gt;&amp;quot;;&lt;br /&gt;
let generateDebugCaseAttributes = false; // Set to true to generate columns for prediction probabilities.&lt;br /&gt;
let casesToPredictFilter = &amp;quot;&amp;lt;JSON filter for cases for which the prediction is to be performed&amp;gt;&amp;quot;;&lt;br /&gt;
let casesToUseForTrainingFilter = &amp;quot;&amp;lt;JSON filter for cases to be used for ML model training&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
&lt;br /&gt;
_system.ML.GenerateCaseAttributePredictionModel(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: resultModelName,                                     // Name of the PA model to generate ti the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,                                  // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,                              // Target project to create the model into.&lt;br /&gt;
  &amp;quot;RecreatePredictionModel&amp;quot;: false,                            // Should a prediction model be overwritten if one already exists for this source model and target model name combination. &lt;br /&gt;
  &amp;quot;TrainingCaseSampleSize&amp;quot;: 10000,                             // Maximum number of cases to use from the source model (random sampled). &lt;br /&gt;
  &amp;quot;CommonConfiguration&amp;quot;: #{                                    // Common parameters used by both training and generation.&lt;br /&gt;
    &amp;quot;output_case_attribute_groups&amp;quot;: [#{&lt;br /&gt;
      &amp;quot;attributes&amp;quot;: [caseAttributeToPredict]                   // Attribute whose value is to be predicted.&lt;br /&gt;
    }]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;TrainingConfiguration&amp;quot;: #{                                  // Training parameters.&lt;br /&gt;
    &amp;quot;max_num_case_attribute_clusters&amp;quot;: 80,&lt;br /&gt;
    &amp;quot;num_epochs_to_train&amp;quot;: 100&lt;br /&gt;
  },                            &lt;br /&gt;
  &amp;quot;GenerationConfiguration&amp;quot;: #{                                // Case attribute generation parameters.&lt;br /&gt;
    &amp;quot;generate_debug_case_attributes&amp;quot;: generateDebugCaseAttributes // Should probability and probability_all-columns be generated as well as the actual prediction created into a new column named Predicted_&amp;lt;attribute name&amp;gt;&lt;br /&gt;
  },                                                       &lt;br /&gt;
  &amp;quot;TrainingDataFilter&amp;quot;: ParseJson(casesToUseForTrainingFilter), // Filter JSON for events to be used for training.&lt;br /&gt;
  &amp;quot;IncompleteCasesFilter&amp;quot;: ParseJson(casesToPredictFilter)      // Filter JSON for events for whose case attribute value is to be predicted.&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Create_Predicted_Eventlog&amp;diff=27618</id>
		<title>Create Predicted Eventlog</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Create_Predicted_Eventlog&amp;diff=27618"/>
		<updated>2026-01-15T11:28:22Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Attribute configuration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article has instructions how to install, configure and use eventlog predictions. The prediction creates a new model that contains the source model data and the predictions. It&#039;s able to predict case attributes for the generated new cases and event attributes for the predicted events. To distinguish the real (source data) and predicted events and cases, there are following attributes in the model:&lt;br /&gt;
* Event attribute &#039;&#039;&#039;Predicted&#039;&#039;&#039; denotes whether the event is from the source data (&#039;&#039;false&#039;&#039;) or whether it&#039;s predicted (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
* Case attribute &#039;&#039;&#039;Generated&#039;&#039;&#039; denotes whether the case is in the source data (&#039;&#039;false&#039;&#039;) or whether the prediction generated it as a new case (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for prediction ==&lt;br /&gt;
Following prerequisites need to be fulfilled to run the eventlog prediction:&lt;br /&gt;
* QPR ProcessAnalyzer 2024.8 or later in use&lt;br /&gt;
* Snowflake connection is configured&lt;br /&gt;
* Source models are stored to Snowflake&lt;br /&gt;
&lt;br /&gt;
== Install prediction to Snowflake ==&lt;br /&gt;
To install the eventlog prediction to Snowflake:&lt;br /&gt;
# Go to Snowflake, and create a Snowflake-managed stage with name &#039;&#039;&#039;DECISION_INTELLIGENCE&#039;&#039;&#039; to the same schema configured to QPR ProcessAnalyzer (in the Snowflake connection string). Use settings in the following image: [[File:Create_Snowflake_stage.png]]&lt;br /&gt;
# Open the created stage and upload the &#039;&#039;&#039;predict.pyz&#039;&#039;&#039; file into the stage (ask the file from your QPR representative).&lt;br /&gt;
# Create the following procedure to the same schema:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sql&amp;quot;&amp;gt;&lt;br /&gt;
CREATE OR REPLACE PROCEDURE QPRPA_SP_PREDICTION(&amp;quot;CONFIGURATION&amp;quot; OBJECT)&lt;br /&gt;
RETURNS OBJECT&lt;br /&gt;
LANGUAGE PYTHON&lt;br /&gt;
STRICT&lt;br /&gt;
RUNTIME_VERSION = &#039;3.11&#039;&lt;br /&gt;
PACKAGES = (&#039;nltk&#039;,&#039;numpy&#039;,&#039;networkx&#039;,&#039;pandas&#039;,&#039;scikit-learn&#039;,&#039;snowflake-snowpark-python&#039;,&#039;tensorflow==2.12.0&#039;,&#039;dill&#039;,&#039;psutil&#039;,&#039;prophet&#039;,&#039;holidays&#039;,&#039;python-kubernetes&#039;,&#039;docker-py&#039;,&#039;cryptography&#039;)&lt;br /&gt;
HANDLER = &#039;main&#039;&lt;br /&gt;
EXECUTE AS OWNER&lt;br /&gt;
AS &#039;&lt;br /&gt;
import sys&lt;br /&gt;
def main(session, parameters_in: dict) -&amp;gt; dict:&lt;br /&gt;
	session.file.get(&#039;&#039;@decision_intelligence/predict.pyz&#039;&#039;, &#039;&#039;/tmp&#039;&#039;)&lt;br /&gt;
	sys.path.append(&#039;&#039;/tmp/predict.pyz&#039;&#039;)&lt;br /&gt;
	import predict&lt;br /&gt;
	return predict.main(session, parameters_in)&lt;br /&gt;
&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Create prediction script in QPR ProcessAnalyzer ==&lt;br /&gt;
1. Create the following example expression script (e.g., with name &#039;&#039;&#039;Create prediction model&#039;&#039;&#039;):&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let completeCaseEventTypeNames = [&amp;quot;&amp;lt;event type name found only in complete cases&amp;gt;&amp;quot;, &amp;quot;&amp;lt;another event type name&amp;gt;&amp;quot;, &amp;quot;...&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
let eventTypeColumnName = sourceModel.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
_system.ML.GeneratePredictionModel(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My prediction model&amp;quot;,      // Name of the PA model to generate to the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,         // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,     // Target project to create the model into.&lt;br /&gt;
  &amp;quot;TrainingConfiguration&amp;quot;: #{         // Training parameters.&lt;br /&gt;
    &amp;quot;num_epochs_to_train&amp;quot;: 200&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;GenerationConfiguration&amp;quot;: #{       // Model generation parameters.&lt;br /&gt;
    &amp;quot;cases_to_generate&amp;quot;: 1000&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;TrainingDataFilter&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [&lt;br /&gt;
      #{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
        &amp;quot;Items&amp;quot;: [&lt;br /&gt;
          #{&lt;br /&gt;
            &amp;quot;Type&amp;quot;: &amp;quot;EventAttributeValue&amp;quot;,&lt;br /&gt;
            &amp;quot;Attribute&amp;quot;: eventTypeColumnName,&lt;br /&gt;
            &amp;quot;Values&amp;quot;: completeCaseEventTypeNames&lt;br /&gt;
          }&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;IncompleteCasesFilter&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [&lt;br /&gt;
      #{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;ExcludeCases&amp;quot;,&lt;br /&gt;
        &amp;quot;Items&amp;quot;: [&lt;br /&gt;
          #{&lt;br /&gt;
            &amp;quot;Type&amp;quot;: &amp;quot;EventAttributeValue&amp;quot;,&lt;br /&gt;
            &amp;quot;Attribute&amp;quot;: eventTypeColumnName,&lt;br /&gt;
            &amp;quot;Values&amp;quot;: completeCaseEventTypeNames&lt;br /&gt;
          }&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;RecreatePredictionModel&amp;quot;: true,    // Should a prediction model be overwritten if one already exists for this source model and target model name combination.&lt;br /&gt;
  &amp;quot;TrainingCaseSampleSize&amp;quot;: 10000     // Maximum number of cases to use from the source model (random sampled).&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;2. Configure prediction for the previously created script as instructed in the next chapter. At minimum, replace the tags listed below with some suitable values:&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;project name&amp;gt;&#039;&#039;&#039;: Name of the project in which the source model is located.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;model name&amp;gt;&#039;&#039;&#039;: Name of the model to be used as source model. This data in this source model will be used to train the prediction model so that it can generate new cases and continuations for incomplete existing cases.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;event type name found only in complete cases&amp;gt;&#039;&#039;&#039;: This example script has been hard-coded to determine whether a case is complete or incomplete based on the existence of this event type.&lt;br /&gt;
&lt;br /&gt;
== Configure prediction ==&lt;br /&gt;
Prediction script has the following settings in the GeneratePredictionModel call:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039;: Name of the QPR ProcessAnalyzer model that is created to the target project. The model will contain the source model content and the predictions.&lt;br /&gt;
* &#039;&#039;&#039;SourceModel&#039;&#039;&#039;: Source model for which the prediction is made. Model can be selected for example based on id with ModelById function or by name with ModelByName function.&lt;br /&gt;
* &#039;&#039;&#039;TargetProject&#039;&#039;&#039;: Target project to create the new model into.&lt;br /&gt;
* &#039;&#039;&#039;RecreatePredictionModel&#039;&#039;&#039;: When &#039;&#039;true&#039;&#039;, a new ML model is trained when the script is run. When &#039;&#039;false&#039;&#039;, the prediction is run using possibly pre-existing ML model. &lt;br /&gt;
* &#039;&#039;&#039;TrainingConfiguration&#039;&#039;&#039;: Training parameters.&lt;br /&gt;
** &#039;&#039;&#039;attributes&#039;&#039;&#039;: Attribute configurations (for more information, see the chapter below).&lt;br /&gt;
** &#039;&#039;&#039;generate_start_time_trend_images&#039;&#039;&#039;: If set to true, two images will be generated for each cross validated Prophet-parameter combination and also for the final selected parameters showing the results of plot and plot_components-functions. &lt;br /&gt;
*** The images will be generated into stage files with the following path names:&lt;br /&gt;
**** plot: @decision_intelligence_testing/{model_name}_st_RMSE={rmse_value or &amp;quot;final&amp;quot;}.png&lt;br /&gt;
****plot_components:  @decision_intelligence_testing/{model_name}_st_RMSE={rmse_value or &amp;quot;final&amp;quot;}_comp.png&lt;br /&gt;
***The default value is false.&lt;br /&gt;
** &#039;&#039;&#039;max_num_case_clusters&#039;&#039;&#039;: Set the maximum number of clusters to divide the case attribute values into.&lt;br /&gt;
*** The default value is 20.&lt;br /&gt;
** &#039;&#039;&#039;max_num_traces_in_training&#039;&#039;&#039;: Set the maximum number of traces used in training.&lt;br /&gt;
*** When training, every case of length N will be split into N traces (a.k.a. prefixes) (p_1, ..., p_N), where p_x contains first x events of the all events of the full case.&lt;br /&gt;
**** If there are more traces available than this configured value, cases to include will be random sampled so that the maximum is exceeded by at most one case.&lt;br /&gt;
**** If null, all the traces will be used, no matter what (may easily lead to running out of memory).&lt;br /&gt;
**** The default value is 100000.&lt;br /&gt;
** &#039;&#039;&#039;num_epochs_to_train&#039;&#039;&#039;: How many times the training set is used in training. The best performing model out of all the iterations will be selected.&lt;br /&gt;
*** The default value is 500.&lt;br /&gt;
** &#039;&#039;&#039;num_extra_years_to_reserve_in_created_model&#039;&#039;&#039;: Number of additional years after the year of the last timestamp in the training data to reserve to the capacity of the created ML model, allowing the model to  be able to predict timestamps in the range between the minimum timestamp year in the training data and the maximum timestamp year plus this value.&lt;br /&gt;
*** The default value is 20.&lt;br /&gt;
** &#039;&#039;&#039;reserve_extra_sequence_length&#039;&#039;&#039;: How many extra events to reserve space for in the ML model compared to the number of events the longest case in the training data has.&lt;br /&gt;
*** The default value is 5.&lt;br /&gt;
** &#039;&#039;&#039;samples_per_epoch&#039;&#039;&#039;: If not null, specifies (approximately) how many traces/prefixes will be used to represent one epoch of data in the training. The actual value used will be made divisible by batch_size using this formula:&lt;br /&gt;
***max(floor(samples_per_epoch / batch_size), 1) * batch_size&lt;br /&gt;
***If null, every epoch will use all the traces/prefixes in the training data.&lt;br /&gt;
***The default value is null&lt;br /&gt;
**&#039;&#039;&#039;validation_split&#039;&#039;&#039;: Percentage of traces/prefixes to use to evaluate the loss and any model metrics at the end of each epoch. The model will not be trained on this data.&lt;br /&gt;
***If 0, separate validation data will not be used. Instead, all the training data will be used also as validation data.&lt;br /&gt;
***The default value is 0.&lt;br /&gt;
* &#039;&#039;&#039;GenerationConfiguration&#039;&#039;&#039;: Event generation parameters. When null, no generation is done. For example, following parameters are supported:&lt;br /&gt;
** &#039;&#039;&#039;avoid_repeated_activities&#039;&#039;&#039;: Array of activity names that should occur at most once in any case. The probability of selecting any of the activities specified in this configuration more than once is set to be 0. &lt;br /&gt;
*** Empty array means that activity generation is not restricted by this setting at all. &lt;br /&gt;
*** null value means that there should not be any activities that can occur more than once (shortcut for specifying all the activity names).&lt;br /&gt;
*** The default value is an empty array.&lt;br /&gt;
** &#039;&#039;&#039;cases_to_generate&#039;&#039;&#039;: Maximum number cases to create. The number of created cases is further limited by the capabilities of the trained model and the &#039;&#039;case_generation_start_time&#039;&#039; and &#039;&#039;case_generation_end_time&#039;&#039; parameters.&lt;br /&gt;
*** The default value is such that the number of cases,  by itself, is not limited.&lt;br /&gt;
** &#039;&#039;&#039;case_generation_start_time&#039;&#039;&#039;: If defined, new cases will be generated after this timestamp (given as string in ISO datetime format). &lt;br /&gt;
*** If undefined, the latest start event timestamp used in the training data is used.&lt;br /&gt;
*** The default value is undefined.&lt;br /&gt;
** &#039;&#039;&#039;case_generation_end_time&#039;&#039;&#039;: If defined, new events and cases will not be generated after this timestamp (given as string in ISO datetime format). E.g., &amp;quot;2015-01-01T00:00:00&amp;quot;.&lt;br /&gt;
*** The default value is unlimited (only limit comes from the capacity of the trained model)&lt;br /&gt;
** &#039;&#039;&#039;generate_debug_event_attributes&#039;&#039;&#039;: &lt;br /&gt;
*** If true, additional columns will be added containing, e.g., probabilities of the selected activity and other activities.&lt;br /&gt;
*** The default value is false.&lt;br /&gt;
** &#039;&#039;&#039;max_num_events&#039;&#039;&#039;:&lt;br /&gt;
*** Specifies the maximum number of events to generate for any case.&lt;br /&gt;
*** If unspecified (=default), the value equals to &#039;&#039;&amp;lt;the maximum number of events in any case in the training data&amp;gt;&#039;&#039;+&#039;&#039;&amp;lt;the value of reserve_extra_sequence_length in training&amp;gt;&#039;&#039;.&lt;br /&gt;
** &#039;&#039;&#039;min_prediction_probability &#039;&#039;&#039;: &lt;br /&gt;
*** The minimum probability of any prediction. If the probability of a prediction is lower than this, it will never be picked. &lt;br /&gt;
*** The default value is 0.01.&lt;br /&gt;
** &#039;&#039;&#039;temperature&#039;&#039;&#039;: &lt;br /&gt;
*** If 0, the generated next activity will always be the one that is the most probable. &lt;br /&gt;
*** If 1, the generated next activity is purely based on the probabilities returned by the trained ML model. &lt;br /&gt;
*** This behavior is interpolated when using values between 0 and 1.&lt;br /&gt;
*** The default value is 0.9.&lt;br /&gt;
* &#039;&#039;&#039;TrainingDataFilter&#039;&#039;&#039;: [[Filtering_in_QPR_ProcessAnalyzer_Queries|Filter]] to select specific cases that are used to train the prediction model. This filter is required to train the model only using the completed cases. Uncompleted cases should not be used for the training, so the model doesn&#039;t incorrectly learn that cases should end like that.&lt;br /&gt;
* &#039;&#039;&#039;IncompleteCasesFilter&#039;&#039;&#039;: Optional [[Filtering_in_QPR_ProcessAnalyzer_Queries|filter]] to select which cases the prediction is made for. To improve performance of the prediction, it&#039;s recommended to include only the incomplete cases for which new events might appear, and skip the completed cases for which new events are not expected anymore.&lt;br /&gt;
* &#039;&#039;&#039;TrainingCaseSampleSize&#039;&#039;&#039;: Maximum number of cases to take from the source model (cases are selected randomly). Use a lower setting to speed up the ML model training. The greater the value, the more subtle phenomena the prediction can learn from the data.&lt;br /&gt;
&lt;br /&gt;
== Attribute configuration ==&lt;br /&gt;
Attribute configuration is used in &#039;&#039;&#039;TrainingConfiguration&#039;&#039;&#039; (see the chapter above) to configure which event- and case attributes should be used in prediction model and how they are used.&lt;br /&gt;
&lt;br /&gt;
The configuration is in the top level split into two sections: &amp;quot;event&amp;quot; and &amp;quot;case&amp;quot;. &amp;quot;Event&amp;quot; is used to configure event attributes, whereas &amp;quot;case&amp;quot; is used for case attributes.&lt;br /&gt;
&lt;br /&gt;
The next level supports one value: &amp;quot;input&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The next level after that, supports the following settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;categorical_groups&#039;&#039;&#039;: An array of categorical attribute group configuration objects used to define groups of attributes that will be bundled together in the trained model, either as separate input- or output features. Each attribute group will form its own input- or output vector used in the model training and generation.&lt;br /&gt;
** If null, only one group will be created with all the available categorical attributes included.&lt;br /&gt;
** The following settings are supported by these objects:&lt;br /&gt;
*** &#039;&#039;&#039;attributes&#039;&#039;&#039;: An array of attribute names.&lt;br /&gt;
**** If null, all the input attributes are to be included in this group.&lt;br /&gt;
*** &#039;&#039;&#039;max_num_clusters&#039;&#039;&#039;: The maximum number of clusters (input- or output vector feature values) to use to represent this group of attributes.&lt;br /&gt;
**** Default value: 20&lt;br /&gt;
**** NOTE: Clustering is used by default to convert a set of attribute values into an input- or output vector used by the prediction model.&lt;br /&gt;
*** &#039;&#039;&#039;ignore_values_threshold:&#039;&#039;&#039; The minimum percentage of objects having a specific attribute value in order for that attribute value to be taken into account as unique attribute value within this categorical group.&lt;br /&gt;
**** Depending on the context, the default value is any one of the following configurations:&lt;br /&gt;
***** ignore_values_threshold_for_case_attribute_values&lt;br /&gt;
****** Used when clustering case attributes when generating attribute values for generated new cases.&lt;br /&gt;
****** Default value is 0.01.&lt;br /&gt;
***** ignore_values_threshold_for_case_attributes&lt;br /&gt;
****** Used when clustering case attributes.&lt;br /&gt;
****** Default value is 0.1.&lt;br /&gt;
***** ignore_values_threshold_for_event_attributes&lt;br /&gt;
****** Used when clustering event attributes.&lt;br /&gt;
****** Default value is 0.1.&lt;br /&gt;
* &#039;&#039;&#039;columns&#039;&#039;&#039;: An array of attribute column configuration objects used to define columns in the input data that are to be used as event- or case attributes.&lt;br /&gt;
** If null, all the columns will be included as categorical attributes (except case id, event type (only for event) and timestamp (only for event) columns).&lt;br /&gt;
** The following settings are supported by these objects:&lt;br /&gt;
*** &#039;&#039;&#039;label&#039;&#039;&#039;: Column name.&lt;br /&gt;
*** &#039;&#039;&#039;type&#039;&#039;&#039;: Type of the column. Supported types are:&lt;br /&gt;
**** &#039;&#039;&#039;categorical&#039;&#039;&#039;: Values can take on one of a limited, and usually fixed, number of possible values.&lt;br /&gt;
**** &#039;&#039;&#039;numeric&#039;&#039;&#039;: Value is considered as a continuous numeric value.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
Use all event attributes as input for the prediction model. In addition, additional machine learning input vector for SAP_User-event data column supporting at most 10 unique values.&lt;br /&gt;
&lt;br /&gt;
In addition, for case attributes, only &amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;, &amp;quot;Account Manager&amp;quot; and &amp;quot;Customer Group&amp;quot; case data columns are used as categorical attributes and &amp;quot;Cost&amp;quot; as numeric attribute. Furthermore, the four categorical case attributes are grouped into three groups, each of which are used as its own input vector for the prediction model.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When generating, all event attributes will be included for generated events as columns. Generated cases will have only &amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;, &amp;quot;Account Manager&amp;quot;,  &amp;quot;Customer Group&amp;quot;, and &amp;quot;Cost&amp;quot; columns.&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;attributes&amp;quot;: #{&lt;br /&gt;
  &amp;quot;event&amp;quot;: #{&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;categorical_groups&amp;quot;: [&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;attributes&amp;quot;: None&lt;br /&gt;
        },&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;attributes&amp;quot;: [&amp;quot;SAP_User&amp;quot;],&lt;br /&gt;
          &amp;quot;max_num_clusters&amp;quot;: 10&lt;br /&gt;
        }&lt;br /&gt;
      ],&lt;br /&gt;
      &amp;quot;columns&amp;quot;: None&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;case&amp;quot;: #{&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;categorical_groups&amp;quot;: [#{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Account Manager&amp;quot;]&lt;br /&gt;
      }, #{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Customer Group&amp;quot;]&lt;br /&gt;
      }, #{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;]&lt;br /&gt;
      }],&lt;br /&gt;
      &amp;quot;columns&amp;quot;: [&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Region&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Product Group&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Account Manager&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Customer Group&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Cost&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;numeric&amp;quot; }&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Predicting case attribute values ==&lt;br /&gt;
QPR ProcessAnalyzer can also be used to, e.g.,  predict the final values of case attributes of running cases. The following script gives an example on how to perform this.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let caseAttributeToPredict = &amp;quot;&amp;lt;name of the case attribute&amp;gt;&amp;quot;;&lt;br /&gt;
let resultModelName = &amp;quot;&amp;lt;name of the model to be created/replaced&amp;gt;&amp;quot;;&lt;br /&gt;
let generateDebugCaseAttributes = false; // Set to true to generate columns for prediction probabilities.&lt;br /&gt;
let casesToPredictFilter = &amp;quot;&amp;lt;JSON filter for cases for which the prediction is to be performed&amp;gt;&amp;quot;;&lt;br /&gt;
let casesToUseForTrainingFilter = &amp;quot;&amp;lt;JSON filter for cases to be used for ML model training&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
&lt;br /&gt;
_system.ML.GenerateCaseAttributePredictionModel(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: resultModelName,                                     // Name of the PA model to generate ti the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,                                  // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,                              // Target project to create the model into.&lt;br /&gt;
  &amp;quot;RecreatePredictionModel&amp;quot;: false,                            // Should a prediction model be overwritten if one already exists for this source model and target model name combination. &lt;br /&gt;
  &amp;quot;TrainingCaseSampleSize&amp;quot;: 10000,                             // Maximum number of cases to use from the source model (random sampled). &lt;br /&gt;
  &amp;quot;CommonConfiguration&amp;quot;: #{                                    // Common parameters used by both training and generation.&lt;br /&gt;
    &amp;quot;output_case_attribute_groups&amp;quot;: [#{&lt;br /&gt;
      &amp;quot;attributes&amp;quot;: [caseAttributeToPredict]                   // Attribute whose value is to be predicted.&lt;br /&gt;
    }]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;TrainingConfiguration&amp;quot;: #{                                  // Training parameters.&lt;br /&gt;
    &amp;quot;max_num_case_attribute_clusters&amp;quot;: 80,&lt;br /&gt;
    &amp;quot;num_epochs_to_train&amp;quot;: 100&lt;br /&gt;
  },                            &lt;br /&gt;
  &amp;quot;GenerationConfiguration&amp;quot;: #{                                // Case attribute generation parameters.&lt;br /&gt;
    &amp;quot;generate_debug_case_attributes&amp;quot;: generateDebugCaseAttributes // Should probability and probability_all-columns be generated as well as the actual prediction created into a new column named Predicted_&amp;lt;attribute name&amp;gt;&lt;br /&gt;
  },                                                       &lt;br /&gt;
  &amp;quot;TrainingDataFilter&amp;quot;: ParseJson(casesToUseForTrainingFilter), // Filter JSON for events to be used for training.&lt;br /&gt;
  &amp;quot;IncompleteCasesFilter&amp;quot;: ParseJson(casesToPredictFilter)      // Filter JSON for events for whose case attribute value is to be predicted.&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Create_Predicted_Eventlog&amp;diff=27617</id>
		<title>Create Predicted Eventlog</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Create_Predicted_Eventlog&amp;diff=27617"/>
		<updated>2026-01-15T11:05:58Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Attribute configuration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article has instructions how to install, configure and use eventlog predictions. The prediction creates a new model that contains the source model data and the predictions. It&#039;s able to predict case attributes for the generated new cases and event attributes for the predicted events. To distinguish the real (source data) and predicted events and cases, there are following attributes in the model:&lt;br /&gt;
* Event attribute &#039;&#039;&#039;Predicted&#039;&#039;&#039; denotes whether the event is from the source data (&#039;&#039;false&#039;&#039;) or whether it&#039;s predicted (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
* Case attribute &#039;&#039;&#039;Generated&#039;&#039;&#039; denotes whether the case is in the source data (&#039;&#039;false&#039;&#039;) or whether the prediction generated it as a new case (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for prediction ==&lt;br /&gt;
Following prerequisites need to be fulfilled to run the eventlog prediction:&lt;br /&gt;
* QPR ProcessAnalyzer 2024.8 or later in use&lt;br /&gt;
* Snowflake connection is configured&lt;br /&gt;
* Source models are stored to Snowflake&lt;br /&gt;
&lt;br /&gt;
== Install prediction to Snowflake ==&lt;br /&gt;
To install the eventlog prediction to Snowflake:&lt;br /&gt;
# Go to Snowflake, and create a Snowflake-managed stage with name &#039;&#039;&#039;DECISION_INTELLIGENCE&#039;&#039;&#039; to the same schema configured to QPR ProcessAnalyzer (in the Snowflake connection string). Use settings in the following image: [[File:Create_Snowflake_stage.png]]&lt;br /&gt;
# Open the created stage and upload the &#039;&#039;&#039;predict.pyz&#039;&#039;&#039; file into the stage (ask the file from your QPR representative).&lt;br /&gt;
# Create the following procedure to the same schema:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sql&amp;quot;&amp;gt;&lt;br /&gt;
CREATE OR REPLACE PROCEDURE QPRPA_SP_PREDICTION(&amp;quot;CONFIGURATION&amp;quot; OBJECT)&lt;br /&gt;
RETURNS OBJECT&lt;br /&gt;
LANGUAGE PYTHON&lt;br /&gt;
STRICT&lt;br /&gt;
RUNTIME_VERSION = &#039;3.11&#039;&lt;br /&gt;
PACKAGES = (&#039;nltk&#039;,&#039;numpy&#039;,&#039;networkx&#039;,&#039;pandas&#039;,&#039;scikit-learn&#039;,&#039;snowflake-snowpark-python&#039;,&#039;tensorflow==2.12.0&#039;,&#039;dill&#039;,&#039;psutil&#039;,&#039;prophet&#039;,&#039;holidays&#039;,&#039;python-kubernetes&#039;,&#039;docker-py&#039;,&#039;cryptography&#039;)&lt;br /&gt;
HANDLER = &#039;main&#039;&lt;br /&gt;
EXECUTE AS OWNER&lt;br /&gt;
AS &#039;&lt;br /&gt;
import sys&lt;br /&gt;
def main(session, parameters_in: dict) -&amp;gt; dict:&lt;br /&gt;
	session.file.get(&#039;&#039;@decision_intelligence/predict.pyz&#039;&#039;, &#039;&#039;/tmp&#039;&#039;)&lt;br /&gt;
	sys.path.append(&#039;&#039;/tmp/predict.pyz&#039;&#039;)&lt;br /&gt;
	import predict&lt;br /&gt;
	return predict.main(session, parameters_in)&lt;br /&gt;
&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Create prediction script in QPR ProcessAnalyzer ==&lt;br /&gt;
1. Create the following example expression script (e.g., with name &#039;&#039;&#039;Create prediction model&#039;&#039;&#039;):&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let completeCaseEventTypeNames = [&amp;quot;&amp;lt;event type name found only in complete cases&amp;gt;&amp;quot;, &amp;quot;&amp;lt;another event type name&amp;gt;&amp;quot;, &amp;quot;...&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
let eventTypeColumnName = sourceModel.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
_system.ML.GeneratePredictionModel(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My prediction model&amp;quot;,      // Name of the PA model to generate to the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,         // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,     // Target project to create the model into.&lt;br /&gt;
  &amp;quot;TrainingConfiguration&amp;quot;: #{         // Training parameters.&lt;br /&gt;
    &amp;quot;num_epochs_to_train&amp;quot;: 200&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;GenerationConfiguration&amp;quot;: #{       // Model generation parameters.&lt;br /&gt;
    &amp;quot;cases_to_generate&amp;quot;: 1000&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;TrainingDataFilter&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [&lt;br /&gt;
      #{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
        &amp;quot;Items&amp;quot;: [&lt;br /&gt;
          #{&lt;br /&gt;
            &amp;quot;Type&amp;quot;: &amp;quot;EventAttributeValue&amp;quot;,&lt;br /&gt;
            &amp;quot;Attribute&amp;quot;: eventTypeColumnName,&lt;br /&gt;
            &amp;quot;Values&amp;quot;: completeCaseEventTypeNames&lt;br /&gt;
          }&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;IncompleteCasesFilter&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [&lt;br /&gt;
      #{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;ExcludeCases&amp;quot;,&lt;br /&gt;
        &amp;quot;Items&amp;quot;: [&lt;br /&gt;
          #{&lt;br /&gt;
            &amp;quot;Type&amp;quot;: &amp;quot;EventAttributeValue&amp;quot;,&lt;br /&gt;
            &amp;quot;Attribute&amp;quot;: eventTypeColumnName,&lt;br /&gt;
            &amp;quot;Values&amp;quot;: completeCaseEventTypeNames&lt;br /&gt;
          }&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;RecreatePredictionModel&amp;quot;: true,    // Should a prediction model be overwritten if one already exists for this source model and target model name combination.&lt;br /&gt;
  &amp;quot;TrainingCaseSampleSize&amp;quot;: 10000     // Maximum number of cases to use from the source model (random sampled).&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;2. Configure prediction for the previously created script as instructed in the next chapter. At minimum, replace the tags listed below with some suitable values:&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;project name&amp;gt;&#039;&#039;&#039;: Name of the project in which the source model is located.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;model name&amp;gt;&#039;&#039;&#039;: Name of the model to be used as source model. This data in this source model will be used to train the prediction model so that it can generate new cases and continuations for incomplete existing cases.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;event type name found only in complete cases&amp;gt;&#039;&#039;&#039;: This example script has been hard-coded to determine whether a case is complete or incomplete based on the existence of this event type.&lt;br /&gt;
&lt;br /&gt;
== Configure prediction ==&lt;br /&gt;
Prediction script has the following settings in the GeneratePredictionModel call:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039;: Name of the QPR ProcessAnalyzer model that is created to the target project. The model will contain the source model content and the predictions.&lt;br /&gt;
* &#039;&#039;&#039;SourceModel&#039;&#039;&#039;: Source model for which the prediction is made. Model can be selected for example based on id with ModelById function or by name with ModelByName function.&lt;br /&gt;
* &#039;&#039;&#039;TargetProject&#039;&#039;&#039;: Target project to create the new model into.&lt;br /&gt;
* &#039;&#039;&#039;RecreatePredictionModel&#039;&#039;&#039;: When &#039;&#039;true&#039;&#039;, a new ML model is trained when the script is run. When &#039;&#039;false&#039;&#039;, the prediction is run using possibly pre-existing ML model. &lt;br /&gt;
* &#039;&#039;&#039;TrainingConfiguration&#039;&#039;&#039;: Training parameters.&lt;br /&gt;
** &#039;&#039;&#039;attributes&#039;&#039;&#039;: Attribute configurations (for more information, see the chapter below).&lt;br /&gt;
** &#039;&#039;&#039;generate_start_time_trend_images&#039;&#039;&#039;: If set to true, two images will be generated for each cross validated Prophet-parameter combination and also for the final selected parameters showing the results of plot and plot_components-functions. &lt;br /&gt;
*** The images will be generated into stage files with the following path names:&lt;br /&gt;
**** plot: @decision_intelligence_testing/{model_name}_st_RMSE={rmse_value or &amp;quot;final&amp;quot;}.png&lt;br /&gt;
****plot_components:  @decision_intelligence_testing/{model_name}_st_RMSE={rmse_value or &amp;quot;final&amp;quot;}_comp.png&lt;br /&gt;
***The default value is false.&lt;br /&gt;
** &#039;&#039;&#039;max_num_case_clusters&#039;&#039;&#039;: Set the maximum number of clusters to divide the case attribute values into.&lt;br /&gt;
*** The default value is 20.&lt;br /&gt;
** &#039;&#039;&#039;max_num_traces_in_training&#039;&#039;&#039;: Set the maximum number of traces used in training.&lt;br /&gt;
*** When training, every case of length N will be split into N traces (a.k.a. prefixes) (p_1, ..., p_N), where p_x contains first x events of the all events of the full case.&lt;br /&gt;
**** If there are more traces available than this configured value, cases to include will be random sampled so that the maximum is exceeded by at most one case.&lt;br /&gt;
**** If null, all the traces will be used, no matter what (may easily lead to running out of memory).&lt;br /&gt;
**** The default value is 100000.&lt;br /&gt;
** &#039;&#039;&#039;num_epochs_to_train&#039;&#039;&#039;: How many times the training set is used in training. The best performing model out of all the iterations will be selected.&lt;br /&gt;
*** The default value is 500.&lt;br /&gt;
** &#039;&#039;&#039;num_extra_years_to_reserve_in_created_model&#039;&#039;&#039;: Number of additional years after the year of the last timestamp in the training data to reserve to the capacity of the created ML model, allowing the model to  be able to predict timestamps in the range between the minimum timestamp year in the training data and the maximum timestamp year plus this value.&lt;br /&gt;
*** The default value is 20.&lt;br /&gt;
** &#039;&#039;&#039;reserve_extra_sequence_length&#039;&#039;&#039;: How many extra events to reserve space for in the ML model compared to the number of events the longest case in the training data has.&lt;br /&gt;
*** The default value is 5.&lt;br /&gt;
** &#039;&#039;&#039;samples_per_epoch&#039;&#039;&#039;: If not null, specifies (approximately) how many traces/prefixes will be used to represent one epoch of data in the training. The actual value used will be made divisible by batch_size using this formula:&lt;br /&gt;
***max(floor(samples_per_epoch / batch_size), 1) * batch_size&lt;br /&gt;
***If null, every epoch will use all the traces/prefixes in the training data.&lt;br /&gt;
***The default value is null&lt;br /&gt;
**&#039;&#039;&#039;validation_split&#039;&#039;&#039;: Percentage of traces/prefixes to use to evaluate the loss and any model metrics at the end of each epoch. The model will not be trained on this data.&lt;br /&gt;
***If 0, separate validation data will not be used. Instead, all the training data will be used also as validation data.&lt;br /&gt;
***The default value is 0.&lt;br /&gt;
* &#039;&#039;&#039;GenerationConfiguration&#039;&#039;&#039;: Event generation parameters. When null, no generation is done. For example, following parameters are supported:&lt;br /&gt;
** &#039;&#039;&#039;avoid_repeated_activities&#039;&#039;&#039;: Array of activity names that should occur at most once in any case. The probability of selecting any of the activities specified in this configuration more than once is set to be 0. &lt;br /&gt;
*** Empty array means that activity generation is not restricted by this setting at all. &lt;br /&gt;
*** null value means that there should not be any activities that can occur more than once (shortcut for specifying all the activity names).&lt;br /&gt;
*** The default value is an empty array.&lt;br /&gt;
** &#039;&#039;&#039;cases_to_generate&#039;&#039;&#039;: Maximum number cases to create. The number of created cases is further limited by the capabilities of the trained model and the &#039;&#039;case_generation_start_time&#039;&#039; and &#039;&#039;case_generation_end_time&#039;&#039; parameters.&lt;br /&gt;
*** The default value is such that the number of cases,  by itself, is not limited.&lt;br /&gt;
** &#039;&#039;&#039;case_generation_start_time&#039;&#039;&#039;: If defined, new cases will be generated after this timestamp (given as string in ISO datetime format). &lt;br /&gt;
*** If undefined, the latest start event timestamp used in the training data is used.&lt;br /&gt;
*** The default value is undefined.&lt;br /&gt;
** &#039;&#039;&#039;case_generation_end_time&#039;&#039;&#039;: If defined, new events and cases will not be generated after this timestamp (given as string in ISO datetime format). E.g., &amp;quot;2015-01-01T00:00:00&amp;quot;.&lt;br /&gt;
*** The default value is unlimited (only limit comes from the capacity of the trained model)&lt;br /&gt;
** &#039;&#039;&#039;generate_debug_event_attributes&#039;&#039;&#039;: &lt;br /&gt;
*** If true, additional columns will be added containing, e.g., probabilities of the selected activity and other activities.&lt;br /&gt;
*** The default value is false.&lt;br /&gt;
** &#039;&#039;&#039;max_num_events&#039;&#039;&#039;:&lt;br /&gt;
*** Specifies the maximum number of events to generate for any case.&lt;br /&gt;
*** If unspecified (=default), the value equals to &#039;&#039;&amp;lt;the maximum number of events in any case in the training data&amp;gt;&#039;&#039;+&#039;&#039;&amp;lt;the value of reserve_extra_sequence_length in training&amp;gt;&#039;&#039;.&lt;br /&gt;
** &#039;&#039;&#039;min_prediction_probability &#039;&#039;&#039;: &lt;br /&gt;
*** The minimum probability of any prediction. If the probability of a prediction is lower than this, it will never be picked. &lt;br /&gt;
*** The default value is 0.01.&lt;br /&gt;
** &#039;&#039;&#039;temperature&#039;&#039;&#039;: &lt;br /&gt;
*** If 0, the generated next activity will always be the one that is the most probable. &lt;br /&gt;
*** If 1, the generated next activity is purely based on the probabilities returned by the trained ML model. &lt;br /&gt;
*** This behavior is interpolated when using values between 0 and 1.&lt;br /&gt;
*** The default value is 0.9.&lt;br /&gt;
* &#039;&#039;&#039;TrainingDataFilter&#039;&#039;&#039;: [[Filtering_in_QPR_ProcessAnalyzer_Queries|Filter]] to select specific cases that are used to train the prediction model. This filter is required to train the model only using the completed cases. Uncompleted cases should not be used for the training, so the model doesn&#039;t incorrectly learn that cases should end like that.&lt;br /&gt;
* &#039;&#039;&#039;IncompleteCasesFilter&#039;&#039;&#039;: Optional [[Filtering_in_QPR_ProcessAnalyzer_Queries|filter]] to select which cases the prediction is made for. To improve performance of the prediction, it&#039;s recommended to include only the incomplete cases for which new events might appear, and skip the completed cases for which new events are not expected anymore.&lt;br /&gt;
* &#039;&#039;&#039;TrainingCaseSampleSize&#039;&#039;&#039;: Maximum number of cases to take from the source model (cases are selected randomly). Use a lower setting to speed up the ML model training. The greater the value, the more subtle phenomena the prediction can learn from the data.&lt;br /&gt;
&lt;br /&gt;
== Attribute configuration ==&lt;br /&gt;
Attribute configuration is used in &#039;&#039;&#039;TrainingConfiguration&#039;&#039;&#039; (see the chapter above) to configure which event- and case attributes should be used in prediction model and how they are used.&lt;br /&gt;
&lt;br /&gt;
The configuration is in the top level split into two sections: &amp;quot;event&amp;quot; and &amp;quot;case&amp;quot;. &amp;quot;Event&amp;quot; is used to configure event attributes, whereas &amp;quot;case&amp;quot; is used for case attributes.&lt;br /&gt;
&lt;br /&gt;
The next level supports one value: &amp;quot;input&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The next level after that, supports the following settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;categorical_groups&#039;&#039;&#039;: An array of categorical attribute group configuration objects used to define groups of attributes that will be bundled together in the trained model, either as separate input- or output features. Each attribute group will form its own input- or output vector used in the model training and generation.&lt;br /&gt;
** If null, only one group will be created with all the available categorical attributes included.&lt;br /&gt;
** The following settings are supported by these objects:&lt;br /&gt;
*** &#039;&#039;&#039;attributes&#039;&#039;&#039;: An array of attribute names.&lt;br /&gt;
**** If null, all the input attributes are to be included in this group.&lt;br /&gt;
*** &#039;&#039;&#039;max_num_clusters&#039;&#039;&#039;: The maximum number of clusters (input- or output vector feature values) to use to represent this group of attributes.&lt;br /&gt;
**** Default value: 20&lt;br /&gt;
**** NOTE: Clustering is used by default to convert a set of attribute values into an input- or output vector used by the prediction model.&lt;br /&gt;
*** &#039;&#039;&#039;ignore_values_threshold:&#039;&#039;&#039; The minimum percentage of objects having a specific attribute value in order for that attribute value to be taken into account as unique attribute value within this categorical group.&lt;br /&gt;
**** Depending on the context, the default value is any one of the following configurations:&lt;br /&gt;
***** ignore_values_threshold_for_case_attribute_values&lt;br /&gt;
***** ignore_values_threshold_for_case_attributes&lt;br /&gt;
***** ignore_values_threshold_for_event_attributes&lt;br /&gt;
* &#039;&#039;&#039;columns&#039;&#039;&#039;: An array of attribute column configuration objects used to define columns in the input data that are to be used as event- or case attributes.&lt;br /&gt;
** If null, all the columns will be included as categorical attributes (except case id, event type (only for event) and timestamp (only for event) columns).&lt;br /&gt;
** The following settings are supported by these objects:&lt;br /&gt;
*** &#039;&#039;&#039;label&#039;&#039;&#039;: Column name.&lt;br /&gt;
*** &#039;&#039;&#039;type&#039;&#039;&#039;: Type of the column. Supported types are:&lt;br /&gt;
**** &#039;&#039;&#039;categorical&#039;&#039;&#039;: Values can take on one of a limited, and usually fixed, number of possible values.&lt;br /&gt;
**** &#039;&#039;&#039;numeric&#039;&#039;&#039;: Value is considered as a continuous numeric value.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
Use all event attributes as input for the prediction model. In addition, additional machine learning input vector for SAP_User-event data column supporting at most 10 unique values.&lt;br /&gt;
&lt;br /&gt;
In addition, for case attributes, only &amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;, &amp;quot;Account Manager&amp;quot; and &amp;quot;Customer Group&amp;quot; case data columns are used as categorical attributes and &amp;quot;Cost&amp;quot; as numeric attribute. Furthermore, the four categorical case attributes are grouped into three groups, each of which are used as its own input vector for the prediction model.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When generating, all event attributes will be included for generated events as columns. Generated cases will have only &amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;, &amp;quot;Account Manager&amp;quot;,  &amp;quot;Customer Group&amp;quot;, and &amp;quot;Cost&amp;quot; columns.&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;attributes&amp;quot;: #{&lt;br /&gt;
  &amp;quot;event&amp;quot;: #{&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;categorical_groups&amp;quot;: [&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;attributes&amp;quot;: None&lt;br /&gt;
        },&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;attributes&amp;quot;: [&amp;quot;SAP_User&amp;quot;],&lt;br /&gt;
          &amp;quot;max_num_clusters&amp;quot;: 10&lt;br /&gt;
        }&lt;br /&gt;
      ],&lt;br /&gt;
      &amp;quot;columns&amp;quot;: None&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;case&amp;quot;: #{&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;categorical_groups&amp;quot;: [#{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Account Manager&amp;quot;]&lt;br /&gt;
      }, #{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Customer Group&amp;quot;]&lt;br /&gt;
      }, #{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;]&lt;br /&gt;
      }],&lt;br /&gt;
      &amp;quot;columns&amp;quot;: [&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Region&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Product Group&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Account Manager&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Customer Group&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Cost&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;numeric&amp;quot; }&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Predicting case attribute values ==&lt;br /&gt;
QPR ProcessAnalyzer can also be used to, e.g.,  predict the final values of case attributes of running cases. The following script gives an example on how to perform this.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let caseAttributeToPredict = &amp;quot;&amp;lt;name of the case attribute&amp;gt;&amp;quot;;&lt;br /&gt;
let resultModelName = &amp;quot;&amp;lt;name of the model to be created/replaced&amp;gt;&amp;quot;;&lt;br /&gt;
let generateDebugCaseAttributes = false; // Set to true to generate columns for prediction probabilities.&lt;br /&gt;
let casesToPredictFilter = &amp;quot;&amp;lt;JSON filter for cases for which the prediction is to be performed&amp;gt;&amp;quot;;&lt;br /&gt;
let casesToUseForTrainingFilter = &amp;quot;&amp;lt;JSON filter for cases to be used for ML model training&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
&lt;br /&gt;
_system.ML.GenerateCaseAttributePredictionModel(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: resultModelName,                                     // Name of the PA model to generate ti the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,                                  // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,                              // Target project to create the model into.&lt;br /&gt;
  &amp;quot;RecreatePredictionModel&amp;quot;: false,                            // Should a prediction model be overwritten if one already exists for this source model and target model name combination. &lt;br /&gt;
  &amp;quot;TrainingCaseSampleSize&amp;quot;: 10000,                             // Maximum number of cases to use from the source model (random sampled). &lt;br /&gt;
  &amp;quot;CommonConfiguration&amp;quot;: #{                                    // Common parameters used by both training and generation.&lt;br /&gt;
    &amp;quot;output_case_attribute_groups&amp;quot;: [#{&lt;br /&gt;
      &amp;quot;attributes&amp;quot;: [caseAttributeToPredict]                   // Attribute whose value is to be predicted.&lt;br /&gt;
    }]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;TrainingConfiguration&amp;quot;: #{                                  // Training parameters.&lt;br /&gt;
    &amp;quot;max_num_case_attribute_clusters&amp;quot;: 80,&lt;br /&gt;
    &amp;quot;num_epochs_to_train&amp;quot;: 100&lt;br /&gt;
  },                            &lt;br /&gt;
  &amp;quot;GenerationConfiguration&amp;quot;: #{                                // Case attribute generation parameters.&lt;br /&gt;
    &amp;quot;generate_debug_case_attributes&amp;quot;: generateDebugCaseAttributes // Should probability and probability_all-columns be generated as well as the actual prediction created into a new column named Predicted_&amp;lt;attribute name&amp;gt;&lt;br /&gt;
  },                                                       &lt;br /&gt;
  &amp;quot;TrainingDataFilter&amp;quot;: ParseJson(casesToUseForTrainingFilter), // Filter JSON for events to be used for training.&lt;br /&gt;
  &amp;quot;IncompleteCasesFilter&amp;quot;: ParseJson(casesToPredictFilter)      // Filter JSON for events for whose case attribute value is to be predicted.&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=27598</id>
		<title>Expression Script Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=27598"/>
		<updated>2026-01-12T08:22:24Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Create a new object-centric model based on already existing object-centric model containing only filtered objects and events */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains script examples written in the QPR ProcessAnalyzer expression language. See how expression scripts can be created in the [[Managing_Scripts#Creating_Script|Workspace]]. For documentation for the syntax, functions and entities can be found from the main page in the [[QPR_ProcessAnalyzer_Wiki#For_Developers|KPI Expression Language]] section.&lt;br /&gt;
&lt;br /&gt;
== Calling Expression Script from Expression ==&lt;br /&gt;
Expression scripts can be called from an expression using the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Run]] function with the following syntax:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: false,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 123.45&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command waits until the run is completed, and the return value of the called script is returned by the Run function call.&lt;br /&gt;
&lt;br /&gt;
Parameters can be passed to the called script, and the parameters are available as variables in the script. The parameters can contain any type of data.&lt;br /&gt;
&lt;br /&gt;
Expression scripts can also be called from a dashboard. Expressions can be stored to scripts instead of dashboards, which is a way to separate complex expressions from dashboards and allow to reuse expressions across several dashboards.&lt;br /&gt;
&lt;br /&gt;
== Calling SQL Script from Expression ==&lt;br /&gt;
SQL script can be called from an expression using the Run function as follows (similar to calling [[#Calling Expression Script from Expression|expression scripts]]):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 321&lt;br /&gt;
});&lt;br /&gt;
let arrayOfAllReports = result.Keys;&lt;br /&gt;
let report1 = result.Report1;&lt;br /&gt;
let report2 = result.Report2;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL scripts can return multiple &#039;&#039;reports&#039;&#039;, which are combined to a dictionary, where the key is the name of the report (&amp;quot;sheet name&amp;quot;) and value is the report data as a DataFrame. See in the above example, how the reports can be accessed by their name.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Call web service===&lt;br /&gt;
Contact to a web service, fetch some data, and store it to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let datatableName = &amp;quot;Web Service Data&amp;quot;;&lt;br /&gt;
let webServiceData = CallWebService(&lt;br /&gt;
    #{&amp;quot;Address&amp;quot;: &amp;quot;https://processanalyzer.onqpr.com/qprpa/api/serverinfo&amp;quot;}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
let targetDatatable = Project.Datatables.Where(name==datatableName);&lt;br /&gt;
if (Count(targetDatatable) == 0) {&lt;br /&gt;
	targetDatatable = Project.CreateDatatable(datatableName)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting value&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Data read&amp;quot;, &amp;quot;DateTime&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
	targetDatatable = targetDatatable[0];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let currentTime = Now;&lt;br /&gt;
let dataAsDf = ToDataFrame(&lt;br /&gt;
	webServiceData.keys.{&lt;br /&gt;
        let key = _;&lt;br /&gt;
        [key, webServiceData[key], currentTime];&lt;br /&gt;
    },&lt;br /&gt;
	[&amp;quot;Setting name&amp;quot;, &amp;quot;Setting value&amp;quot;, &amp;quot;Data read&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
targetDatatable.Import(dataAsDf, #{&amp;quot;Append&amp;quot;:true});&lt;br /&gt;
WriteLog(`${CountTop(dataAsDf.Rows)} rows written to datatable`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Store data to datatable ===&lt;br /&gt;
Get all models in the system and store them to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let newDatatable = Project&lt;br /&gt;
    .CreateDatatable(&amp;quot;Models list &amp;quot; + ToString(Now, &amp;quot;dd.MM.yyyy HH:mm:ss&amp;quot;))&lt;br /&gt;
    .AddColumn(&amp;quot;Model name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Project name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Created time&amp;quot;, &amp;quot;DateTime&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Cases&amp;quot;, &amp;quot;Integer&amp;quot;);&lt;br /&gt;
let startTime = Now;&lt;br /&gt;
let modelsData = ToDataFrame(&lt;br /&gt;
    Models.([Name, Project.Name, CreatedDate, NCases]),&lt;br /&gt;
    [&amp;quot;Model name&amp;quot;, &amp;quot;Project name&amp;quot;, &amp;quot;Created time&amp;quot;, &amp;quot;Cases&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
WriteLog(`Listing models took ${(Now - startTime).TotalSeconds.Round(2)} seconds.`);&lt;br /&gt;
newDatatable.Import(modelsData);&lt;br /&gt;
WriteLog(`Datatable ${newDatatable.Id} created.`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data (Snowflake) ===&lt;br /&gt;
Following script converts textual data from the column &amp;quot;TimestampString&amp;quot; to dates to column &amp;quot;Timestamp&amp;quot; by trying different time formats and using the first suitable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
DatatableById(1)&lt;br /&gt;
  .UpdateRows(&lt;br /&gt;
    true,&lt;br /&gt;
    &amp;quot;Timestamp&amp;quot;,&lt;br /&gt;
    Coalesce(&lt;br /&gt;
      TryToTimestamp(Column(&amp;quot;TimestampString&amp;quot;), &amp;quot;DD.MM.YYYY HH24:MI:SS.FF3&amp;quot;),&lt;br /&gt;
      TryToTimestamp(Column(&amp;quot;TimestampString&amp;quot;), &amp;quot;YYYY-MM-DD HH24:MI:SS.FF3&amp;quot;),&lt;br /&gt;
      TryToTimestamp(Column(&amp;quot;TimestampString&amp;quot;), &amp;quot;YYYY/MM/DD HH24:MI:SS.FF3&amp;quot;)&lt;br /&gt;
    )&lt;br /&gt;
  );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data (in-memory) ===&lt;br /&gt;
This script can be used to convert a single column into numerical data type. To use the script, you need to setup the following in the beginning of the script:&lt;br /&gt;
* Project name where the datatable is located.&lt;br /&gt;
* Datatable name&lt;br /&gt;
* Name of the column to be converted&lt;br /&gt;
&lt;br /&gt;
Note that the conversion fails, if there is data that cannot be converted into numerical format. The conversion assumes that period (.) is used as the decimal point. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let projectName = &amp;quot;New Project&amp;quot;;&lt;br /&gt;
let datatableName = &amp;quot;qpr processanalyzer events&amp;quot;;&lt;br /&gt;
let columnName = &amp;quot;Event order in case&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let project = (Projects.Where(Name==projectName))[0];&lt;br /&gt;
let datatable = (project.Datatables.Where(Name==datatableName))[0];&lt;br /&gt;
DatatableById(datatable.Id).DataFrame&lt;br /&gt;
.SetColumns([&lt;br /&gt;
	columnName: () =&amp;gt; {&lt;br /&gt;
		let data = Column(columnName);&lt;br /&gt;
		if (data == null) {&lt;br /&gt;
			null;&lt;br /&gt;
		 } else {&lt;br /&gt;
			ToFloat(data);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
])&lt;br /&gt;
.Persist(datatable.Name, [&amp;quot;ProjectId&amp;quot;: project.Id, &amp;quot;Append&amp;quot;: false]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of converting to numeric (with the &#039;&#039;ToFloat&#039;&#039; function), data can be converted into string using the &#039;&#039;ToString&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Show DataFrame as HTML table ===&lt;br /&gt;
&lt;br /&gt;
This script defines a function to show dataframe as a HTML table, and uses the function for a literal dataframe.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function dataframeToHtmlTable(df) {&lt;br /&gt;
	return&lt;br /&gt;
`&amp;lt;table&amp;gt;&lt;br /&gt;
	&amp;lt;tr&amp;gt;&lt;br /&gt;
		${StringJoin(&amp;quot;\r\n\t\t&amp;quot;,  + df.columns.`&amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;`)}&lt;br /&gt;
	&amp;lt;/tr&amp;gt;&lt;br /&gt;
	${StringJoin(&amp;quot;&amp;quot;, df.Rows.(&lt;br /&gt;
		&amp;quot;\r\n\t&amp;lt;tr&amp;gt;&amp;quot; + StringJoin(&amp;quot;&amp;quot;, _.`\r\n\t\t&amp;lt;td&amp;gt;${ToString(_)}&amp;lt;/td&amp;gt;`) + &amp;quot;\r\n\t&amp;lt;/tr&amp;gt;&amp;quot;&lt;br /&gt;
	))}&lt;br /&gt;
&amp;lt;/table&amp;gt;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let data = ToDataFrame(&lt;br /&gt;
	[&lt;br /&gt;
		[&amp;quot;one&amp;quot;, &amp;quot;two&amp;quot;, &amp;quot;three&amp;quot;],&lt;br /&gt;
		[&amp;quot;four&amp;quot;, &amp;quot;five&amp;quot;, &amp;quot;six&amp;quot;],&lt;br /&gt;
		[&amp;quot;seven&amp;quot;, &amp;quot;eight&amp;quot;, &amp;quot;nine&amp;quot;]&lt;br /&gt;
	],&lt;br /&gt;
	[&amp;quot;Column 1&amp;quot;, &amp;quot;Column 2&amp;quot;, &amp;quot;Column 3&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
return dataframeToHtmlTable(data);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy local datatables to Snowflake ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
// Copies all datatables in a project to another project including datatable contents.&lt;br /&gt;
// Usage instructions:&lt;br /&gt;
// 1. Create expression script in the project from where you want to copy the datatables.&lt;br /&gt;
// 2. Create a new project named as &amp;quot;&amp;lt;name of the project to be moved&amp;gt; - Snowflake&amp;quot;. New datatables will be created here. E.g., when moving project named &amp;quot;SAP_OrderToCash&amp;quot;, the target project should be named as &amp;quot;SAP_OrderToCash - Snowflake&amp;quot;.&lt;br /&gt;
// 3. Run the script.&lt;br /&gt;
// NOTE: Columns of type &amp;quot;Any&amp;quot; will be created as &amp;quot;String&amp;quot;-columns in Snowflake, thus it is recommended that actual data types are set for the tables prior to the move.&lt;br /&gt;
&lt;br /&gt;
let sourceProject = Project;&lt;br /&gt;
let sourceProjectName = Project.Name;&lt;br /&gt;
let targetProjectName = `${sourceProjectName} - Snowflake`;&lt;br /&gt;
let targetProject = First(Projects.Where(Name == targetProjectName));&lt;br /&gt;
if (IsNull(targetProject)) {&lt;br /&gt;
  WriteLog(`Unable to find target project named &amp;quot;${targetProjectName}&amp;quot;. Aborting operation.`);&lt;br /&gt;
  return;&lt;br /&gt;
}&lt;br /&gt;
let dts = sourceProject.DataTables;&lt;br /&gt;
WriteLog(`Copying all ${CountTop(dts)} data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
dts.{&lt;br /&gt;
  let sourceDt = _;&lt;br /&gt;
  WriteLog(`Starting to copy data table &amp;quot;${Name}&amp;quot; (id: ${Id}) having ${NRows} rows and ${NColumns} columns.`);&lt;br /&gt;
  let targetDt;&lt;br /&gt;
  targetDt = targetProject.DatatableByName(sourceDt.Name);&lt;br /&gt;
  if (targetDt == null) {&lt;br /&gt;
    targetDt = targetProject.CreateDataTable(sourceDt.Name, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: targetProject.Id})});&lt;br /&gt;
    targetDt.Import(sourceDt.SqlDataFrame);&lt;br /&gt;
    WriteLog(`Finished copying data table &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  } else {&lt;br /&gt;
    WriteLog(`Datatable already exist &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
WriteLog(`Finished copying all the data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to copy the data but only create the Snowflake datatables with columns, you can change the line 22 to&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
targetDt.Import(sourceDt.SqlDataFrame.head(0));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy single datatable to Snowflake ===&lt;br /&gt;
This script creates a copy of a single datatable to Snowflake. Replace the &#039;&#039;&amp;lt;tableId1&amp;gt;&#039;&#039; with the id of the source datatable.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function CopyDataTableToSnowflake(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let sourceDt = DataTableById(dataTableId);&lt;br /&gt;
  sourceDt.SqlDataFrame.Persist(`${sourceDt.Name} - Snowflake`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: sourceDt.Project.Id})});&lt;br /&gt;
}&lt;br /&gt;
CopyDataTableToSnowflake(&amp;lt;tableId1&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a copy of a data table that has all Any-type columns changed to String-type columns ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function ConvertAnyDataTypesToStringsToNewTable(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let dt = DataTableById(dataTableId);&lt;br /&gt;
  let sdf = dt.SqlDataFrame;&lt;br /&gt;
  let cts = dt.ColumnTypes;&lt;br /&gt;
  cts.{&lt;br /&gt;
    let ct = _;&lt;br /&gt;
    if (ct.DataType == &amp;quot;Any&amp;quot;) {&lt;br /&gt;
      let n = ct.Name;&lt;br /&gt;
      sdf = sdf.WithColumn(ct.Name, #sql{Cast(Column(Variable(&amp;quot;n&amp;quot;)), &amp;quot;ShortString&amp;quot;)});&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  sdf.Persist(`${dt.Name} - Converted`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;ProjectId&amp;quot;: dt.Project.Id});&lt;br /&gt;
}&lt;br /&gt;
ConvertAnyDataTypesToStringsToNewTable(&amp;lt;dataTableId&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Query number of rows in given data table having a datetime value in given year grouped by month and return resulting table as CSV ===&lt;br /&gt;
SqlDataFrame is used in order to prevent loading the whole datatable into memory first. Filtering is performed as first operation in order to minimize the amount of required work for the data source of the data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
DataTableById(&amp;lt;data table id&amp;gt;)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .Where(#sql{2014 == Year(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .WithColumn(&amp;quot;Month&amp;quot;, #sql{Month(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .GroupBy([&amp;quot;Month&amp;quot;]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
  .OrderByColumns([&amp;quot;Month&amp;quot;], [true])&lt;br /&gt;
  .Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Function for filtering SqlDataFrame by removing rows having, or replacing, the most infrequently occurring column values ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/***&lt;br /&gt;
 * @name ColumnWithMinUsage&lt;br /&gt;
 * @descripion&lt;br /&gt;
 * Generic function that can be used to filter out the most infrequently occurring attribute values or replace their Values&lt;br /&gt;
 * with given common value.&lt;br /&gt;
 * @param df:&lt;br /&gt;
 * DataFrame to operate on.&lt;br /&gt;
 * @param columnName:&lt;br /&gt;
 * Name of the column to be filtered.&lt;br /&gt;
 * @param newColumnName:&lt;br /&gt;
 * Name of the column that will contain the new value of the original column after filtering (if includeOthers was applied).&lt;br /&gt;
 * @param maxNumUniqueValues:&lt;br /&gt;
 * Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
 * @param minValueUsage:&lt;br /&gt;
 * Minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
 * @param includeOthers:&lt;br /&gt;
 * Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
 * If not empty/null, defines the name used for these other-values.&lt;br /&gt;
 */&lt;br /&gt;
function ColumnWithMinUsage(df, columnName, newColumnName, maxNumUniqueValues, minValueUsage, includeOthers)&lt;br /&gt;
{&lt;br /&gt;
  let all = df&lt;br /&gt;
	.GroupBy([])&lt;br /&gt;
	.Aggregate([&amp;quot;NAllTotal&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
	.WithColumn(&amp;quot;__Join2&amp;quot;, #sql{1});&lt;br /&gt;
  let minValueUsageEnabled = !IsNullTop(minValueUsage);&lt;br /&gt;
  let maxNumUniqueValuesEnabled = !IsNullTop(maxNumUniqueValues);&lt;br /&gt;
  if (minValueUsageEnabled || maxNumUniqueValuesEnabled) {&lt;br /&gt;
	// Perform column value-based filtering if minValueUsageEnabled or maxNumUniqueValuesEnabled is defined.&lt;br /&gt;
    let valueColumnName = &amp;quot;__ValueNew&amp;quot;;&lt;br /&gt;
	let filteredValuesColumns = [valueColumnName: columnName];&lt;br /&gt;
	let filteredValues = df&lt;br /&gt;
	  .GroupBy([columnName]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;]);&lt;br /&gt;
	if (minValueUsageEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithColumn(&amp;quot;__Join&amp;quot;, #sql{1})&lt;br /&gt;
		.Join(all, [&amp;quot;__Join&amp;quot;: &amp;quot;__Join2&amp;quot;], &amp;quot;leftouter&amp;quot;)&lt;br /&gt;
        .WithColumn(&amp;quot;Usage&amp;quot;, #sql{Column(&amp;quot;Count&amp;quot;) / Column(&amp;quot;NAllTotal&amp;quot;)});&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;Usage&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
	if (maxNumUniqueValuesEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithRowNumberColumn(&amp;quot;RowNumber&amp;quot;, [&amp;quot;Count&amp;quot;], null, [false]);&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;RowNumber&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	filteredValues = filteredValues&lt;br /&gt;
	  .Select(filteredValuesColumns);&lt;br /&gt;
&lt;br /&gt;
	// Generate select returning all the accepted values.&lt;br /&gt;
	let allValues = filteredValues&lt;br /&gt;
	  .(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;gt;= #expr{minValueUsage}}) : _)&lt;br /&gt;
	  .(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;lt;= #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
	  .Select([valueColumnName, newColumnName: valueColumnName]);&lt;br /&gt;
&lt;br /&gt;
	if (!IsNullTop(includeOthers)) {&lt;br /&gt;
	  // If includeOthers is defined, replace original values with the variable defined in includeOthers.&lt;br /&gt;
	  let otherValues = filteredValues&lt;br /&gt;
		.(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;lt; #expr{minValueUsage}}) : _)&lt;br /&gt;
		.(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;gt; #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
		.WithColumn(newColumnName, #sql{#expr{includeOthers}})&lt;br /&gt;
		.Select([valueColumnName, newColumnName]);&lt;br /&gt;
	  allValues = allValues.Append(otherValues)&lt;br /&gt;
	}&lt;br /&gt;
	df.Join(allValues, [columnName: valueColumnName], &amp;quot;inner&amp;quot;)&lt;br /&gt;
	  .RemoveColumns([valueColumnName]);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// The following example will return only rows containing two of the most common values for Region-column.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, 2, null, null);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&lt;br /&gt;
// The following example will return all input rows, but will replace the values of rows whose Region-column&lt;br /&gt;
// has a value used by less than 15% of all the rows with a new value: &amp;quot;_Others&amp;quot;.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, null, 0.15, &amp;quot;_Others&amp;quot;);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export model events and cases ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function ExportModelEvents(m) {&lt;br /&gt;
  let attrs = m.EventAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Events.Concat(&lt;br /&gt;
      [Case.Name, Type.Name, ToString(TimeStamp, &amp;quot;yyyy-MM-dd HH:mm:ss.fff&amp;quot;)], &lt;br /&gt;
      {let evt = _; attrs.{let att = _; evt.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;, &amp;quot;EventType&amp;quot;, &amp;quot;TimeStamp&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
function ExportModelCases(m) {&lt;br /&gt;
  let attrs = m.CaseAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Cases.Concat(&lt;br /&gt;
      [Name], &lt;br /&gt;
      {let cas = _; attrs.{let att = _; cas.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).EventsDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).CasesDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Calculate all the value usages of a single column for each event in event data table ===&lt;br /&gt;
This query could be used, e.g., to find out the maximum resource usage for every resource found in the event data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function WithUsageColumns(resourceColumn)&lt;br /&gt;
{&lt;br /&gt;
  function WithTotalUsageColumnOfSingleResource(resourceColumn, resourceValue)&lt;br /&gt;
  {&lt;br /&gt;
    _&lt;br /&gt;
      .WithColumn(&amp;quot;_Prev&amp;quot;, #sql{Lag(Column(resourceColumn), [TimeStamp, EventType], [true, true], [CaseId], 1, null)})&lt;br /&gt;
      .WithColumn(&amp;quot;_UsageDiff&amp;quot;, #sql{&lt;br /&gt;
        CaseWhen(&lt;br /&gt;
          Column(resourceColumn) == Column(&amp;quot;_Prev&amp;quot;), 0, &lt;br /&gt;
          Column(&amp;quot;_Prev&amp;quot;) == #expr{resourceValue}, -1,&lt;br /&gt;
          Column(resourceColumn) == #expr{resourceValue}, 1,&lt;br /&gt;
          0)&lt;br /&gt;
      })&lt;br /&gt;
      .WithColumn(`${resourceValue}_Usage`, #sql{Sum(Column(&amp;quot;_UsageDiff&amp;quot;), [TimeStamp, EventType])})&lt;br /&gt;
      .RemoveColumns([&amp;quot;_Prev&amp;quot;, &amp;quot;_UsageDiff&amp;quot;])&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let sdf = _;&lt;br /&gt;
  let allValues = sdf.SelectDistinct([resourceColumn]).OrderByColumns([resourceColumn], [true]).Collect().Column(resourceColumn);&lt;br /&gt;
  allValues.{&lt;br /&gt;
    let v = _;&lt;br /&gt;
    sdf = sdf.WithTotalUsageColumnOfSingleResource(resourceColumn, v)&lt;br /&gt;
  }&lt;br /&gt;
  sdf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
dt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .WithUsageColumns(&amp;lt;resource column name&amp;gt;)&lt;br /&gt;
  .OrderByColumns([dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]], [true])&lt;br /&gt;
  .Collect().ToCsv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;model id&amp;gt; is the id of the model containing event data to be examined.&lt;br /&gt;
* &amp;lt;resource column name&amp;gt; is the name of the column in the event data table of the specified model containing the resource being used by that event.&lt;br /&gt;
&lt;br /&gt;
NOTE: This expression uses functionalities that are only supported in Snowflake-based data tables.&lt;br /&gt;
&lt;br /&gt;
=== Create new Snowflake model from filter ===&lt;br /&gt;
This script creates a new Snowflake model (and two datatables for cases and events) containing filtered event log from given filter id. The script also works if the model doesn&#039;t have a cases datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let filter = FilterById(1); // filter id&lt;br /&gt;
let model = filter.model;&lt;br /&gt;
let project = model.project;&lt;br /&gt;
let nameSuffix = &amp;quot; - &amp;quot; + filter.name + &amp;quot; - &amp;quot; + ToString(Now, &amp;quot;dd-MM-yyyy HH:mm:ss&amp;quot;);&lt;br /&gt;
let eventsDatatableName = model.EventsDataTable.Name + nameSuffix;&lt;br /&gt;
if (eventsDatatableName.length &amp;gt; 440) {&lt;br /&gt;
  eventsDatatableName = eventsDatatableName.Substring(eventsDatatableName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
let eventsData = model&lt;br /&gt;
  .EventsDataTable&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .ApplyFilter(&lt;br /&gt;
    filter.rules,&lt;br /&gt;
    model.CasesDataTable?.SqlDataFrame&lt;br /&gt;
  );&lt;br /&gt;
project&lt;br /&gt;
  .CreateDatatable(eventsDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
  .Import(eventsData);&lt;br /&gt;
let modelConfiguration = model.Configuration;&lt;br /&gt;
modelConfiguration.DataSource.Events.Set(&amp;quot;DataTableName&amp;quot;, eventsDatatableName);&lt;br /&gt;
if (model.CasesDataTable != null) {&lt;br /&gt;
  let eventsDataCaseIdColumn = &amp;quot;CaseId_&amp;quot; + ToString(Random());&lt;br /&gt;
  let casesDatatableName = model.CasesDataTable.Name + nameSuffix;&lt;br /&gt;
  if (casesDatatableName.length &amp;gt; 440) {&lt;br /&gt;
    casesDatatableName = casesDatatableName.Substring(casesDatatableName.length - 440);&lt;br /&gt;
  }&lt;br /&gt;
  let casesData = model&lt;br /&gt;
    .CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .join(&lt;br /&gt;
	  eventsData.SelectDistinct([eventsDataCaseIdColumn: modelConfiguration.DataSource.Events.Columns.CaseId]),&lt;br /&gt;
      [modelConfiguration.DataSource.Cases.Columns.CaseId: eventsDataCaseIdColumn]&lt;br /&gt;
	).Select(model.CasesDataTable.ColumnNames);&lt;br /&gt;
  project&lt;br /&gt;
    .CreateDatatable(casesDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
    .Import(casesData);&lt;br /&gt;
  modelConfiguration.DataSource.Cases.Set(&amp;quot;DataTableName&amp;quot;, casesDatatableName);&lt;br /&gt;
}&lt;br /&gt;
let modelName = model.Name + nameSuffix;&lt;br /&gt;
if (modelName &amp;gt; 440) {&lt;br /&gt;
  modelName = modelName.Substring(modelName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
project&lt;br /&gt;
  .CreateModel(#{    &lt;br /&gt;
    &amp;quot;Name&amp;quot;: modelName,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: model.Description,&lt;br /&gt;
    &amp;quot;Configuration&amp;quot;: modelConfiguration&lt;br /&gt;
  });&lt;br /&gt;
return modelName;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Creating a model consisting of multiple copies of cases in an existing model ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @name CreateTestModel&lt;br /&gt;
 * @description&lt;br /&gt;
 * Creates a new model (or overwrites an existing) to given target project with given number of &lt;br /&gt;
 * repetitions of given source model.&lt;br /&gt;
 * Each repetition will generate &amp;quot;&amp;lt;N&amp;gt;-&amp;quot;-prefix to CaseId-columns, where N equals to the repeat index.&lt;br /&gt;
 * @param sourceModel&lt;br /&gt;
 * PA model used for the source data and from where the connection is copied for the target model if a &lt;br /&gt;
 * new one has to be created.&lt;br /&gt;
 * @param numRepeats&lt;br /&gt;
 * Number of times the data in the source model should be repeated in the generated model.&lt;br /&gt;
 * @param targetProject&lt;br /&gt;
 * Project in which the target model resides.&lt;br /&gt;
 * @param targetModelName&lt;br /&gt;
 * Specifies the name of the test model in the given target project. If a model already exists with &lt;br /&gt;
 * given name, event and case data in this model will be replaced with the new generated event and &lt;br /&gt;
 * case data.&lt;br /&gt;
 * @returns&lt;br /&gt;
 * Model object of the test model having the newly generated data.&lt;br /&gt;
 */&lt;br /&gt;
function CreateTestModel(sourceModel, numRepeats, targetProject, targetModelName) &lt;br /&gt;
{&lt;br /&gt;
  let eventsColumnMappings = sourceModel.EventsDataTable.ColumnMappings;&lt;br /&gt;
  let casesColumnMappings = sourceModel.CasesDataTable.ColumnMappings;&lt;br /&gt;
  let connection = sourceModel.EventsDataTable.DataSourceConnection;&lt;br /&gt;
&lt;br /&gt;
  function CreateResultModel()&lt;br /&gt;
  {&lt;br /&gt;
    function GetTable(tableName) &lt;br /&gt;
    {&lt;br /&gt;
      let tableConfiguration = #{&lt;br /&gt;
        &amp;quot;Name&amp;quot;: tableName,&lt;br /&gt;
        &amp;quot;Connection&amp;quot;: connection&lt;br /&gt;
      };&lt;br /&gt;
      let resultTable = targetProject.DataTableByName(tableName);&lt;br /&gt;
      if (resultTable == null)&lt;br /&gt;
      {&lt;br /&gt;
        resultTable = targetProject.CreateDataTable(tableConfiguration)&lt;br /&gt;
          .Modify(#{&amp;quot;NameInDataSource&amp;quot;: null})&lt;br /&gt;
          .Synchronize();&lt;br /&gt;
      }&lt;br /&gt;
      return resultTable;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let eventsTableName = `${targetModelName} - events`;&lt;br /&gt;
    let casesTableName = `${targetModelName} - cases`;&lt;br /&gt;
    let targetModel = targetProject.ModelByName(targetModelName);&lt;br /&gt;
    let eventsTable, casesTable = null;&lt;br /&gt;
&lt;br /&gt;
    if (targetModel != null)&lt;br /&gt;
    {&lt;br /&gt;
      eventsTable = targetModel.EventsDataTable;&lt;br /&gt;
      casesTable = targetModel.CasesDataTable;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      eventsTable = GetTable(eventsTableName);&lt;br /&gt;
      if (sourceModel.CasesDataTable != null) {&lt;br /&gt;
        casesTable = GetTable(casesTableName);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      let timestampMapping = eventsColumnMappings[&amp;quot;TimeStamp&amp;quot;];&lt;br /&gt;
      eventsColumnMappings.Remove(&amp;quot;TimeStamp&amp;quot;);&lt;br /&gt;
      eventsColumnMappings.Set(&amp;quot;Timestamp&amp;quot;, timestampMapping);&lt;br /&gt;
&lt;br /&gt;
      let modelConfiguration = #{&lt;br /&gt;
        &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
          &amp;quot;Events&amp;quot;:#{&lt;br /&gt;
            &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
            &amp;quot;DataTableName&amp;quot;: eventsTableName,&lt;br /&gt;
            &amp;quot;Columns&amp;quot;: eventsColumnMappings&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      if (casesColumnMappings != null) {&lt;br /&gt;
        modelConfiguration[&amp;quot;DataSource&amp;quot;].Set(&amp;quot;Cases&amp;quot;, #{&lt;br /&gt;
          &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
          &amp;quot;DataTableName&amp;quot;: casesTableName,&lt;br /&gt;
          &amp;quot;Columns&amp;quot;: casesColumnMappings&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      targetModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: targetModelName, &amp;quot;Configuration&amp;quot;: modelConfiguration});&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    eventsTable.Truncate();&lt;br /&gt;
    casesTable?.Truncate();&lt;br /&gt;
&lt;br /&gt;
    return #{&lt;br /&gt;
      &amp;quot;TargetModel&amp;quot;: targetModel,&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTable,&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: casesTable&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function RepeatNTimes(sourceDf, caseIdColumn, numRepeats)&lt;br /&gt;
  {&lt;br /&gt;
    let resultDf = null;&lt;br /&gt;
    for (let i = 1; i &amp;lt;= numRepeats; ++i) {&lt;br /&gt;
      let iterationDf = sourceDf&lt;br /&gt;
        .WithColumn(caseIdColumn, #sql{Concat(#expr{i}, &amp;quot;-&amp;quot;, Column(#expr{caseIdColumn}))});&lt;br /&gt;
      resultDf = resultDf == null ? iterationDf : resultDf.Append(iterationDf); &lt;br /&gt;
    }&lt;br /&gt;
    resultDf;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let resultModel = CreateResultModel();&lt;br /&gt;
  let sourceEventDataDf = sourceModel.EventsDataTable.SqlDataFrame;&lt;br /&gt;
  let resultEventDataDf = RepeatNTimes(sourceEventDataDf, eventsColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
  resultModel[&amp;quot;Events&amp;quot;].Import(resultEventDataDf);&lt;br /&gt;
&lt;br /&gt;
  let sourceCaseDataDf = sourceModel.CasesDataTable?.SqlDataFrame;&lt;br /&gt;
  if (sourceCaseDataDf != null) {&lt;br /&gt;
    let resultCaseDataDf = RepeatNTimes(sourceCaseDataDf, casesColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
    resultModel[&amp;quot;Cases&amp;quot;].Import(resultCaseDataDf);&lt;br /&gt;
  }&lt;br /&gt;
  resultModel[&amp;quot;TargetModel&amp;quot;];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example usage:&amp;lt;blockquote&amp;gt;CreateTestModel(ProjectByName(&amp;quot;Project&amp;quot;).ModelByName(&amp;quot;SAP_OrderToCash - Snowflake&amp;quot;), 3, ProjectByName(&amp;quot;TestData&amp;quot;), &amp;quot;TestModel&amp;quot;);&amp;lt;/blockquote&amp;gt;Creates a new model named &amp;quot;TestModel&amp;quot; (or overwrites old one) into project named &amp;quot;TestData&amp;quot; containing the data from model &amp;quot;SAP_OrderToCash - Snowflake&amp;quot; in project &amp;quot;Project&amp;quot; repeated three times.&lt;br /&gt;
&lt;br /&gt;
=== Analyzing declare patterns found in event log ===&lt;br /&gt;
&lt;br /&gt;
This is an example expression that shows how POSIX-style regular expressions can be used to search for cases in an event log having certain event type patterns [https://www.researchgate.net/publication/277631859_Generating_Event_Logs_Through_the_Simulation_of_Declare_Models declare patterns].&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
let sdf = dt.SqlDataFrame.Head(1000);&lt;br /&gt;
let caseIdColumn = dt.ColumnMappings[&amp;quot;CaseId&amp;quot;], timeStampColumn = dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;], eventTypeColumn = dt.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let eventTypesDf = sdf&lt;br /&gt;
  .SelectDistinct([eventTypeColumn])&lt;br /&gt;
  .OrderByColumns([eventTypeColumn], [true])&lt;br /&gt;
  .WithRowNumberColumn(&amp;quot;Token&amp;quot;, [eventTypeColumn])&lt;br /&gt;
  .WithColumn(&amp;quot;Token&amp;quot;, #sql{Char(Column(&amp;quot;Token&amp;quot;) + Unicode(&amp;quot;a&amp;quot;) - 1)});&lt;br /&gt;
&lt;br /&gt;
sdf = sdf&lt;br /&gt;
  .Join(eventTypesDf.Select([&amp;quot;_EventType2&amp;quot;: eventTypeColumn, &amp;quot;Token&amp;quot;]), [eventTypeColumn: &amp;quot;_EventType2&amp;quot;], &amp;quot;leftouter&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function RespondedExistencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*((a.*b.*)|(b.*a.*))*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*((${a}.*${b}.*)|(${b}.*${a}.*))*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a.*b)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}.*${b})*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function AlternateResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a[^a]*b[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}[^${a}]*${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ChainResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(ab[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function PrecedencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^b]*(a.*b)*[^b]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${b}]*(${a}.*${b})*[^${b}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let tracesDf = sdf&lt;br /&gt;
  .GroupBy([caseIdColumn])&lt;br /&gt;
  .Aggregate([&amp;quot;Trace&amp;quot;: &amp;quot;Token&amp;quot;], [#{&amp;quot;Function&amp;quot;: &amp;quot;list&amp;quot;, &amp;quot;Ordering&amp;quot;: [timeStampColumn], &amp;quot;Separator&amp;quot;: &amp;quot;&amp;quot;}])&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceNL&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceCA&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternNL&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternCA&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternNL&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternCA&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternNL&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternCA&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternNL&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternCA&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
[tracesDf.Collect().ToCsv(), eventTypesDf.Collect().ToCsv()]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Perform a query and send results as E-mail in a HTML table ===&lt;br /&gt;
This example requires two scripts that are both located in the same project:&lt;br /&gt;
&lt;br /&gt;
# Expression-type script to execute (can be, e.g., scheduled to run daily).&lt;br /&gt;
# Expression-type script containing the query JSON to use as basis for the e-mail. In this example, this script is named as &amp;quot;Send query as E-mail - query JSON&amp;quot;. The contents of this script is just the JSON representation of a query that can be extracted, e.g., from any PA chart view.&lt;br /&gt;
&lt;br /&gt;
Script #1 should contain the following code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let replyToAddress = &amp;quot;noreply@test.com&amp;quot;;&lt;br /&gt;
let recipientsArray = [&amp;quot;testuser@test.com&amp;quot;];&lt;br /&gt;
let queryConfiguration = ParseJson(Project.ScriptByName(&amp;quot;Send query as E-mail - query JSON&amp;quot;).Code)&lt;br /&gt;
  .Set(&amp;quot;IncludeCollect&amp;quot;, true);&lt;br /&gt;
let resultDf = Query(queryConfiguration);&lt;br /&gt;
let mailBodyHtml = resultDf.`&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
  &amp;lt;caption&amp;gt;Example query&amp;lt;/caption&amp;gt;&lt;br /&gt;
  &amp;lt;thead&amp;gt;&lt;br /&gt;
    &amp;lt;tr&amp;gt;&lt;br /&gt;
      ${StringJoin(&amp;quot;&amp;quot;, _.Columns.`&lt;br /&gt;
        &amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;&lt;br /&gt;
      `)}&lt;br /&gt;
    &amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;/thead&amp;gt;&lt;br /&gt;
  &amp;lt;tbody&amp;gt;&lt;br /&gt;
    ${StringJoin(&amp;quot;&amp;quot;, _.Rows.`&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
        ${StringJoin(&amp;quot;&amp;quot;, _.`&lt;br /&gt;
          &amp;lt;td&amp;gt;${_}&amp;lt;/td&amp;gt;&lt;br /&gt;
        `)}&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
    `)}&lt;br /&gt;
  &amp;lt;/tbody&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
`;&lt;br /&gt;
&lt;br /&gt;
SendEmail(#{&lt;br /&gt;
  &amp;quot;ReplyTo&amp;quot;: [replyToAddress],&lt;br /&gt;
  &amp;quot;To&amp;quot;: recipientsArray,&lt;br /&gt;
  &amp;quot;Subject&amp;quot;: &amp;quot;Example query E-mail&amp;quot;,&lt;br /&gt;
  &amp;quot;IsBodyHtml&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Body&amp;quot;: mailBodyHtml&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;NOTE: QPR ProcessAnalyzer Server has to have SMTP server configured. Also, remember to update the values of replyToAddress and recipientsArray before using.&lt;br /&gt;
&lt;br /&gt;
=== Converting a case-centric model to object-centric model ===&lt;br /&gt;
This function serves as an example on how a case-centric model could be converted into an object-centric model having just one object type: &amp;quot;Case&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ConvertCCModelToOCModel(model, newModelName) &lt;br /&gt;
{&lt;br /&gt;
  let connection = model.EventsDataTable.DataSourceConnection;&lt;br /&gt;
  let caseIdColumn = model.EventsDataTable.ColumnMappings[&amp;quot;CaseId&amp;quot;];&lt;br /&gt;
  let eventToObjectTableName = `${newModelName} - event-to-object`;&lt;br /&gt;
  let eventsTableName = `${newModelName} - events`;&lt;br /&gt;
  let objectsTableName = `${newModelName} - objects`;&lt;br /&gt;
          &lt;br /&gt;
  let eventsDf = model.EventsDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelEventType&amp;quot;: model.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;],&lt;br /&gt;
      &amp;quot;OcelEventTime&amp;quot;: model.EventsDataTable.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelEventId&amp;quot;, #sql{Concat(&amp;quot;evt-&amp;quot;, Cast(RowNumber([Column(&amp;quot;OcelEventTime&amp;quot;)]), &amp;quot;String&amp;quot;))});&lt;br /&gt;
&lt;br /&gt;
  eventsDf&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelEventToObjectSourceId&amp;quot;: &amp;quot;OcelEventId&amp;quot;,&lt;br /&gt;
      &amp;quot;OcelEventToObjectTargetId&amp;quot;: caseIdColumn])&lt;br /&gt;
    .Select([&amp;quot;OcelEventToObjectSourceId&amp;quot;, &amp;quot;OcelEventToObjectTargetId&amp;quot;])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelEventToObjectQualifier&amp;quot;, #sql{#expr{caseIdColumn} })&lt;br /&gt;
    .Persist(eventToObjectTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  eventsDf&lt;br /&gt;
    .RemoveColumns([caseIdColumn])&lt;br /&gt;
    .Persist(eventsTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  let casesDt = model.CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelObjectId&amp;quot;: model.CasesDataTable.ColumnMappings[&amp;quot;CaseId&amp;quot;]&lt;br /&gt;
    ])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelObjectType&amp;quot;, #sql{&amp;quot;Case&amp;quot;})&lt;br /&gt;
    .Persist(objectsTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  let newConfiguration = #{&lt;br /&gt;
    &amp;quot;OcelDataSource&amp;quot;: #{&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTableName,&lt;br /&gt;
      &amp;quot;Objects&amp;quot;: objectsTableName,&lt;br /&gt;
      &amp;quot;EventToObject&amp;quot;: eventToObjectTableName&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  model.Project  &lt;br /&gt;
    .CreateModel(#{      &lt;br /&gt;
      &amp;quot;Name&amp;quot;: newModelName,  &lt;br /&gt;
      &amp;quot;Description&amp;quot;: model.Description,  &lt;br /&gt;
      &amp;quot;Configuration&amp;quot;: newConfiguration  &lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let ccModel = ModelById(&amp;lt;model id&amp;gt;);&lt;br /&gt;
ConvertCCModelToOCModel(ccModel, `ocel - ${ccModel.Name}`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Show duplicate rows in datatable ===&lt;br /&gt;
This script returns all rows in a datatable appearing more than once (sorted by the most frequent occurrences first). Additionally, the number of occurrences is returned as the last column. Replace the datatable id with the correct one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let datatableId = 1;&lt;br /&gt;
let rowCountColumn = &amp;quot;Row count&amp;quot;;&lt;br /&gt;
let columns = DatatableById(datatableId).Columns.Name;&lt;br /&gt;
DatatableById(datatableId)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .GroupBy(columns)&lt;br /&gt;
  .Aggregate(&lt;br /&gt;
    [rowCountColumn: columns[0]],&lt;br /&gt;
    [&amp;quot;Count&amp;quot;]&lt;br /&gt;
  )&lt;br /&gt;
  .Where(Column(rowCountColumn) &amp;gt; 1)&lt;br /&gt;
  .OrderByColumns([rowCountColumn], [false])&lt;br /&gt;
  .Collect()&lt;br /&gt;
  .ToCSV();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a new object-centric model based on already existing object-centric model containing only filtered objects and events ===&lt;br /&gt;
Script for creating a new object-centric model based on another already existing object-centric model that applies given filter to the original model and creates all the needed tables to store only the relevant rows for all the object-centric data tables.&lt;br /&gt;
&lt;br /&gt;
NOTE: Replace &amp;lt;nowiki&amp;gt;&amp;lt;source model id&amp;gt;, &amp;lt;target model name&amp;gt;, &amp;lt;target project id&amp;gt; as well as the value of ocelItems with the applicable values before use.&amp;lt;/nowiki&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let newModelName = &amp;quot;&amp;lt;target model name&amp;gt;&amp;quot;;&lt;br /&gt;
let sourceModelId = &amp;lt;source model id&amp;gt;;&lt;br /&gt;
let targetProjectId = &amp;lt;target project id&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
// Replace the value of the ocelItems below with the configuration of object-centric filter to apply.&lt;br /&gt;
// Note: Use expression language syntax here, not JSON.&lt;br /&gt;
let ocelItems = [#{&lt;br /&gt;
  &amp;quot;Include&amp;quot;: true,&lt;br /&gt;
  &amp;quot;ObjectAttributeValue&amp;quot;: #{&lt;br /&gt;
    &amp;quot;ObjectType&amp;quot;: &amp;quot;Purchase Order&amp;quot;,&lt;br /&gt;
    &amp;quot;Attribute&amp;quot;: &amp;quot;po_product&amp;quot;,&lt;br /&gt;
    &amp;quot;Values&amp;quot;: [&lt;br /&gt;
      &amp;quot;0Cows&amp;quot;&lt;br /&gt;
    ]&lt;br /&gt;
  }&lt;br /&gt;
}];&lt;br /&gt;
&lt;br /&gt;
let m = ModelById(sourceModelId);&lt;br /&gt;
if (!m.IsOcelModel)&lt;br /&gt;
  throw `Model ${m.Name} is not an OCPM model`;&lt;br /&gt;
if (CountTop(m.CheckModelValidity()) &amp;gt; 0)&lt;br /&gt;
  throw `Model ${m.Name} is not a valid OCPM model`;&lt;br /&gt;
&lt;br /&gt;
let targetProject = ProjectById(targetProjectId);&lt;br /&gt;
if (CountTop(targetProject.Models.Where(Name == newModelName)) &amp;gt; 0)&lt;br /&gt;
  throw `Model having name ${newModelName} already exists in project ${targetProject.Name}. Rename or delete the existing model before running this script.`;&lt;br /&gt;
&lt;br /&gt;
let newConfiguration = #{&lt;br /&gt;
  &amp;quot;OcelDataSource&amp;quot;: #{&lt;br /&gt;
    &amp;quot;EventTypes&amp;quot;: #{},&lt;br /&gt;
    &amp;quot;ObjectTypes&amp;quot;: #{}&lt;br /&gt;
  }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
WriteLog(&amp;quot;Creating common tables...&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function PersistDataFrame(dataFrame, configurationName, configurationDict, dataTableName)&lt;br /&gt;
{&lt;br /&gt;
  WriteLog(`Creating filtered table: ${dataTableName}`);&lt;br /&gt;
  configurationDict.Set(configurationName, dataTableName);&lt;br /&gt;
  return dataFrame.Persist(dataTableName, #{&amp;quot;ProjectId&amp;quot;: targetProjectId, &amp;quot;Append&amp;quot;: false});&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let objectsDf = m.CacheTableSqlDataFrame(#{&lt;br /&gt;
  &amp;quot;CacheTableType&amp;quot;: &amp;quot;Objects&amp;quot;,&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: #{},&lt;br /&gt;
  &amp;quot;OcelItems&amp;quot;: ocelItems &lt;br /&gt;
}).SetPrimaryKey(m.OcelObjects.PrimaryKey);&lt;br /&gt;
let newObjectsDt = PersistDataFrame(objectsDf, &amp;quot;Objects&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - objects`);&lt;br /&gt;
&lt;br /&gt;
let objectToObjectDf = m.CacheTableSqlDataFrame(#{&lt;br /&gt;
  &amp;quot;CacheTableType&amp;quot;: &amp;quot;ObjectToObject&amp;quot;,&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: #{},&lt;br /&gt;
  &amp;quot;OcelItems&amp;quot;: ocelItems &lt;br /&gt;
}).SetPrimaryKey(m.OcelObjectToObject.PrimaryKey)&lt;br /&gt;
  .Select([&amp;quot;OcelObjectToObjectSourceId&amp;quot;, &amp;quot;OcelObjectToObjectTargetId&amp;quot;, &amp;quot;OcelObjectToObjectQualifier&amp;quot;]);&lt;br /&gt;
let newObjectToObjectDt = PersistDataFrame(objectToObjectDf, &amp;quot;ObjectToObject&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - object-to-object`);&lt;br /&gt;
&lt;br /&gt;
let eventToObjectDf = m.CacheTableSqlDataFrame(#{&lt;br /&gt;
  &amp;quot;CacheTableType&amp;quot;: &amp;quot;EventToObject&amp;quot;,&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: #{},&lt;br /&gt;
  &amp;quot;OcelItems&amp;quot;: ocelItems &lt;br /&gt;
}).SetPrimaryKey(m.OcelEventToObject.PrimaryKey)&lt;br /&gt;
  .Select([&amp;quot;OcelEventToObjectSourceId&amp;quot;, &amp;quot;OcelEventToObjectTargetId&amp;quot;, &amp;quot;OcelEventToObjectQualifier&amp;quot;]);&lt;br /&gt;
let newEventToObjectDt = PersistDataFrame(eventToObjectDf, &amp;quot;EventToObject&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - event-to-object`);&lt;br /&gt;
&lt;br /&gt;
let eventsDf = m.OcelEvents.SqlDataFrame&lt;br /&gt;
  .Join(eventToObjectDf.SelectDistinct([&amp;quot;OcelEventToObjectSourceId&amp;quot;]), [&amp;quot;OcelEventId&amp;quot;: &amp;quot;OcelEventToObjectSourceId&amp;quot;], &amp;quot;inner&amp;quot;)&lt;br /&gt;
  .RemoveColumns([&amp;quot;OcelEventToObjectSourceId&amp;quot;])&lt;br /&gt;
  .SetPrimaryKey(m.OcelEvents.PrimaryKey);&lt;br /&gt;
let newEventsDt = PersistDataFrame(eventsDf, &amp;quot;Events&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - events`);&lt;br /&gt;
&lt;br /&gt;
WriteLog(&amp;quot;Creating event type tables...&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
m.Configuration.OcelDataSource.EventTypes.ToArray().{&lt;br /&gt;
  let ar = _;&lt;br /&gt;
  let eventTypeName = GetContext(ar);&lt;br /&gt;
  let eventTypeDt = m.OcelEventType(eventTypeName);&lt;br /&gt;
  let eventTypeDf = eventTypeDt.SqlDataFrame&lt;br /&gt;
    .Join(newEventsDt.SqlDataFrame.Select([&amp;quot;OcelEventId&amp;quot;]), [&amp;quot;OcelEventTypeEventId&amp;quot;: &amp;quot;OcelEventId&amp;quot;], &amp;quot;inner&amp;quot;)&lt;br /&gt;
    .RemoveColumns([&amp;quot;OcelEventId&amp;quot;])&lt;br /&gt;
    .SetPrimaryKey(eventTypeDt.PrimaryKey);&lt;br /&gt;
  PersistDataFrame(eventTypeDf, eventTypeName, newConfiguration.OcelDataSource.EventTypes, `${newModelName} - eventtype - ${eventTypeName}`);&lt;br /&gt;
};&lt;br /&gt;
  &lt;br /&gt;
WriteLog(&amp;quot;Creating object type tables...&amp;quot;);&lt;br /&gt;
let newUniqueObjectsDf = newObjectsDt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .SelectDistinct([&amp;quot;OcelObjectId&amp;quot;]);&lt;br /&gt;
&lt;br /&gt;
m.Configuration.OcelDataSource.ObjectTypes.ToArray().{&lt;br /&gt;
  let ar = _;&lt;br /&gt;
  let objectTypeName = GetContext(ar);&lt;br /&gt;
  let objectTypeDt = m.OcelObjectType(objectTypeName);&lt;br /&gt;
  let objectTypeDf = objectTypeDt.SqlDataFrame&lt;br /&gt;
    .Join(newUniqueObjectsDf, [&amp;quot;OcelObjectTypeObjectId&amp;quot;: &amp;quot;OcelObjectId&amp;quot;], &amp;quot;inner&amp;quot;)&lt;br /&gt;
    .RemoveColumns([&amp;quot;OcelObjectId&amp;quot;])&lt;br /&gt;
    .SetPrimaryKey(objectTypeDt.PrimaryKey);&lt;br /&gt;
  PersistDataFrame(objectTypeDf, objectTypeName, newConfiguration.OcelDataSource.ObjectTypes, `${newModelName} - objecttype - ${objectTypeName}`);&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
WriteLog(`Creating model ${newModelName}...`);&lt;br /&gt;
&lt;br /&gt;
let newModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: newModelName, &amp;quot;Configuration&amp;quot;: newConfiguration});&lt;br /&gt;
&lt;br /&gt;
WriteLog(`Model (id=${newModel.Id}) created with configuration:\r\n${ToJson(newConfiguration)}`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=27597</id>
		<title>Expression Script Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=27597"/>
		<updated>2026-01-12T08:18:53Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains script examples written in the QPR ProcessAnalyzer expression language. See how expression scripts can be created in the [[Managing_Scripts#Creating_Script|Workspace]]. For documentation for the syntax, functions and entities can be found from the main page in the [[QPR_ProcessAnalyzer_Wiki#For_Developers|KPI Expression Language]] section.&lt;br /&gt;
&lt;br /&gt;
== Calling Expression Script from Expression ==&lt;br /&gt;
Expression scripts can be called from an expression using the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Run]] function with the following syntax:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: false,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 123.45&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command waits until the run is completed, and the return value of the called script is returned by the Run function call.&lt;br /&gt;
&lt;br /&gt;
Parameters can be passed to the called script, and the parameters are available as variables in the script. The parameters can contain any type of data.&lt;br /&gt;
&lt;br /&gt;
Expression scripts can also be called from a dashboard. Expressions can be stored to scripts instead of dashboards, which is a way to separate complex expressions from dashboards and allow to reuse expressions across several dashboards.&lt;br /&gt;
&lt;br /&gt;
== Calling SQL Script from Expression ==&lt;br /&gt;
SQL script can be called from an expression using the Run function as follows (similar to calling [[#Calling Expression Script from Expression|expression scripts]]):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 321&lt;br /&gt;
});&lt;br /&gt;
let arrayOfAllReports = result.Keys;&lt;br /&gt;
let report1 = result.Report1;&lt;br /&gt;
let report2 = result.Report2;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL scripts can return multiple &#039;&#039;reports&#039;&#039;, which are combined to a dictionary, where the key is the name of the report (&amp;quot;sheet name&amp;quot;) and value is the report data as a DataFrame. See in the above example, how the reports can be accessed by their name.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Call web service===&lt;br /&gt;
Contact to a web service, fetch some data, and store it to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let datatableName = &amp;quot;Web Service Data&amp;quot;;&lt;br /&gt;
let webServiceData = CallWebService(&lt;br /&gt;
    #{&amp;quot;Address&amp;quot;: &amp;quot;https://processanalyzer.onqpr.com/qprpa/api/serverinfo&amp;quot;}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
let targetDatatable = Project.Datatables.Where(name==datatableName);&lt;br /&gt;
if (Count(targetDatatable) == 0) {&lt;br /&gt;
	targetDatatable = Project.CreateDatatable(datatableName)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting value&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Data read&amp;quot;, &amp;quot;DateTime&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
	targetDatatable = targetDatatable[0];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let currentTime = Now;&lt;br /&gt;
let dataAsDf = ToDataFrame(&lt;br /&gt;
	webServiceData.keys.{&lt;br /&gt;
        let key = _;&lt;br /&gt;
        [key, webServiceData[key], currentTime];&lt;br /&gt;
    },&lt;br /&gt;
	[&amp;quot;Setting name&amp;quot;, &amp;quot;Setting value&amp;quot;, &amp;quot;Data read&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
targetDatatable.Import(dataAsDf, #{&amp;quot;Append&amp;quot;:true});&lt;br /&gt;
WriteLog(`${CountTop(dataAsDf.Rows)} rows written to datatable`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Store data to datatable ===&lt;br /&gt;
Get all models in the system and store them to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let newDatatable = Project&lt;br /&gt;
    .CreateDatatable(&amp;quot;Models list &amp;quot; + ToString(Now, &amp;quot;dd.MM.yyyy HH:mm:ss&amp;quot;))&lt;br /&gt;
    .AddColumn(&amp;quot;Model name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Project name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Created time&amp;quot;, &amp;quot;DateTime&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Cases&amp;quot;, &amp;quot;Integer&amp;quot;);&lt;br /&gt;
let startTime = Now;&lt;br /&gt;
let modelsData = ToDataFrame(&lt;br /&gt;
    Models.([Name, Project.Name, CreatedDate, NCases]),&lt;br /&gt;
    [&amp;quot;Model name&amp;quot;, &amp;quot;Project name&amp;quot;, &amp;quot;Created time&amp;quot;, &amp;quot;Cases&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
WriteLog(`Listing models took ${(Now - startTime).TotalSeconds.Round(2)} seconds.`);&lt;br /&gt;
newDatatable.Import(modelsData);&lt;br /&gt;
WriteLog(`Datatable ${newDatatable.Id} created.`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data (Snowflake) ===&lt;br /&gt;
Following script converts textual data from the column &amp;quot;TimestampString&amp;quot; to dates to column &amp;quot;Timestamp&amp;quot; by trying different time formats and using the first suitable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
DatatableById(1)&lt;br /&gt;
  .UpdateRows(&lt;br /&gt;
    true,&lt;br /&gt;
    &amp;quot;Timestamp&amp;quot;,&lt;br /&gt;
    Coalesce(&lt;br /&gt;
      TryToTimestamp(Column(&amp;quot;TimestampString&amp;quot;), &amp;quot;DD.MM.YYYY HH24:MI:SS.FF3&amp;quot;),&lt;br /&gt;
      TryToTimestamp(Column(&amp;quot;TimestampString&amp;quot;), &amp;quot;YYYY-MM-DD HH24:MI:SS.FF3&amp;quot;),&lt;br /&gt;
      TryToTimestamp(Column(&amp;quot;TimestampString&amp;quot;), &amp;quot;YYYY/MM/DD HH24:MI:SS.FF3&amp;quot;)&lt;br /&gt;
    )&lt;br /&gt;
  );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data (in-memory) ===&lt;br /&gt;
This script can be used to convert a single column into numerical data type. To use the script, you need to setup the following in the beginning of the script:&lt;br /&gt;
* Project name where the datatable is located.&lt;br /&gt;
* Datatable name&lt;br /&gt;
* Name of the column to be converted&lt;br /&gt;
&lt;br /&gt;
Note that the conversion fails, if there is data that cannot be converted into numerical format. The conversion assumes that period (.) is used as the decimal point. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let projectName = &amp;quot;New Project&amp;quot;;&lt;br /&gt;
let datatableName = &amp;quot;qpr processanalyzer events&amp;quot;;&lt;br /&gt;
let columnName = &amp;quot;Event order in case&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let project = (Projects.Where(Name==projectName))[0];&lt;br /&gt;
let datatable = (project.Datatables.Where(Name==datatableName))[0];&lt;br /&gt;
DatatableById(datatable.Id).DataFrame&lt;br /&gt;
.SetColumns([&lt;br /&gt;
	columnName: () =&amp;gt; {&lt;br /&gt;
		let data = Column(columnName);&lt;br /&gt;
		if (data == null) {&lt;br /&gt;
			null;&lt;br /&gt;
		 } else {&lt;br /&gt;
			ToFloat(data);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
])&lt;br /&gt;
.Persist(datatable.Name, [&amp;quot;ProjectId&amp;quot;: project.Id, &amp;quot;Append&amp;quot;: false]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of converting to numeric (with the &#039;&#039;ToFloat&#039;&#039; function), data can be converted into string using the &#039;&#039;ToString&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Show DataFrame as HTML table ===&lt;br /&gt;
&lt;br /&gt;
This script defines a function to show dataframe as a HTML table, and uses the function for a literal dataframe.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function dataframeToHtmlTable(df) {&lt;br /&gt;
	return&lt;br /&gt;
`&amp;lt;table&amp;gt;&lt;br /&gt;
	&amp;lt;tr&amp;gt;&lt;br /&gt;
		${StringJoin(&amp;quot;\r\n\t\t&amp;quot;,  + df.columns.`&amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;`)}&lt;br /&gt;
	&amp;lt;/tr&amp;gt;&lt;br /&gt;
	${StringJoin(&amp;quot;&amp;quot;, df.Rows.(&lt;br /&gt;
		&amp;quot;\r\n\t&amp;lt;tr&amp;gt;&amp;quot; + StringJoin(&amp;quot;&amp;quot;, _.`\r\n\t\t&amp;lt;td&amp;gt;${ToString(_)}&amp;lt;/td&amp;gt;`) + &amp;quot;\r\n\t&amp;lt;/tr&amp;gt;&amp;quot;&lt;br /&gt;
	))}&lt;br /&gt;
&amp;lt;/table&amp;gt;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let data = ToDataFrame(&lt;br /&gt;
	[&lt;br /&gt;
		[&amp;quot;one&amp;quot;, &amp;quot;two&amp;quot;, &amp;quot;three&amp;quot;],&lt;br /&gt;
		[&amp;quot;four&amp;quot;, &amp;quot;five&amp;quot;, &amp;quot;six&amp;quot;],&lt;br /&gt;
		[&amp;quot;seven&amp;quot;, &amp;quot;eight&amp;quot;, &amp;quot;nine&amp;quot;]&lt;br /&gt;
	],&lt;br /&gt;
	[&amp;quot;Column 1&amp;quot;, &amp;quot;Column 2&amp;quot;, &amp;quot;Column 3&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
return dataframeToHtmlTable(data);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy local datatables to Snowflake ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
// Copies all datatables in a project to another project including datatable contents.&lt;br /&gt;
// Usage instructions:&lt;br /&gt;
// 1. Create expression script in the project from where you want to copy the datatables.&lt;br /&gt;
// 2. Create a new project named as &amp;quot;&amp;lt;name of the project to be moved&amp;gt; - Snowflake&amp;quot;. New datatables will be created here. E.g., when moving project named &amp;quot;SAP_OrderToCash&amp;quot;, the target project should be named as &amp;quot;SAP_OrderToCash - Snowflake&amp;quot;.&lt;br /&gt;
// 3. Run the script.&lt;br /&gt;
// NOTE: Columns of type &amp;quot;Any&amp;quot; will be created as &amp;quot;String&amp;quot;-columns in Snowflake, thus it is recommended that actual data types are set for the tables prior to the move.&lt;br /&gt;
&lt;br /&gt;
let sourceProject = Project;&lt;br /&gt;
let sourceProjectName = Project.Name;&lt;br /&gt;
let targetProjectName = `${sourceProjectName} - Snowflake`;&lt;br /&gt;
let targetProject = First(Projects.Where(Name == targetProjectName));&lt;br /&gt;
if (IsNull(targetProject)) {&lt;br /&gt;
  WriteLog(`Unable to find target project named &amp;quot;${targetProjectName}&amp;quot;. Aborting operation.`);&lt;br /&gt;
  return;&lt;br /&gt;
}&lt;br /&gt;
let dts = sourceProject.DataTables;&lt;br /&gt;
WriteLog(`Copying all ${CountTop(dts)} data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
dts.{&lt;br /&gt;
  let sourceDt = _;&lt;br /&gt;
  WriteLog(`Starting to copy data table &amp;quot;${Name}&amp;quot; (id: ${Id}) having ${NRows} rows and ${NColumns} columns.`);&lt;br /&gt;
  let targetDt;&lt;br /&gt;
  targetDt = targetProject.DatatableByName(sourceDt.Name);&lt;br /&gt;
  if (targetDt == null) {&lt;br /&gt;
    targetDt = targetProject.CreateDataTable(sourceDt.Name, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: targetProject.Id})});&lt;br /&gt;
    targetDt.Import(sourceDt.SqlDataFrame);&lt;br /&gt;
    WriteLog(`Finished copying data table &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  } else {&lt;br /&gt;
    WriteLog(`Datatable already exist &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
WriteLog(`Finished copying all the data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to copy the data but only create the Snowflake datatables with columns, you can change the line 22 to&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
targetDt.Import(sourceDt.SqlDataFrame.head(0));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy single datatable to Snowflake ===&lt;br /&gt;
This script creates a copy of a single datatable to Snowflake. Replace the &#039;&#039;&amp;lt;tableId1&amp;gt;&#039;&#039; with the id of the source datatable.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function CopyDataTableToSnowflake(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let sourceDt = DataTableById(dataTableId);&lt;br /&gt;
  sourceDt.SqlDataFrame.Persist(`${sourceDt.Name} - Snowflake`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: sourceDt.Project.Id})});&lt;br /&gt;
}&lt;br /&gt;
CopyDataTableToSnowflake(&amp;lt;tableId1&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a copy of a data table that has all Any-type columns changed to String-type columns ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function ConvertAnyDataTypesToStringsToNewTable(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let dt = DataTableById(dataTableId);&lt;br /&gt;
  let sdf = dt.SqlDataFrame;&lt;br /&gt;
  let cts = dt.ColumnTypes;&lt;br /&gt;
  cts.{&lt;br /&gt;
    let ct = _;&lt;br /&gt;
    if (ct.DataType == &amp;quot;Any&amp;quot;) {&lt;br /&gt;
      let n = ct.Name;&lt;br /&gt;
      sdf = sdf.WithColumn(ct.Name, #sql{Cast(Column(Variable(&amp;quot;n&amp;quot;)), &amp;quot;ShortString&amp;quot;)});&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  sdf.Persist(`${dt.Name} - Converted`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;ProjectId&amp;quot;: dt.Project.Id});&lt;br /&gt;
}&lt;br /&gt;
ConvertAnyDataTypesToStringsToNewTable(&amp;lt;dataTableId&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Query number of rows in given data table having a datetime value in given year grouped by month and return resulting table as CSV ===&lt;br /&gt;
SqlDataFrame is used in order to prevent loading the whole datatable into memory first. Filtering is performed as first operation in order to minimize the amount of required work for the data source of the data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
DataTableById(&amp;lt;data table id&amp;gt;)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .Where(#sql{2014 == Year(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .WithColumn(&amp;quot;Month&amp;quot;, #sql{Month(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .GroupBy([&amp;quot;Month&amp;quot;]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
  .OrderByColumns([&amp;quot;Month&amp;quot;], [true])&lt;br /&gt;
  .Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Function for filtering SqlDataFrame by removing rows having, or replacing, the most infrequently occurring column values ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/***&lt;br /&gt;
 * @name ColumnWithMinUsage&lt;br /&gt;
 * @descripion&lt;br /&gt;
 * Generic function that can be used to filter out the most infrequently occurring attribute values or replace their Values&lt;br /&gt;
 * with given common value.&lt;br /&gt;
 * @param df:&lt;br /&gt;
 * DataFrame to operate on.&lt;br /&gt;
 * @param columnName:&lt;br /&gt;
 * Name of the column to be filtered.&lt;br /&gt;
 * @param newColumnName:&lt;br /&gt;
 * Name of the column that will contain the new value of the original column after filtering (if includeOthers was applied).&lt;br /&gt;
 * @param maxNumUniqueValues:&lt;br /&gt;
 * Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
 * @param minValueUsage:&lt;br /&gt;
 * Minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
 * @param includeOthers:&lt;br /&gt;
 * Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
 * If not empty/null, defines the name used for these other-values.&lt;br /&gt;
 */&lt;br /&gt;
function ColumnWithMinUsage(df, columnName, newColumnName, maxNumUniqueValues, minValueUsage, includeOthers)&lt;br /&gt;
{&lt;br /&gt;
  let all = df&lt;br /&gt;
	.GroupBy([])&lt;br /&gt;
	.Aggregate([&amp;quot;NAllTotal&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
	.WithColumn(&amp;quot;__Join2&amp;quot;, #sql{1});&lt;br /&gt;
  let minValueUsageEnabled = !IsNullTop(minValueUsage);&lt;br /&gt;
  let maxNumUniqueValuesEnabled = !IsNullTop(maxNumUniqueValues);&lt;br /&gt;
  if (minValueUsageEnabled || maxNumUniqueValuesEnabled) {&lt;br /&gt;
	// Perform column value-based filtering if minValueUsageEnabled or maxNumUniqueValuesEnabled is defined.&lt;br /&gt;
    let valueColumnName = &amp;quot;__ValueNew&amp;quot;;&lt;br /&gt;
	let filteredValuesColumns = [valueColumnName: columnName];&lt;br /&gt;
	let filteredValues = df&lt;br /&gt;
	  .GroupBy([columnName]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;]);&lt;br /&gt;
	if (minValueUsageEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithColumn(&amp;quot;__Join&amp;quot;, #sql{1})&lt;br /&gt;
		.Join(all, [&amp;quot;__Join&amp;quot;: &amp;quot;__Join2&amp;quot;], &amp;quot;leftouter&amp;quot;)&lt;br /&gt;
        .WithColumn(&amp;quot;Usage&amp;quot;, #sql{Column(&amp;quot;Count&amp;quot;) / Column(&amp;quot;NAllTotal&amp;quot;)});&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;Usage&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
	if (maxNumUniqueValuesEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithRowNumberColumn(&amp;quot;RowNumber&amp;quot;, [&amp;quot;Count&amp;quot;], null, [false]);&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;RowNumber&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	filteredValues = filteredValues&lt;br /&gt;
	  .Select(filteredValuesColumns);&lt;br /&gt;
&lt;br /&gt;
	// Generate select returning all the accepted values.&lt;br /&gt;
	let allValues = filteredValues&lt;br /&gt;
	  .(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;gt;= #expr{minValueUsage}}) : _)&lt;br /&gt;
	  .(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;lt;= #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
	  .Select([valueColumnName, newColumnName: valueColumnName]);&lt;br /&gt;
&lt;br /&gt;
	if (!IsNullTop(includeOthers)) {&lt;br /&gt;
	  // If includeOthers is defined, replace original values with the variable defined in includeOthers.&lt;br /&gt;
	  let otherValues = filteredValues&lt;br /&gt;
		.(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;lt; #expr{minValueUsage}}) : _)&lt;br /&gt;
		.(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;gt; #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
		.WithColumn(newColumnName, #sql{#expr{includeOthers}})&lt;br /&gt;
		.Select([valueColumnName, newColumnName]);&lt;br /&gt;
	  allValues = allValues.Append(otherValues)&lt;br /&gt;
	}&lt;br /&gt;
	df.Join(allValues, [columnName: valueColumnName], &amp;quot;inner&amp;quot;)&lt;br /&gt;
	  .RemoveColumns([valueColumnName]);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// The following example will return only rows containing two of the most common values for Region-column.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, 2, null, null);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&lt;br /&gt;
// The following example will return all input rows, but will replace the values of rows whose Region-column&lt;br /&gt;
// has a value used by less than 15% of all the rows with a new value: &amp;quot;_Others&amp;quot;.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, null, 0.15, &amp;quot;_Others&amp;quot;);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export model events and cases ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function ExportModelEvents(m) {&lt;br /&gt;
  let attrs = m.EventAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Events.Concat(&lt;br /&gt;
      [Case.Name, Type.Name, ToString(TimeStamp, &amp;quot;yyyy-MM-dd HH:mm:ss.fff&amp;quot;)], &lt;br /&gt;
      {let evt = _; attrs.{let att = _; evt.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;, &amp;quot;EventType&amp;quot;, &amp;quot;TimeStamp&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
function ExportModelCases(m) {&lt;br /&gt;
  let attrs = m.CaseAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Cases.Concat(&lt;br /&gt;
      [Name], &lt;br /&gt;
      {let cas = _; attrs.{let att = _; cas.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).EventsDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).CasesDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Calculate all the value usages of a single column for each event in event data table ===&lt;br /&gt;
This query could be used, e.g., to find out the maximum resource usage for every resource found in the event data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function WithUsageColumns(resourceColumn)&lt;br /&gt;
{&lt;br /&gt;
  function WithTotalUsageColumnOfSingleResource(resourceColumn, resourceValue)&lt;br /&gt;
  {&lt;br /&gt;
    _&lt;br /&gt;
      .WithColumn(&amp;quot;_Prev&amp;quot;, #sql{Lag(Column(resourceColumn), [TimeStamp, EventType], [true, true], [CaseId], 1, null)})&lt;br /&gt;
      .WithColumn(&amp;quot;_UsageDiff&amp;quot;, #sql{&lt;br /&gt;
        CaseWhen(&lt;br /&gt;
          Column(resourceColumn) == Column(&amp;quot;_Prev&amp;quot;), 0, &lt;br /&gt;
          Column(&amp;quot;_Prev&amp;quot;) == #expr{resourceValue}, -1,&lt;br /&gt;
          Column(resourceColumn) == #expr{resourceValue}, 1,&lt;br /&gt;
          0)&lt;br /&gt;
      })&lt;br /&gt;
      .WithColumn(`${resourceValue}_Usage`, #sql{Sum(Column(&amp;quot;_UsageDiff&amp;quot;), [TimeStamp, EventType])})&lt;br /&gt;
      .RemoveColumns([&amp;quot;_Prev&amp;quot;, &amp;quot;_UsageDiff&amp;quot;])&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let sdf = _;&lt;br /&gt;
  let allValues = sdf.SelectDistinct([resourceColumn]).OrderByColumns([resourceColumn], [true]).Collect().Column(resourceColumn);&lt;br /&gt;
  allValues.{&lt;br /&gt;
    let v = _;&lt;br /&gt;
    sdf = sdf.WithTotalUsageColumnOfSingleResource(resourceColumn, v)&lt;br /&gt;
  }&lt;br /&gt;
  sdf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
dt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .WithUsageColumns(&amp;lt;resource column name&amp;gt;)&lt;br /&gt;
  .OrderByColumns([dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]], [true])&lt;br /&gt;
  .Collect().ToCsv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;model id&amp;gt; is the id of the model containing event data to be examined.&lt;br /&gt;
* &amp;lt;resource column name&amp;gt; is the name of the column in the event data table of the specified model containing the resource being used by that event.&lt;br /&gt;
&lt;br /&gt;
NOTE: This expression uses functionalities that are only supported in Snowflake-based data tables.&lt;br /&gt;
&lt;br /&gt;
=== Create new Snowflake model from filter ===&lt;br /&gt;
This script creates a new Snowflake model (and two datatables for cases and events) containing filtered event log from given filter id. The script also works if the model doesn&#039;t have a cases datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let filter = FilterById(1); // filter id&lt;br /&gt;
let model = filter.model;&lt;br /&gt;
let project = model.project;&lt;br /&gt;
let nameSuffix = &amp;quot; - &amp;quot; + filter.name + &amp;quot; - &amp;quot; + ToString(Now, &amp;quot;dd-MM-yyyy HH:mm:ss&amp;quot;);&lt;br /&gt;
let eventsDatatableName = model.EventsDataTable.Name + nameSuffix;&lt;br /&gt;
if (eventsDatatableName.length &amp;gt; 440) {&lt;br /&gt;
  eventsDatatableName = eventsDatatableName.Substring(eventsDatatableName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
let eventsData = model&lt;br /&gt;
  .EventsDataTable&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .ApplyFilter(&lt;br /&gt;
    filter.rules,&lt;br /&gt;
    model.CasesDataTable?.SqlDataFrame&lt;br /&gt;
  );&lt;br /&gt;
project&lt;br /&gt;
  .CreateDatatable(eventsDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
  .Import(eventsData);&lt;br /&gt;
let modelConfiguration = model.Configuration;&lt;br /&gt;
modelConfiguration.DataSource.Events.Set(&amp;quot;DataTableName&amp;quot;, eventsDatatableName);&lt;br /&gt;
if (model.CasesDataTable != null) {&lt;br /&gt;
  let eventsDataCaseIdColumn = &amp;quot;CaseId_&amp;quot; + ToString(Random());&lt;br /&gt;
  let casesDatatableName = model.CasesDataTable.Name + nameSuffix;&lt;br /&gt;
  if (casesDatatableName.length &amp;gt; 440) {&lt;br /&gt;
    casesDatatableName = casesDatatableName.Substring(casesDatatableName.length - 440);&lt;br /&gt;
  }&lt;br /&gt;
  let casesData = model&lt;br /&gt;
    .CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .join(&lt;br /&gt;
	  eventsData.SelectDistinct([eventsDataCaseIdColumn: modelConfiguration.DataSource.Events.Columns.CaseId]),&lt;br /&gt;
      [modelConfiguration.DataSource.Cases.Columns.CaseId: eventsDataCaseIdColumn]&lt;br /&gt;
	).Select(model.CasesDataTable.ColumnNames);&lt;br /&gt;
  project&lt;br /&gt;
    .CreateDatatable(casesDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
    .Import(casesData);&lt;br /&gt;
  modelConfiguration.DataSource.Cases.Set(&amp;quot;DataTableName&amp;quot;, casesDatatableName);&lt;br /&gt;
}&lt;br /&gt;
let modelName = model.Name + nameSuffix;&lt;br /&gt;
if (modelName &amp;gt; 440) {&lt;br /&gt;
  modelName = modelName.Substring(modelName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
project&lt;br /&gt;
  .CreateModel(#{    &lt;br /&gt;
    &amp;quot;Name&amp;quot;: modelName,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: model.Description,&lt;br /&gt;
    &amp;quot;Configuration&amp;quot;: modelConfiguration&lt;br /&gt;
  });&lt;br /&gt;
return modelName;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Creating a model consisting of multiple copies of cases in an existing model ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @name CreateTestModel&lt;br /&gt;
 * @description&lt;br /&gt;
 * Creates a new model (or overwrites an existing) to given target project with given number of &lt;br /&gt;
 * repetitions of given source model.&lt;br /&gt;
 * Each repetition will generate &amp;quot;&amp;lt;N&amp;gt;-&amp;quot;-prefix to CaseId-columns, where N equals to the repeat index.&lt;br /&gt;
 * @param sourceModel&lt;br /&gt;
 * PA model used for the source data and from where the connection is copied for the target model if a &lt;br /&gt;
 * new one has to be created.&lt;br /&gt;
 * @param numRepeats&lt;br /&gt;
 * Number of times the data in the source model should be repeated in the generated model.&lt;br /&gt;
 * @param targetProject&lt;br /&gt;
 * Project in which the target model resides.&lt;br /&gt;
 * @param targetModelName&lt;br /&gt;
 * Specifies the name of the test model in the given target project. If a model already exists with &lt;br /&gt;
 * given name, event and case data in this model will be replaced with the new generated event and &lt;br /&gt;
 * case data.&lt;br /&gt;
 * @returns&lt;br /&gt;
 * Model object of the test model having the newly generated data.&lt;br /&gt;
 */&lt;br /&gt;
function CreateTestModel(sourceModel, numRepeats, targetProject, targetModelName) &lt;br /&gt;
{&lt;br /&gt;
  let eventsColumnMappings = sourceModel.EventsDataTable.ColumnMappings;&lt;br /&gt;
  let casesColumnMappings = sourceModel.CasesDataTable.ColumnMappings;&lt;br /&gt;
  let connection = sourceModel.EventsDataTable.DataSourceConnection;&lt;br /&gt;
&lt;br /&gt;
  function CreateResultModel()&lt;br /&gt;
  {&lt;br /&gt;
    function GetTable(tableName) &lt;br /&gt;
    {&lt;br /&gt;
      let tableConfiguration = #{&lt;br /&gt;
        &amp;quot;Name&amp;quot;: tableName,&lt;br /&gt;
        &amp;quot;Connection&amp;quot;: connection&lt;br /&gt;
      };&lt;br /&gt;
      let resultTable = targetProject.DataTableByName(tableName);&lt;br /&gt;
      if (resultTable == null)&lt;br /&gt;
      {&lt;br /&gt;
        resultTable = targetProject.CreateDataTable(tableConfiguration)&lt;br /&gt;
          .Modify(#{&amp;quot;NameInDataSource&amp;quot;: null})&lt;br /&gt;
          .Synchronize();&lt;br /&gt;
      }&lt;br /&gt;
      return resultTable;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let eventsTableName = `${targetModelName} - events`;&lt;br /&gt;
    let casesTableName = `${targetModelName} - cases`;&lt;br /&gt;
    let targetModel = targetProject.ModelByName(targetModelName);&lt;br /&gt;
    let eventsTable, casesTable = null;&lt;br /&gt;
&lt;br /&gt;
    if (targetModel != null)&lt;br /&gt;
    {&lt;br /&gt;
      eventsTable = targetModel.EventsDataTable;&lt;br /&gt;
      casesTable = targetModel.CasesDataTable;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      eventsTable = GetTable(eventsTableName);&lt;br /&gt;
      if (sourceModel.CasesDataTable != null) {&lt;br /&gt;
        casesTable = GetTable(casesTableName);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      let timestampMapping = eventsColumnMappings[&amp;quot;TimeStamp&amp;quot;];&lt;br /&gt;
      eventsColumnMappings.Remove(&amp;quot;TimeStamp&amp;quot;);&lt;br /&gt;
      eventsColumnMappings.Set(&amp;quot;Timestamp&amp;quot;, timestampMapping);&lt;br /&gt;
&lt;br /&gt;
      let modelConfiguration = #{&lt;br /&gt;
        &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
          &amp;quot;Events&amp;quot;:#{&lt;br /&gt;
            &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
            &amp;quot;DataTableName&amp;quot;: eventsTableName,&lt;br /&gt;
            &amp;quot;Columns&amp;quot;: eventsColumnMappings&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      if (casesColumnMappings != null) {&lt;br /&gt;
        modelConfiguration[&amp;quot;DataSource&amp;quot;].Set(&amp;quot;Cases&amp;quot;, #{&lt;br /&gt;
          &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
          &amp;quot;DataTableName&amp;quot;: casesTableName,&lt;br /&gt;
          &amp;quot;Columns&amp;quot;: casesColumnMappings&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      targetModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: targetModelName, &amp;quot;Configuration&amp;quot;: modelConfiguration});&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    eventsTable.Truncate();&lt;br /&gt;
    casesTable?.Truncate();&lt;br /&gt;
&lt;br /&gt;
    return #{&lt;br /&gt;
      &amp;quot;TargetModel&amp;quot;: targetModel,&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTable,&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: casesTable&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function RepeatNTimes(sourceDf, caseIdColumn, numRepeats)&lt;br /&gt;
  {&lt;br /&gt;
    let resultDf = null;&lt;br /&gt;
    for (let i = 1; i &amp;lt;= numRepeats; ++i) {&lt;br /&gt;
      let iterationDf = sourceDf&lt;br /&gt;
        .WithColumn(caseIdColumn, #sql{Concat(#expr{i}, &amp;quot;-&amp;quot;, Column(#expr{caseIdColumn}))});&lt;br /&gt;
      resultDf = resultDf == null ? iterationDf : resultDf.Append(iterationDf); &lt;br /&gt;
    }&lt;br /&gt;
    resultDf;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let resultModel = CreateResultModel();&lt;br /&gt;
  let sourceEventDataDf = sourceModel.EventsDataTable.SqlDataFrame;&lt;br /&gt;
  let resultEventDataDf = RepeatNTimes(sourceEventDataDf, eventsColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
  resultModel[&amp;quot;Events&amp;quot;].Import(resultEventDataDf);&lt;br /&gt;
&lt;br /&gt;
  let sourceCaseDataDf = sourceModel.CasesDataTable?.SqlDataFrame;&lt;br /&gt;
  if (sourceCaseDataDf != null) {&lt;br /&gt;
    let resultCaseDataDf = RepeatNTimes(sourceCaseDataDf, casesColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
    resultModel[&amp;quot;Cases&amp;quot;].Import(resultCaseDataDf);&lt;br /&gt;
  }&lt;br /&gt;
  resultModel[&amp;quot;TargetModel&amp;quot;];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example usage:&amp;lt;blockquote&amp;gt;CreateTestModel(ProjectByName(&amp;quot;Project&amp;quot;).ModelByName(&amp;quot;SAP_OrderToCash - Snowflake&amp;quot;), 3, ProjectByName(&amp;quot;TestData&amp;quot;), &amp;quot;TestModel&amp;quot;);&amp;lt;/blockquote&amp;gt;Creates a new model named &amp;quot;TestModel&amp;quot; (or overwrites old one) into project named &amp;quot;TestData&amp;quot; containing the data from model &amp;quot;SAP_OrderToCash - Snowflake&amp;quot; in project &amp;quot;Project&amp;quot; repeated three times.&lt;br /&gt;
&lt;br /&gt;
=== Analyzing declare patterns found in event log ===&lt;br /&gt;
&lt;br /&gt;
This is an example expression that shows how POSIX-style regular expressions can be used to search for cases in an event log having certain event type patterns [https://www.researchgate.net/publication/277631859_Generating_Event_Logs_Through_the_Simulation_of_Declare_Models declare patterns].&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
let sdf = dt.SqlDataFrame.Head(1000);&lt;br /&gt;
let caseIdColumn = dt.ColumnMappings[&amp;quot;CaseId&amp;quot;], timeStampColumn = dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;], eventTypeColumn = dt.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let eventTypesDf = sdf&lt;br /&gt;
  .SelectDistinct([eventTypeColumn])&lt;br /&gt;
  .OrderByColumns([eventTypeColumn], [true])&lt;br /&gt;
  .WithRowNumberColumn(&amp;quot;Token&amp;quot;, [eventTypeColumn])&lt;br /&gt;
  .WithColumn(&amp;quot;Token&amp;quot;, #sql{Char(Column(&amp;quot;Token&amp;quot;) + Unicode(&amp;quot;a&amp;quot;) - 1)});&lt;br /&gt;
&lt;br /&gt;
sdf = sdf&lt;br /&gt;
  .Join(eventTypesDf.Select([&amp;quot;_EventType2&amp;quot;: eventTypeColumn, &amp;quot;Token&amp;quot;]), [eventTypeColumn: &amp;quot;_EventType2&amp;quot;], &amp;quot;leftouter&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function RespondedExistencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*((a.*b.*)|(b.*a.*))*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*((${a}.*${b}.*)|(${b}.*${a}.*))*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a.*b)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}.*${b})*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function AlternateResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a[^a]*b[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}[^${a}]*${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ChainResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(ab[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function PrecedencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^b]*(a.*b)*[^b]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${b}]*(${a}.*${b})*[^${b}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let tracesDf = sdf&lt;br /&gt;
  .GroupBy([caseIdColumn])&lt;br /&gt;
  .Aggregate([&amp;quot;Trace&amp;quot;: &amp;quot;Token&amp;quot;], [#{&amp;quot;Function&amp;quot;: &amp;quot;list&amp;quot;, &amp;quot;Ordering&amp;quot;: [timeStampColumn], &amp;quot;Separator&amp;quot;: &amp;quot;&amp;quot;}])&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceNL&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceCA&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternNL&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternCA&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternNL&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternCA&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternNL&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternCA&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternNL&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternCA&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
[tracesDf.Collect().ToCsv(), eventTypesDf.Collect().ToCsv()]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Perform a query and send results as E-mail in a HTML table ===&lt;br /&gt;
This example requires two scripts that are both located in the same project:&lt;br /&gt;
&lt;br /&gt;
# Expression-type script to execute (can be, e.g., scheduled to run daily).&lt;br /&gt;
# Expression-type script containing the query JSON to use as basis for the e-mail. In this example, this script is named as &amp;quot;Send query as E-mail - query JSON&amp;quot;. The contents of this script is just the JSON representation of a query that can be extracted, e.g., from any PA chart view.&lt;br /&gt;
&lt;br /&gt;
Script #1 should contain the following code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let replyToAddress = &amp;quot;noreply@test.com&amp;quot;;&lt;br /&gt;
let recipientsArray = [&amp;quot;testuser@test.com&amp;quot;];&lt;br /&gt;
let queryConfiguration = ParseJson(Project.ScriptByName(&amp;quot;Send query as E-mail - query JSON&amp;quot;).Code)&lt;br /&gt;
  .Set(&amp;quot;IncludeCollect&amp;quot;, true);&lt;br /&gt;
let resultDf = Query(queryConfiguration);&lt;br /&gt;
let mailBodyHtml = resultDf.`&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
  &amp;lt;caption&amp;gt;Example query&amp;lt;/caption&amp;gt;&lt;br /&gt;
  &amp;lt;thead&amp;gt;&lt;br /&gt;
    &amp;lt;tr&amp;gt;&lt;br /&gt;
      ${StringJoin(&amp;quot;&amp;quot;, _.Columns.`&lt;br /&gt;
        &amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;&lt;br /&gt;
      `)}&lt;br /&gt;
    &amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;/thead&amp;gt;&lt;br /&gt;
  &amp;lt;tbody&amp;gt;&lt;br /&gt;
    ${StringJoin(&amp;quot;&amp;quot;, _.Rows.`&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
        ${StringJoin(&amp;quot;&amp;quot;, _.`&lt;br /&gt;
          &amp;lt;td&amp;gt;${_}&amp;lt;/td&amp;gt;&lt;br /&gt;
        `)}&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
    `)}&lt;br /&gt;
  &amp;lt;/tbody&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
`;&lt;br /&gt;
&lt;br /&gt;
SendEmail(#{&lt;br /&gt;
  &amp;quot;ReplyTo&amp;quot;: [replyToAddress],&lt;br /&gt;
  &amp;quot;To&amp;quot;: recipientsArray,&lt;br /&gt;
  &amp;quot;Subject&amp;quot;: &amp;quot;Example query E-mail&amp;quot;,&lt;br /&gt;
  &amp;quot;IsBodyHtml&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Body&amp;quot;: mailBodyHtml&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;NOTE: QPR ProcessAnalyzer Server has to have SMTP server configured. Also, remember to update the values of replyToAddress and recipientsArray before using.&lt;br /&gt;
&lt;br /&gt;
=== Converting a case-centric model to object-centric model ===&lt;br /&gt;
This function serves as an example on how a case-centric model could be converted into an object-centric model having just one object type: &amp;quot;Case&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ConvertCCModelToOCModel(model, newModelName) &lt;br /&gt;
{&lt;br /&gt;
  let connection = model.EventsDataTable.DataSourceConnection;&lt;br /&gt;
  let caseIdColumn = model.EventsDataTable.ColumnMappings[&amp;quot;CaseId&amp;quot;];&lt;br /&gt;
  let eventToObjectTableName = `${newModelName} - event-to-object`;&lt;br /&gt;
  let eventsTableName = `${newModelName} - events`;&lt;br /&gt;
  let objectsTableName = `${newModelName} - objects`;&lt;br /&gt;
          &lt;br /&gt;
  let eventsDf = model.EventsDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelEventType&amp;quot;: model.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;],&lt;br /&gt;
      &amp;quot;OcelEventTime&amp;quot;: model.EventsDataTable.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelEventId&amp;quot;, #sql{Concat(&amp;quot;evt-&amp;quot;, Cast(RowNumber([Column(&amp;quot;OcelEventTime&amp;quot;)]), &amp;quot;String&amp;quot;))});&lt;br /&gt;
&lt;br /&gt;
  eventsDf&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelEventToObjectSourceId&amp;quot;: &amp;quot;OcelEventId&amp;quot;,&lt;br /&gt;
      &amp;quot;OcelEventToObjectTargetId&amp;quot;: caseIdColumn])&lt;br /&gt;
    .Select([&amp;quot;OcelEventToObjectSourceId&amp;quot;, &amp;quot;OcelEventToObjectTargetId&amp;quot;])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelEventToObjectQualifier&amp;quot;, #sql{#expr{caseIdColumn} })&lt;br /&gt;
    .Persist(eventToObjectTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  eventsDf&lt;br /&gt;
    .RemoveColumns([caseIdColumn])&lt;br /&gt;
    .Persist(eventsTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  let casesDt = model.CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelObjectId&amp;quot;: model.CasesDataTable.ColumnMappings[&amp;quot;CaseId&amp;quot;]&lt;br /&gt;
    ])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelObjectType&amp;quot;, #sql{&amp;quot;Case&amp;quot;})&lt;br /&gt;
    .Persist(objectsTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  let newConfiguration = #{&lt;br /&gt;
    &amp;quot;OcelDataSource&amp;quot;: #{&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTableName,&lt;br /&gt;
      &amp;quot;Objects&amp;quot;: objectsTableName,&lt;br /&gt;
      &amp;quot;EventToObject&amp;quot;: eventToObjectTableName&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  model.Project  &lt;br /&gt;
    .CreateModel(#{      &lt;br /&gt;
      &amp;quot;Name&amp;quot;: newModelName,  &lt;br /&gt;
      &amp;quot;Description&amp;quot;: model.Description,  &lt;br /&gt;
      &amp;quot;Configuration&amp;quot;: newConfiguration  &lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let ccModel = ModelById(&amp;lt;model id&amp;gt;);&lt;br /&gt;
ConvertCCModelToOCModel(ccModel, `ocel - ${ccModel.Name}`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Show duplicate rows in datatable ===&lt;br /&gt;
This script returns all rows in a datatable appearing more than once (sorted by the most frequent occurrences first). Additionally, the number of occurrences is returned as the last column. Replace the datatable id with the correct one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let datatableId = 1;&lt;br /&gt;
let rowCountColumn = &amp;quot;Row count&amp;quot;;&lt;br /&gt;
let columns = DatatableById(datatableId).Columns.Name;&lt;br /&gt;
DatatableById(datatableId)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .GroupBy(columns)&lt;br /&gt;
  .Aggregate(&lt;br /&gt;
    [rowCountColumn: columns[0]],&lt;br /&gt;
    [&amp;quot;Count&amp;quot;]&lt;br /&gt;
  )&lt;br /&gt;
  .Where(Column(rowCountColumn) &amp;gt; 1)&lt;br /&gt;
  .OrderByColumns([rowCountColumn], [false])&lt;br /&gt;
  .Collect()&lt;br /&gt;
  .ToCSV();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a new object-centric model based on already existing object-centric model containing only filtered objects and events ===&lt;br /&gt;
Script for creating a new OCPM-model based on another already existing model in PA that applies given filter to the original model and creates all the needed tables to store only the relevant rows for all the OCPM data tables.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let newModelName = &amp;quot;&amp;lt;target model name&amp;gt;&amp;quot;;&lt;br /&gt;
let sourceModelId = &amp;lt;source model id&amp;gt;;&lt;br /&gt;
let targetProjectId = &amp;lt;target project id&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
// Replace the value of the ocelItems below with the configuration of object-centric filter to apply.&lt;br /&gt;
// Note: Use expression language syntax here, not JSON.&lt;br /&gt;
let ocelItems = [#{&lt;br /&gt;
  &amp;quot;Include&amp;quot;: true,&lt;br /&gt;
  &amp;quot;ObjectAttributeValue&amp;quot;: #{&lt;br /&gt;
    &amp;quot;ObjectType&amp;quot;: &amp;quot;Purchase Order&amp;quot;,&lt;br /&gt;
    &amp;quot;Attribute&amp;quot;: &amp;quot;po_product&amp;quot;,&lt;br /&gt;
    &amp;quot;Values&amp;quot;: [&lt;br /&gt;
      &amp;quot;0Cows&amp;quot;&lt;br /&gt;
    ]&lt;br /&gt;
  }&lt;br /&gt;
}];&lt;br /&gt;
&lt;br /&gt;
let m = ModelById(sourceModelId);&lt;br /&gt;
if (!m.IsOcelModel)&lt;br /&gt;
  throw `Model ${m.Name} is not an OCPM model`;&lt;br /&gt;
if (CountTop(m.CheckModelValidity()) &amp;gt; 0)&lt;br /&gt;
  throw `Model ${m.Name} is not a valid OCPM model`;&lt;br /&gt;
&lt;br /&gt;
let targetProject = ProjectById(targetProjectId);&lt;br /&gt;
if (CountTop(targetProject.Models.Where(Name == newModelName)) &amp;gt; 0)&lt;br /&gt;
  throw `Model having name ${newModelName} already exists in project ${targetProject.Name}. Rename or delete the existing model before running this script.`;&lt;br /&gt;
&lt;br /&gt;
let newConfiguration = #{&lt;br /&gt;
  &amp;quot;OcelDataSource&amp;quot;: #{&lt;br /&gt;
    &amp;quot;EventTypes&amp;quot;: #{},&lt;br /&gt;
    &amp;quot;ObjectTypes&amp;quot;: #{}&lt;br /&gt;
  }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
WriteLog(&amp;quot;Creating common tables...&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function PersistDataFrame(dataFrame, configurationName, configurationDict, dataTableName)&lt;br /&gt;
{&lt;br /&gt;
  WriteLog(`Creating filtered table: ${dataTableName}`);&lt;br /&gt;
  configurationDict.Set(configurationName, dataTableName);&lt;br /&gt;
  return dataFrame.Persist(dataTableName, #{&amp;quot;ProjectId&amp;quot;: targetProjectId, &amp;quot;Append&amp;quot;: false});&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let objectsDf = m.CacheTableSqlDataFrame(#{&lt;br /&gt;
  &amp;quot;CacheTableType&amp;quot;: &amp;quot;Objects&amp;quot;,&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: #{},&lt;br /&gt;
  &amp;quot;OcelItems&amp;quot;: ocelItems &lt;br /&gt;
}).SetPrimaryKey(m.OcelObjects.PrimaryKey);&lt;br /&gt;
let newObjectsDt = PersistDataFrame(objectsDf, &amp;quot;Objects&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - objects`);&lt;br /&gt;
&lt;br /&gt;
let objectToObjectDf = m.CacheTableSqlDataFrame(#{&lt;br /&gt;
  &amp;quot;CacheTableType&amp;quot;: &amp;quot;ObjectToObject&amp;quot;,&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: #{},&lt;br /&gt;
  &amp;quot;OcelItems&amp;quot;: ocelItems &lt;br /&gt;
}).SetPrimaryKey(m.OcelObjectToObject.PrimaryKey)&lt;br /&gt;
  .Select([&amp;quot;OcelObjectToObjectSourceId&amp;quot;, &amp;quot;OcelObjectToObjectTargetId&amp;quot;, &amp;quot;OcelObjectToObjectQualifier&amp;quot;]);&lt;br /&gt;
let newObjectToObjectDt = PersistDataFrame(objectToObjectDf, &amp;quot;ObjectToObject&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - object-to-object`);&lt;br /&gt;
&lt;br /&gt;
let eventToObjectDf = m.CacheTableSqlDataFrame(#{&lt;br /&gt;
  &amp;quot;CacheTableType&amp;quot;: &amp;quot;EventToObject&amp;quot;,&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: #{},&lt;br /&gt;
  &amp;quot;OcelItems&amp;quot;: ocelItems &lt;br /&gt;
}).SetPrimaryKey(m.OcelEventToObject.PrimaryKey)&lt;br /&gt;
  .Select([&amp;quot;OcelEventToObjectSourceId&amp;quot;, &amp;quot;OcelEventToObjectTargetId&amp;quot;, &amp;quot;OcelEventToObjectQualifier&amp;quot;]);&lt;br /&gt;
let newEventToObjectDt = PersistDataFrame(eventToObjectDf, &amp;quot;EventToObject&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - event-to-object`);&lt;br /&gt;
&lt;br /&gt;
let eventsDf = m.OcelEvents.SqlDataFrame&lt;br /&gt;
  .Join(eventToObjectDf.SelectDistinct([&amp;quot;OcelEventToObjectSourceId&amp;quot;]), [&amp;quot;OcelEventId&amp;quot;: &amp;quot;OcelEventToObjectSourceId&amp;quot;], &amp;quot;inner&amp;quot;)&lt;br /&gt;
  .RemoveColumns([&amp;quot;OcelEventToObjectSourceId&amp;quot;])&lt;br /&gt;
  .SetPrimaryKey(m.OcelEvents.PrimaryKey);&lt;br /&gt;
let newEventsDt = PersistDataFrame(eventsDf, &amp;quot;Events&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - events`);&lt;br /&gt;
&lt;br /&gt;
WriteLog(&amp;quot;Creating event type tables...&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
m.Configuration.OcelDataSource.EventTypes.ToArray().{&lt;br /&gt;
  let ar = _;&lt;br /&gt;
  let eventTypeName = GetContext(ar);&lt;br /&gt;
  let eventTypeDt = m.OcelEventType(eventTypeName);&lt;br /&gt;
  let eventTypeDf = eventTypeDt.SqlDataFrame&lt;br /&gt;
    .Join(newEventsDt.SqlDataFrame.Select([&amp;quot;OcelEventId&amp;quot;]), [&amp;quot;OcelEventTypeEventId&amp;quot;: &amp;quot;OcelEventId&amp;quot;], &amp;quot;inner&amp;quot;)&lt;br /&gt;
    .RemoveColumns([&amp;quot;OcelEventId&amp;quot;])&lt;br /&gt;
    .SetPrimaryKey(eventTypeDt.PrimaryKey);&lt;br /&gt;
  PersistDataFrame(eventTypeDf, eventTypeName, newConfiguration.OcelDataSource.EventTypes, `${newModelName} - eventtype - ${eventTypeName}`);&lt;br /&gt;
};&lt;br /&gt;
  &lt;br /&gt;
WriteLog(&amp;quot;Creating object type tables...&amp;quot;);&lt;br /&gt;
let newUniqueObjectsDf = newObjectsDt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .SelectDistinct([&amp;quot;OcelObjectId&amp;quot;]);&lt;br /&gt;
&lt;br /&gt;
m.Configuration.OcelDataSource.ObjectTypes.ToArray().{&lt;br /&gt;
  let ar = _;&lt;br /&gt;
  let objectTypeName = GetContext(ar);&lt;br /&gt;
  let objectTypeDt = m.OcelObjectType(objectTypeName);&lt;br /&gt;
  let objectTypeDf = objectTypeDt.SqlDataFrame&lt;br /&gt;
    .Join(newUniqueObjectsDf, [&amp;quot;OcelObjectTypeObjectId&amp;quot;: &amp;quot;OcelObjectId&amp;quot;], &amp;quot;inner&amp;quot;)&lt;br /&gt;
    .RemoveColumns([&amp;quot;OcelObjectId&amp;quot;])&lt;br /&gt;
    .SetPrimaryKey(objectTypeDt.PrimaryKey);&lt;br /&gt;
  PersistDataFrame(objectTypeDf, objectTypeName, newConfiguration.OcelDataSource.ObjectTypes, `${newModelName} - objecttype - ${objectTypeName}`);&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
WriteLog(`Creating model ${newModelName}...`);&lt;br /&gt;
&lt;br /&gt;
let newModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: newModelName, &amp;quot;Configuration&amp;quot;: newConfiguration});&lt;br /&gt;
&lt;br /&gt;
WriteLog(`Model (id=${newModel.Id}) created with configuration:\r\n${ToJson(newConfiguration)}`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=27596</id>
		<title>Expression Script Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=27596"/>
		<updated>2026-01-12T08:17:08Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains script examples written in the QPR ProcessAnalyzer expression language. See how expression scripts can be created in the [[Managing_Scripts#Creating_Script|Workspace]]. For documentation for the syntax, functions and entities can be found from the main page in the [[QPR_ProcessAnalyzer_Wiki#For_Developers|KPI Expression Language]] section.&lt;br /&gt;
&lt;br /&gt;
== Calling Expression Script from Expression ==&lt;br /&gt;
Expression scripts can be called from an expression using the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Run]] function with the following syntax:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: false,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 123.45&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command waits until the run is completed, and the return value of the called script is returned by the Run function call.&lt;br /&gt;
&lt;br /&gt;
Parameters can be passed to the called script, and the parameters are available as variables in the script. The parameters can contain any type of data.&lt;br /&gt;
&lt;br /&gt;
Expression scripts can also be called from a dashboard. Expressions can be stored to scripts instead of dashboards, which is a way to separate complex expressions from dashboards and allow to reuse expressions across several dashboards.&lt;br /&gt;
&lt;br /&gt;
== Calling SQL Script from Expression ==&lt;br /&gt;
SQL script can be called from an expression using the Run function as follows (similar to calling [[#Calling Expression Script from Expression|expression scripts]]):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 321&lt;br /&gt;
});&lt;br /&gt;
let arrayOfAllReports = result.Keys;&lt;br /&gt;
let report1 = result.Report1;&lt;br /&gt;
let report2 = result.Report2;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL scripts can return multiple &#039;&#039;reports&#039;&#039;, which are combined to a dictionary, where the key is the name of the report (&amp;quot;sheet name&amp;quot;) and value is the report data as a DataFrame. See in the above example, how the reports can be accessed by their name.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Call web service===&lt;br /&gt;
Contact to a web service, fetch some data, and store it to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let datatableName = &amp;quot;Web Service Data&amp;quot;;&lt;br /&gt;
let webServiceData = CallWebService(&lt;br /&gt;
    #{&amp;quot;Address&amp;quot;: &amp;quot;https://processanalyzer.onqpr.com/qprpa/api/serverinfo&amp;quot;}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
let targetDatatable = Project.Datatables.Where(name==datatableName);&lt;br /&gt;
if (Count(targetDatatable) == 0) {&lt;br /&gt;
	targetDatatable = Project.CreateDatatable(datatableName)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting value&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Data read&amp;quot;, &amp;quot;DateTime&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
	targetDatatable = targetDatatable[0];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let currentTime = Now;&lt;br /&gt;
let dataAsDf = ToDataFrame(&lt;br /&gt;
	webServiceData.keys.{&lt;br /&gt;
        let key = _;&lt;br /&gt;
        [key, webServiceData[key], currentTime];&lt;br /&gt;
    },&lt;br /&gt;
	[&amp;quot;Setting name&amp;quot;, &amp;quot;Setting value&amp;quot;, &amp;quot;Data read&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
targetDatatable.Import(dataAsDf, #{&amp;quot;Append&amp;quot;:true});&lt;br /&gt;
WriteLog(`${CountTop(dataAsDf.Rows)} rows written to datatable`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Store data to datatable ===&lt;br /&gt;
Get all models in the system and store them to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let newDatatable = Project&lt;br /&gt;
    .CreateDatatable(&amp;quot;Models list &amp;quot; + ToString(Now, &amp;quot;dd.MM.yyyy HH:mm:ss&amp;quot;))&lt;br /&gt;
    .AddColumn(&amp;quot;Model name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Project name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Created time&amp;quot;, &amp;quot;DateTime&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Cases&amp;quot;, &amp;quot;Integer&amp;quot;);&lt;br /&gt;
let startTime = Now;&lt;br /&gt;
let modelsData = ToDataFrame(&lt;br /&gt;
    Models.([Name, Project.Name, CreatedDate, NCases]),&lt;br /&gt;
    [&amp;quot;Model name&amp;quot;, &amp;quot;Project name&amp;quot;, &amp;quot;Created time&amp;quot;, &amp;quot;Cases&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
WriteLog(`Listing models took ${(Now - startTime).TotalSeconds.Round(2)} seconds.`);&lt;br /&gt;
newDatatable.Import(modelsData);&lt;br /&gt;
WriteLog(`Datatable ${newDatatable.Id} created.`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data (Snowflake) ===&lt;br /&gt;
Following script converts textual data from the column &amp;quot;TimestampString&amp;quot; to dates to column &amp;quot;Timestamp&amp;quot; by trying different time formats and using the first suitable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
DatatableById(1)&lt;br /&gt;
  .UpdateRows(&lt;br /&gt;
    true,&lt;br /&gt;
    &amp;quot;Timestamp&amp;quot;,&lt;br /&gt;
    Coalesce(&lt;br /&gt;
      TryToTimestamp(Column(&amp;quot;TimestampString&amp;quot;), &amp;quot;DD.MM.YYYY HH24:MI:SS.FF3&amp;quot;),&lt;br /&gt;
      TryToTimestamp(Column(&amp;quot;TimestampString&amp;quot;), &amp;quot;YYYY-MM-DD HH24:MI:SS.FF3&amp;quot;),&lt;br /&gt;
      TryToTimestamp(Column(&amp;quot;TimestampString&amp;quot;), &amp;quot;YYYY/MM/DD HH24:MI:SS.FF3&amp;quot;)&lt;br /&gt;
    )&lt;br /&gt;
  );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data (in-memory) ===&lt;br /&gt;
This script can be used to convert a single column into numerical data type. To use the script, you need to setup the following in the beginning of the script:&lt;br /&gt;
* Project name where the datatable is located.&lt;br /&gt;
* Datatable name&lt;br /&gt;
* Name of the column to be converted&lt;br /&gt;
&lt;br /&gt;
Note that the conversion fails, if there is data that cannot be converted into numerical format. The conversion assumes that period (.) is used as the decimal point. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let projectName = &amp;quot;New Project&amp;quot;;&lt;br /&gt;
let datatableName = &amp;quot;qpr processanalyzer events&amp;quot;;&lt;br /&gt;
let columnName = &amp;quot;Event order in case&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let project = (Projects.Where(Name==projectName))[0];&lt;br /&gt;
let datatable = (project.Datatables.Where(Name==datatableName))[0];&lt;br /&gt;
DatatableById(datatable.Id).DataFrame&lt;br /&gt;
.SetColumns([&lt;br /&gt;
	columnName: () =&amp;gt; {&lt;br /&gt;
		let data = Column(columnName);&lt;br /&gt;
		if (data == null) {&lt;br /&gt;
			null;&lt;br /&gt;
		 } else {&lt;br /&gt;
			ToFloat(data);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
])&lt;br /&gt;
.Persist(datatable.Name, [&amp;quot;ProjectId&amp;quot;: project.Id, &amp;quot;Append&amp;quot;: false]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of converting to numeric (with the &#039;&#039;ToFloat&#039;&#039; function), data can be converted into string using the &#039;&#039;ToString&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Show DataFrame as HTML table ===&lt;br /&gt;
&lt;br /&gt;
This script defines a function to show dataframe as a HTML table, and uses the function for a literal dataframe.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function dataframeToHtmlTable(df) {&lt;br /&gt;
	return&lt;br /&gt;
`&amp;lt;table&amp;gt;&lt;br /&gt;
	&amp;lt;tr&amp;gt;&lt;br /&gt;
		${StringJoin(&amp;quot;\r\n\t\t&amp;quot;,  + df.columns.`&amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;`)}&lt;br /&gt;
	&amp;lt;/tr&amp;gt;&lt;br /&gt;
	${StringJoin(&amp;quot;&amp;quot;, df.Rows.(&lt;br /&gt;
		&amp;quot;\r\n\t&amp;lt;tr&amp;gt;&amp;quot; + StringJoin(&amp;quot;&amp;quot;, _.`\r\n\t\t&amp;lt;td&amp;gt;${ToString(_)}&amp;lt;/td&amp;gt;`) + &amp;quot;\r\n\t&amp;lt;/tr&amp;gt;&amp;quot;&lt;br /&gt;
	))}&lt;br /&gt;
&amp;lt;/table&amp;gt;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let data = ToDataFrame(&lt;br /&gt;
	[&lt;br /&gt;
		[&amp;quot;one&amp;quot;, &amp;quot;two&amp;quot;, &amp;quot;three&amp;quot;],&lt;br /&gt;
		[&amp;quot;four&amp;quot;, &amp;quot;five&amp;quot;, &amp;quot;six&amp;quot;],&lt;br /&gt;
		[&amp;quot;seven&amp;quot;, &amp;quot;eight&amp;quot;, &amp;quot;nine&amp;quot;]&lt;br /&gt;
	],&lt;br /&gt;
	[&amp;quot;Column 1&amp;quot;, &amp;quot;Column 2&amp;quot;, &amp;quot;Column 3&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
return dataframeToHtmlTable(data);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy local datatables to Snowflake ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
// Copies all datatables in a project to another project including datatable contents.&lt;br /&gt;
// Usage instructions:&lt;br /&gt;
// 1. Create expression script in the project from where you want to copy the datatables.&lt;br /&gt;
// 2. Create a new project named as &amp;quot;&amp;lt;name of the project to be moved&amp;gt; - Snowflake&amp;quot;. New datatables will be created here. E.g., when moving project named &amp;quot;SAP_OrderToCash&amp;quot;, the target project should be named as &amp;quot;SAP_OrderToCash - Snowflake&amp;quot;.&lt;br /&gt;
// 3. Run the script.&lt;br /&gt;
// NOTE: Columns of type &amp;quot;Any&amp;quot; will be created as &amp;quot;String&amp;quot;-columns in Snowflake, thus it is recommended that actual data types are set for the tables prior to the move.&lt;br /&gt;
&lt;br /&gt;
let sourceProject = Project;&lt;br /&gt;
let sourceProjectName = Project.Name;&lt;br /&gt;
let targetProjectName = `${sourceProjectName} - Snowflake`;&lt;br /&gt;
let targetProject = First(Projects.Where(Name == targetProjectName));&lt;br /&gt;
if (IsNull(targetProject)) {&lt;br /&gt;
  WriteLog(`Unable to find target project named &amp;quot;${targetProjectName}&amp;quot;. Aborting operation.`);&lt;br /&gt;
  return;&lt;br /&gt;
}&lt;br /&gt;
let dts = sourceProject.DataTables;&lt;br /&gt;
WriteLog(`Copying all ${CountTop(dts)} data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
dts.{&lt;br /&gt;
  let sourceDt = _;&lt;br /&gt;
  WriteLog(`Starting to copy data table &amp;quot;${Name}&amp;quot; (id: ${Id}) having ${NRows} rows and ${NColumns} columns.`);&lt;br /&gt;
  let targetDt;&lt;br /&gt;
  targetDt = targetProject.DatatableByName(sourceDt.Name);&lt;br /&gt;
  if (targetDt == null) {&lt;br /&gt;
    targetDt = targetProject.CreateDataTable(sourceDt.Name, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: targetProject.Id})});&lt;br /&gt;
    targetDt.Import(sourceDt.SqlDataFrame);&lt;br /&gt;
    WriteLog(`Finished copying data table &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  } else {&lt;br /&gt;
    WriteLog(`Datatable already exist &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
WriteLog(`Finished copying all the data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to copy the data but only create the Snowflake datatables with columns, you can change the line 22 to&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
targetDt.Import(sourceDt.SqlDataFrame.head(0));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy single datatable to Snowflake ===&lt;br /&gt;
This script creates a copy of a single datatable to Snowflake. Replace the &#039;&#039;&amp;lt;tableId1&amp;gt;&#039;&#039; with the id of the source datatable.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function CopyDataTableToSnowflake(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let sourceDt = DataTableById(dataTableId);&lt;br /&gt;
  sourceDt.SqlDataFrame.Persist(`${sourceDt.Name} - Snowflake`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: sourceDt.Project.Id})});&lt;br /&gt;
}&lt;br /&gt;
CopyDataTableToSnowflake(&amp;lt;tableId1&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a copy of a data table that has all Any-type columns changed to String-type columns ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function ConvertAnyDataTypesToStringsToNewTable(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let dt = DataTableById(dataTableId);&lt;br /&gt;
  let sdf = dt.SqlDataFrame;&lt;br /&gt;
  let cts = dt.ColumnTypes;&lt;br /&gt;
  cts.{&lt;br /&gt;
    let ct = _;&lt;br /&gt;
    if (ct.DataType == &amp;quot;Any&amp;quot;) {&lt;br /&gt;
      let n = ct.Name;&lt;br /&gt;
      sdf = sdf.WithColumn(ct.Name, #sql{Cast(Column(Variable(&amp;quot;n&amp;quot;)), &amp;quot;ShortString&amp;quot;)});&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  sdf.Persist(`${dt.Name} - Converted`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;ProjectId&amp;quot;: dt.Project.Id});&lt;br /&gt;
}&lt;br /&gt;
ConvertAnyDataTypesToStringsToNewTable(&amp;lt;dataTableId&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Query number of rows in given data table having a datetime value in given year grouped by month and return resulting table as CSV ===&lt;br /&gt;
SqlDataFrame is used in order to prevent loading the whole datatable into memory first. Filtering is performed as first operation in order to minimize the amount of required work for the data source of the data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
DataTableById(&amp;lt;data table id&amp;gt;)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .Where(#sql{2014 == Year(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .WithColumn(&amp;quot;Month&amp;quot;, #sql{Month(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .GroupBy([&amp;quot;Month&amp;quot;]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
  .OrderByColumns([&amp;quot;Month&amp;quot;], [true])&lt;br /&gt;
  .Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Function for filtering SqlDataFrame by removing rows having, or replacing, the most infrequently occurring column values ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/***&lt;br /&gt;
 * @name ColumnWithMinUsage&lt;br /&gt;
 * @descripion&lt;br /&gt;
 * Generic function that can be used to filter out the most infrequently occurring attribute values or replace their Values&lt;br /&gt;
 * with given common value.&lt;br /&gt;
 * @param df:&lt;br /&gt;
 * DataFrame to operate on.&lt;br /&gt;
 * @param columnName:&lt;br /&gt;
 * Name of the column to be filtered.&lt;br /&gt;
 * @param newColumnName:&lt;br /&gt;
 * Name of the column that will contain the new value of the original column after filtering (if includeOthers was applied).&lt;br /&gt;
 * @param maxNumUniqueValues:&lt;br /&gt;
 * Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
 * @param minValueUsage:&lt;br /&gt;
 * Minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
 * @param includeOthers:&lt;br /&gt;
 * Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
 * If not empty/null, defines the name used for these other-values.&lt;br /&gt;
 */&lt;br /&gt;
function ColumnWithMinUsage(df, columnName, newColumnName, maxNumUniqueValues, minValueUsage, includeOthers)&lt;br /&gt;
{&lt;br /&gt;
  let all = df&lt;br /&gt;
	.GroupBy([])&lt;br /&gt;
	.Aggregate([&amp;quot;NAllTotal&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
	.WithColumn(&amp;quot;__Join2&amp;quot;, #sql{1});&lt;br /&gt;
  let minValueUsageEnabled = !IsNullTop(minValueUsage);&lt;br /&gt;
  let maxNumUniqueValuesEnabled = !IsNullTop(maxNumUniqueValues);&lt;br /&gt;
  if (minValueUsageEnabled || maxNumUniqueValuesEnabled) {&lt;br /&gt;
	// Perform column value-based filtering if minValueUsageEnabled or maxNumUniqueValuesEnabled is defined.&lt;br /&gt;
    let valueColumnName = &amp;quot;__ValueNew&amp;quot;;&lt;br /&gt;
	let filteredValuesColumns = [valueColumnName: columnName];&lt;br /&gt;
	let filteredValues = df&lt;br /&gt;
	  .GroupBy([columnName]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;]);&lt;br /&gt;
	if (minValueUsageEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithColumn(&amp;quot;__Join&amp;quot;, #sql{1})&lt;br /&gt;
		.Join(all, [&amp;quot;__Join&amp;quot;: &amp;quot;__Join2&amp;quot;], &amp;quot;leftouter&amp;quot;)&lt;br /&gt;
        .WithColumn(&amp;quot;Usage&amp;quot;, #sql{Column(&amp;quot;Count&amp;quot;) / Column(&amp;quot;NAllTotal&amp;quot;)});&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;Usage&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
	if (maxNumUniqueValuesEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithRowNumberColumn(&amp;quot;RowNumber&amp;quot;, [&amp;quot;Count&amp;quot;], null, [false]);&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;RowNumber&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	filteredValues = filteredValues&lt;br /&gt;
	  .Select(filteredValuesColumns);&lt;br /&gt;
&lt;br /&gt;
	// Generate select returning all the accepted values.&lt;br /&gt;
	let allValues = filteredValues&lt;br /&gt;
	  .(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;gt;= #expr{minValueUsage}}) : _)&lt;br /&gt;
	  .(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;lt;= #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
	  .Select([valueColumnName, newColumnName: valueColumnName]);&lt;br /&gt;
&lt;br /&gt;
	if (!IsNullTop(includeOthers)) {&lt;br /&gt;
	  // If includeOthers is defined, replace original values with the variable defined in includeOthers.&lt;br /&gt;
	  let otherValues = filteredValues&lt;br /&gt;
		.(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;lt; #expr{minValueUsage}}) : _)&lt;br /&gt;
		.(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;gt; #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
		.WithColumn(newColumnName, #sql{#expr{includeOthers}})&lt;br /&gt;
		.Select([valueColumnName, newColumnName]);&lt;br /&gt;
	  allValues = allValues.Append(otherValues)&lt;br /&gt;
	}&lt;br /&gt;
	df.Join(allValues, [columnName: valueColumnName], &amp;quot;inner&amp;quot;)&lt;br /&gt;
	  .RemoveColumns([valueColumnName]);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// The following example will return only rows containing two of the most common values for Region-column.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, 2, null, null);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&lt;br /&gt;
// The following example will return all input rows, but will replace the values of rows whose Region-column&lt;br /&gt;
// has a value used by less than 15% of all the rows with a new value: &amp;quot;_Others&amp;quot;.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, null, 0.15, &amp;quot;_Others&amp;quot;);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export model events and cases ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function ExportModelEvents(m) {&lt;br /&gt;
  let attrs = m.EventAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Events.Concat(&lt;br /&gt;
      [Case.Name, Type.Name, ToString(TimeStamp, &amp;quot;yyyy-MM-dd HH:mm:ss.fff&amp;quot;)], &lt;br /&gt;
      {let evt = _; attrs.{let att = _; evt.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;, &amp;quot;EventType&amp;quot;, &amp;quot;TimeStamp&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
function ExportModelCases(m) {&lt;br /&gt;
  let attrs = m.CaseAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Cases.Concat(&lt;br /&gt;
      [Name], &lt;br /&gt;
      {let cas = _; attrs.{let att = _; cas.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).EventsDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).CasesDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Calculate all the value usages of a single column for each event in event data table ===&lt;br /&gt;
This query could be used, e.g., to find out the maximum resource usage for every resource found in the event data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function WithUsageColumns(resourceColumn)&lt;br /&gt;
{&lt;br /&gt;
  function WithTotalUsageColumnOfSingleResource(resourceColumn, resourceValue)&lt;br /&gt;
  {&lt;br /&gt;
    _&lt;br /&gt;
      .WithColumn(&amp;quot;_Prev&amp;quot;, #sql{Lag(Column(resourceColumn), [TimeStamp, EventType], [true, true], [CaseId], 1, null)})&lt;br /&gt;
      .WithColumn(&amp;quot;_UsageDiff&amp;quot;, #sql{&lt;br /&gt;
        CaseWhen(&lt;br /&gt;
          Column(resourceColumn) == Column(&amp;quot;_Prev&amp;quot;), 0, &lt;br /&gt;
          Column(&amp;quot;_Prev&amp;quot;) == #expr{resourceValue}, -1,&lt;br /&gt;
          Column(resourceColumn) == #expr{resourceValue}, 1,&lt;br /&gt;
          0)&lt;br /&gt;
      })&lt;br /&gt;
      .WithColumn(`${resourceValue}_Usage`, #sql{Sum(Column(&amp;quot;_UsageDiff&amp;quot;), [TimeStamp, EventType])})&lt;br /&gt;
      .RemoveColumns([&amp;quot;_Prev&amp;quot;, &amp;quot;_UsageDiff&amp;quot;])&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let sdf = _;&lt;br /&gt;
  let allValues = sdf.SelectDistinct([resourceColumn]).OrderByColumns([resourceColumn], [true]).Collect().Column(resourceColumn);&lt;br /&gt;
  allValues.{&lt;br /&gt;
    let v = _;&lt;br /&gt;
    sdf = sdf.WithTotalUsageColumnOfSingleResource(resourceColumn, v)&lt;br /&gt;
  }&lt;br /&gt;
  sdf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
dt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .WithUsageColumns(&amp;lt;resource column name&amp;gt;)&lt;br /&gt;
  .OrderByColumns([dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]], [true])&lt;br /&gt;
  .Collect().ToCsv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;model id&amp;gt; is the id of the model containing event data to be examined.&lt;br /&gt;
* &amp;lt;resource column name&amp;gt; is the name of the column in the event data table of the specified model containing the resource being used by that event.&lt;br /&gt;
&lt;br /&gt;
NOTE: This expression uses functionalities that are only supported in Snowflake-based data tables.&lt;br /&gt;
&lt;br /&gt;
=== Create new Snowflake model from filter ===&lt;br /&gt;
This script creates a new Snowflake model (and two datatables for cases and events) containing filtered event log from given filter id. The script also works if the model doesn&#039;t have a cases datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let filter = FilterById(1); // filter id&lt;br /&gt;
let model = filter.model;&lt;br /&gt;
let project = model.project;&lt;br /&gt;
let nameSuffix = &amp;quot; - &amp;quot; + filter.name + &amp;quot; - &amp;quot; + ToString(Now, &amp;quot;dd-MM-yyyy HH:mm:ss&amp;quot;);&lt;br /&gt;
let eventsDatatableName = model.EventsDataTable.Name + nameSuffix;&lt;br /&gt;
if (eventsDatatableName.length &amp;gt; 440) {&lt;br /&gt;
  eventsDatatableName = eventsDatatableName.Substring(eventsDatatableName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
let eventsData = model&lt;br /&gt;
  .EventsDataTable&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .ApplyFilter(&lt;br /&gt;
    filter.rules,&lt;br /&gt;
    model.CasesDataTable?.SqlDataFrame&lt;br /&gt;
  );&lt;br /&gt;
project&lt;br /&gt;
  .CreateDatatable(eventsDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
  .Import(eventsData);&lt;br /&gt;
let modelConfiguration = model.Configuration;&lt;br /&gt;
modelConfiguration.DataSource.Events.Set(&amp;quot;DataTableName&amp;quot;, eventsDatatableName);&lt;br /&gt;
if (model.CasesDataTable != null) {&lt;br /&gt;
  let eventsDataCaseIdColumn = &amp;quot;CaseId_&amp;quot; + ToString(Random());&lt;br /&gt;
  let casesDatatableName = model.CasesDataTable.Name + nameSuffix;&lt;br /&gt;
  if (casesDatatableName.length &amp;gt; 440) {&lt;br /&gt;
    casesDatatableName = casesDatatableName.Substring(casesDatatableName.length - 440);&lt;br /&gt;
  }&lt;br /&gt;
  let casesData = model&lt;br /&gt;
    .CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .join(&lt;br /&gt;
	  eventsData.SelectDistinct([eventsDataCaseIdColumn: modelConfiguration.DataSource.Events.Columns.CaseId]),&lt;br /&gt;
      [modelConfiguration.DataSource.Cases.Columns.CaseId: eventsDataCaseIdColumn]&lt;br /&gt;
	).Select(model.CasesDataTable.ColumnNames);&lt;br /&gt;
  project&lt;br /&gt;
    .CreateDatatable(casesDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
    .Import(casesData);&lt;br /&gt;
  modelConfiguration.DataSource.Cases.Set(&amp;quot;DataTableName&amp;quot;, casesDatatableName);&lt;br /&gt;
}&lt;br /&gt;
let modelName = model.Name + nameSuffix;&lt;br /&gt;
if (modelName &amp;gt; 440) {&lt;br /&gt;
  modelName = modelName.Substring(modelName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
project&lt;br /&gt;
  .CreateModel(#{    &lt;br /&gt;
    &amp;quot;Name&amp;quot;: modelName,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: model.Description,&lt;br /&gt;
    &amp;quot;Configuration&amp;quot;: modelConfiguration&lt;br /&gt;
  });&lt;br /&gt;
return modelName;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Creating a model consisting of multiple copies of cases in an existing model ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @name CreateTestModel&lt;br /&gt;
 * @description&lt;br /&gt;
 * Creates a new model (or overwrites an existing) to given target project with given number of &lt;br /&gt;
 * repetitions of given source model.&lt;br /&gt;
 * Each repetition will generate &amp;quot;&amp;lt;N&amp;gt;-&amp;quot;-prefix to CaseId-columns, where N equals to the repeat index.&lt;br /&gt;
 * @param sourceModel&lt;br /&gt;
 * PA model used for the source data and from where the connection is copied for the target model if a &lt;br /&gt;
 * new one has to be created.&lt;br /&gt;
 * @param numRepeats&lt;br /&gt;
 * Number of times the data in the source model should be repeated in the generated model.&lt;br /&gt;
 * @param targetProject&lt;br /&gt;
 * Project in which the target model resides.&lt;br /&gt;
 * @param targetModelName&lt;br /&gt;
 * Specifies the name of the test model in the given target project. If a model already exists with &lt;br /&gt;
 * given name, event and case data in this model will be replaced with the new generated event and &lt;br /&gt;
 * case data.&lt;br /&gt;
 * @returns&lt;br /&gt;
 * Model object of the test model having the newly generated data.&lt;br /&gt;
 */&lt;br /&gt;
function CreateTestModel(sourceModel, numRepeats, targetProject, targetModelName) &lt;br /&gt;
{&lt;br /&gt;
  let eventsColumnMappings = sourceModel.EventsDataTable.ColumnMappings;&lt;br /&gt;
  let casesColumnMappings = sourceModel.CasesDataTable.ColumnMappings;&lt;br /&gt;
  let connection = sourceModel.EventsDataTable.DataSourceConnection;&lt;br /&gt;
&lt;br /&gt;
  function CreateResultModel()&lt;br /&gt;
  {&lt;br /&gt;
    function GetTable(tableName) &lt;br /&gt;
    {&lt;br /&gt;
      let tableConfiguration = #{&lt;br /&gt;
        &amp;quot;Name&amp;quot;: tableName,&lt;br /&gt;
        &amp;quot;Connection&amp;quot;: connection&lt;br /&gt;
      };&lt;br /&gt;
      let resultTable = targetProject.DataTableByName(tableName);&lt;br /&gt;
      if (resultTable == null)&lt;br /&gt;
      {&lt;br /&gt;
        resultTable = targetProject.CreateDataTable(tableConfiguration)&lt;br /&gt;
          .Modify(#{&amp;quot;NameInDataSource&amp;quot;: null})&lt;br /&gt;
          .Synchronize();&lt;br /&gt;
      }&lt;br /&gt;
      return resultTable;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let eventsTableName = `${targetModelName} - events`;&lt;br /&gt;
    let casesTableName = `${targetModelName} - cases`;&lt;br /&gt;
    let targetModel = targetProject.ModelByName(targetModelName);&lt;br /&gt;
    let eventsTable, casesTable = null;&lt;br /&gt;
&lt;br /&gt;
    if (targetModel != null)&lt;br /&gt;
    {&lt;br /&gt;
      eventsTable = targetModel.EventsDataTable;&lt;br /&gt;
      casesTable = targetModel.CasesDataTable;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      eventsTable = GetTable(eventsTableName);&lt;br /&gt;
      if (sourceModel.CasesDataTable != null) {&lt;br /&gt;
        casesTable = GetTable(casesTableName);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      let timestampMapping = eventsColumnMappings[&amp;quot;TimeStamp&amp;quot;];&lt;br /&gt;
      eventsColumnMappings.Remove(&amp;quot;TimeStamp&amp;quot;);&lt;br /&gt;
      eventsColumnMappings.Set(&amp;quot;Timestamp&amp;quot;, timestampMapping);&lt;br /&gt;
&lt;br /&gt;
      let modelConfiguration = #{&lt;br /&gt;
        &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
          &amp;quot;Events&amp;quot;:#{&lt;br /&gt;
            &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
            &amp;quot;DataTableName&amp;quot;: eventsTableName,&lt;br /&gt;
            &amp;quot;Columns&amp;quot;: eventsColumnMappings&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      if (casesColumnMappings != null) {&lt;br /&gt;
        modelConfiguration[&amp;quot;DataSource&amp;quot;].Set(&amp;quot;Cases&amp;quot;, #{&lt;br /&gt;
          &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
          &amp;quot;DataTableName&amp;quot;: casesTableName,&lt;br /&gt;
          &amp;quot;Columns&amp;quot;: casesColumnMappings&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      targetModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: targetModelName, &amp;quot;Configuration&amp;quot;: modelConfiguration});&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    eventsTable.Truncate();&lt;br /&gt;
    casesTable?.Truncate();&lt;br /&gt;
&lt;br /&gt;
    return #{&lt;br /&gt;
      &amp;quot;TargetModel&amp;quot;: targetModel,&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTable,&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: casesTable&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function RepeatNTimes(sourceDf, caseIdColumn, numRepeats)&lt;br /&gt;
  {&lt;br /&gt;
    let resultDf = null;&lt;br /&gt;
    for (let i = 1; i &amp;lt;= numRepeats; ++i) {&lt;br /&gt;
      let iterationDf = sourceDf&lt;br /&gt;
        .WithColumn(caseIdColumn, #sql{Concat(#expr{i}, &amp;quot;-&amp;quot;, Column(#expr{caseIdColumn}))});&lt;br /&gt;
      resultDf = resultDf == null ? iterationDf : resultDf.Append(iterationDf); &lt;br /&gt;
    }&lt;br /&gt;
    resultDf;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let resultModel = CreateResultModel();&lt;br /&gt;
  let sourceEventDataDf = sourceModel.EventsDataTable.SqlDataFrame;&lt;br /&gt;
  let resultEventDataDf = RepeatNTimes(sourceEventDataDf, eventsColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
  resultModel[&amp;quot;Events&amp;quot;].Import(resultEventDataDf);&lt;br /&gt;
&lt;br /&gt;
  let sourceCaseDataDf = sourceModel.CasesDataTable?.SqlDataFrame;&lt;br /&gt;
  if (sourceCaseDataDf != null) {&lt;br /&gt;
    let resultCaseDataDf = RepeatNTimes(sourceCaseDataDf, casesColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
    resultModel[&amp;quot;Cases&amp;quot;].Import(resultCaseDataDf);&lt;br /&gt;
  }&lt;br /&gt;
  resultModel[&amp;quot;TargetModel&amp;quot;];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example usage:&amp;lt;blockquote&amp;gt;CreateTestModel(ProjectByName(&amp;quot;Project&amp;quot;).ModelByName(&amp;quot;SAP_OrderToCash - Snowflake&amp;quot;), 3, ProjectByName(&amp;quot;TestData&amp;quot;), &amp;quot;TestModel&amp;quot;);&amp;lt;/blockquote&amp;gt;Creates a new model named &amp;quot;TestModel&amp;quot; (or overwrites old one) into project named &amp;quot;TestData&amp;quot; containing the data from model &amp;quot;SAP_OrderToCash - Snowflake&amp;quot; in project &amp;quot;Project&amp;quot; repeated three times.&lt;br /&gt;
&lt;br /&gt;
=== Analyzing declare patterns found in event log ===&lt;br /&gt;
&lt;br /&gt;
This is an example expression that shows how POSIX-style regular expressions can be used to search for cases in an event log having certain event type patterns [https://www.researchgate.net/publication/277631859_Generating_Event_Logs_Through_the_Simulation_of_Declare_Models declare patterns].&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
let sdf = dt.SqlDataFrame.Head(1000);&lt;br /&gt;
let caseIdColumn = dt.ColumnMappings[&amp;quot;CaseId&amp;quot;], timeStampColumn = dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;], eventTypeColumn = dt.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let eventTypesDf = sdf&lt;br /&gt;
  .SelectDistinct([eventTypeColumn])&lt;br /&gt;
  .OrderByColumns([eventTypeColumn], [true])&lt;br /&gt;
  .WithRowNumberColumn(&amp;quot;Token&amp;quot;, [eventTypeColumn])&lt;br /&gt;
  .WithColumn(&amp;quot;Token&amp;quot;, #sql{Char(Column(&amp;quot;Token&amp;quot;) + Unicode(&amp;quot;a&amp;quot;) - 1)});&lt;br /&gt;
&lt;br /&gt;
sdf = sdf&lt;br /&gt;
  .Join(eventTypesDf.Select([&amp;quot;_EventType2&amp;quot;: eventTypeColumn, &amp;quot;Token&amp;quot;]), [eventTypeColumn: &amp;quot;_EventType2&amp;quot;], &amp;quot;leftouter&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function RespondedExistencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*((a.*b.*)|(b.*a.*))*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*((${a}.*${b}.*)|(${b}.*${a}.*))*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a.*b)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}.*${b})*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function AlternateResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a[^a]*b[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}[^${a}]*${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ChainResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(ab[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function PrecedencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^b]*(a.*b)*[^b]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${b}]*(${a}.*${b})*[^${b}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let tracesDf = sdf&lt;br /&gt;
  .GroupBy([caseIdColumn])&lt;br /&gt;
  .Aggregate([&amp;quot;Trace&amp;quot;: &amp;quot;Token&amp;quot;], [#{&amp;quot;Function&amp;quot;: &amp;quot;list&amp;quot;, &amp;quot;Ordering&amp;quot;: [timeStampColumn], &amp;quot;Separator&amp;quot;: &amp;quot;&amp;quot;}])&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceNL&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceCA&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternNL&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternCA&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternNL&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternCA&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternNL&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternCA&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternNL&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternCA&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
[tracesDf.Collect().ToCsv(), eventTypesDf.Collect().ToCsv()]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Perform a query and send results as E-mail in a HTML table ===&lt;br /&gt;
This example requires two scripts that are both located in the same project:&lt;br /&gt;
&lt;br /&gt;
# Expression-type script to execute (can be, e.g., scheduled to run daily).&lt;br /&gt;
# Expression-type script containing the query JSON to use as basis for the e-mail. In this example, this script is named as &amp;quot;Send query as E-mail - query JSON&amp;quot;. The contents of this script is just the JSON representation of a query that can be extracted, e.g., from any PA chart view.&lt;br /&gt;
&lt;br /&gt;
Script #1 should contain the following code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let replyToAddress = &amp;quot;noreply@test.com&amp;quot;;&lt;br /&gt;
let recipientsArray = [&amp;quot;testuser@test.com&amp;quot;];&lt;br /&gt;
let queryConfiguration = ParseJson(Project.ScriptByName(&amp;quot;Send query as E-mail - query JSON&amp;quot;).Code)&lt;br /&gt;
  .Set(&amp;quot;IncludeCollect&amp;quot;, true);&lt;br /&gt;
let resultDf = Query(queryConfiguration);&lt;br /&gt;
let mailBodyHtml = resultDf.`&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
  &amp;lt;caption&amp;gt;Example query&amp;lt;/caption&amp;gt;&lt;br /&gt;
  &amp;lt;thead&amp;gt;&lt;br /&gt;
    &amp;lt;tr&amp;gt;&lt;br /&gt;
      ${StringJoin(&amp;quot;&amp;quot;, _.Columns.`&lt;br /&gt;
        &amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;&lt;br /&gt;
      `)}&lt;br /&gt;
    &amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;/thead&amp;gt;&lt;br /&gt;
  &amp;lt;tbody&amp;gt;&lt;br /&gt;
    ${StringJoin(&amp;quot;&amp;quot;, _.Rows.`&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
        ${StringJoin(&amp;quot;&amp;quot;, _.`&lt;br /&gt;
          &amp;lt;td&amp;gt;${_}&amp;lt;/td&amp;gt;&lt;br /&gt;
        `)}&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
    `)}&lt;br /&gt;
  &amp;lt;/tbody&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
`;&lt;br /&gt;
&lt;br /&gt;
SendEmail(#{&lt;br /&gt;
  &amp;quot;ReplyTo&amp;quot;: [replyToAddress],&lt;br /&gt;
  &amp;quot;To&amp;quot;: recipientsArray,&lt;br /&gt;
  &amp;quot;Subject&amp;quot;: &amp;quot;Example query E-mail&amp;quot;,&lt;br /&gt;
  &amp;quot;IsBodyHtml&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Body&amp;quot;: mailBodyHtml&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;NOTE: QPR ProcessAnalyzer Server has to have SMTP server configured. Also, remember to update the values of replyToAddress and recipientsArray before using.&lt;br /&gt;
&lt;br /&gt;
=== Converting a case-centric model to object-centric model ===&lt;br /&gt;
This function serves as an example on how a case-centric model could be converted into an object-centric model having just one object type: &amp;quot;Case&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ConvertCCModelToOCModel(model, newModelName) &lt;br /&gt;
{&lt;br /&gt;
  let connection = model.EventsDataTable.DataSourceConnection;&lt;br /&gt;
  let caseIdColumn = model.EventsDataTable.ColumnMappings[&amp;quot;CaseId&amp;quot;];&lt;br /&gt;
  let eventToObjectTableName = `${newModelName} - event-to-object`;&lt;br /&gt;
  let eventsTableName = `${newModelName} - events`;&lt;br /&gt;
  let objectsTableName = `${newModelName} - objects`;&lt;br /&gt;
          &lt;br /&gt;
  let eventsDf = model.EventsDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelEventType&amp;quot;: model.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;],&lt;br /&gt;
      &amp;quot;OcelEventTime&amp;quot;: model.EventsDataTable.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelEventId&amp;quot;, #sql{Concat(&amp;quot;evt-&amp;quot;, Cast(RowNumber([Column(&amp;quot;OcelEventTime&amp;quot;)]), &amp;quot;String&amp;quot;))});&lt;br /&gt;
&lt;br /&gt;
  eventsDf&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelEventToObjectSourceId&amp;quot;: &amp;quot;OcelEventId&amp;quot;,&lt;br /&gt;
      &amp;quot;OcelEventToObjectTargetId&amp;quot;: caseIdColumn])&lt;br /&gt;
    .Select([&amp;quot;OcelEventToObjectSourceId&amp;quot;, &amp;quot;OcelEventToObjectTargetId&amp;quot;])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelEventToObjectQualifier&amp;quot;, #sql{#expr{caseIdColumn} })&lt;br /&gt;
    .Persist(eventToObjectTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  eventsDf&lt;br /&gt;
    .RemoveColumns([caseIdColumn])&lt;br /&gt;
    .Persist(eventsTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  let casesDt = model.CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelObjectId&amp;quot;: model.CasesDataTable.ColumnMappings[&amp;quot;CaseId&amp;quot;]&lt;br /&gt;
    ])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelObjectType&amp;quot;, #sql{&amp;quot;Case&amp;quot;})&lt;br /&gt;
    .Persist(objectsTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  let newConfiguration = #{&lt;br /&gt;
    &amp;quot;OcelDataSource&amp;quot;: #{&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTableName,&lt;br /&gt;
      &amp;quot;Objects&amp;quot;: objectsTableName,&lt;br /&gt;
      &amp;quot;EventToObject&amp;quot;: eventToObjectTableName&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  model.Project  &lt;br /&gt;
    .CreateModel(#{      &lt;br /&gt;
      &amp;quot;Name&amp;quot;: newModelName,  &lt;br /&gt;
      &amp;quot;Description&amp;quot;: model.Description,  &lt;br /&gt;
      &amp;quot;Configuration&amp;quot;: newConfiguration  &lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let ccModel = ModelById(&amp;lt;model id&amp;gt;);&lt;br /&gt;
ConvertCCModelToOCModel(ccModel, `ocel - ${ccModel.Name}`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Show duplicate rows in datatable ===&lt;br /&gt;
This script returns all rows in a datatable appearing more than once (sorted by the most frequent occurrences first). Additionally, the number of occurrences is returned as the last column. Replace the datatable id with the correct one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let datatableId = 1;&lt;br /&gt;
let rowCountColumn = &amp;quot;Row count&amp;quot;;&lt;br /&gt;
let columns = DatatableById(datatableId).Columns.Name;&lt;br /&gt;
DatatableById(datatableId)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .GroupBy(columns)&lt;br /&gt;
  .Aggregate(&lt;br /&gt;
    [rowCountColumn: columns[0]],&lt;br /&gt;
    [&amp;quot;Count&amp;quot;]&lt;br /&gt;
  )&lt;br /&gt;
  .Where(Column(rowCountColumn) &amp;gt; 1)&lt;br /&gt;
  .OrderByColumns([rowCountColumn], [false])&lt;br /&gt;
  .Collect()&lt;br /&gt;
  .ToCSV();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a new object-centric model based on already existing object-centric model containing only filtered objects and events ===&lt;br /&gt;
Script for creating a new OCPM-model based on another already existing model in PA that applies given filter to the original model and creates all the needed tables to store only the relevant rows for all the OCPM data tables.&lt;br /&gt;
&lt;br /&gt;
NOTE: Replace &amp;lt;nowiki&amp;gt;&amp;lt;source model id&amp;gt;, &amp;lt;target model name&amp;gt;, &amp;lt;target project id&amp;gt; as well as the value of ocelItems with the applicable values before use.&amp;lt;/nowiki&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let newModelName = &amp;quot;&amp;lt;target model name&amp;gt;&amp;quot;;&lt;br /&gt;
let sourceModelId = &amp;lt;source model id&amp;gt;;&lt;br /&gt;
let targetProjectId = &amp;lt;target project id&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
// Replace the value of the ocelItems below with the configuration of object-centric filter to apply.&lt;br /&gt;
// Note: Use expression language syntax here, not JSON.&lt;br /&gt;
let ocelItems = [#{&lt;br /&gt;
  &amp;quot;Include&amp;quot;: true,&lt;br /&gt;
  &amp;quot;ObjectAttributeValue&amp;quot;: #{&lt;br /&gt;
    &amp;quot;ObjectType&amp;quot;: &amp;quot;Purchase Order&amp;quot;,&lt;br /&gt;
    &amp;quot;Attribute&amp;quot;: &amp;quot;po_product&amp;quot;,&lt;br /&gt;
    &amp;quot;Values&amp;quot;: [&lt;br /&gt;
      &amp;quot;0Cows&amp;quot;&lt;br /&gt;
    ]&lt;br /&gt;
  }&lt;br /&gt;
}];&lt;br /&gt;
&lt;br /&gt;
let m = ModelById(sourceModelId);&lt;br /&gt;
if (!m.IsOcelModel)&lt;br /&gt;
  throw `Model ${m.Name} is not an OCPM model`;&lt;br /&gt;
if (CountTop(m.CheckModelValidity()) &amp;gt; 0)&lt;br /&gt;
  throw `Model ${m.Name} is not a valid OCPM model`;&lt;br /&gt;
&lt;br /&gt;
let targetProject = ProjectById(targetProjectId);&lt;br /&gt;
if (CountTop(targetProject.Models.Where(Name == newModelName)) &amp;gt; 0)&lt;br /&gt;
  throw `Model having name ${newModelName} already exists in project ${targetProject.Name}. Rename or delete the existing model before running this script.`;&lt;br /&gt;
&lt;br /&gt;
let newConfiguration = #{&lt;br /&gt;
  &amp;quot;OcelDataSource&amp;quot;: #{&lt;br /&gt;
    &amp;quot;EventTypes&amp;quot;: #{},&lt;br /&gt;
    &amp;quot;ObjectTypes&amp;quot;: #{}&lt;br /&gt;
  }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
WriteLog(&amp;quot;Creating common tables...&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function PersistDataFrame(dataFrame, configurationName, configurationDict, dataTableName)&lt;br /&gt;
{&lt;br /&gt;
  WriteLog(`Creating filtered table: ${dataTableName}`);&lt;br /&gt;
  configurationDict.Set(configurationName, dataTableName);&lt;br /&gt;
  return dataFrame.Persist(dataTableName, #{&amp;quot;ProjectId&amp;quot;: targetProjectId, &amp;quot;Append&amp;quot;: false});&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let objectsDf = m.CacheTableSqlDataFrame(#{&lt;br /&gt;
  &amp;quot;CacheTableType&amp;quot;: &amp;quot;Objects&amp;quot;,&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: #{},&lt;br /&gt;
  &amp;quot;OcelItems&amp;quot;: ocelItems &lt;br /&gt;
}).SetPrimaryKey(m.OcelObjects.PrimaryKey);&lt;br /&gt;
let newObjectsDt = PersistDataFrame(objectsDf, &amp;quot;Objects&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - objects`);&lt;br /&gt;
&lt;br /&gt;
let objectToObjectDf = m.CacheTableSqlDataFrame(#{&lt;br /&gt;
  &amp;quot;CacheTableType&amp;quot;: &amp;quot;ObjectToObject&amp;quot;,&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: #{},&lt;br /&gt;
  &amp;quot;OcelItems&amp;quot;: ocelItems &lt;br /&gt;
}).SetPrimaryKey(m.OcelObjectToObject.PrimaryKey)&lt;br /&gt;
  .Select([&amp;quot;OcelObjectToObjectSourceId&amp;quot;, &amp;quot;OcelObjectToObjectTargetId&amp;quot;, &amp;quot;OcelObjectToObjectQualifier&amp;quot;]);&lt;br /&gt;
let newObjectToObjectDt = PersistDataFrame(objectToObjectDf, &amp;quot;ObjectToObject&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - object-to-object`);&lt;br /&gt;
&lt;br /&gt;
let eventToObjectDf = m.CacheTableSqlDataFrame(#{&lt;br /&gt;
  &amp;quot;CacheTableType&amp;quot;: &amp;quot;EventToObject&amp;quot;,&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: #{},&lt;br /&gt;
  &amp;quot;OcelItems&amp;quot;: ocelItems &lt;br /&gt;
}).SetPrimaryKey(m.OcelEventToObject.PrimaryKey)&lt;br /&gt;
  .Select([&amp;quot;OcelEventToObjectSourceId&amp;quot;, &amp;quot;OcelEventToObjectTargetId&amp;quot;, &amp;quot;OcelEventToObjectQualifier&amp;quot;]);&lt;br /&gt;
let newEventToObjectDt = PersistDataFrame(eventToObjectDf, &amp;quot;EventToObject&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - event-to-object`);&lt;br /&gt;
&lt;br /&gt;
let eventsDf = m.OcelEvents.SqlDataFrame&lt;br /&gt;
  .Join(eventToObjectDf.SelectDistinct([&amp;quot;OcelEventToObjectSourceId&amp;quot;]), [&amp;quot;OcelEventId&amp;quot;: &amp;quot;OcelEventToObjectSourceId&amp;quot;], &amp;quot;inner&amp;quot;)&lt;br /&gt;
  .RemoveColumns([&amp;quot;OcelEventToObjectSourceId&amp;quot;])&lt;br /&gt;
  .SetPrimaryKey(m.OcelEvents.PrimaryKey);&lt;br /&gt;
let newEventsDt = PersistDataFrame(eventsDf, &amp;quot;Events&amp;quot;, newConfiguration.OcelDataSource, `${newModelName} - events`);&lt;br /&gt;
&lt;br /&gt;
WriteLog(&amp;quot;Creating event type tables...&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
m.Configuration.OcelDataSource.EventTypes.ToArray().{&lt;br /&gt;
  let ar = _;&lt;br /&gt;
  let eventTypeName = GetContext(ar);&lt;br /&gt;
  let eventTypeDt = m.OcelEventType(eventTypeName);&lt;br /&gt;
  let eventTypeDf = eventTypeDt.SqlDataFrame&lt;br /&gt;
    .Join(newEventsDt.SqlDataFrame.Select([&amp;quot;OcelEventId&amp;quot;]), [&amp;quot;OcelEventTypeEventId&amp;quot;: &amp;quot;OcelEventId&amp;quot;], &amp;quot;inner&amp;quot;)&lt;br /&gt;
    .RemoveColumns([&amp;quot;OcelEventId&amp;quot;])&lt;br /&gt;
    .SetPrimaryKey(eventTypeDt.PrimaryKey);&lt;br /&gt;
  PersistDataFrame(eventTypeDf, eventTypeName, newConfiguration.OcelDataSource.EventTypes, `${newModelName} - eventtype - ${eventTypeName}`);&lt;br /&gt;
};&lt;br /&gt;
  &lt;br /&gt;
WriteLog(&amp;quot;Creating object type tables...&amp;quot;);&lt;br /&gt;
let newUniqueObjectsDf = newObjectsDt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .SelectDistinct([&amp;quot;OcelObjectId&amp;quot;]);&lt;br /&gt;
&lt;br /&gt;
m.Configuration.OcelDataSource.ObjectTypes.ToArray().{&lt;br /&gt;
  let ar = _;&lt;br /&gt;
  let objectTypeName = GetContext(ar);&lt;br /&gt;
  let objectTypeDt = m.OcelObjectType(objectTypeName);&lt;br /&gt;
  let objectTypeDf = objectTypeDt.SqlDataFrame&lt;br /&gt;
    .Join(newUniqueObjectsDf, [&amp;quot;OcelObjectTypeObjectId&amp;quot;: &amp;quot;OcelObjectId&amp;quot;], &amp;quot;inner&amp;quot;)&lt;br /&gt;
    .RemoveColumns([&amp;quot;OcelObjectId&amp;quot;])&lt;br /&gt;
    .SetPrimaryKey(objectTypeDt.PrimaryKey);&lt;br /&gt;
  PersistDataFrame(objectTypeDf, objectTypeName, newConfiguration.OcelDataSource.ObjectTypes, `${newModelName} - objecttype - ${objectTypeName}`);&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
WriteLog(`Creating model ${newModelName}...`);&lt;br /&gt;
&lt;br /&gt;
let newModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: newModelName, &amp;quot;Configuration&amp;quot;: newConfiguration});&lt;br /&gt;
&lt;br /&gt;
WriteLog(`Model (id=${newModel.Id}) created with configuration:\r\n${ToJson(newConfiguration)}`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Embed_to_Website&amp;diff=27267</id>
		<title>Embed to Website</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Embed_to_Website&amp;diff=27267"/>
		<updated>2025-11-11T12:58:02Z</updated>

		<summary type="html">&lt;p&gt;MarHink: Added required change details on Cross-Origin-Opener-Policy header.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer can be embedded to another website, such as Microsoft Sharepoint. The embedding is based on the &#039;&#039;iframe&#039;&#039; html element. By default, QPR ProcessAnalyzer only allows to be embedded by a website in the same origin that is restricted by the Content-Security-Policy HTTL header. More information how to change the CSP setting in [[QPR_ProcessAnalyzer_Security_Hardening#HTTP_Response_Headers|Security Hardening]]. Note also the special behavior when using the [[#Using_SAML_authentication_with_embedding|SAML authentication with embedding]].&lt;br /&gt;
&lt;br /&gt;
== Example embedding webpage ==&lt;br /&gt;
This is a simple example page containing an iframe element that embeds QPR ProcessAnalyzer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
  &amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;iframe src=&amp;quot;https://processanalyzer.company.com/qprpa/ui/#/dashboard?sys:dashboardIdentifier=/MyProject/MyDashboard&amp;quot; height=&amp;quot;600&amp;quot; width=&amp;quot;900&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;br /&gt;
  &amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Change CSP frame-ancestors setting ==&lt;br /&gt;
By default, the Content-Security-Policy HTTL header &#039;&#039;&#039;frame-ancestors&#039;&#039;&#039; directive is set to &#039;&#039;&#039;self&#039;&#039;&#039;, allowing the parent website where QPR ProcessAnalyzer is embedded to, to be located in the same origin. If the parent website is in another origin, the CSP needs to be changed as follows (allowing to embed QPR ProcessAnalyzer in the example.com website):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
frame-ancestors &#039;self&#039; example.com;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the CSP prevents the embedded website from showing, there will be a descriptive error message in the browser console. It contains details which CSP directive didn&#039;t allow to open the page.&lt;br /&gt;
&lt;br /&gt;
More information about the Content-Security-Policy HTTL header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP.&lt;br /&gt;
&lt;br /&gt;
== Using SAML authentication with embedding ==&lt;br /&gt;
When QPR ProcessAnalyzer is embedded into another website with the [[SAML_2.0_Federated_Authentication|SAML authentication]] enabled, the authentication process is handled in a separate browser window or tab. This is necessary because some identity providers (IdP) do not permit authentication within an embedded frame (iframe element). Consequently, when SAML authentication begins, the identity provider&#039;s page will open in a new browser window. While this window is active, the embedded (original) window displays a message informing the user that authentication is in progress in a separate window.&lt;br /&gt;
&lt;br /&gt;
If the browser has popup blocking enabled, the new window cannot open. In such cases, a descriptive message prompts the user to disable popup blocking. Additionally, the user can manually open the authentication window by clicking the embedded frame, even if popups are blocked. Once the identity provider login is successful, the new window will close, and the original browser window will redirect to QPR ProcessAnalyzer. However, if the separate browser window is closed before completing the login, the original window will display the standard QPR ProcessAnalyzer login view.&lt;br /&gt;
&lt;br /&gt;
In addition, for SAML authentication to work correctly when the identity provider is located on a &#039;&#039;&#039;different origin&#039;&#039;&#039; than the QPR ProcessAnalyzer server itself, the &amp;lt;code&amp;gt;Cross-Origin-Opener-Policy&amp;lt;/code&amp;gt; HTTP header must be changed from its default value of &amp;lt;code&amp;gt;&amp;quot;same-origin&amp;quot;&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;&amp;quot;unsafe-none&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
----&#039;&#039;&#039;Additional Information:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cross-Origin-Opener-Policy (COOP) Header:&#039;&#039;&#039; This is a response header that provides a way for a document to control whether a new document opened in a top-level context (like a new tab or window) shares the same browsing context group as its opener. Setting it to &amp;lt;code&amp;gt;unsafe-none&amp;lt;/code&amp;gt; allows the cross-origin authentication flow to complete successfully in this embedded scenario. For more technical details, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Create_Predicted_Eventlog&amp;diff=27122</id>
		<title>Create Predicted Eventlog</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Create_Predicted_Eventlog&amp;diff=27122"/>
		<updated>2025-10-21T06:59:26Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Predicting case attribute values */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article has instructions how to install, configure and use eventlog predictions. The prediction creates a new model that contains the source model data and the predictions. It&#039;s able to predict case attributes for the generated new cases and event attributes for the predicted events. To distinguish the real (source data) and predicted events and cases, there are following attributes in the model:&lt;br /&gt;
* Event attribute &#039;&#039;&#039;Predicted&#039;&#039;&#039; denotes whether the event is from the source data (&#039;&#039;false&#039;&#039;) or whether it&#039;s predicted (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
* Case attribute &#039;&#039;&#039;Generated&#039;&#039;&#039; denotes whether the case is in the source data (&#039;&#039;false&#039;&#039;) or whether the prediction generated it as a new case (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for prediction ==&lt;br /&gt;
Following prerequisites need to be fulfilled to run the eventlog prediction:&lt;br /&gt;
* QPR ProcessAnalyzer 2024.8 or later in use&lt;br /&gt;
* Snowflake connection is configured&lt;br /&gt;
* Source models are stored to Snowflake&lt;br /&gt;
&lt;br /&gt;
== Install prediction to Snowflake ==&lt;br /&gt;
To install the eventlog prediction to Snowflake:&lt;br /&gt;
# Go to Snowflake, and create a Snowflake-managed stage with name &#039;&#039;&#039;DECISION_INTELLIGENCE&#039;&#039;&#039; to the same schema configured to QPR ProcessAnalyzer (in the Snowflake connection string). Use settings in the following image: [[File:Create_Snowflake_stage.png]]&lt;br /&gt;
# Open the created stage and upload the &#039;&#039;&#039;predict.pyz&#039;&#039;&#039; file into the stage (ask the file from your QPR representative).&lt;br /&gt;
# Create the following procedure to the same schema:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sql&amp;quot;&amp;gt;&lt;br /&gt;
CREATE OR REPLACE PROCEDURE QPRPA_SP_PREDICTION(&amp;quot;CONFIGURATION&amp;quot; OBJECT)&lt;br /&gt;
RETURNS OBJECT&lt;br /&gt;
LANGUAGE PYTHON&lt;br /&gt;
STRICT&lt;br /&gt;
RUNTIME_VERSION = &#039;3.11&#039;&lt;br /&gt;
PACKAGES = (&#039;nltk&#039;,&#039;numpy&#039;,&#039;networkx&#039;,&#039;pandas&#039;,&#039;scikit-learn&#039;,&#039;snowflake-snowpark-python&#039;,&#039;tensorflow==2.12.0&#039;,&#039;dill&#039;,&#039;psutil&#039;,&#039;prophet&#039;,&#039;holidays&#039;,&#039;python-kubernetes&#039;,&#039;docker-py&#039;,&#039;cryptography&#039;)&lt;br /&gt;
HANDLER = &#039;main&#039;&lt;br /&gt;
EXECUTE AS OWNER&lt;br /&gt;
AS &#039;&lt;br /&gt;
import sys&lt;br /&gt;
def main(session, parameters_in: dict) -&amp;gt; dict:&lt;br /&gt;
	session.file.get(&#039;&#039;@decision_intelligence/predict.pyz&#039;&#039;, &#039;&#039;/tmp&#039;&#039;)&lt;br /&gt;
	sys.path.append(&#039;&#039;/tmp/predict.pyz&#039;&#039;)&lt;br /&gt;
	import predict&lt;br /&gt;
	return predict.main(session, parameters_in)&lt;br /&gt;
&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Create prediction script in QPR ProcessAnalyzer ==&lt;br /&gt;
1. Create the following example expression script (e.g., with name &#039;&#039;&#039;Create prediction model&#039;&#039;&#039;):&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let completeCaseEventTypeName = &amp;quot;&amp;lt;event type name found only in complete cases&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
let eventTypeColumnName = sourceModel.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
_system.ML.GeneratePredictionModel(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My prediction model&amp;quot;,      // Name of the PA model to generate ti the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,         // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,     // Target project to create the model into.&lt;br /&gt;
  &amp;quot;TrainingConfiguration&amp;quot;: #{         // Training parameters.&lt;br /&gt;
    &amp;quot;num_epochs_to_train&amp;quot;: 200&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;GenerationConfiguration&amp;quot;: #{       // Model generation parameters.&lt;br /&gt;
    &amp;quot;cases_to_generate&amp;quot;: 1000&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;TrainingDataFilter&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [&lt;br /&gt;
      #{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
        &amp;quot;Items&amp;quot;: [&lt;br /&gt;
          #{&lt;br /&gt;
            &amp;quot;Type&amp;quot;: &amp;quot;EventAttributeValue&amp;quot;,&lt;br /&gt;
            &amp;quot;Attribute&amp;quot;: eventTypeColumnName,&lt;br /&gt;
            &amp;quot;StringifiedValues&amp;quot;: [&lt;br /&gt;
              `0${completeCaseEventTypeName}`&lt;br /&gt;
            ]&lt;br /&gt;
          }&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;IncompleteCasesFilter&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [&lt;br /&gt;
      #{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;ExcludeCases&amp;quot;,&lt;br /&gt;
        &amp;quot;Items&amp;quot;: [&lt;br /&gt;
          #{&lt;br /&gt;
            &amp;quot;Type&amp;quot;: &amp;quot;EventAttributeValue&amp;quot;,&lt;br /&gt;
            &amp;quot;Attribute&amp;quot;: eventTypeColumnName,&lt;br /&gt;
            &amp;quot;StringifiedValues&amp;quot;: [&lt;br /&gt;
              `0${completeCaseEventTypeName}`&lt;br /&gt;
            ]&lt;br /&gt;
          }&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;RecreatePredictionModel&amp;quot;: true,    // Should a prediction model be overwritten if one already exists for this source model and target model name combination.&lt;br /&gt;
  &amp;quot;TrainingCaseSampleSize&amp;quot;: 10000     // Maximum number of cases to use from the source model (random sampled).&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;2. Configure prediction for the previously created script as instructed in the next chapter. At minimum, replace the tags listed below with some suitable values:&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;project name&amp;gt;&#039;&#039;&#039;: Name of the project in which the source model is located.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;model name&amp;gt;&#039;&#039;&#039;: Name of the model to be used as source model. This data in this source model will be used to train the prediction model so that it can generate new cases and continuations for incomplete existing cases.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;event type name found only in complete cases&amp;gt;&#039;&#039;&#039;: This example script has been hard-coded to determine whether a case is complete or incomplete based on the existence of this event type.&lt;br /&gt;
&lt;br /&gt;
== Configure prediction ==&lt;br /&gt;
Prediction script has the following settings in the GeneratePredictionModel call:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039;: Name of the QPR ProcessAnalyzer model that is created to the target project. The model will contain the source model content and the predictions.&lt;br /&gt;
* &#039;&#039;&#039;SourceModel&#039;&#039;&#039;: Source model for which the prediction is made. Model can be selected for example based on id with ModelById function or by name with ModelByName function.&lt;br /&gt;
* &#039;&#039;&#039;TargetProject&#039;&#039;&#039;: Target project to create the new model into.&lt;br /&gt;
* &#039;&#039;&#039;RecreatePredictionModel&#039;&#039;&#039;: When &#039;&#039;true&#039;&#039;, a new ML model is trained when the script is run. When &#039;&#039;false&#039;&#039;, the prediction is run using possibly pre-existing ML model. &lt;br /&gt;
* &#039;&#039;&#039;TrainingConfiguration&#039;&#039;&#039;: Training parameters.&lt;br /&gt;
** &#039;&#039;&#039;attributes&#039;&#039;&#039;: Attribute configurations (for more information, see the chapter below).&lt;br /&gt;
** &#039;&#039;&#039;generate_start_time_trend_images&#039;&#039;&#039;: If set to true, two images will be generated for each cross validated Prophet-parameter combination and also for the final selected parameters showing the results of plot and plot_components-functions. &lt;br /&gt;
*** The images will be generated into stage files with the following path names:&lt;br /&gt;
**** plot: @decision_intelligence_testing/{model_name}_st_RMSE={rmse_value or &amp;quot;final&amp;quot;}.png&lt;br /&gt;
****plot_components:  @decision_intelligence_testing/{model_name}_st_RMSE={rmse_value or &amp;quot;final&amp;quot;}_comp.png&lt;br /&gt;
***The default value is false.&lt;br /&gt;
** &#039;&#039;&#039;max_num_case_clusters&#039;&#039;&#039;: Set the maximum number of clusters to divide the case attribute values into.&lt;br /&gt;
*** The default value is 20.&lt;br /&gt;
** &#039;&#039;&#039;max_num_traces_in_training&#039;&#039;&#039;: Set the maximum number of traces used in training.&lt;br /&gt;
*** When training, every case of length N will be split into N traces (a.k.a. prefixes) (p_1, ..., p_N), where p_x contains first x events of the all events of the full case.&lt;br /&gt;
**** If there are more traces available than this configured value, cases to include will be random sampled so that the maximum is exceeded by at most one case.&lt;br /&gt;
**** If null, all the traces will be used, no matter what (may easily lead to running out of memory).&lt;br /&gt;
**** The default value is 100000.&lt;br /&gt;
** &#039;&#039;&#039;num_epochs_to_train&#039;&#039;&#039;: How many times the training set is used in training. The best performing model out of all the iterations will be selected.&lt;br /&gt;
*** The default value is 500.&lt;br /&gt;
** &#039;&#039;&#039;num_extra_years_to_reserve_in_created_model&#039;&#039;&#039;: Number of additional years after the year of the last timestamp in the training data to reserve to the capacity of the created ML model, allowing the model to  be able to predict timestamps in the range between the minimum timestamp year in the training data and the maximum timestamp year plus this value.&lt;br /&gt;
*** The default value is 20.&lt;br /&gt;
** &#039;&#039;&#039;reserve_extra_sequence_length&#039;&#039;&#039;: How many extra events to reserve space for in the ML model compared to the number of events the longest case in the training data has.&lt;br /&gt;
*** The default value is 5.&lt;br /&gt;
** &#039;&#039;&#039;samples_per_epoch&#039;&#039;&#039;: If not null, specifies (approximately) how many traces/prefixes will be used to represent one epoch of data in the training. The actual value used will be made divisible by batch_size using this formula:&lt;br /&gt;
***max(floor(samples_per_epoch / batch_size), 1) * batch_size&lt;br /&gt;
***If null, every epoch will use all the traces/prefixes in the training data.&lt;br /&gt;
***The default value is null&lt;br /&gt;
**&#039;&#039;&#039;validation_split&#039;&#039;&#039;: Percentage of traces/prefixes to use to evaluate the loss and any model metrics at the end of each epoch. The model will not be trained on this data.&lt;br /&gt;
***If 0, separate validation data will not be used. Instead, all the training data will be used also as validation data.&lt;br /&gt;
***The default value is 0.&lt;br /&gt;
* &#039;&#039;&#039;GenerationConfiguration&#039;&#039;&#039;: Event generation parameters. When null, no generation is done. For example, following parameters are supported:&lt;br /&gt;
** &#039;&#039;&#039;avoid_repeated_activities&#039;&#039;&#039;: Array of activity names that should occur at most once in any case. The probability of selecting any of the activities specified in this configuration more than once is set to be 0. &lt;br /&gt;
*** Empty array means that activity generation is not restricted by this setting at all. &lt;br /&gt;
*** null value means that there should not be any activities that can occur more than once (shortcut for specifying all the activity names).&lt;br /&gt;
*** The default value is an empty array.&lt;br /&gt;
** &#039;&#039;&#039;cases_to_generate&#039;&#039;&#039;: Maximum number cases to create. The number of created cases is further limited by the capabilities of the trained model and the &#039;&#039;case_generation_start_time&#039;&#039; and &#039;&#039;case_generation_end_time&#039;&#039; parameters.&lt;br /&gt;
*** The default value is such that the number of cases,  by itself, is not limited.&lt;br /&gt;
** &#039;&#039;&#039;case_generation_start_time&#039;&#039;&#039;: If defined, new cases will be generated after this timestamp (given as string in ISO datetime format). &lt;br /&gt;
*** If undefined, the latest start event timestamp used in the training data is used.&lt;br /&gt;
*** The default value is undefined.&lt;br /&gt;
** &#039;&#039;&#039;case_generation_end_time&#039;&#039;&#039;: If defined, new events and cases will not be generated after this timestamp (given as string in ISO datetime format). E.g., &amp;quot;2015-01-01T00:00:00&amp;quot;.&lt;br /&gt;
*** The default value is unlimited (only limit comes from the capacity of the trained model)&lt;br /&gt;
** &#039;&#039;&#039;generate_debug_event_attributes&#039;&#039;&#039;: &lt;br /&gt;
*** If true, additional columns will be added containing, e.g., probabilities of the selected activity and other activities.&lt;br /&gt;
*** The default value is false.&lt;br /&gt;
** &#039;&#039;&#039;max_num_events&#039;&#039;&#039;:&lt;br /&gt;
*** Specifies the maximum number of events to generate for any case.&lt;br /&gt;
*** If unspecified (=default), the value equals to &#039;&#039;&amp;lt;the maximum number of events in any case in the training data&amp;gt;&#039;&#039;+&#039;&#039;&amp;lt;the value of reserve_extra_sequence_length in training&amp;gt;&#039;&#039;.&lt;br /&gt;
** &#039;&#039;&#039;min_prediction_probability &#039;&#039;&#039;: &lt;br /&gt;
*** The minimum probability of any prediction. If the probability of a prediction is lower than this, it will never be picked. &lt;br /&gt;
*** The default value is 0.01.&lt;br /&gt;
** &#039;&#039;&#039;temperature&#039;&#039;&#039;: &lt;br /&gt;
*** If 0, the generated next activity will always be the one that is the most probable. &lt;br /&gt;
*** If 1, the generated next activity is purely based on the probabilities returned by the trained ML model. &lt;br /&gt;
*** This behavior is interpolated when using values between 0 and 1.&lt;br /&gt;
*** The default value is 0.9.&lt;br /&gt;
* &#039;&#039;&#039;TrainingDataFilter&#039;&#039;&#039;: [[Filtering_in_QPR_ProcessAnalyzer_Queries|Filter]] to select specific cases that are used to train the prediction model. This filter is required to train the model only using the completed cases. Uncompleted cases should not be used for the training, so the model doesn&#039;t incorrectly learn that cases should end like that.&lt;br /&gt;
* &#039;&#039;&#039;IncompleteCasesFilter&#039;&#039;&#039;: Optional [[Filtering_in_QPR_ProcessAnalyzer_Queries|filter]] to select which cases the prediction is made for. To improve performance of the prediction, it&#039;s recommended to include only the incomplete cases for which new events might appear, and skip the completed cases for which new events are not expected anymore.&lt;br /&gt;
* &#039;&#039;&#039;TrainingCaseSampleSize&#039;&#039;&#039;: Maximum number of cases to take from the source model (cases are selected randomly). Use a lower setting to speed up the ML model training. The greater the value, the more subtle phenomena the prediction can learn from the data.&lt;br /&gt;
&lt;br /&gt;
== Attribute configuration ==&lt;br /&gt;
Attribute configuration is used in &#039;&#039;&#039;TrainingConfiguration&#039;&#039;&#039; (see the chapter above) to configure which event- and case attributes should be used in prediction model and how they are used.&lt;br /&gt;
&lt;br /&gt;
The configuration is in the top level split into two sections: &amp;quot;event&amp;quot; and &amp;quot;case&amp;quot;. &amp;quot;Event&amp;quot; is used to configure event attributes, whereas &amp;quot;case&amp;quot; is used for case attributes.&lt;br /&gt;
&lt;br /&gt;
The next level supports one value: &amp;quot;input&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The next level after that, supports the following settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;categorical_groups&#039;&#039;&#039;: An array of categorical attribute group configuration objects used to define groups of attributes that will be bundled together in the trained model, either as separate input- or output features. Each attribute group will form its own input- or output vector used in the model training and generation.&lt;br /&gt;
** If null, only one group will be created with all the available categorical attributes included.&lt;br /&gt;
** The following settings are supported by these objects:&lt;br /&gt;
*** &#039;&#039;&#039;attributes&#039;&#039;&#039;: An array of attribute names.&lt;br /&gt;
**** If null, all the input attributes are to be included in this group.&lt;br /&gt;
*** &#039;&#039;&#039;max_num_clusters&#039;&#039;&#039;: The maximum number of clusters (input- or output vector feature values) to use to represent this group of attributes.&lt;br /&gt;
**** Default value: 20&lt;br /&gt;
**** NOTE: Clustering is used by default to convert a set of attribute values into an input- or output vector used by the prediction model.&lt;br /&gt;
* &#039;&#039;&#039;columns&#039;&#039;&#039;: An array of attribute column configuration objects used to define columns in the input data that are to be used as event- or case attributes.&lt;br /&gt;
** If null, all the columns will be included as categorical attributes (except case id, event type (only for event) and timestamp (only for event) columns).&lt;br /&gt;
** The following settings are supported by these objects:&lt;br /&gt;
*** &#039;&#039;&#039;label&#039;&#039;&#039;: Column name.&lt;br /&gt;
*** &#039;&#039;&#039;type&#039;&#039;&#039;: Type of the column. Supported types are:&lt;br /&gt;
**** &#039;&#039;&#039;categorical&#039;&#039;&#039;: Values can take on one of a limited, and usually fixed, number of possible values.&lt;br /&gt;
**** &#039;&#039;&#039;numeric&#039;&#039;&#039;: Value is considered as a continuous numeric value.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
Use all event attributes as input for the prediction model. In addition, additional machine learning input vector for SAP_User-event data column supporting at most 10 unique values.&lt;br /&gt;
&lt;br /&gt;
In addition, for case attributes, only &amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;, &amp;quot;Account Manager&amp;quot; and &amp;quot;Customer Group&amp;quot; case data columns are used as categorical attributes and &amp;quot;Cost&amp;quot; as numeric attribute. Furthermore, the four categorical case attributes are grouped into three groups, each of which are used as its own input vector for the prediction model.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When generating, all event attributes will be included for generated events as columns. Generated cases will have only &amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;, &amp;quot;Account Manager&amp;quot;,  &amp;quot;Customer Group&amp;quot;, and &amp;quot;Cost&amp;quot; columns.&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;attributes&amp;quot;: #{&lt;br /&gt;
  &amp;quot;event&amp;quot;: #{&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;categorical_groups&amp;quot;: [&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;attributes&amp;quot;: None&lt;br /&gt;
        },&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;attributes&amp;quot;: [&amp;quot;SAP_User&amp;quot;],&lt;br /&gt;
          &amp;quot;max_num_clusters&amp;quot;: 10&lt;br /&gt;
        }&lt;br /&gt;
      ],&lt;br /&gt;
      &amp;quot;columns&amp;quot;: None&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;case&amp;quot;: #{&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;categorical_groups&amp;quot;: [#{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Account Manager&amp;quot;]&lt;br /&gt;
      }, #{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Customer Group&amp;quot;]&lt;br /&gt;
      }, #{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;]&lt;br /&gt;
      }],&lt;br /&gt;
      &amp;quot;columns&amp;quot;: [&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Region&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Product Group&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Account Manager&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Customer Group&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Cost&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;numeric&amp;quot; }&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Predicting case attribute values ==&lt;br /&gt;
QPR ProcessAnalyzer can also be used to, e.g.,  predict the final values of case attributes of running cases. The following script gives an example on how to perform this.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let caseAttributeToPredict = &amp;quot;&amp;lt;name of the case attribute&amp;gt;&amp;quot;;&lt;br /&gt;
let resultModelName = &amp;quot;&amp;lt;name of the model to be created/replaced&amp;gt;&amp;quot;;&lt;br /&gt;
let generateDebugCaseAttributes = false; // Set to true to generate columns for prediction probabilities.&lt;br /&gt;
let casesToPredictFilter = &amp;quot;&amp;lt;JSON filter for cases for which the prediction is to be performed&amp;gt;&amp;quot;;&lt;br /&gt;
let casesToUseForTrainingFilter = &amp;quot;&amp;lt;JSON filter for cases to be used for ML model training&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
&lt;br /&gt;
_system.ML.GenerateCaseAttributePredictionModel(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: resultModelName,                                     // Name of the PA model to generate ti the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,                                  // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,                              // Target project to create the model into.&lt;br /&gt;
  &amp;quot;RecreatePredictionModel&amp;quot;: false,                            // Should a prediction model be overwritten if one already exists for this source model and target model name combination. &lt;br /&gt;
  &amp;quot;TrainingCaseSampleSize&amp;quot;: 10000,                             // Maximum number of cases to use from the source model (random sampled). &lt;br /&gt;
  &amp;quot;CommonConfiguration&amp;quot;: #{                                    // Common parameters used by both training and generation.&lt;br /&gt;
    &amp;quot;output_case_attribute_groups&amp;quot;: [#{&lt;br /&gt;
      &amp;quot;attributes&amp;quot;: [caseAttributeToPredict]                   // Attribute whose value is to be predicted.&lt;br /&gt;
    }]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;TrainingConfiguration&amp;quot;: #{                                  // Training parameters.&lt;br /&gt;
    &amp;quot;max_num_case_attribute_clusters&amp;quot;: 80,&lt;br /&gt;
    &amp;quot;num_epochs_to_train&amp;quot;: 100&lt;br /&gt;
  },                            &lt;br /&gt;
  &amp;quot;GenerationConfiguration&amp;quot;: #{                                // Case attribute generation parameters.&lt;br /&gt;
    &amp;quot;generate_debug_case_attributes&amp;quot;: generateDebugCaseAttributes // Should probability and probability_all-columns be generated as well as the actual prediction created into a new column named Predicted_&amp;lt;attribute name&amp;gt;&lt;br /&gt;
  },                                                       &lt;br /&gt;
  &amp;quot;TrainingDataFilter&amp;quot;: ParseJson(casesToUseForTrainingFilter), // Filter JSON for events to be used for training.&lt;br /&gt;
  &amp;quot;IncompleteCasesFilter&amp;quot;: ParseJson(casesToPredictFilter)      // Filter JSON for events for whose case attribute value is to be predicted.&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Create_Predicted_Eventlog&amp;diff=27121</id>
		<title>Create Predicted Eventlog</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Create_Predicted_Eventlog&amp;diff=27121"/>
		<updated>2025-10-21T06:58:09Z</updated>

		<summary type="html">&lt;p&gt;MarHink: Added example of predicting case attribute values&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article has instructions how to install, configure and use eventlog predictions. The prediction creates a new model that contains the source model data and the predictions. It&#039;s able to predict case attributes for the generated new cases and event attributes for the predicted events. To distinguish the real (source data) and predicted events and cases, there are following attributes in the model:&lt;br /&gt;
* Event attribute &#039;&#039;&#039;Predicted&#039;&#039;&#039; denotes whether the event is from the source data (&#039;&#039;false&#039;&#039;) or whether it&#039;s predicted (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
* Case attribute &#039;&#039;&#039;Generated&#039;&#039;&#039; denotes whether the case is in the source data (&#039;&#039;false&#039;&#039;) or whether the prediction generated it as a new case (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for prediction ==&lt;br /&gt;
Following prerequisites need to be fulfilled to run the eventlog prediction:&lt;br /&gt;
* QPR ProcessAnalyzer 2024.8 or later in use&lt;br /&gt;
* Snowflake connection is configured&lt;br /&gt;
* Source models are stored to Snowflake&lt;br /&gt;
&lt;br /&gt;
== Install prediction to Snowflake ==&lt;br /&gt;
To install the eventlog prediction to Snowflake:&lt;br /&gt;
# Go to Snowflake, and create a Snowflake-managed stage with name &#039;&#039;&#039;DECISION_INTELLIGENCE&#039;&#039;&#039; to the same schema configured to QPR ProcessAnalyzer (in the Snowflake connection string). Use settings in the following image: [[File:Create_Snowflake_stage.png]]&lt;br /&gt;
# Open the created stage and upload the &#039;&#039;&#039;predict.pyz&#039;&#039;&#039; file into the stage (ask the file from your QPR representative).&lt;br /&gt;
# Create the following procedure to the same schema:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sql&amp;quot;&amp;gt;&lt;br /&gt;
CREATE OR REPLACE PROCEDURE QPRPA_SP_PREDICTION(&amp;quot;CONFIGURATION&amp;quot; OBJECT)&lt;br /&gt;
RETURNS OBJECT&lt;br /&gt;
LANGUAGE PYTHON&lt;br /&gt;
STRICT&lt;br /&gt;
RUNTIME_VERSION = &#039;3.11&#039;&lt;br /&gt;
PACKAGES = (&#039;nltk&#039;,&#039;numpy&#039;,&#039;networkx&#039;,&#039;pandas&#039;,&#039;scikit-learn&#039;,&#039;snowflake-snowpark-python&#039;,&#039;tensorflow==2.12.0&#039;,&#039;dill&#039;,&#039;psutil&#039;,&#039;prophet&#039;,&#039;holidays&#039;,&#039;python-kubernetes&#039;,&#039;docker-py&#039;,&#039;cryptography&#039;)&lt;br /&gt;
HANDLER = &#039;main&#039;&lt;br /&gt;
EXECUTE AS OWNER&lt;br /&gt;
AS &#039;&lt;br /&gt;
import sys&lt;br /&gt;
def main(session, parameters_in: dict) -&amp;gt; dict:&lt;br /&gt;
	session.file.get(&#039;&#039;@decision_intelligence/predict.pyz&#039;&#039;, &#039;&#039;/tmp&#039;&#039;)&lt;br /&gt;
	sys.path.append(&#039;&#039;/tmp/predict.pyz&#039;&#039;)&lt;br /&gt;
	import predict&lt;br /&gt;
	return predict.main(session, parameters_in)&lt;br /&gt;
&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Create prediction script in QPR ProcessAnalyzer ==&lt;br /&gt;
1. Create the following example expression script (e.g., with name &#039;&#039;&#039;Create prediction model&#039;&#039;&#039;):&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let completeCaseEventTypeName = &amp;quot;&amp;lt;event type name found only in complete cases&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
let eventTypeColumnName = sourceModel.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
_system.ML.GeneratePredictionModel(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My prediction model&amp;quot;,      // Name of the PA model to generate ti the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,         // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,     // Target project to create the model into.&lt;br /&gt;
  &amp;quot;TrainingConfiguration&amp;quot;: #{         // Training parameters.&lt;br /&gt;
    &amp;quot;num_epochs_to_train&amp;quot;: 200&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;GenerationConfiguration&amp;quot;: #{       // Model generation parameters.&lt;br /&gt;
    &amp;quot;cases_to_generate&amp;quot;: 1000&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;TrainingDataFilter&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [&lt;br /&gt;
      #{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
        &amp;quot;Items&amp;quot;: [&lt;br /&gt;
          #{&lt;br /&gt;
            &amp;quot;Type&amp;quot;: &amp;quot;EventAttributeValue&amp;quot;,&lt;br /&gt;
            &amp;quot;Attribute&amp;quot;: eventTypeColumnName,&lt;br /&gt;
            &amp;quot;StringifiedValues&amp;quot;: [&lt;br /&gt;
              `0${completeCaseEventTypeName}`&lt;br /&gt;
            ]&lt;br /&gt;
          }&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;IncompleteCasesFilter&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Items&amp;quot;: [&lt;br /&gt;
      #{&lt;br /&gt;
        &amp;quot;Type&amp;quot;: &amp;quot;ExcludeCases&amp;quot;,&lt;br /&gt;
        &amp;quot;Items&amp;quot;: [&lt;br /&gt;
          #{&lt;br /&gt;
            &amp;quot;Type&amp;quot;: &amp;quot;EventAttributeValue&amp;quot;,&lt;br /&gt;
            &amp;quot;Attribute&amp;quot;: eventTypeColumnName,&lt;br /&gt;
            &amp;quot;StringifiedValues&amp;quot;: [&lt;br /&gt;
              `0${completeCaseEventTypeName}`&lt;br /&gt;
            ]&lt;br /&gt;
          }&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;RecreatePredictionModel&amp;quot;: true,    // Should a prediction model be overwritten if one already exists for this source model and target model name combination.&lt;br /&gt;
  &amp;quot;TrainingCaseSampleSize&amp;quot;: 10000     // Maximum number of cases to use from the source model (random sampled).&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;2. Configure prediction for the previously created script as instructed in the next chapter. At minimum, replace the tags listed below with some suitable values:&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;project name&amp;gt;&#039;&#039;&#039;: Name of the project in which the source model is located.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;model name&amp;gt;&#039;&#039;&#039;: Name of the model to be used as source model. This data in this source model will be used to train the prediction model so that it can generate new cases and continuations for incomplete existing cases.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;event type name found only in complete cases&amp;gt;&#039;&#039;&#039;: This example script has been hard-coded to determine whether a case is complete or incomplete based on the existence of this event type.&lt;br /&gt;
&lt;br /&gt;
== Configure prediction ==&lt;br /&gt;
Prediction script has the following settings in the GeneratePredictionModel call:&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039;: Name of the QPR ProcessAnalyzer model that is created to the target project. The model will contain the source model content and the predictions.&lt;br /&gt;
* &#039;&#039;&#039;SourceModel&#039;&#039;&#039;: Source model for which the prediction is made. Model can be selected for example based on id with ModelById function or by name with ModelByName function.&lt;br /&gt;
* &#039;&#039;&#039;TargetProject&#039;&#039;&#039;: Target project to create the new model into.&lt;br /&gt;
* &#039;&#039;&#039;RecreatePredictionModel&#039;&#039;&#039;: When &#039;&#039;true&#039;&#039;, a new ML model is trained when the script is run. When &#039;&#039;false&#039;&#039;, the prediction is run using possibly pre-existing ML model. &lt;br /&gt;
* &#039;&#039;&#039;TrainingConfiguration&#039;&#039;&#039;: Training parameters.&lt;br /&gt;
** &#039;&#039;&#039;attributes&#039;&#039;&#039;: Attribute configurations (for more information, see the chapter below).&lt;br /&gt;
** &#039;&#039;&#039;generate_start_time_trend_images&#039;&#039;&#039;: If set to true, two images will be generated for each cross validated Prophet-parameter combination and also for the final selected parameters showing the results of plot and plot_components-functions. &lt;br /&gt;
*** The images will be generated into stage files with the following path names:&lt;br /&gt;
**** plot: @decision_intelligence_testing/{model_name}_st_RMSE={rmse_value or &amp;quot;final&amp;quot;}.png&lt;br /&gt;
****plot_components:  @decision_intelligence_testing/{model_name}_st_RMSE={rmse_value or &amp;quot;final&amp;quot;}_comp.png&lt;br /&gt;
***The default value is false.&lt;br /&gt;
** &#039;&#039;&#039;max_num_case_clusters&#039;&#039;&#039;: Set the maximum number of clusters to divide the case attribute values into.&lt;br /&gt;
*** The default value is 20.&lt;br /&gt;
** &#039;&#039;&#039;max_num_traces_in_training&#039;&#039;&#039;: Set the maximum number of traces used in training.&lt;br /&gt;
*** When training, every case of length N will be split into N traces (a.k.a. prefixes) (p_1, ..., p_N), where p_x contains first x events of the all events of the full case.&lt;br /&gt;
**** If there are more traces available than this configured value, cases to include will be random sampled so that the maximum is exceeded by at most one case.&lt;br /&gt;
**** If null, all the traces will be used, no matter what (may easily lead to running out of memory).&lt;br /&gt;
**** The default value is 100000.&lt;br /&gt;
** &#039;&#039;&#039;num_epochs_to_train&#039;&#039;&#039;: How many times the training set is used in training. The best performing model out of all the iterations will be selected.&lt;br /&gt;
*** The default value is 500.&lt;br /&gt;
** &#039;&#039;&#039;num_extra_years_to_reserve_in_created_model&#039;&#039;&#039;: Number of additional years after the year of the last timestamp in the training data to reserve to the capacity of the created ML model, allowing the model to  be able to predict timestamps in the range between the minimum timestamp year in the training data and the maximum timestamp year plus this value.&lt;br /&gt;
*** The default value is 20.&lt;br /&gt;
** &#039;&#039;&#039;reserve_extra_sequence_length&#039;&#039;&#039;: How many extra events to reserve space for in the ML model compared to the number of events the longest case in the training data has.&lt;br /&gt;
*** The default value is 5.&lt;br /&gt;
** &#039;&#039;&#039;samples_per_epoch&#039;&#039;&#039;: If not null, specifies (approximately) how many traces/prefixes will be used to represent one epoch of data in the training. The actual value used will be made divisible by batch_size using this formula:&lt;br /&gt;
***max(floor(samples_per_epoch / batch_size), 1) * batch_size&lt;br /&gt;
***If null, every epoch will use all the traces/prefixes in the training data.&lt;br /&gt;
***The default value is null&lt;br /&gt;
**&#039;&#039;&#039;validation_split&#039;&#039;&#039;: Percentage of traces/prefixes to use to evaluate the loss and any model metrics at the end of each epoch. The model will not be trained on this data.&lt;br /&gt;
***If 0, separate validation data will not be used. Instead, all the training data will be used also as validation data.&lt;br /&gt;
***The default value is 0.&lt;br /&gt;
* &#039;&#039;&#039;GenerationConfiguration&#039;&#039;&#039;: Event generation parameters. When null, no generation is done. For example, following parameters are supported:&lt;br /&gt;
** &#039;&#039;&#039;avoid_repeated_activities&#039;&#039;&#039;: Array of activity names that should occur at most once in any case. The probability of selecting any of the activities specified in this configuration more than once is set to be 0. &lt;br /&gt;
*** Empty array means that activity generation is not restricted by this setting at all. &lt;br /&gt;
*** null value means that there should not be any activities that can occur more than once (shortcut for specifying all the activity names).&lt;br /&gt;
*** The default value is an empty array.&lt;br /&gt;
** &#039;&#039;&#039;cases_to_generate&#039;&#039;&#039;: Maximum number cases to create. The number of created cases is further limited by the capabilities of the trained model and the &#039;&#039;case_generation_start_time&#039;&#039; and &#039;&#039;case_generation_end_time&#039;&#039; parameters.&lt;br /&gt;
*** The default value is such that the number of cases,  by itself, is not limited.&lt;br /&gt;
** &#039;&#039;&#039;case_generation_start_time&#039;&#039;&#039;: If defined, new cases will be generated after this timestamp (given as string in ISO datetime format). &lt;br /&gt;
*** If undefined, the latest start event timestamp used in the training data is used.&lt;br /&gt;
*** The default value is undefined.&lt;br /&gt;
** &#039;&#039;&#039;case_generation_end_time&#039;&#039;&#039;: If defined, new events and cases will not be generated after this timestamp (given as string in ISO datetime format). E.g., &amp;quot;2015-01-01T00:00:00&amp;quot;.&lt;br /&gt;
*** The default value is unlimited (only limit comes from the capacity of the trained model)&lt;br /&gt;
** &#039;&#039;&#039;generate_debug_event_attributes&#039;&#039;&#039;: &lt;br /&gt;
*** If true, additional columns will be added containing, e.g., probabilities of the selected activity and other activities.&lt;br /&gt;
*** The default value is false.&lt;br /&gt;
** &#039;&#039;&#039;max_num_events&#039;&#039;&#039;:&lt;br /&gt;
*** Specifies the maximum number of events to generate for any case.&lt;br /&gt;
*** If unspecified (=default), the value equals to &#039;&#039;&amp;lt;the maximum number of events in any case in the training data&amp;gt;&#039;&#039;+&#039;&#039;&amp;lt;the value of reserve_extra_sequence_length in training&amp;gt;&#039;&#039;.&lt;br /&gt;
** &#039;&#039;&#039;min_prediction_probability &#039;&#039;&#039;: &lt;br /&gt;
*** The minimum probability of any prediction. If the probability of a prediction is lower than this, it will never be picked. &lt;br /&gt;
*** The default value is 0.01.&lt;br /&gt;
** &#039;&#039;&#039;temperature&#039;&#039;&#039;: &lt;br /&gt;
*** If 0, the generated next activity will always be the one that is the most probable. &lt;br /&gt;
*** If 1, the generated next activity is purely based on the probabilities returned by the trained ML model. &lt;br /&gt;
*** This behavior is interpolated when using values between 0 and 1.&lt;br /&gt;
*** The default value is 0.9.&lt;br /&gt;
* &#039;&#039;&#039;TrainingDataFilter&#039;&#039;&#039;: [[Filtering_in_QPR_ProcessAnalyzer_Queries|Filter]] to select specific cases that are used to train the prediction model. This filter is required to train the model only using the completed cases. Uncompleted cases should not be used for the training, so the model doesn&#039;t incorrectly learn that cases should end like that.&lt;br /&gt;
* &#039;&#039;&#039;IncompleteCasesFilter&#039;&#039;&#039;: Optional [[Filtering_in_QPR_ProcessAnalyzer_Queries|filter]] to select which cases the prediction is made for. To improve performance of the prediction, it&#039;s recommended to include only the incomplete cases for which new events might appear, and skip the completed cases for which new events are not expected anymore.&lt;br /&gt;
* &#039;&#039;&#039;TrainingCaseSampleSize&#039;&#039;&#039;: Maximum number of cases to take from the source model (cases are selected randomly). Use a lower setting to speed up the ML model training. The greater the value, the more subtle phenomena the prediction can learn from the data.&lt;br /&gt;
&lt;br /&gt;
== Attribute configuration ==&lt;br /&gt;
Attribute configuration is used in &#039;&#039;&#039;TrainingConfiguration&#039;&#039;&#039; (see the chapter above) to configure which event- and case attributes should be used in prediction model and how they are used.&lt;br /&gt;
&lt;br /&gt;
The configuration is in the top level split into two sections: &amp;quot;event&amp;quot; and &amp;quot;case&amp;quot;. &amp;quot;Event&amp;quot; is used to configure event attributes, whereas &amp;quot;case&amp;quot; is used for case attributes.&lt;br /&gt;
&lt;br /&gt;
The next level supports one value: &amp;quot;input&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The next level after that, supports the following settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;categorical_groups&#039;&#039;&#039;: An array of categorical attribute group configuration objects used to define groups of attributes that will be bundled together in the trained model, either as separate input- or output features. Each attribute group will form its own input- or output vector used in the model training and generation.&lt;br /&gt;
** If null, only one group will be created with all the available categorical attributes included.&lt;br /&gt;
** The following settings are supported by these objects:&lt;br /&gt;
*** &#039;&#039;&#039;attributes&#039;&#039;&#039;: An array of attribute names.&lt;br /&gt;
**** If null, all the input attributes are to be included in this group.&lt;br /&gt;
*** &#039;&#039;&#039;max_num_clusters&#039;&#039;&#039;: The maximum number of clusters (input- or output vector feature values) to use to represent this group of attributes.&lt;br /&gt;
**** Default value: 20&lt;br /&gt;
**** NOTE: Clustering is used by default to convert a set of attribute values into an input- or output vector used by the prediction model.&lt;br /&gt;
* &#039;&#039;&#039;columns&#039;&#039;&#039;: An array of attribute column configuration objects used to define columns in the input data that are to be used as event- or case attributes.&lt;br /&gt;
** If null, all the columns will be included as categorical attributes (except case id, event type (only for event) and timestamp (only for event) columns).&lt;br /&gt;
** The following settings are supported by these objects:&lt;br /&gt;
*** &#039;&#039;&#039;label&#039;&#039;&#039;: Column name.&lt;br /&gt;
*** &#039;&#039;&#039;type&#039;&#039;&#039;: Type of the column. Supported types are:&lt;br /&gt;
**** &#039;&#039;&#039;categorical&#039;&#039;&#039;: Values can take on one of a limited, and usually fixed, number of possible values.&lt;br /&gt;
**** &#039;&#039;&#039;numeric&#039;&#039;&#039;: Value is considered as a continuous numeric value.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
Use all event attributes as input for the prediction model. In addition, additional machine learning input vector for SAP_User-event data column supporting at most 10 unique values.&lt;br /&gt;
&lt;br /&gt;
In addition, for case attributes, only &amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;, &amp;quot;Account Manager&amp;quot; and &amp;quot;Customer Group&amp;quot; case data columns are used as categorical attributes and &amp;quot;Cost&amp;quot; as numeric attribute. Furthermore, the four categorical case attributes are grouped into three groups, each of which are used as its own input vector for the prediction model.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When generating, all event attributes will be included for generated events as columns. Generated cases will have only &amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;, &amp;quot;Account Manager&amp;quot;,  &amp;quot;Customer Group&amp;quot;, and &amp;quot;Cost&amp;quot; columns.&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
&amp;quot;attributes&amp;quot;: #{&lt;br /&gt;
  &amp;quot;event&amp;quot;: #{&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;categorical_groups&amp;quot;: [&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;attributes&amp;quot;: None&lt;br /&gt;
        },&lt;br /&gt;
        #{&lt;br /&gt;
          &amp;quot;attributes&amp;quot;: [&amp;quot;SAP_User&amp;quot;],&lt;br /&gt;
          &amp;quot;max_num_clusters&amp;quot;: 10&lt;br /&gt;
        }&lt;br /&gt;
      ],&lt;br /&gt;
      &amp;quot;columns&amp;quot;: None&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;case&amp;quot;: #{&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;categorical_groups&amp;quot;: [#{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Account Manager&amp;quot;]&lt;br /&gt;
      }, #{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Customer Group&amp;quot;]&lt;br /&gt;
      }, #{&lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [&amp;quot;Region&amp;quot;, &amp;quot;Product Group&amp;quot;]&lt;br /&gt;
      }],&lt;br /&gt;
      &amp;quot;columns&amp;quot;: [&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Region&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Product Group&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Account Manager&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Customer Group&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;categorical&amp;quot; },&lt;br /&gt;
        #{ &amp;quot;label&amp;quot;: &amp;quot;Cost&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;numeric&amp;quot; }&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Predicting case attribute values ==&lt;br /&gt;
QPR ProcessAnalyzer can also be used to, e.g.,  predict the final values of case attributes of running cases. The following script gives an example on how to perform this.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let caseAttributeToPredict = &amp;quot;&amp;lt;name of the case attribute&amp;gt;&amp;quot;;&lt;br /&gt;
let resultModelName = &amp;quot;&amp;lt;name of the model to be created/replaced&amp;gt;&amp;quot;;&lt;br /&gt;
let generateDebugCaseAttributes = false;&lt;br /&gt;
let casesToPredictFilter = &amp;quot;&amp;lt;JSON filter for cases for which the prediction is to be performed&amp;gt;&amp;quot;;&lt;br /&gt;
let casesToUseForTrainingFilter = &amp;quot;&amp;lt;JSON filter for cases to be used for ML model training&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
&lt;br /&gt;
_system.ML.GenerateCaseAttributePredictionModel(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: resultModelName,                                     // Name of the PA model to generate ti the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,                                  // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,                              // Target project to create the model into.&lt;br /&gt;
  &amp;quot;RecreatePredictionModel&amp;quot;: false,                            // Should a prediction model be overwritten if one already exists for this source model and target model name combination. &lt;br /&gt;
  &amp;quot;TrainingCaseSampleSize&amp;quot;: 10000,                             // Maximum number of cases to use from the source model (random sampled). &lt;br /&gt;
  &amp;quot;CommonConfiguration&amp;quot;: #{                                    // Common parameters used by both training and generation.&lt;br /&gt;
    &amp;quot;output_case_attribute_groups&amp;quot;: [#{&lt;br /&gt;
      &amp;quot;attributes&amp;quot;: [caseAttributeToPredict]                   // Attribute whose value is to be predicted.&lt;br /&gt;
    }]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;TrainingConfiguration&amp;quot;: #{                                  // Training parameters.&lt;br /&gt;
    &amp;quot;max_num_case_attribute_clusters&amp;quot;: 80,&lt;br /&gt;
    &amp;quot;num_epochs_to_train&amp;quot;: 100&lt;br /&gt;
  },                            &lt;br /&gt;
  &amp;quot;GenerationConfiguration&amp;quot;: #{                                // Case attribute generation parameters.&lt;br /&gt;
    &amp;quot;generate_debug_case_attributes&amp;quot;: generateDebugCaseAttributes // Should probability and probability_all-columns be generated as well as the actual prediction created into a new column named Predicted_&amp;lt;attribute name&amp;gt;&lt;br /&gt;
  },                                                       &lt;br /&gt;
  &amp;quot;TrainingDataFilter&amp;quot;: ParseJson(casesToUseForTrainingFilter), // Filter JSON for events to be used for training.&lt;br /&gt;
  &amp;quot;IncompleteCasesFilter&amp;quot;: ParseJson(casesToPredictFilter)      // Filter JSON for events for whose case attribute value is to be predicted.&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=SAML_2.0_Federated_Authentication&amp;diff=27096</id>
		<title>SAML 2.0 Federated Authentication</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=SAML_2.0_Federated_Authentication&amp;diff=27096"/>
		<updated>2025-10-03T07:28:15Z</updated>

		<summary type="html">&lt;p&gt;MarHink: Updated SAML related URLs to have the correct casings.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer supports authenticating users with federated authentication using the SAML 2.0 protocol. QPR ProcessAnalyzer works as a &#039;&#039;&#039;service provider (SP)&#039;&#039;&#039; and uses an external &#039;&#039;&#039;identity providers (IdP)&#039;&#039;&#039; to provide user identity (i.e. authenticating users). Commonly used identity providers are Azure AD and Microsoft Active Directory Federation Services (ADFS).&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
When QPR ProcessAnalyzer is configured as a SAML 2.0 service provider (SP), users can authenticate to QPR ProcessAnalyzer via the configured SAML 2.0 identity provider (IdP). When accessing QPR ProcessAnalyzer, users are automatically redirected to the identity provider for authentication. When the authentication is done, users are redirected back to QPR ProcessAnalyzer where user is then automatically logged in. When using federated authentication, users don&#039;t normally see the QPR ProcessAnalyzer login page. The login page can be accessed (e.g. when login using QPR ProcessAnalyzer user management credentials) by adding &#039;&#039;&#039;forceLogin=1&#039;&#039;&#039; parameter to the url, e.g. &amp;lt;nowiki&amp;gt;https://customer.onqpr.com/QPRPA/ui/#/login?forceLogin=1&amp;lt;/nowiki&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
QPR ProcessAnalyzer can also automatically redirect users to the identity provider from the url &#039;&#039;&#039;/qprpa/Saml2&#039;&#039;&#039;, e.g. &amp;lt;nowiki&amp;gt;https://customer.onqpr.com/qprpa/Saml2&amp;lt;/nowiki&amp;gt;. Redirection to this url can be configured to IIS, when users access QPR ProcessAnalyzer with the server name only, e.g. &amp;lt;nowiki&amp;gt;https://customer.onqpr.com&amp;lt;/nowiki&amp;gt;. The advantage of using this url is that the QPR ProcessAnalyzer web application is not loaded before the authentication, making the authentication flow faster. When going to the identity provider using a url starting with &amp;lt;nowiki&amp;gt;https://customer.onqpr.com/QPRPA/ui/&amp;lt;/nowiki&amp;gt;, the QPR ProcessAnalyzer web application is loaded before going to the identity provider.&lt;br /&gt;
&lt;br /&gt;
When a user logs in to QPR ProcessAnalyzer for the first time, user account is created to QPR ProcessAnalyzer user management. This account can only log in using the federated authentication, because the user account doesn&#039;t have a password in QPR ProcessAnalyzer. User accounts are matched between QPR ProcessAnalyzer and the identity provider using usernames.&lt;br /&gt;
&lt;br /&gt;
When the &#039;&#039;SAMLGroupsAttribute&#039;&#039; setting has been configured, QPR ProcessAnalyzer user management is kept in synchronization with the identity provider information regarding which groups the users are belonging to. This way, the operative user management will be done outside QPR ProcessAnalyzer. Still, the groups to be used need to be created beforehand and permissions assigned to the groups.&lt;br /&gt;
&lt;br /&gt;
Additional notes for the SAML authentication:&lt;br /&gt;
* QPR ProcessAnalyzer needs to use https when SAML 2.0 authentication is used.&lt;br /&gt;
* The identity provider needs to publish the identity provider metadata, because QPR ProcessAnalyzer reads the identity provider settings from there.&lt;br /&gt;
* QPR ProcessAnalyzer only supports SAML POST binding (e.g., SAML redirect binding is not supported).&lt;br /&gt;
*&#039;&#039;SAML AuthnRequests&#039;&#039; are signed using an out-of-the-box certificate embedded to QPR ProcessAnalyzer. This certificate has validity until 1.1.2035. It&#039;s also possible to use a custom certificate (see the SAMLSigningCertificate setting). The certificate public key is available in the service provider metadata published by the [[Web API: saml2|QPR ProcessAnalyzer Web API]].&lt;br /&gt;
*&#039;&#039;SAML Assertions&#039;&#039; must be signed (by the identity provider) to be accepted by QPR ProcessAnalyzer.&lt;br /&gt;
*SAML Assertions can optionally be encrypted by the identity provider. This requires the SAMLEncryptionCertificate setting to be defined in the [[PA_Configuration_database_table#SAML_2.0_Federated_Authentication_Settings|QPR ProcessAnalyzer configuration table]].&lt;br /&gt;
* Logout request to identity provider is not supported by QPR ProcessAnalyzer.&lt;br /&gt;
* If user clicks the logout button, user is redirected to the QPR ProcessAnalyzer login page. There user can click the &#039;&#039;&#039;Log in using SSO&#039;&#039;&#039; button to relogin.&lt;br /&gt;
* If the QPR ProcessAnalyzer session expires, user is redirected back to the identity provider for relogin.&lt;br /&gt;
&lt;br /&gt;
==Configuring SAML to QPR ProcessAnalyzer==&lt;br /&gt;
&lt;br /&gt;
To configure the SAML 2.0 authentication, follow these steps:&lt;br /&gt;
# Define settings &#039;&#039;&#039;SAMLMetadataUrl&#039;&#039;&#039;, &#039;&#039;&#039;ServiceProviderLocation&#039;&#039;&#039;, &#039;&#039;&#039;SAMLUserIdAttribute&#039;&#039;&#039;, &#039;&#039;&#039;SAMLGroupsAttribute&#039;&#039;&#039; (optional), &#039;&#039;&#039;SAMLEncryptionCertificate&#039;&#039;&#039; (optional), and &#039;&#039;&#039;SAMLSigningCertificate&#039;&#039;&#039; (optional) in the [[PA_Configuration_database_table#SAML_2.0_Federated_Authentication_Settings|QPR ProcessAnalyzer configuration table]]. QPR ProcessAnalyzer needs to be restarted for the settings to take effect.&lt;br /&gt;
# Configure a redirection from the root path of the QPR ProcessAnalyzer server to &#039;&#039;&#039;/qprpa/Saml2&#039;&#039;&#039;, so that users are automatically redirected to the identity provider for authentication.&lt;br /&gt;
# The identity provider configuration depends on which identity provide is used. See below for help how to configure [[#Using Azure AD as Identity Provider|Azure AD]] and [[#Using ADFS as Identity Provider|ADFS]] as the identity provider.&lt;br /&gt;
&lt;br /&gt;
If there are any issues with the authentication, please check the [[QPR_ProcessAnalyzer_Logs|QPR ProcessAnalyzer logs]].&lt;br /&gt;
&lt;br /&gt;
==Using Azure AD as Identity Provider==&lt;br /&gt;
Azure Active Directory (AAD) can be used as an identity provider to login to QPR ProcessAnalyzer. Following configurations are needed:&lt;br /&gt;
# Login to https://portal.azure.com as a cloud application admin or an application admin for your Azure AD tenant.&lt;br /&gt;
# Click &#039;&#039;&#039;Azure Active Directory&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;Enterprise Applications&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;New application&#039;&#039;&#039;. Select &#039;&#039;&#039;Non-gallery application&#039;&#039;&#039;.&lt;br /&gt;
# Define &#039;&#039;&#039;Name&#039;&#039;&#039; for the application, e.g., &amp;quot;QPR ProcessAnalyzer&amp;quot;.&lt;br /&gt;
# Go to &#039;&#039;&#039;Manage&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;Single sign-on&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;SAML&#039;&#039;&#039;.&lt;br /&gt;
# Click &#039;&#039;&#039;Edit&#039;&#039;&#039; pencil on the &#039;&#039;&#039;Basic SAML Authentication&#039;&#039;&#039; and define following settings (where &amp;lt;hostname&amp;gt; is the name of the QPR ProcessAnalyzer server):&lt;br /&gt;
##&#039;&#039;&#039;Identifier (Entity ID):&#039;&#039;&#039; https://&amp;lt;hostname&amp;gt;/qprpa/Saml2&lt;br /&gt;
## &#039;&#039;&#039;Reply URL (Assertion Consumer Service URL):&#039;&#039;&#039; https://&amp;lt;hostname&amp;gt;/qprpa/Saml2/Acs&lt;br /&gt;
## &#039;&#039;&#039;Sign on URL:&#039;&#039;&#039; It&#039;s recommended to leave this setting empty.&lt;br /&gt;
# Copy the &#039;&#039;&#039;App Federation Metadata Url&#039;&#039;&#039; to the clipboard to store it to  to the &#039;&#039;SAMLMetadataUrl&#039;&#039; setting in the QPR ProcessAnalyzer configuration table.&lt;br /&gt;
# If you want QPR Application to synchronize group membership between Azure AD and QPR ProcessAnalyzer, please add also the &#039;&#039;&#039;Group Claim&#039;&#039;&#039; from User &#039;&#039;&#039;Attributes &amp;amp; Claims&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
More information about Azure Active Directory: https://docs.microsoft.com/en-us/azure/active-directory/&lt;br /&gt;
&lt;br /&gt;
Instructions for setting up the optional SAML assertions encryption: https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/howto-saml-token-encryption?WT.mc_id=migration_service_aad_-inproduct-azureportal.&lt;br /&gt;
&lt;br /&gt;
==Using ADFS as Identity Provider==&lt;br /&gt;
ADFS (Active Directory Federation Services) can be used as an identity provider to login to QPR ProcessAnalyzer. For ADFS setup, follow the ADFS configuration guide in https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/create-a-relying-party-trust with the following notes:&lt;br /&gt;
* Step 4: Select option &#039;&#039;&#039;Enter data about the relying party manually&#039;&#039;&#039; as metadata is not available.&lt;br /&gt;
* Step 5: Name can be chosen freely.&lt;br /&gt;
* Step 7: Disable option &#039;&#039;&#039;Enable support for the WS-Federation Passive protocol&#039;&#039;&#039;. Select option &#039;&#039;&#039;Enable support for the SAML 2.0 WebSSO protocol&#039;&#039;&#039; and define url &#039;&#039;&#039;https://&amp;lt;hostname&amp;gt;/qprpa/Saml2/Acs&#039;&#039;&#039; where SERVERNAME is the QPR ProcessAnalyzer server hostname.&lt;br /&gt;
* Step 8: Define url &#039;&#039;&#039;https://&amp;lt;hostname&amp;gt;/qprpa/Saml2/Acs&#039;&#039;&#039; where &amp;lt;hostname&amp;gt; is the QPR ProcessAnalyzer server hostname.&lt;br /&gt;
* Step 11: Select option &#039;&#039;&#039;Configure claims issuance policy for this application&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
c:[Type == &amp;quot;http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&amp;quot;, Issuer == &amp;quot;AD AUTHORITY&amp;quot;]&lt;br /&gt;
=&amp;gt; issue(store = &amp;quot;Active Directory&amp;quot;, types = (&amp;quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn&amp;quot;, &amp;quot;http://schemas.xmlsoap.org/claims/CommonName&amp;quot;, &amp;quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress&amp;quot;, &amp;quot;http://schemas.xmlsoap.org/claims/Group&amp;quot;), query = &amp;quot;;userPrincipalName,displayName,mail,tokenGroups;{0}&amp;quot;, param = c.Value);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== SAML 2.0 Authentication API ==&lt;br /&gt;
QPR ProcessAnalyzer has [[Web_API:_saml2/acs|/Saml2/Acs]] endpoint which accepts a SAML assertion from the IdP and returns a HTTP redirection to QPR ProcessAnalyzer Web UI. The url contains a &#039;&#039;sys:samlHash&#039;&#039; parameter which is used by the Web UI to login the user using the [[Web_API:_Token|/token]] endpoint (to get a session token to use in the interactions with the Web API).&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&#039;&#039;&#039;Problem&#039;&#039;&#039;: When trying to authenticate with SAML, QPR ProcessAnalyzer responds: &#039;&#039;Bad Request - Request too long&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Solution&#039;&#039;&#039;: This error comes when the SAML assertion (message from IdP to QPR ProcessAnalyzer) contains too long data. The SAML assertion is encoded to an http header (as part of a cookie), which has a certain allowed maximum length. One option is to increase the limits in the Windows http.sys web server (https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/iis/iisadmin-service-inetinfo/httpsys-registry-windows). Alternatively, it may be possible reduce the amount of data included by the IdP to the SAML assertion.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
* General information about federated authentication: https://en.wikipedia.org/wiki/Federated_identity&lt;br /&gt;
* General information about SAML 2.0: https://en.wikipedia.org/wiki/SAML_2.0&lt;br /&gt;
* SAML 2.0 in Azure AD: https://docs.microsoft.com/en-us/azure/active-directory/develop/single-sign-on-saml-protocol&lt;br /&gt;
* General information about ADFS: https://en.wikipedia.org/wiki/Active_Directory_Federation_Services&lt;br /&gt;
* ADFS documentation: https://msdn.microsoft.com/en-us/library/bb897402.aspx&lt;br /&gt;
&lt;br /&gt;
[[Category: QPR ProcessAnalyzer]]&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Create_Simulated_Eventlog&amp;diff=26796</id>
		<title>Create Simulated Eventlog</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Create_Simulated_Eventlog&amp;diff=26796"/>
		<updated>2025-08-22T14:22:18Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Transformation: event_resource_limits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article has instructions how to install, configure and use eventlog simulations. The simulation creates a new model that contains both the source model data and the new simulated data. Case attribute &#039;&#039;&#039;Simulated&#039;&#039;&#039; can be used to determine whether the case is in the source data (&#039;&#039;false&#039;&#039;) or whether the simulation generated it as a new simulated case (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for simulation ==&lt;br /&gt;
As prerequisite, prediction must be installed into the used Snowflake as described in [[Create Predicted Eventlog|Install prediction in Snowflake]].&lt;br /&gt;
&lt;br /&gt;
== Create simulation script in QPR ProcessAnalyzer ==&lt;br /&gt;
You can create your own simulation script from scratch, or use one of the scripts provided below as a starting point.&lt;br /&gt;
&lt;br /&gt;
=== Creating a simulation model that deletes specific events ===&lt;br /&gt;
1. Create the following example expression script (e.g., with name &amp;quot;&#039;&#039;&#039;Create simulation model&#039;&#039;&#039; &#039;&#039;&#039;- delete events&#039;&#039;&#039;&amp;quot;):&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let flowFromEventType = &amp;quot;&amp;lt;from event type of a flow to modify&amp;gt;&amp;quot;, flowToEventType = &amp;quot;&amp;lt;to event type of a flow to modify&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
_system.ML.ApplyTransformations(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My simulation model - delete&amp;quot;,   // Name of the PA model to generate to the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,               // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,           // Target project to create the model into.&lt;br /&gt;
  &amp;quot;Transformations&amp;quot;: [#{                    // Transformation configurations.&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;modify_flow_durations&amp;quot;,&lt;br /&gt;
    &amp;quot;column&amp;quot;: sourceModel.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;],&lt;br /&gt;
    &amp;quot;flows&amp;quot;: [#{&lt;br /&gt;
      &amp;quot;from&amp;quot;: flowFromEventType,&lt;br /&gt;
      &amp;quot;to&amp;quot;: flowToEventType,&lt;br /&gt;
      &amp;quot;probability&amp;quot;: 1.0,&lt;br /&gt;
      &amp;quot;operation&amp;quot;: #{&lt;br /&gt;
        &amp;quot;type&amp;quot;: &amp;quot;set_value&amp;quot;,&lt;br /&gt;
        &amp;quot;value&amp;quot;: 0.0&lt;br /&gt;
      },&lt;br /&gt;
      &amp;quot;delete&amp;quot;: true&lt;br /&gt;
    }]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;2. Configure simulation for the previously created script as instructed in the next chapter. At minimum, replace the tags listed below with some suitable values:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;project name&amp;gt;&#039;&#039;&#039;: Name of the project in which the source model is located.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;model name&amp;gt;&#039;&#039;&#039;: Name of the model to be used as source model. This data in this source model will be used as source data to be modified by the simulation transformations.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;from event type of a flow to modify&amp;gt;&#039;&#039;&#039;: From-event type name of flows from which the from-event is to be deleted.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;to event type of a flow to modify&amp;gt;&#039;&#039;&#039;: To-event type name of flows from which the from-event is to be deleted.&lt;br /&gt;
&lt;br /&gt;
=== Create simulation model automating all the resources belonging to the same role as specified resource ===&lt;br /&gt;
1. Create the following example expression script (e.g., with name &amp;quot;&#039;&#039;&#039;Create simulation model&#039;&#039;&#039; &#039;&#039;&#039;- automate&#039;&#039;&#039;&amp;quot;):&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let resourceColumnName = &amp;quot;&amp;lt;event data column having resource names&amp;gt;&amp;quot;;&lt;br /&gt;
let resourceNameToAutomate = &amp;quot;&amp;lt;resource value whose role should be automated&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
_system.ML.ApplyTransformations(#{&lt;br /&gt;
  &amp;quot;PredictionProcedureName&amp;quot;: &amp;quot;qprpa_sp_prediction&amp;quot;,&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My simulation model - automate&amp;quot;,   // Name of the PA model to generate to the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,                 // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,             // Target project to create the model into.&lt;br /&gt;
  &amp;quot;Transformations&amp;quot;: [#{                      // Transformation configurations&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;resources_to_roles&amp;quot;,&lt;br /&gt;
    &amp;quot;resource_column&amp;quot;: resourceColumnName,&lt;br /&gt;
    &amp;quot;role_column&amp;quot;: &amp;quot;Role&amp;quot;,&lt;br /&gt;
    &amp;quot;role_name_template&amp;quot;: &amp;quot;Role %d&amp;quot;&lt;br /&gt;
  }, #{&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;modify_flow_durations&amp;quot;,&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;role_name&amp;quot;: #{&lt;br /&gt;
        &amp;quot;input&amp;quot;: &amp;quot;resource_to_role_map&amp;quot;,&lt;br /&gt;
        &amp;quot;value_path&amp;quot;: [resourceNameToAutomate]&lt;br /&gt;
      }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;column&amp;quot;: &amp;quot;Role&amp;quot;,&lt;br /&gt;
    &amp;quot;flows&amp;quot;: [#{&lt;br /&gt;
      &amp;quot;from_input&amp;quot;: &amp;quot;role_name&amp;quot;,&lt;br /&gt;
      &amp;quot;operation&amp;quot;: #{&lt;br /&gt;
        &amp;quot;type&amp;quot;: &amp;quot;set_value&amp;quot;,&lt;br /&gt;
        &amp;quot;value&amp;quot;: 1,&lt;br /&gt;
        &amp;quot;probability&amp;quot;: 0.5&lt;br /&gt;
      }&lt;br /&gt;
    }]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;2. Configure simulation for the previously created script as instructed in the next chapter. At minimum, replace the tags listed below with some suitable values:&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;project name&amp;gt;&#039;&#039;&#039;: Name of the project in which the source model is located.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;model name&amp;gt;&#039;&#039;&#039;: Name of the model to be used as source model. This data in this source model will be used as source data to be modified by the simulation transformations.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;event data column having resource names&amp;gt;&#039;&#039;&#039;: Name of the event data column that contains resource names.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;resource value whose role should be automated&amp;gt;&#039;&#039;&#039;: Name of the resource whose role is to be automated.&lt;br /&gt;
&lt;br /&gt;
== Configure simulation ==&lt;br /&gt;
Simulation script has the following settings in the ApplyTransformations call:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039;: Name of the QPR ProcessAnalyzer model that is created to the target project. The model will contain the source model content and the predictions.&lt;br /&gt;
* &#039;&#039;&#039;SourceModel&#039;&#039;&#039;: Source model for which the simulation is made. Model can be selected for example based on id with ModelById function or by name with ModelByName function.&lt;br /&gt;
* &#039;&#039;&#039;TargetProject&#039;&#039;&#039;: Target project to create the new model into.&lt;br /&gt;
* &#039;&#039;&#039;Transformations&#039;&#039;&#039;: Array of transformation configuration objects. Each object supports the following parameters:&lt;br /&gt;
** &#039;&#039;&#039;type&#039;&#039;&#039;: Defines the type of the transformation to perform. See below for more details on the supported transformations. Supported values are:&lt;br /&gt;
*** &#039;&#039;&#039;enforce_resource_limits&#039;&#039;&#039;: Used to modify given event log using given maximum resource limits.&lt;br /&gt;
*** &#039;&#039;&#039;extract_max_resource_usages&#039;&#039;&#039;: Used to extract, for every value of a specified column, the maximum number of concurrent cases in given event log that have that value. &lt;br /&gt;
*** &#039;&#039;&#039;generate&#039;&#039;&#039;: Used to generate a new event log using a trained ML model.&lt;br /&gt;
*** &#039;&#039;&#039;modify_flow_durations&#039;&#039;&#039;: Used to modify durations of flows and possibly remove events having specific flows.&lt;br /&gt;
*** &#039;&#039;&#039;modify_values&#039;&#039;&#039;: Used to modify values of a dictionary given as input (e.g., dictionary generated by extract_max_resource_usages).&lt;br /&gt;
*** &#039;&#039;&#039;resources_to_roles&#039;&#039;&#039;: Performs &amp;quot;organization mining&amp;quot; by trying to group together column values (e.g., resources) that are used in similar fashion in given event log (e.g., resources that are often present in similar set of activities).&lt;br /&gt;
**&#039;&#039;&#039;input&#039;&#039;&#039;: Can be used to specify that given transformation input parameters get their values from the previous transformation result.&lt;br /&gt;
***Value can be either direct mapping by just the name of the transformation result property, or it can be a value mapping configuration object that supports the following parameters:&lt;br /&gt;
****&#039;&#039;&#039;input&#039;&#039;&#039;: Name of the parameter to get from the previous transformation result as the root object of the actual value to extract.&lt;br /&gt;
****&#039;&#039;&#039;value_path&#039;&#039;&#039;: An array of property names to traverse into the root object.&lt;br /&gt;
&lt;br /&gt;
=== Transformation: enforce_resource_limits ===&lt;br /&gt;
Using given input data, this transformation generates a new event log which does not exceed the concurrency limitations of specified column values. &lt;br /&gt;
&lt;br /&gt;
Event rows are traversed in time order, and if at some point a limit would be exceeded, instead of outputting the actual event, a new copy of the actual event, with copied event properties, is created to represent the queue for the actual event. &lt;br /&gt;
&lt;br /&gt;
Only after an event leaves from the column value that contains a queue, the event that had been waiting for the longest in the queue will be generated (following the FIFO-principle).&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
* &#039;&#039;&#039;column&#039;&#039;&#039;: Name of the column having the values whose concurrent usage is to be limited by specified limits&lt;br /&gt;
* &#039;&#039;&#039;limits&#039;&#039;&#039;: Specifies an object containing key-value -pairs where keys are column values and values contain an integer specifying the maximum number of concurrent cases in the given event log that can contain given value.&lt;br /&gt;
* &#039;&#039;&#039;queue_event_activity_name&#039;&#039;&#039;: If set, specifies the name template used for queue-events. In this template, when a queue event is created, %s is replaced with the name of the activity this queue event is queuing to.&lt;br /&gt;
** If not set, the activity name is not altered at all for the queue event.&lt;br /&gt;
* &#039;&#039;&#039;queue_event_column&#039;&#039;&#039;: &lt;br /&gt;
** If queue_event_activity_name is set:&lt;br /&gt;
*** If the event represents a queue-event, the value in this column specifies the name of the queue-activity.&lt;br /&gt;
*** Otherwise, the value is null.&lt;br /&gt;
** If queue_event_activity_name is not set:&lt;br /&gt;
*** If the event represents a queue-event, the value in this column True.&lt;br /&gt;
*** Otherwise, the value is False.&lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Event log to operate on.&lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
Event log with resource limits enforced.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;enforce_resource_limits&amp;quot;,&lt;br /&gt;
  &amp;quot;queue_event_column&amp;quot;: &amp;quot;Queue&amp;quot;,&lt;br /&gt;
  &amp;quot;queue_event_activity_name&amp;quot;: &amp;quot;%s - Queue&amp;quot;,&lt;br /&gt;
  &amp;quot;limits&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Role 3&amp;quot;: None&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;input&amp;quot;: #{&lt;br /&gt;
    &amp;quot;limits&amp;quot;: &amp;quot;role_limits&amp;quot;,&lt;br /&gt;
    &amp;quot;column&amp;quot;: &amp;quot;role_column&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transformation: extract_max_resource_usages ===&lt;br /&gt;
Extract, for every value of a specified column, the maximum number of concurrent cases in given event log that have that value.&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;resource_column&#039;&#039;&#039;:&lt;br /&gt;
** The name of the column representing the resources whose maximum concurrent case usages are to be calculated.&lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Event log to operate on.&lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
&lt;br /&gt;
* max_resource_usages:&lt;br /&gt;
** A dictionary object containing resource names as keys (unique resource_column values) and their maximum usage in the event log.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;extract_max_resource_usages&amp;quot;,&lt;br /&gt;
  &amp;quot;resource_column&amp;quot;: &amp;quot;SAP_User&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transformation: generate ===&lt;br /&gt;
Generate a new event log using the configured model [[Create Predicted Eventlog|prediction generation parameters (GenerationConfiguration)]].&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
Supports all the same parameters as those supported by model prediction generation configuration.&lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Does not support inputs.&lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
Generated event log.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;generate&amp;quot;,&lt;br /&gt;
  &amp;quot;model_name&amp;quot;: &amp;quot;ML model&amp;quot;,&lt;br /&gt;
  &amp;quot;cases_to_generate&amp;quot;: 100,&lt;br /&gt;
  &amp;quot;max_num_events&amp;quot;: 20&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transformation: modify_flow_durations ===&lt;br /&gt;
Modify durations of flows and possibly remove events having specific flows.&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;column&#039;&#039;&#039;: The name of the column based on which the flows are created. Usually this is the column containing activities, but could also be, e.g., organization units, users, …&lt;br /&gt;
* &#039;&#039;&#039;flows&#039;&#039;&#039;: Flows to transform. Contains an array of flow transformation configuration objects. Each object defines transformations performed on one flow type defined by starting and ending column values. Supports the following properties:&lt;br /&gt;
** &#039;&#039;&#039;delete&#039;&#039;&#039;: Same as delete_from.&lt;br /&gt;
** &#039;&#039;&#039;delete_from&#039;&#039;&#039;: If defined, specifies whether the &amp;quot;from event&amp;quot; of the matched flow should be removed after applying the operation.&lt;br /&gt;
** &#039;&#039;&#039;delete_to&#039;&#039;&#039;: If defined, specifies whether the &amp;quot;to event&amp;quot; of the matched flow should be removed after applying the operation.&lt;br /&gt;
** &#039;&#039;&#039;from&#039;&#039;&#039;: Column value starting the flow.&lt;br /&gt;
*** If this and from_input are both undefined, any starting value is accepted.&lt;br /&gt;
** &#039;&#039;&#039;from_input&#039;&#039;&#039;: If defined, specifies the name of the transformation-level parameter from which the actual column value starting the flow is read from.&lt;br /&gt;
*** Overrides the value defined in from-parameter.&lt;br /&gt;
** &#039;&#039;&#039;operation&#039;&#039;&#039;: Specifies the actual flow duration modification operation to perform as value modification configuration object where the value is the duration in seconds. Supports the following properties:&lt;br /&gt;
*** &#039;&#039;&#039;probability&#039;&#039;&#039;: If defined, specifies the percentage probability of applying the operation to any matching instance of the flow. &lt;br /&gt;
**** Value should be a numeric value between 0 and 1.0. &lt;br /&gt;
**** This probability applies only to this operation.&lt;br /&gt;
**** The default value is 1.0.&lt;br /&gt;
*** &#039;&#039;&#039;type&#039;&#039;&#039;: Type of the operation. The following types are supported:&lt;br /&gt;
**** &#039;&#039;&#039;add&#039;&#039;&#039;: Sets the value to be the current value plus the number specified by the value.&lt;br /&gt;
**** &#039;&#039;&#039;multiply&#039;&#039;&#039;: Sets the value to be the current value multiplied by the number specified by the value.&lt;br /&gt;
**** &#039;&#039;&#039;set_value&#039;&#039;&#039;: Sets the value to be exactly the number specified by the value.&lt;br /&gt;
*** &#039;&#039;&#039;value&#039;&#039;&#039;: Value used by the operation.&lt;br /&gt;
** &#039;&#039;&#039;probability&#039;&#039;&#039;: If defined, specifies the percentage probability of applying the operation to any matching instance of the flow.&lt;br /&gt;
*** Value should be a numeric value between 0 and 1.0.&lt;br /&gt;
*** This probability applies, in addition to the operation specified by the operation-parameter, also to any possible other transformations, such as event deletion.&lt;br /&gt;
*** Default value is 1.0&lt;br /&gt;
** &#039;&#039;&#039;to&#039;&#039;&#039;: Column value ending the flow.&lt;br /&gt;
*** If this and to_input are both undefined, any ending value is accepted.&lt;br /&gt;
** &#039;&#039;&#039;to_input&#039;&#039;&#039;: If defined, specifies the name of the transformation-level parameter to which the actual column value ending the flow is read from.&lt;br /&gt;
*** Overrides the value defined in to-parameter.&lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Event log to operate on.&lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
Transformed event log.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;modify_flow_durations&amp;quot;,&lt;br /&gt;
  &amp;quot;column&amp;quot;: &amp;quot;Organization&amp;quot;,&lt;br /&gt;
  &amp;quot;flows&amp;quot;: [#{&lt;br /&gt;
    &amp;quot;from&amp;quot;: &amp;quot;Delivery&amp;quot;,&lt;br /&gt;
    &amp;quot;operation&amp;quot;: #{&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;set_value&amp;quot;,&lt;br /&gt;
      &amp;quot;value&amp;quot;: 0.0&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;delete&amp;quot;: true&lt;br /&gt;
  }]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transformation: modify_values ===&lt;br /&gt;
Modify values of an object given as input (e.g., object generated by extract_max_resource_usages).&lt;br /&gt;
&lt;br /&gt;
Due to the required inputs, this transformation can&#039;t be the first transformation to perform.&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;values&#039;&#039;&#039;: Array of value configuration objects. Each object supports the following properties:&lt;br /&gt;
** &#039;&#039;&#039;input&#039;&#039;&#039;: Name of the result to modify, where the result is the output of the previous transformation.&lt;br /&gt;
** &#039;&#039;&#039;input_key_from&#039;&#039;&#039;: If defined, specifies the the name of the property of a input object whose value contains the name of the property whose value is to be modified.&lt;br /&gt;
** &#039;&#039;&#039;input_key_value_path&#039;&#039;&#039;: If input_key_from is defined and is represented as an object, this configuration should specify an array of property names to traverse into the object. &lt;br /&gt;
*** The value at the end of this path will be used as the name of the property to modify in the input.&lt;br /&gt;
** &#039;&#039;&#039;operation&#039;&#039;&#039;: Specifies the actual value modification operation to perform as value modification configuration object. Supports the following properties:&lt;br /&gt;
*** &#039;&#039;&#039;probability&#039;&#039;&#039;: If defined, specifies the percentage probability of applying the operation to any matching instance of the flow. &lt;br /&gt;
**** Value should be a numeric value between 0 and 1.0. &lt;br /&gt;
**** This probability applies only to this operation.&lt;br /&gt;
**** The default value is 1.0.&lt;br /&gt;
*** &#039;&#039;&#039;type&#039;&#039;&#039;: Type of the operation. The following types are supported:&lt;br /&gt;
**** &#039;&#039;&#039;add&#039;&#039;&#039;: Sets the value to be the current value plus the number specified by the value.&lt;br /&gt;
**** &#039;&#039;&#039;multiply&#039;&#039;&#039;: Sets the value to be the current value multiplied by the number specified by the value.&lt;br /&gt;
**** &#039;&#039;&#039;set_value&#039;&#039;&#039;: Sets the value to be exactly the number specified by the value.&lt;br /&gt;
*** &#039;&#039;&#039;value&#039;&#039;&#039;: Value used by the operation.&lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Output of the previous transformation operation. &lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
The same output as the previous performed transformation, except with the specified value modifications applied.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;modify_values&amp;quot;,&lt;br /&gt;
  &amp;quot;values&amp;quot;: [#{&lt;br /&gt;
    &amp;quot;input&amp;quot;: &amp;quot;role_limits&amp;quot;,&lt;br /&gt;
    &amp;quot;input_key_from&amp;quot;: &amp;quot;resource_to_role_map&amp;quot;,&lt;br /&gt;
    &amp;quot;input_key_value_path&amp;quot;: [&amp;quot;Tina&amp;quot;],&lt;br /&gt;
    &amp;quot;operation&amp;quot;: #{&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;multiply&amp;quot;,&lt;br /&gt;
      &amp;quot;value&amp;quot;: 0.5&lt;br /&gt;
    }&lt;br /&gt;
  }]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transformation: resources_to_roles ===&lt;br /&gt;
Performs &amp;quot;organization mining&amp;quot; by grouping together column values (e.g., resources) that are used in similar fashion in given event log. E.g., resources that are often present in similar set of activities.&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;resource_column&#039;&#039;&#039;: The name of the column containing names of resources.&lt;br /&gt;
* &#039;&#039;&#039;resource_limits&#039;&#039;&#039;: Contains an object dictionary containing resource names with their maximum concurrent usages.&lt;br /&gt;
** If set, when building role_limits output, these values will be summed for each resource into the resulting role-based usage limit.&lt;br /&gt;
** If not set, each resource in a role will be counted as one, when calculating the role_limits.&lt;br /&gt;
* &#039;&#039;&#039;role_column&#039;&#039;&#039;: The name of the column to be created and whose values will indicate the role in which the resource belongs to.&lt;br /&gt;
* &#039;&#039;&#039;role_name_template&#039;&#039;&#039;: If set, specifies the name template used for role names. In this template, %d will be replaced by a numeric value starting from 1. &lt;br /&gt;
** The default value is &amp;quot;Role %d&amp;quot;.&lt;br /&gt;
* &#039;&#039;&#039;similarity_threshold&#039;&#039;&#039;: The minimum value of Pearson correlation coefficient calculated between two resources in order for them to be considered as having the same role.&lt;br /&gt;
** The default value is 0.7 &lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Event log to operate on.&lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
&lt;br /&gt;
* Transformed event log.&lt;br /&gt;
* Result dictionary object containing the following properties:&lt;br /&gt;
** &#039;&#039;&#039;resource_column&#039;&#039;&#039;: The name of the column containing names of resources.&lt;br /&gt;
** &#039;&#039;&#039;resource_to_role_map&#039;&#039;&#039;: An object containing resource names as property names and role names as value.&lt;br /&gt;
** &#039;&#039;&#039;role_column&#039;&#039;&#039;: The name of the generated column whose values will indicate the role in which the resource belongs to.&lt;br /&gt;
** &#039;&#039;&#039;role_limits&#039;&#039;&#039;: An object containing role names as property names and maximum usage for that role as value.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;resources_to_roles&amp;quot;,&lt;br /&gt;
  &amp;quot;resource_column&amp;quot;: &amp;quot;SAP_User&amp;quot;,&lt;br /&gt;
  &amp;quot;role_column&amp;quot;: &amp;quot;Role&amp;quot;,&lt;br /&gt;
  &amp;quot;role_name_template&amp;quot;: &amp;quot;Role %d&amp;quot;,&lt;br /&gt;
  &amp;quot;input&amp;quot;: #{&lt;br /&gt;
    &amp;quot;resource_limits&amp;quot;: &amp;quot;max_resource_usages&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Create_Simulated_Eventlog&amp;diff=26795</id>
		<title>Create Simulated Eventlog</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Create_Simulated_Eventlog&amp;diff=26795"/>
		<updated>2025-08-22T13:50:46Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Transformation: modify_flow_durations */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article has instructions how to install, configure and use eventlog simulations. The simulation creates a new model that contains both the source model data and the new simulated data. Case attribute &#039;&#039;&#039;Simulated&#039;&#039;&#039; can be used to determine whether the case is in the source data (&#039;&#039;false&#039;&#039;) or whether the simulation generated it as a new simulated case (&#039;&#039;true&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for simulation ==&lt;br /&gt;
As prerequisite, prediction must be installed into the used Snowflake as described in [[Create Predicted Eventlog|Install prediction in Snowflake]].&lt;br /&gt;
&lt;br /&gt;
== Create simulation script in QPR ProcessAnalyzer ==&lt;br /&gt;
You can create your own simulation script from scratch, or use one of the scripts provided below as a starting point.&lt;br /&gt;
&lt;br /&gt;
=== Creating a simulation model that deletes specific events ===&lt;br /&gt;
1. Create the following example expression script (e.g., with name &amp;quot;&#039;&#039;&#039;Create simulation model&#039;&#039;&#039; &#039;&#039;&#039;- delete events&#039;&#039;&#039;&amp;quot;):&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let flowFromEventType = &amp;quot;&amp;lt;from event type of a flow to modify&amp;gt;&amp;quot;, flowToEventType = &amp;quot;&amp;lt;to event type of a flow to modify&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
_system.ML.ApplyTransformations(#{&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My simulation model - delete&amp;quot;,   // Name of the PA model to generate to the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,               // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,           // Target project to create the model into.&lt;br /&gt;
  &amp;quot;Transformations&amp;quot;: [#{                    // Transformation configurations.&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;modify_flow_durations&amp;quot;,&lt;br /&gt;
    &amp;quot;column&amp;quot;: sourceModel.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;],&lt;br /&gt;
    &amp;quot;flows&amp;quot;: [#{&lt;br /&gt;
      &amp;quot;from&amp;quot;: flowFromEventType,&lt;br /&gt;
      &amp;quot;to&amp;quot;: flowToEventType,&lt;br /&gt;
      &amp;quot;probability&amp;quot;: 1.0,&lt;br /&gt;
      &amp;quot;operation&amp;quot;: #{&lt;br /&gt;
        &amp;quot;type&amp;quot;: &amp;quot;set_value&amp;quot;,&lt;br /&gt;
        &amp;quot;value&amp;quot;: 0.0&lt;br /&gt;
      },&lt;br /&gt;
      &amp;quot;delete&amp;quot;: true&lt;br /&gt;
    }]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;2. Configure simulation for the previously created script as instructed in the next chapter. At minimum, replace the tags listed below with some suitable values:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;project name&amp;gt;&#039;&#039;&#039;: Name of the project in which the source model is located.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;model name&amp;gt;&#039;&#039;&#039;: Name of the model to be used as source model. This data in this source model will be used as source data to be modified by the simulation transformations.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;from event type of a flow to modify&amp;gt;&#039;&#039;&#039;: From-event type name of flows from which the from-event is to be deleted.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;to event type of a flow to modify&amp;gt;&#039;&#039;&#039;: To-event type name of flows from which the from-event is to be deleted.&lt;br /&gt;
&lt;br /&gt;
=== Create simulation model automating all the resources belonging to the same role as specified resource ===&lt;br /&gt;
1. Create the following example expression script (e.g., with name &amp;quot;&#039;&#039;&#039;Create simulation model&#039;&#039;&#039; &#039;&#039;&#039;- automate&#039;&#039;&#039;&amp;quot;):&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sourceModel = ProjectByName(&amp;quot;&amp;lt;project name&amp;gt;&amp;quot;).ModelByName(&amp;quot;&amp;lt;model name&amp;gt;&amp;quot;);&lt;br /&gt;
let resourceColumnName = &amp;quot;&amp;lt;event data column having resource names&amp;gt;&amp;quot;;&lt;br /&gt;
let resourceNameToAutomate = &amp;quot;&amp;lt;resource value whose role should be automated&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let targetProject = Project;&lt;br /&gt;
_system.ML.ApplyTransformations(#{&lt;br /&gt;
  &amp;quot;PredictionProcedureName&amp;quot;: &amp;quot;qprpa_sp_prediction&amp;quot;,&lt;br /&gt;
  &amp;quot;Name&amp;quot;: &amp;quot;My simulation model - automate&amp;quot;,   // Name of the PA model to generate to the target project.&lt;br /&gt;
  &amp;quot;SourceModel&amp;quot;: sourceModel,                 // Snowflake-based PA model used for training the prediction model.&lt;br /&gt;
  &amp;quot;TargetProject&amp;quot;: targetProject,             // Target project to create the model into.&lt;br /&gt;
  &amp;quot;Transformations&amp;quot;: [#{                      // Transformation configurations&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;resources_to_roles&amp;quot;,&lt;br /&gt;
    &amp;quot;resource_column&amp;quot;: resourceColumnName,&lt;br /&gt;
    &amp;quot;role_column&amp;quot;: &amp;quot;Role&amp;quot;,&lt;br /&gt;
    &amp;quot;role_name_template&amp;quot;: &amp;quot;Role %d&amp;quot;&lt;br /&gt;
  }, #{&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;modify_flow_durations&amp;quot;,&lt;br /&gt;
    &amp;quot;input&amp;quot;: #{&lt;br /&gt;
      &amp;quot;role_name&amp;quot;: #{&lt;br /&gt;
        &amp;quot;input&amp;quot;: &amp;quot;resource_to_role_map&amp;quot;,&lt;br /&gt;
        &amp;quot;value_path&amp;quot;: [resourceNameToAutomate]&lt;br /&gt;
      }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;column&amp;quot;: &amp;quot;Role&amp;quot;,&lt;br /&gt;
    &amp;quot;flows&amp;quot;: [#{&lt;br /&gt;
      &amp;quot;from_input&amp;quot;: &amp;quot;role_name&amp;quot;,&lt;br /&gt;
      &amp;quot;operation&amp;quot;: #{&lt;br /&gt;
        &amp;quot;type&amp;quot;: &amp;quot;set_value&amp;quot;,&lt;br /&gt;
        &amp;quot;value&amp;quot;: 1,&lt;br /&gt;
        &amp;quot;probability&amp;quot;: 0.5&lt;br /&gt;
      }&lt;br /&gt;
    }]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;2. Configure simulation for the previously created script as instructed in the next chapter. At minimum, replace the tags listed below with some suitable values:&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;project name&amp;gt;&#039;&#039;&#039;: Name of the project in which the source model is located.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;model name&amp;gt;&#039;&#039;&#039;: Name of the model to be used as source model. This data in this source model will be used as source data to be modified by the simulation transformations.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;event data column having resource names&amp;gt;&#039;&#039;&#039;: Name of the event data column that contains resource names.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;resource value whose role should be automated&amp;gt;&#039;&#039;&#039;: Name of the resource whose role is to be automated.&lt;br /&gt;
&lt;br /&gt;
== Configure simulation ==&lt;br /&gt;
Simulation script has the following settings in the ApplyTransformations call:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039;: Name of the QPR ProcessAnalyzer model that is created to the target project. The model will contain the source model content and the predictions.&lt;br /&gt;
* &#039;&#039;&#039;SourceModel&#039;&#039;&#039;: Source model for which the simulation is made. Model can be selected for example based on id with ModelById function or by name with ModelByName function.&lt;br /&gt;
* &#039;&#039;&#039;TargetProject&#039;&#039;&#039;: Target project to create the new model into.&lt;br /&gt;
* &#039;&#039;&#039;Transformations&#039;&#039;&#039;: Array of transformation configuration objects. Each object supports the following parameters:&lt;br /&gt;
** &#039;&#039;&#039;type&#039;&#039;&#039;: Defines the type of the transformation to perform. See below for more details on the supported transformations. Supported values are:&lt;br /&gt;
*** &#039;&#039;&#039;enforce_resource_limits&#039;&#039;&#039;: Used to modify given event log using given maximum resource limits.&lt;br /&gt;
*** &#039;&#039;&#039;extract_max_resource_usages&#039;&#039;&#039;: Used to extract, for every value of a specified column, the maximum number of concurrent cases in given event log that have that value. &lt;br /&gt;
*** &#039;&#039;&#039;generate&#039;&#039;&#039;: Used to generate a new event log using a trained ML model.&lt;br /&gt;
*** &#039;&#039;&#039;modify_flow_durations&#039;&#039;&#039;: Used to modify durations of flows and possibly remove events having specific flows.&lt;br /&gt;
*** &#039;&#039;&#039;modify_values&#039;&#039;&#039;: Used to modify values of a dictionary given as input (e.g., dictionary generated by extract_max_resource_usages).&lt;br /&gt;
*** &#039;&#039;&#039;resources_to_roles&#039;&#039;&#039;: Performs &amp;quot;organization mining&amp;quot; by trying to group together column values (e.g., resources) that are used in similar fashion in given event log (e.g., resources that are often present in similar set of activities).&lt;br /&gt;
**&#039;&#039;&#039;input&#039;&#039;&#039;: Can be used to specify that given transformation input parameters get their values from the previous transformation result.&lt;br /&gt;
***Value can be either direct mapping by just the name of the transformation result property, or it can be a value mapping configuration object that supports the following parameters:&lt;br /&gt;
****&#039;&#039;&#039;input&#039;&#039;&#039;: Name of the parameter to get from the previous transformation result as the root object of the actual value to extract.&lt;br /&gt;
****&#039;&#039;&#039;value_path&#039;&#039;&#039;: An array of property names to traverse into the root object.&lt;br /&gt;
&lt;br /&gt;
=== Transformation: event_resource_limits ===&lt;br /&gt;
Using given input data, this transformation generates a new event log which does not exceed the concurrency limitations of specified column values. &lt;br /&gt;
&lt;br /&gt;
Event rows are traversed in time order, and if at some point a limit would be exceeded, instead of outputting the actual event, a new copy of the actual event, with copied event properties, is created to represent the queue for the actual event. &lt;br /&gt;
&lt;br /&gt;
Only after an event leaves from the column value that contains a queue, the event that had been waiting for the longest in the queue will be generated (following the FIFO-principle).&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
* &#039;&#039;&#039;column&#039;&#039;&#039;: Name of the column having the values whose concurrent usage is to be limited by specified limits&lt;br /&gt;
* &#039;&#039;&#039;limits&#039;&#039;&#039;: Specifies an object containing key-value -pairs where keys are column values and values contain an integer specifying the maximum number of concurrent cases in the given event log that can contain given value.&lt;br /&gt;
* &#039;&#039;&#039;queue_event_activity_name&#039;&#039;&#039;: If set, specifies the name template used for queue-events. In this template, when a queue event is created, %s is replaced with the name of the activity this queue event is queuing to.&lt;br /&gt;
** If not set, the activity name is not altered at all for the queue event.&lt;br /&gt;
* &#039;&#039;&#039;queue_event_column&#039;&#039;&#039;: &lt;br /&gt;
** If queue_event_activity_name is set:&lt;br /&gt;
*** If the event represents a queue-event, the value in this column specifies the name of the queue-activity.&lt;br /&gt;
*** Otherwise, the value is null.&lt;br /&gt;
** If queue_event_activity_name is not set:&lt;br /&gt;
*** If the event represents a queue-event, the value in this column True.&lt;br /&gt;
*** Otherwise, the value is False.&lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Event log to operate on.&lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
Event log with resource limits enforced.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;enforce_resource_limits&amp;quot;,&lt;br /&gt;
  &amp;quot;queue_event_column&amp;quot;: &amp;quot;Queue&amp;quot;,&lt;br /&gt;
  &amp;quot;queue_event_activity_name&amp;quot;: &amp;quot;%s - Queue&amp;quot;,&lt;br /&gt;
  &amp;quot;limits&amp;quot;: #{&lt;br /&gt;
    &amp;quot;Role 3&amp;quot;: None&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;input&amp;quot;: #{&lt;br /&gt;
    &amp;quot;limits&amp;quot;: &amp;quot;role_limits&amp;quot;,&lt;br /&gt;
    &amp;quot;column&amp;quot;: &amp;quot;role_column&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transformation: extract_max_resource_usages ===&lt;br /&gt;
Extract, for every value of a specified column, the maximum number of concurrent cases in given event log that have that value.&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;resource_column&#039;&#039;&#039;:&lt;br /&gt;
** The name of the column representing the resources whose maximum concurrent case usages are to be calculated.&lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Event log to operate on.&lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
&lt;br /&gt;
* max_resource_usages:&lt;br /&gt;
** A dictionary object containing resource names as keys (unique resource_column values) and their maximum usage in the event log.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;extract_max_resource_usages&amp;quot;,&lt;br /&gt;
  &amp;quot;resource_column&amp;quot;: &amp;quot;SAP_User&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transformation: generate ===&lt;br /&gt;
Generate a new event log using the configured model [[Create Predicted Eventlog|prediction generation parameters (GenerationConfiguration)]].&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
Supports all the same parameters as those supported by model prediction generation configuration.&lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Does not support inputs.&lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
Generated event log.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;generate&amp;quot;,&lt;br /&gt;
  &amp;quot;model_name&amp;quot;: &amp;quot;ML model&amp;quot;,&lt;br /&gt;
  &amp;quot;cases_to_generate&amp;quot;: 100,&lt;br /&gt;
  &amp;quot;max_num_events&amp;quot;: 20&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transformation: modify_flow_durations ===&lt;br /&gt;
Modify durations of flows and possibly remove events having specific flows.&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;column&#039;&#039;&#039;: The name of the column based on which the flows are created. Usually this is the column containing activities, but could also be, e.g., organization units, users, …&lt;br /&gt;
* &#039;&#039;&#039;flows&#039;&#039;&#039;: Flows to transform. Contains an array of flow transformation configuration objects. Each object defines transformations performed on one flow type defined by starting and ending column values. Supports the following properties:&lt;br /&gt;
** &#039;&#039;&#039;delete&#039;&#039;&#039;: Same as delete_from.&lt;br /&gt;
** &#039;&#039;&#039;delete_from&#039;&#039;&#039;: If defined, specifies whether the &amp;quot;from event&amp;quot; of the matched flow should be removed after applying the operation.&lt;br /&gt;
** &#039;&#039;&#039;delete_to&#039;&#039;&#039;: If defined, specifies whether the &amp;quot;to event&amp;quot; of the matched flow should be removed after applying the operation.&lt;br /&gt;
** &#039;&#039;&#039;from&#039;&#039;&#039;: Column value starting the flow.&lt;br /&gt;
*** If this and from_input are both undefined, any starting value is accepted.&lt;br /&gt;
** &#039;&#039;&#039;from_input&#039;&#039;&#039;: If defined, specifies the name of the transformation-level parameter from which the actual column value starting the flow is read from.&lt;br /&gt;
*** Overrides the value defined in from-parameter.&lt;br /&gt;
** &#039;&#039;&#039;operation&#039;&#039;&#039;: Specifies the actual flow duration modification operation to perform as value modification configuration object where the value is the duration in seconds. Supports the following properties:&lt;br /&gt;
*** &#039;&#039;&#039;probability&#039;&#039;&#039;: If defined, specifies the percentage probability of applying the operation to any matching instance of the flow. &lt;br /&gt;
**** Value should be a numeric value between 0 and 1.0. &lt;br /&gt;
**** This probability applies only to this operation.&lt;br /&gt;
**** The default value is 1.0.&lt;br /&gt;
*** &#039;&#039;&#039;type&#039;&#039;&#039;: Type of the operation. The following types are supported:&lt;br /&gt;
**** &#039;&#039;&#039;add&#039;&#039;&#039;: Sets the value to be the current value plus the number specified by the value.&lt;br /&gt;
**** &#039;&#039;&#039;multiply&#039;&#039;&#039;: Sets the value to be the current value multiplied by the number specified by the value.&lt;br /&gt;
**** &#039;&#039;&#039;set_value&#039;&#039;&#039;: Sets the value to be exactly the number specified by the value.&lt;br /&gt;
*** &#039;&#039;&#039;value&#039;&#039;&#039;: Value used by the operation.&lt;br /&gt;
** &#039;&#039;&#039;probability&#039;&#039;&#039;: If defined, specifies the percentage probability of applying the operation to any matching instance of the flow.&lt;br /&gt;
*** Value should be a numeric value between 0 and 1.0.&lt;br /&gt;
*** This probability applies, in addition to the operation specified by the operation-parameter, also to any possible other transformations, such as event deletion.&lt;br /&gt;
*** Default value is 1.0&lt;br /&gt;
** &#039;&#039;&#039;to&#039;&#039;&#039;: Column value ending the flow.&lt;br /&gt;
*** If this and to_input are both undefined, any ending value is accepted.&lt;br /&gt;
** &#039;&#039;&#039;to_input&#039;&#039;&#039;: If defined, specifies the name of the transformation-level parameter to which the actual column value ending the flow is read from.&lt;br /&gt;
*** Overrides the value defined in to-parameter.&lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Event log to operate on.&lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
Transformed event log.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;modify_flow_durations&amp;quot;,&lt;br /&gt;
  &amp;quot;column&amp;quot;: &amp;quot;Organization&amp;quot;,&lt;br /&gt;
  &amp;quot;flows&amp;quot;: [#{&lt;br /&gt;
    &amp;quot;from&amp;quot;: &amp;quot;Delivery&amp;quot;,&lt;br /&gt;
    &amp;quot;operation&amp;quot;: #{&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;set_value&amp;quot;,&lt;br /&gt;
      &amp;quot;value&amp;quot;: 0.0&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;delete&amp;quot;: true&lt;br /&gt;
  }]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transformation: modify_values ===&lt;br /&gt;
Modify values of an object given as input (e.g., object generated by extract_max_resource_usages).&lt;br /&gt;
&lt;br /&gt;
Due to the required inputs, this transformation can&#039;t be the first transformation to perform.&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;values&#039;&#039;&#039;: Array of value configuration objects. Each object supports the following properties:&lt;br /&gt;
** &#039;&#039;&#039;input&#039;&#039;&#039;: Name of the result to modify, where the result is the output of the previous transformation.&lt;br /&gt;
** &#039;&#039;&#039;input_key_from&#039;&#039;&#039;: If defined, specifies the the name of the property of a input object whose value contains the name of the property whose value is to be modified.&lt;br /&gt;
** &#039;&#039;&#039;input_key_value_path&#039;&#039;&#039;: If input_key_from is defined and is represented as an object, this configuration should specify an array of property names to traverse into the object. &lt;br /&gt;
*** The value at the end of this path will be used as the name of the property to modify in the input.&lt;br /&gt;
** &#039;&#039;&#039;operation&#039;&#039;&#039;: Specifies the actual value modification operation to perform as value modification configuration object. Supports the following properties:&lt;br /&gt;
*** &#039;&#039;&#039;probability&#039;&#039;&#039;: If defined, specifies the percentage probability of applying the operation to any matching instance of the flow. &lt;br /&gt;
**** Value should be a numeric value between 0 and 1.0. &lt;br /&gt;
**** This probability applies only to this operation.&lt;br /&gt;
**** The default value is 1.0.&lt;br /&gt;
*** &#039;&#039;&#039;type&#039;&#039;&#039;: Type of the operation. The following types are supported:&lt;br /&gt;
**** &#039;&#039;&#039;add&#039;&#039;&#039;: Sets the value to be the current value plus the number specified by the value.&lt;br /&gt;
**** &#039;&#039;&#039;multiply&#039;&#039;&#039;: Sets the value to be the current value multiplied by the number specified by the value.&lt;br /&gt;
**** &#039;&#039;&#039;set_value&#039;&#039;&#039;: Sets the value to be exactly the number specified by the value.&lt;br /&gt;
*** &#039;&#039;&#039;value&#039;&#039;&#039;: Value used by the operation.&lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Output of the previous transformation operation. &lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
The same output as the previous performed transformation, except with the specified value modifications applied.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;modify_values&amp;quot;,&lt;br /&gt;
  &amp;quot;values&amp;quot;: [#{&lt;br /&gt;
    &amp;quot;input&amp;quot;: &amp;quot;role_limits&amp;quot;,&lt;br /&gt;
    &amp;quot;input_key_from&amp;quot;: &amp;quot;resource_to_role_map&amp;quot;,&lt;br /&gt;
    &amp;quot;input_key_value_path&amp;quot;: [&amp;quot;Tina&amp;quot;],&lt;br /&gt;
    &amp;quot;operation&amp;quot;: #{&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;multiply&amp;quot;,&lt;br /&gt;
      &amp;quot;value&amp;quot;: 0.5&lt;br /&gt;
    }&lt;br /&gt;
  }]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transformation: resources_to_roles ===&lt;br /&gt;
Performs &amp;quot;organization mining&amp;quot; by grouping together column values (e.g., resources) that are used in similar fashion in given event log. E.g., resources that are often present in similar set of activities.&lt;br /&gt;
&lt;br /&gt;
==== Supported parameters ====&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;resource_column&#039;&#039;&#039;: The name of the column containing names of resources.&lt;br /&gt;
* &#039;&#039;&#039;resource_limits&#039;&#039;&#039;: Contains an object dictionary containing resource names with their maximum concurrent usages.&lt;br /&gt;
** If set, when building role_limits output, these values will be summed for each resource into the resulting role-based usage limit.&lt;br /&gt;
** If not set, each resource in a role will be counted as one, when calculating the role_limits.&lt;br /&gt;
* &#039;&#039;&#039;role_column&#039;&#039;&#039;: The name of the column to be created and whose values will indicate the role in which the resource belongs to.&lt;br /&gt;
* &#039;&#039;&#039;role_name_template&#039;&#039;&#039;: If set, specifies the name template used for role names. In this template, %d will be replaced by a numeric value starting from 1. &lt;br /&gt;
** The default value is &amp;quot;Role %d&amp;quot;.&lt;br /&gt;
* &#039;&#039;&#039;similarity_threshold&#039;&#039;&#039;: The minimum value of Pearson correlation coefficient calculated between two resources in order for them to be considered as having the same role.&lt;br /&gt;
** The default value is 0.7 &lt;br /&gt;
&lt;br /&gt;
==== Inputs ====&lt;br /&gt;
Event log to operate on.&lt;br /&gt;
&lt;br /&gt;
==== Outputs ====&lt;br /&gt;
&lt;br /&gt;
* Transformed event log.&lt;br /&gt;
* Result dictionary object containing the following properties:&lt;br /&gt;
** &#039;&#039;&#039;resource_column&#039;&#039;&#039;: The name of the column containing names of resources.&lt;br /&gt;
** &#039;&#039;&#039;resource_to_role_map&#039;&#039;&#039;: An object containing resource names as property names and role names as value.&lt;br /&gt;
** &#039;&#039;&#039;role_column&#039;&#039;&#039;: The name of the generated column whose values will indicate the role in which the resource belongs to.&lt;br /&gt;
** &#039;&#039;&#039;role_limits&#039;&#039;&#039;: An object containing role names as property names and maximum usage for that role as value.&lt;br /&gt;
&lt;br /&gt;
==== Example ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
#{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;resources_to_roles&amp;quot;,&lt;br /&gt;
  &amp;quot;resource_column&amp;quot;: &amp;quot;SAP_User&amp;quot;,&lt;br /&gt;
  &amp;quot;role_column&amp;quot;: &amp;quot;Role&amp;quot;,&lt;br /&gt;
  &amp;quot;role_name_template&amp;quot;: &amp;quot;Role %d&amp;quot;,&lt;br /&gt;
  &amp;quot;input&amp;quot;: #{&lt;br /&gt;
    &amp;quot;resource_limits&amp;quot;: &amp;quot;max_resource_usages&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=26264</id>
		<title>Expression Script Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=26264"/>
		<updated>2025-04-25T11:27:24Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains script examples written in the QPR ProcessAnalyzer expression language. See how expression scripts can be created in the [[Managing_Scripts#Creating_Script|Workspace]]. For documentation for the syntax, functions and entities can be found from the main page in the [[QPR_ProcessAnalyzer_Wiki#For_Developers|KPI Expression Language]] section.&lt;br /&gt;
&lt;br /&gt;
== Calling Expression Script from Expression ==&lt;br /&gt;
Expression scripts can be called from an expression using the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Run]] function with the following syntax:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: false,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 123.45&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command waits until the run is completed, and the return value of the called script is returned by the Run function call.&lt;br /&gt;
&lt;br /&gt;
Parameters can be passed to the called script, and the parameters are available as variables in the script. The parameters can contain any type of data.&lt;br /&gt;
&lt;br /&gt;
Expression scripts can also be called from a dashboard. Expressions can be stored to scripts instead of dashboards, which is a way to separate complex expressions from dashboards and allow to reuse expressions across several dashboards.&lt;br /&gt;
&lt;br /&gt;
== Calling SQL Script from Expression ==&lt;br /&gt;
SQL script can be called from an expression using the Run function as follows (similar to calling [[#Calling Expression Script from Expression|expression scripts]]):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 321&lt;br /&gt;
});&lt;br /&gt;
let arrayOfAllReports = result.Keys;&lt;br /&gt;
let report1 = result.Report1;&lt;br /&gt;
let report2 = result.Report2;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL scripts can return multiple &#039;&#039;reports&#039;&#039;, which are combined to a dictionary, where the key is the name of the report (&amp;quot;sheet name&amp;quot;) and value is the report data as a DataFrame. See in the above example, how the reports can be accessed by their name.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Call web service===&lt;br /&gt;
Contact to a web service, fetch some data, and store it to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let datatableName = &amp;quot;Web Service Data&amp;quot;;&lt;br /&gt;
let webServiceData = CallWebService(&lt;br /&gt;
    #{&amp;quot;Address&amp;quot;: &amp;quot;https://processanalyzer.onqpr.com/qprpa/api/serverinfo&amp;quot;}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
let targetDatatable = Project.Datatables.Where(name==datatableName);&lt;br /&gt;
if (Count(targetDatatable) == 0) {&lt;br /&gt;
	targetDatatable = Project.CreateDatatable(datatableName)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting value&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Data read&amp;quot;, &amp;quot;DateTime&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
	targetDatatable = targetDatatable[0];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let currentTime = Now;&lt;br /&gt;
let dataAsDf = ToDataFrame(&lt;br /&gt;
	webServiceData.keys.{&lt;br /&gt;
        let key = _;&lt;br /&gt;
        [key, webServiceData[key], currentTime];&lt;br /&gt;
    },&lt;br /&gt;
	[&amp;quot;Setting name&amp;quot;, &amp;quot;Setting value&amp;quot;, &amp;quot;Data read&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
targetDatatable.Import(dataAsDf, #{&amp;quot;Append&amp;quot;:true});&lt;br /&gt;
WriteLog(`${CountTop(dataAsDf.Rows)} rows written to datatable`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Store data to datatable ===&lt;br /&gt;
Get all models in the system and store them to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let newDatatable = Project&lt;br /&gt;
    .CreateDatatable(&amp;quot;Models list &amp;quot; + ToString(Now, &amp;quot;dd.MM.yyyy HH:mm:ss&amp;quot;))&lt;br /&gt;
    .AddColumn(&amp;quot;Model name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Project name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Created time&amp;quot;, &amp;quot;DateTime&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Cases&amp;quot;, &amp;quot;Integer&amp;quot;);&lt;br /&gt;
let startTime = Now;&lt;br /&gt;
let modelsData = ToDataFrame(&lt;br /&gt;
    Models.([Name, Project.Name, CreatedDate, NCases]),&lt;br /&gt;
    [&amp;quot;Model name&amp;quot;, &amp;quot;Project name&amp;quot;, &amp;quot;Created time&amp;quot;, &amp;quot;Cases&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
WriteLog(`Listing models took ${(Now - startTime).TotalSeconds.Round(2)} seconds.`);&lt;br /&gt;
newDatatable.Import(modelsData);&lt;br /&gt;
WriteLog(`Datatable ${newDatatable.Id} created.`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data ===&lt;br /&gt;
This script can be used to convert a single column into numerical data type. To use the script, you need to setup the following in the beginning of the script:&lt;br /&gt;
* Project name where the datatable is located.&lt;br /&gt;
* Datatable name&lt;br /&gt;
* Name of the column to be converted&lt;br /&gt;
&lt;br /&gt;
Note that the conversion fails, if there is data that cannot be converted into numerical format. The conversion assumes that period (.) is used as the decimal point. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let projectName = &amp;quot;New Project&amp;quot;;&lt;br /&gt;
let datatableName = &amp;quot;qpr processanalyzer events&amp;quot;;&lt;br /&gt;
let columnName = &amp;quot;Event order in case&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let project = (Projects.Where(Name==projectName))[0];&lt;br /&gt;
let datatable = (project.Datatables.Where(Name==datatableName))[0];&lt;br /&gt;
DatatableById(datatable.Id).DataFrame&lt;br /&gt;
.SetColumns([&lt;br /&gt;
	columnName: () =&amp;gt; {&lt;br /&gt;
		let data = Column(columnName);&lt;br /&gt;
		if (data == null) {&lt;br /&gt;
			null;&lt;br /&gt;
		 } else {&lt;br /&gt;
			ToFloat(data);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
])&lt;br /&gt;
.Persist(datatable.Name, [&amp;quot;ProjectId&amp;quot;: project.Id, &amp;quot;Append&amp;quot;: false]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of converting to numeric (with the &#039;&#039;ToFloat&#039;&#039; function), data can be converted into string using the &#039;&#039;ToString&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Show DataFrame as HTML table ===&lt;br /&gt;
&lt;br /&gt;
This script defines a function to show dataframe as a HTML table, and uses the function for a literal dataframe.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function dataframeToHtmlTable(df) {&lt;br /&gt;
	return&lt;br /&gt;
`&amp;lt;table&amp;gt;&lt;br /&gt;
	&amp;lt;tr&amp;gt;&lt;br /&gt;
		${StringJoin(&amp;quot;\r\n\t\t&amp;quot;,  + df.columns.`&amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;`)}&lt;br /&gt;
	&amp;lt;/tr&amp;gt;&lt;br /&gt;
	${StringJoin(&amp;quot;&amp;quot;, df.Rows.(&lt;br /&gt;
		&amp;quot;\r\n\t&amp;lt;tr&amp;gt;&amp;quot; + StringJoin(&amp;quot;&amp;quot;, _.`\r\n\t\t&amp;lt;td&amp;gt;${ToString(_)}&amp;lt;/td&amp;gt;`) + &amp;quot;\r\n\t&amp;lt;/tr&amp;gt;&amp;quot;&lt;br /&gt;
	))}&lt;br /&gt;
&amp;lt;/table&amp;gt;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let data = ToDataFrame(&lt;br /&gt;
	[&lt;br /&gt;
		[&amp;quot;one&amp;quot;, &amp;quot;two&amp;quot;, &amp;quot;three&amp;quot;],&lt;br /&gt;
		[&amp;quot;four&amp;quot;, &amp;quot;five&amp;quot;, &amp;quot;six&amp;quot;],&lt;br /&gt;
		[&amp;quot;seven&amp;quot;, &amp;quot;eight&amp;quot;, &amp;quot;nine&amp;quot;]&lt;br /&gt;
	],&lt;br /&gt;
	[&amp;quot;Column 1&amp;quot;, &amp;quot;Column 2&amp;quot;, &amp;quot;Column 3&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
return dataframeToHtmlTable(data);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy local datatables to Snowflake ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
// Copies all datatables in a project to another project including datatable contents.&lt;br /&gt;
// Usage instructions:&lt;br /&gt;
// 1. Create expression script in the project from where you want to copy the datatables.&lt;br /&gt;
// 2. Create a new project named as &amp;quot;&amp;lt;name of the project to be moved&amp;gt; - Snowflake&amp;quot;. New datatables will be created here. E.g., when moving project named &amp;quot;SAP_OrderToCash&amp;quot;, the target project should be named as &amp;quot;SAP_OrderToCash - Snowflake&amp;quot;.&lt;br /&gt;
// 3. Run the script.&lt;br /&gt;
// NOTE: Columns of type &amp;quot;Any&amp;quot; will be created as &amp;quot;String&amp;quot;-columns in Snowflake, thus it is recommended that actual data types are set for the tables prior to the move.&lt;br /&gt;
&lt;br /&gt;
let sourceProject = Project;&lt;br /&gt;
let sourceProjectName = Project.Name;&lt;br /&gt;
let targetProjectName = `${sourceProjectName} - Snowflake`;&lt;br /&gt;
let targetProject = First(Projects.Where(Name == targetProjectName));&lt;br /&gt;
if (IsNull(targetProject)) {&lt;br /&gt;
  WriteLog(`Unable to find target project named &amp;quot;${targetProjectName}&amp;quot;. Aborting operation.`);&lt;br /&gt;
  return;&lt;br /&gt;
}&lt;br /&gt;
let dts = sourceProject.DataTables;&lt;br /&gt;
WriteLog(`Copying all ${CountTop(dts)} data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
dts.{&lt;br /&gt;
  let sourceDt = _;&lt;br /&gt;
  WriteLog(`Starting to copy data table &amp;quot;${Name}&amp;quot; (id: ${Id}) having ${NRows} rows and ${NColumns} columns.`);&lt;br /&gt;
  let targetDt;&lt;br /&gt;
  targetDt = targetProject.DatatableByName(sourceDt.Name);&lt;br /&gt;
  if (targetDt == null) {&lt;br /&gt;
    targetDt = targetProject.CreateDataTable(sourceDt.Name, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: targetProject.Id})});&lt;br /&gt;
    targetDt.Import(sourceDt.SqlDataFrame);&lt;br /&gt;
    WriteLog(`Finished copying data table &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  } else {&lt;br /&gt;
    WriteLog(`Datatable already exist &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
WriteLog(`Finished copying all the data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to copy the data but only create the Snowflake datatables with columns, you can change the line 22 to&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
targetDt.Import(sourceDt.SqlDataFrame.head(0));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy single datatable to Snowflake ===&lt;br /&gt;
This script creates a copy of a single datatable to Snowflake. Replace the &#039;&#039;&amp;lt;tableId1&amp;gt;&#039;&#039; with the id of the source datatable.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function CopyDataTableToSnowflake(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let sourceDt = DataTableById(dataTableId);&lt;br /&gt;
  sourceDt.SqlDataFrame.Persist(`${sourceDt.Name} - Snowflake`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: sourceDt.Project.Id})});&lt;br /&gt;
}&lt;br /&gt;
CopyDataTableToSnowflake(&amp;lt;tableId1&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a copy of a data table that has all Any-type columns changed to String-type columns ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function ConvertAnyDataTypesToStringsToNewTable(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let dt = DataTableById(dataTableId);&lt;br /&gt;
  let sdf = dt.SqlDataFrame;&lt;br /&gt;
  let cts = dt.ColumnTypes;&lt;br /&gt;
  cts.{&lt;br /&gt;
    let ct = _;&lt;br /&gt;
    if (ct.DataType == &amp;quot;Any&amp;quot;) {&lt;br /&gt;
      let n = ct.Name;&lt;br /&gt;
      sdf = sdf.WithColumn(ct.Name, #sql{Cast(Column(Variable(&amp;quot;n&amp;quot;)), &amp;quot;ShortString&amp;quot;)});&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  sdf.Persist(`${dt.Name} - Converted`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;ProjectId&amp;quot;: dt.Project.Id});&lt;br /&gt;
}&lt;br /&gt;
ConvertAnyDataTypesToStringsToNewTable(&amp;lt;dataTableId&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Query number of rows in given data table having a datetime value in given year grouped by month and return resulting table as CSV ===&lt;br /&gt;
SqlDataFrame is used in order to prevent loading the whole datatable into memory first. Filtering is performed as first operation in order to minimize the amount of required work for the data source of the data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
DataTableById(&amp;lt;data table id&amp;gt;)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .Where(#sql{2014 == Year(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .WithColumn(&amp;quot;Month&amp;quot;, #sql{Month(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .GroupBy([&amp;quot;Month&amp;quot;]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
  .OrderByColumns([&amp;quot;Month&amp;quot;], [true])&lt;br /&gt;
  .Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Function for filtering SqlDataFrame by removing rows having, or replacing, the most infrequently occurring column values ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/***&lt;br /&gt;
 * @name ColumnWithMinUsage&lt;br /&gt;
 * @descripion&lt;br /&gt;
 * Generic function that can be used to filter out the most infrequently occurring attribute values or replace their Values&lt;br /&gt;
 * with given common value.&lt;br /&gt;
 * @param df:&lt;br /&gt;
 * DataFrame to operate on.&lt;br /&gt;
 * @param columnName:&lt;br /&gt;
 * Name of the column to be filtered.&lt;br /&gt;
 * @param newColumnName:&lt;br /&gt;
 * Name of the column that will contain the new value of the original column after filtering (if includeOthers was applied).&lt;br /&gt;
 * @param maxNumUniqueValues:&lt;br /&gt;
 * Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
 * @param minValueUsage:&lt;br /&gt;
 * Minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
 * @param includeOthers:&lt;br /&gt;
 * Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
 * If not empty/null, defines the name used for these other-values.&lt;br /&gt;
 */&lt;br /&gt;
function ColumnWithMinUsage(df, columnName, newColumnName, maxNumUniqueValues, minValueUsage, includeOthers)&lt;br /&gt;
{&lt;br /&gt;
  let all = df&lt;br /&gt;
	.GroupBy([])&lt;br /&gt;
	.Aggregate([&amp;quot;NAllTotal&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
	.WithColumn(&amp;quot;__Join2&amp;quot;, #sql{1});&lt;br /&gt;
  let minValueUsageEnabled = !IsNullTop(minValueUsage);&lt;br /&gt;
  let maxNumUniqueValuesEnabled = !IsNullTop(maxNumUniqueValues);&lt;br /&gt;
  if (minValueUsageEnabled || maxNumUniqueValuesEnabled) {&lt;br /&gt;
	// Perform column value-based filtering if minValueUsageEnabled or maxNumUniqueValuesEnabled is defined.&lt;br /&gt;
    let valueColumnName = &amp;quot;__ValueNew&amp;quot;;&lt;br /&gt;
	let filteredValuesColumns = [valueColumnName: columnName];&lt;br /&gt;
	let filteredValues = df&lt;br /&gt;
	  .GroupBy([columnName]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;]);&lt;br /&gt;
	if (minValueUsageEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithColumn(&amp;quot;__Join&amp;quot;, #sql{1})&lt;br /&gt;
		.Join(all, [&amp;quot;__Join&amp;quot;: &amp;quot;__Join2&amp;quot;], &amp;quot;leftouter&amp;quot;)&lt;br /&gt;
        .WithColumn(&amp;quot;Usage&amp;quot;, #sql{Column(&amp;quot;Count&amp;quot;) / Column(&amp;quot;NAllTotal&amp;quot;)});&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;Usage&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
	if (maxNumUniqueValuesEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithRowNumberColumn(&amp;quot;RowNumber&amp;quot;, [&amp;quot;Count&amp;quot;], null, [false]);&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;RowNumber&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	filteredValues = filteredValues&lt;br /&gt;
	  .Select(filteredValuesColumns);&lt;br /&gt;
&lt;br /&gt;
	// Generate select returning all the accepted values.&lt;br /&gt;
	let allValues = filteredValues&lt;br /&gt;
	  .(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;gt;= #expr{minValueUsage}}) : _)&lt;br /&gt;
	  .(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;lt;= #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
	  .Select([valueColumnName, newColumnName: valueColumnName]);&lt;br /&gt;
&lt;br /&gt;
	if (!IsNullTop(includeOthers)) {&lt;br /&gt;
	  // If includeOthers is defined, replace original values with the variable defined in includeOthers.&lt;br /&gt;
	  let otherValues = filteredValues&lt;br /&gt;
		.(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;lt; #expr{minValueUsage}}) : _)&lt;br /&gt;
		.(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;gt; #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
		.WithColumn(newColumnName, #sql{#expr{includeOthers}})&lt;br /&gt;
		.Select([valueColumnName, newColumnName]);&lt;br /&gt;
	  allValues = allValues.Append(otherValues)&lt;br /&gt;
	}&lt;br /&gt;
	df.Join(allValues, [columnName: valueColumnName], &amp;quot;inner&amp;quot;)&lt;br /&gt;
	  .RemoveColumns([valueColumnName]);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// The following example will return only rows containing two of the most common values for Region-column.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, 2, null, null);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&lt;br /&gt;
// The following example will return all input rows, but will replace the values of rows whose Region-column&lt;br /&gt;
// has a value used by less than 15% of all the rows with a new value: &amp;quot;_Others&amp;quot;.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, null, 0.15, &amp;quot;_Others&amp;quot;);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export model events and cases ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function ExportModelEvents(m) {&lt;br /&gt;
  let attrs = m.EventAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Events.Concat(&lt;br /&gt;
      [Case.Name, Type.Name, ToString(TimeStamp, &amp;quot;yyyy-MM-dd HH:mm:ss.fff&amp;quot;)], &lt;br /&gt;
      {let evt = _; attrs.{let att = _; evt.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;, &amp;quot;EventType&amp;quot;, &amp;quot;TimeStamp&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
function ExportModelCases(m) {&lt;br /&gt;
  let attrs = m.CaseAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Cases.Concat(&lt;br /&gt;
      [Name], &lt;br /&gt;
      {let cas = _; attrs.{let att = _; cas.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).EventsDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).CasesDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Calculate all the value usages of a single column for each event in event data table ===&lt;br /&gt;
This query could be used, e.g., to find out the maximum resource usage for every resource found in the event data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function WithUsageColumns(resourceColumn)&lt;br /&gt;
{&lt;br /&gt;
  function WithTotalUsageColumnOfSingleResource(resourceColumn, resourceValue)&lt;br /&gt;
  {&lt;br /&gt;
    _&lt;br /&gt;
      .WithColumn(&amp;quot;_Prev&amp;quot;, #sql{Lag(Column(resourceColumn), [TimeStamp, EventType], [true, true], [CaseId], 1, null)})&lt;br /&gt;
      .WithColumn(&amp;quot;_UsageDiff&amp;quot;, #sql{&lt;br /&gt;
        CaseWhen(&lt;br /&gt;
          Column(resourceColumn) == Column(&amp;quot;_Prev&amp;quot;), 0, &lt;br /&gt;
          Column(&amp;quot;_Prev&amp;quot;) == #expr{resourceValue}, -1,&lt;br /&gt;
          Column(resourceColumn) == #expr{resourceValue}, 1,&lt;br /&gt;
          0)&lt;br /&gt;
      })&lt;br /&gt;
      .WithColumn(`${resourceValue}_Usage`, #sql{Sum(Column(&amp;quot;_UsageDiff&amp;quot;), [TimeStamp, EventType])})&lt;br /&gt;
      .RemoveColumns([&amp;quot;_Prev&amp;quot;, &amp;quot;_UsageDiff&amp;quot;])&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let sdf = _;&lt;br /&gt;
  let allValues = sdf.SelectDistinct([resourceColumn]).OrderByColumns([resourceColumn], [true]).Collect().Column(resourceColumn);&lt;br /&gt;
  allValues.{&lt;br /&gt;
    let v = _;&lt;br /&gt;
    sdf = sdf.WithTotalUsageColumnOfSingleResource(resourceColumn, v)&lt;br /&gt;
  }&lt;br /&gt;
  sdf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
dt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .WithUsageColumns(&amp;lt;resource column name&amp;gt;)&lt;br /&gt;
  .OrderByColumns([dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]], [true])&lt;br /&gt;
  .Collect().ToCsv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;model id&amp;gt; is the id of the model containing event data to be examined.&lt;br /&gt;
* &amp;lt;resource column name&amp;gt; is the name of the column in the event data table of the specified model containing the resource being used by that event.&lt;br /&gt;
&lt;br /&gt;
NOTE: This expression uses functionalities that are only supported in Snowflake-based data tables.&lt;br /&gt;
&lt;br /&gt;
=== Create new Snowflake model from filter ===&lt;br /&gt;
This script creates a new Snowflake model (and two datatables for cases and events) containing filtered event log from given filter id. The script also works if the model doesn&#039;t have a cases datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let filter = FilterById(1); // filter id&lt;br /&gt;
let model = filter.model;&lt;br /&gt;
let project = model.project;&lt;br /&gt;
let nameSuffix = &amp;quot; - &amp;quot; + filter.name + &amp;quot; - &amp;quot; + ToString(Now, &amp;quot;dd-MM-yyyy HH:mm:ss&amp;quot;);&lt;br /&gt;
let eventsDatatableName = model.EventsDataTable.Name + nameSuffix;&lt;br /&gt;
if (eventsDatatableName.length &amp;gt; 440) {&lt;br /&gt;
  eventsDatatableName = eventsDatatableName.Substring(eventsDatatableName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
let eventsData = model&lt;br /&gt;
  .EventsDataTable&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .ApplyFilter(&lt;br /&gt;
    filter.rules,&lt;br /&gt;
    model.CasesDataTable?.SqlDataFrame&lt;br /&gt;
  );&lt;br /&gt;
project&lt;br /&gt;
  .CreateDatatable(eventsDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
  .Import(eventsData);&lt;br /&gt;
let modelConfiguration = model.Configuration;&lt;br /&gt;
modelConfiguration.DataSource.Events.Set(&amp;quot;DataTableName&amp;quot;, eventsDatatableName);&lt;br /&gt;
if (model.CasesDataTable != null) {&lt;br /&gt;
  let eventsDataCaseIdColumn = &amp;quot;CaseId_&amp;quot; + ToString(Random());&lt;br /&gt;
  let casesDatatableName = model.CasesDataTable.Name + nameSuffix;&lt;br /&gt;
  if (casesDatatableName.length &amp;gt; 440) {&lt;br /&gt;
    casesDatatableName = casesDatatableName.Substring(casesDatatableName.length - 440);&lt;br /&gt;
  }&lt;br /&gt;
  let casesData = model&lt;br /&gt;
    .CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .join(&lt;br /&gt;
	  eventsData.SelectDistinct([eventsDataCaseIdColumn: modelConfiguration.DataSource.Events.Columns.CaseId]),&lt;br /&gt;
      [modelConfiguration.DataSource.Cases.Columns.CaseId: eventsDataCaseIdColumn]&lt;br /&gt;
	).Select(model.CasesDataTable.ColumnNames);&lt;br /&gt;
  project&lt;br /&gt;
    .CreateDatatable(casesDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
    .Import(casesData);&lt;br /&gt;
  modelConfiguration.DataSource.Cases.Set(&amp;quot;DataTableName&amp;quot;, casesDatatableName);&lt;br /&gt;
}&lt;br /&gt;
let modelName = model.Name + nameSuffix;&lt;br /&gt;
if (modelName &amp;gt; 440) {&lt;br /&gt;
  modelName = modelName.Substring(modelName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
project&lt;br /&gt;
  .CreateModel(#{    &lt;br /&gt;
    &amp;quot;Name&amp;quot;: modelName,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: model.Description,&lt;br /&gt;
    &amp;quot;Configuration&amp;quot;: modelConfiguration&lt;br /&gt;
  });&lt;br /&gt;
return modelName;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Creating a model consisting of multiple copies of cases in an existing model ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @name CreateTestModel&lt;br /&gt;
 * @description&lt;br /&gt;
 * Creates a new model (or overwrites an existing) to given target project with given number of &lt;br /&gt;
 * repetitions of given source model.&lt;br /&gt;
 * Each repetition will generate &amp;quot;&amp;lt;N&amp;gt;-&amp;quot;-prefix to CaseId-columns, where N equals to the repeat index.&lt;br /&gt;
 * @param sourceModel&lt;br /&gt;
 * PA model used for the source data and from where the connection is copied for the target model if a &lt;br /&gt;
 * new one has to be created.&lt;br /&gt;
 * @param numRepeats&lt;br /&gt;
 * Number of times the data in the source model should be repeated in the generated model.&lt;br /&gt;
 * @param targetProject&lt;br /&gt;
 * Project in which the target model resides.&lt;br /&gt;
 * @param targetModelName&lt;br /&gt;
 * Specifies the name of the test model in the given target project. If a model already exists with &lt;br /&gt;
 * given name, event and case data in this model will be replaced with the new generated event and &lt;br /&gt;
 * case data.&lt;br /&gt;
 * @returns&lt;br /&gt;
 * Model object of the test model having the newly generated data.&lt;br /&gt;
 */&lt;br /&gt;
function CreateTestModel(sourceModel, numRepeats, targetProject, targetModelName) &lt;br /&gt;
{&lt;br /&gt;
  let eventsColumnMappings = sourceModel.EventsDataTable.ColumnMappings;&lt;br /&gt;
  let casesColumnMappings = sourceModel.CasesDataTable.ColumnMappings;&lt;br /&gt;
  let connection = sourceModel.EventsDataTable.DataSourceConnection;&lt;br /&gt;
&lt;br /&gt;
  function CreateResultModel()&lt;br /&gt;
  {&lt;br /&gt;
    function GetTable(tableName) &lt;br /&gt;
    {&lt;br /&gt;
      let tableConfiguration = #{&lt;br /&gt;
        &amp;quot;Name&amp;quot;: tableName,&lt;br /&gt;
        &amp;quot;Connection&amp;quot;: connection&lt;br /&gt;
      };&lt;br /&gt;
      let resultTable = targetProject.DataTableByName(tableName);&lt;br /&gt;
      if (resultTable == null)&lt;br /&gt;
      {&lt;br /&gt;
        resultTable = targetProject.CreateDataTable(tableConfiguration)&lt;br /&gt;
          .Modify(#{&amp;quot;NameInDataSource&amp;quot;: null})&lt;br /&gt;
          .Synchronize();&lt;br /&gt;
      }&lt;br /&gt;
      return resultTable;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let eventsTableName = `${targetModelName} - events`;&lt;br /&gt;
    let casesTableName = `${targetModelName} - cases`;&lt;br /&gt;
    let targetModel = targetProject.ModelByName(targetModelName);&lt;br /&gt;
    let eventsTable, casesTable = null;&lt;br /&gt;
&lt;br /&gt;
    if (targetModel != null)&lt;br /&gt;
    {&lt;br /&gt;
      eventsTable = targetModel.EventsDataTable;&lt;br /&gt;
      casesTable = targetModel.CasesDataTable;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      eventsTable = GetTable(eventsTableName);&lt;br /&gt;
      if (sourceModel.CasesDataTable != null) {&lt;br /&gt;
        casesTable = GetTable(casesTableName);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      let timestampMapping = eventsColumnMappings[&amp;quot;TimeStamp&amp;quot;];&lt;br /&gt;
      eventsColumnMappings.Remove(&amp;quot;TimeStamp&amp;quot;);&lt;br /&gt;
      eventsColumnMappings.Set(&amp;quot;Timestamp&amp;quot;, timestampMapping);&lt;br /&gt;
&lt;br /&gt;
      let modelConfiguration = #{&lt;br /&gt;
        &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
          &amp;quot;Events&amp;quot;:#{&lt;br /&gt;
            &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
            &amp;quot;DataTableName&amp;quot;: eventsTableName,&lt;br /&gt;
            &amp;quot;Columns&amp;quot;: eventsColumnMappings&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      if (casesColumnMappings != null) {&lt;br /&gt;
        modelConfiguration[&amp;quot;DataSource&amp;quot;].Set(&amp;quot;Cases&amp;quot;, #{&lt;br /&gt;
          &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
          &amp;quot;DataTableName&amp;quot;: casesTableName,&lt;br /&gt;
          &amp;quot;Columns&amp;quot;: casesColumnMappings&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      targetModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: targetModelName, &amp;quot;Configuration&amp;quot;: modelConfiguration});&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    eventsTable.Truncate();&lt;br /&gt;
    casesTable?.Truncate();&lt;br /&gt;
&lt;br /&gt;
    return #{&lt;br /&gt;
      &amp;quot;TargetModel&amp;quot;: targetModel,&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTable,&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: casesTable&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function RepeatNTimes(sourceDf, caseIdColumn, numRepeats)&lt;br /&gt;
  {&lt;br /&gt;
    let resultDf = null;&lt;br /&gt;
    for (let i = 1; i &amp;lt;= numRepeats; ++i) {&lt;br /&gt;
      let iterationDf = sourceDf&lt;br /&gt;
        .WithColumn(caseIdColumn, #sql{Concat(#expr{i}, &amp;quot;-&amp;quot;, Column(#expr{caseIdColumn}))});&lt;br /&gt;
      resultDf = resultDf == null ? iterationDf : resultDf.Append(iterationDf); &lt;br /&gt;
    }&lt;br /&gt;
    resultDf;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let resultModel = CreateResultModel();&lt;br /&gt;
  let sourceEventDataDf = sourceModel.EventsDataTable.SqlDataFrame;&lt;br /&gt;
  let resultEventDataDf = RepeatNTimes(sourceEventDataDf, eventsColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
  resultModel[&amp;quot;Events&amp;quot;].Import(resultEventDataDf);&lt;br /&gt;
&lt;br /&gt;
  let sourceCaseDataDf = sourceModel.CasesDataTable?.SqlDataFrame;&lt;br /&gt;
  if (sourceCaseDataDf != null) {&lt;br /&gt;
    let resultCaseDataDf = RepeatNTimes(sourceCaseDataDf, casesColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
    resultModel[&amp;quot;Cases&amp;quot;].Import(resultCaseDataDf);&lt;br /&gt;
  }&lt;br /&gt;
  resultModel[&amp;quot;TargetModel&amp;quot;];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example usage:&amp;lt;blockquote&amp;gt;CreateTestModel(ProjectByName(&amp;quot;Project&amp;quot;).ModelByName(&amp;quot;SAP_OrderToCash - Snowflake&amp;quot;), 3, ProjectByName(&amp;quot;TestData&amp;quot;), &amp;quot;TestModel&amp;quot;);&amp;lt;/blockquote&amp;gt;Creates a new model named &amp;quot;TestModel&amp;quot; (or overwrites old one) into project named &amp;quot;TestData&amp;quot; containing the data from model &amp;quot;SAP_OrderToCash - Snowflake&amp;quot; in project &amp;quot;Project&amp;quot; repeated three times.&lt;br /&gt;
&lt;br /&gt;
=== Analyzing declare patterns found in event log ===&lt;br /&gt;
&lt;br /&gt;
This is an example expression that shows how POSIX-style regular expressions can be used to search for cases in an event log having certain event type patterns [https://www.researchgate.net/publication/277631859_Generating_Event_Logs_Through_the_Simulation_of_Declare_Models declare patterns].&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
let sdf = dt.SqlDataFrame.Head(1000);&lt;br /&gt;
let caseIdColumn = dt.ColumnMappings[&amp;quot;CaseId&amp;quot;], timeStampColumn = dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;], eventTypeColumn = dt.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let eventTypesDf = sdf&lt;br /&gt;
  .SelectDistinct([eventTypeColumn])&lt;br /&gt;
  .OrderByColumns([eventTypeColumn], [true])&lt;br /&gt;
  .WithRowNumberColumn(&amp;quot;Token&amp;quot;, [eventTypeColumn])&lt;br /&gt;
  .WithColumn(&amp;quot;Token&amp;quot;, #sql{Char(Column(&amp;quot;Token&amp;quot;) + Unicode(&amp;quot;a&amp;quot;) - 1)});&lt;br /&gt;
&lt;br /&gt;
sdf = sdf&lt;br /&gt;
  .Join(eventTypesDf.Select([&amp;quot;_EventType2&amp;quot;: eventTypeColumn, &amp;quot;Token&amp;quot;]), [eventTypeColumn: &amp;quot;_EventType2&amp;quot;], &amp;quot;leftouter&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function RespondedExistencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*((a.*b.*)|(b.*a.*))*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*((${a}.*${b}.*)|(${b}.*${a}.*))*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a.*b)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}.*${b})*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function AlternateResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a[^a]*b[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}[^${a}]*${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ChainResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(ab[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function PrecedencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^b]*(a.*b)*[^b]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${b}]*(${a}.*${b})*[^${b}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let tracesDf = sdf&lt;br /&gt;
  .GroupBy([caseIdColumn])&lt;br /&gt;
  .Aggregate([&amp;quot;Trace&amp;quot;: &amp;quot;Token&amp;quot;], [#{&amp;quot;Function&amp;quot;: &amp;quot;list&amp;quot;, &amp;quot;Ordering&amp;quot;: [timeStampColumn], &amp;quot;Separator&amp;quot;: &amp;quot;&amp;quot;}])&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceNL&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceCA&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternNL&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternCA&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternNL&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternCA&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternNL&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternCA&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternNL&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternCA&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
[tracesDf.Collect().ToCsv(), eventTypesDf.Collect().ToCsv()]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Perform a query and send results as E-mail in a HTML table ===&lt;br /&gt;
This example requires two scripts that are both located in the same project:&lt;br /&gt;
&lt;br /&gt;
# Expression-type script to execute (can be, e.g., scheduled to run daily).&lt;br /&gt;
# Expression-type script containing the query JSON to use as basis for the e-mail. In this example, this script is named as &amp;quot;Send query as E-mail - query JSON&amp;quot;. The contents of this script is just the JSON representation of a query that can be extracted, e.g., from any PA chart view.&lt;br /&gt;
&lt;br /&gt;
Script #1 should contain the following code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let replyToAddress = &amp;quot;noreply@test.com&amp;quot;;&lt;br /&gt;
let recipientsArray = [&amp;quot;testuser@test.com&amp;quot;];&lt;br /&gt;
let queryConfiguration = ParseJson(Project.ScriptByName(&amp;quot;Send query as E-mail - query JSON&amp;quot;).Code)&lt;br /&gt;
  .Set(&amp;quot;IncludeCollect&amp;quot;, true);&lt;br /&gt;
let resultDf = Query(queryConfiguration);&lt;br /&gt;
let mailBodyHtml = resultDf.`&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
  &amp;lt;caption&amp;gt;Example query&amp;lt;/caption&amp;gt;&lt;br /&gt;
  &amp;lt;thead&amp;gt;&lt;br /&gt;
    &amp;lt;tr&amp;gt;&lt;br /&gt;
      ${StringJoin(&amp;quot;&amp;quot;, _.Columns.`&lt;br /&gt;
        &amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;&lt;br /&gt;
      `)}&lt;br /&gt;
    &amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;/thead&amp;gt;&lt;br /&gt;
  &amp;lt;tbody&amp;gt;&lt;br /&gt;
    ${StringJoin(&amp;quot;&amp;quot;, _.Rows.`&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
        ${StringJoin(&amp;quot;&amp;quot;, _.`&lt;br /&gt;
          &amp;lt;td&amp;gt;${_}&amp;lt;/td&amp;gt;&lt;br /&gt;
        `)}&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
    `)}&lt;br /&gt;
  &amp;lt;/tbody&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
`;&lt;br /&gt;
&lt;br /&gt;
SendEmail(#{&lt;br /&gt;
  &amp;quot;ReplyTo&amp;quot;: [replyToAddress],&lt;br /&gt;
  &amp;quot;To&amp;quot;: recipientsArray,&lt;br /&gt;
  &amp;quot;Subject&amp;quot;: &amp;quot;Example query E-mail&amp;quot;,&lt;br /&gt;
  &amp;quot;IsBodyHtml&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Body&amp;quot;: mailBodyHtml&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;NOTE: QPR ProcessAnalyzer Server has to have SMTP server configured. Also, remember to update the values of replyToAddress and recipientsArray before using.&lt;br /&gt;
&lt;br /&gt;
=== Converting a case-centric model to object-centric model ===&lt;br /&gt;
This function serves as an example on how a case-centric model could be converted into an object-centric model having just one object type: &amp;quot;Case&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ConvertCCModelToOCModel(model, newModelName) &lt;br /&gt;
{&lt;br /&gt;
  let connection = model.EventsDataTable.DataSourceConnection;&lt;br /&gt;
  let caseIdColumn = model.EventsDataTable.ColumnMappings[&amp;quot;CaseId&amp;quot;];&lt;br /&gt;
  let eventToObjectTableName = `${newModelName} - event-to-object`;&lt;br /&gt;
  let eventsTableName = `${newModelName} - events`;&lt;br /&gt;
  let objectsTableName = `${newModelName} - objects`;&lt;br /&gt;
          &lt;br /&gt;
  let eventsDf = model.EventsDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelEventType&amp;quot;: model.EventsDataTable.ColumnMappings[&amp;quot;EventType&amp;quot;],&lt;br /&gt;
      &amp;quot;OcelEventTime&amp;quot;: model.EventsDataTable.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelEventId&amp;quot;, #sql{Concat(&amp;quot;evt-&amp;quot;, Cast(RowNumber([Column(&amp;quot;OcelEventTime&amp;quot;)]), &amp;quot;String&amp;quot;))});&lt;br /&gt;
&lt;br /&gt;
  eventsDf&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelEventToObjectSourceId&amp;quot;: &amp;quot;OcelEventId&amp;quot;,&lt;br /&gt;
      &amp;quot;OcelEventToObjectTargetId&amp;quot;: caseIdColumn])&lt;br /&gt;
    .Select([&amp;quot;OcelEventToObjectSourceId&amp;quot;, &amp;quot;OcelEventToObjectTargetId&amp;quot;])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelEventToObjectQualifier&amp;quot;, #sql{#expr{caseIdColumn} })&lt;br /&gt;
    .Persist(eventToObjectTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  eventsDf&lt;br /&gt;
    .RemoveColumns([caseIdColumn])&lt;br /&gt;
    .Persist(eventsTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  let casesDt = model.CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .RenameColumns([&lt;br /&gt;
      &amp;quot;OcelObjectId&amp;quot;: model.CasesDataTable.ColumnMappings[&amp;quot;CaseId&amp;quot;]&lt;br /&gt;
    ])&lt;br /&gt;
    .WithColumn(&amp;quot;OcelObjectType&amp;quot;, #sql{&amp;quot;Case&amp;quot;})&lt;br /&gt;
    .Persist(objectsTableName, #{&amp;quot;Connection&amp;quot;: connection});&lt;br /&gt;
&lt;br /&gt;
  let newConfiguration = #{&lt;br /&gt;
    &amp;quot;OcelDataSource&amp;quot;: #{&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTableName,&lt;br /&gt;
      &amp;quot;Objects&amp;quot;: objectsTableName,&lt;br /&gt;
      &amp;quot;EventToObject&amp;quot;: eventToObjectTableName&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
  model.Project  &lt;br /&gt;
    .CreateModel(#{      &lt;br /&gt;
      &amp;quot;Name&amp;quot;: newModelName,  &lt;br /&gt;
      &amp;quot;Description&amp;quot;: model.Description,  &lt;br /&gt;
      &amp;quot;Configuration&amp;quot;: newConfiguration  &lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let ccModel = ModelById(&amp;lt;model id&amp;gt;);&lt;br /&gt;
ConvertCCModelToOCModel(ccModel, `ocel - ${ccModel.Name}`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Object-centric_Process_Mining_Model&amp;diff=26041</id>
		<title>Object-centric Process Mining Model</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Object-centric_Process_Mining_Model&amp;diff=26041"/>
		<updated>2025-03-25T15:28:23Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Object-centric model structure */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer supports object-centric process mining (OCPM) based on the OCEL 2.0 standard (https://www.ocel-standard.org). To use object-centric functionality, you need to transform data into the [[#Object-centric_model_structure|suitable format]] for the [[#Create_object-centric_model|object-centric model]]. Object-centric models can be analyzed in the object-centric flowchart and with (case-centric) charts because the object-centric model can be converted into a case-centric eventlog using [[#Object-centric_perspectives|perspectives]]. To use the OCPM functionality, Snowflake needs to be used as the calculation engine.&lt;br /&gt;
&lt;br /&gt;
== Create object-centric model ==&lt;br /&gt;
Create a new object-centric model as follows:&lt;br /&gt;
# In the Workspace, open the project where to create the model.&lt;br /&gt;
# Select &#039;&#039;&#039;NEW&#039;&#039;&#039; in the top right menu and select &#039;&#039;&#039;model&#039;&#039;&#039;.&lt;br /&gt;
# Define a name for the new model.&lt;br /&gt;
# Set &#039;&#039;&#039;Model type&#039;&#039;&#039; as &#039;&#039;&#039;Object-centric&#039;&#039;&#039;.&lt;br /&gt;
# Click &#039;&#039;&#039;Create&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Configure object-centric model datatables ==&lt;br /&gt;
Datatables for the object-centric model need to exist in the same project as the model. Datatables can be set for the model as follows:&lt;br /&gt;
# In the Workspace, select the object-centric model and click &#039;&#039;&#039;Properties&#039;&#039;&#039;.&lt;br /&gt;
# In the model properties dialog, open the &#039;&#039;&#039;Datasource&#039;&#039;&#039; tab.&lt;br /&gt;
# Add a following kind of json configuration to the textbox:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Objects&amp;quot;: &amp;quot;OCPM: objects&amp;quot;,&lt;br /&gt;
  &amp;quot;Events&amp;quot;: &amp;quot;OCPM: events&amp;quot;,&lt;br /&gt;
  &amp;quot;ObjectToObject&amp;quot;: &amp;quot;OCPM: object-object&amp;quot;,&lt;br /&gt;
  &amp;quot;EventToObject&amp;quot;: &amp;quot;OCPM: event-object&amp;quot;,&lt;br /&gt;
  &amp;quot;ObjectTypes&amp;quot;: {&lt;br /&gt;
    &amp;quot;Invoice&amp;quot;: &amp;quot;OCPM object: Invoice&amp;quot;,&lt;br /&gt;
    &amp;quot;Payment&amp;quot;: &amp;quot;OCPM object: Payment&amp;quot;,&lt;br /&gt;
    &amp;quot;Purchase Order&amp;quot;: &amp;quot;OCPM object: Purchase Order&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;EventTypes&amp;quot;: { &lt;br /&gt;
    &amp;quot;Approve Purchase Requisition&amp;quot;: &amp;quot;OCPM event: Approve Purchase Requisition&amp;quot;,&lt;br /&gt;
    &amp;quot;Change PO Quantity&amp;quot;: &amp;quot;OCPM event: Change PO Quantity&amp;quot;,&lt;br /&gt;
    &amp;quot;Create Purchase Order&amp;quot;: &amp;quot;OCPM event: Create Purchase Order&amp;quot;,&lt;br /&gt;
    &amp;quot;Insert Invoice&amp;quot;: &amp;quot;OCPM event: Insert Invoice&amp;quot;,&lt;br /&gt;
    &amp;quot;Insert Payment&amp;quot;: &amp;quot;OCPM event: Insert Payment&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The json configuration needs to have following properties:&lt;br /&gt;
* &#039;&#039;&#039;Objects&#039;&#039;&#039;: Objects datatable name.&lt;br /&gt;
* &#039;&#039;&#039;Events&#039;&#039;&#039;: Events datatable name.&lt;br /&gt;
* &#039;&#039;&#039;ObjectToObject&#039;&#039;&#039;: Object-to-object relation datatable name.&lt;br /&gt;
* &#039;&#039;&#039;EventToObject&#039;&#039;&#039;: Event-to-object relation datatable name.&lt;br /&gt;
* &#039;&#039;&#039;ObjectTypes&#039;&#039;&#039;: Key-value-pairs of object type datatable names. Note that object names need to match with object names in the objects datatable.&lt;br /&gt;
* &#039;&#039;&#039;EventTypes&#039;&#039;&#039;: Key-value-pairs of event type datatable names. Note that event names need to match with event names in the events datatable.&lt;br /&gt;
&lt;br /&gt;
It&#039;s also possible that all object attributes and all event attributes are in the same table:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Objects&amp;quot;: &amp;quot;OCPM: objects&amp;quot;,&lt;br /&gt;
  &amp;quot;Events&amp;quot;: &amp;quot;OCPM: events&amp;quot;,&lt;br /&gt;
  &amp;quot;ObjectToObject&amp;quot;: &amp;quot;OCPM: object-object&amp;quot;,&lt;br /&gt;
  &amp;quot;EventToObject&amp;quot;: &amp;quot;OCPM: event-object&amp;quot;,&lt;br /&gt;
  &amp;quot;ObjectTypes&amp;quot;: {&lt;br /&gt;
    &amp;quot;Invoice&amp;quot;: &amp;quot;OCPM object attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Payment&amp;quot;: &amp;quot;OCPM object attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Purchase Order&amp;quot;: &amp;quot;OCPM object attributes&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;EventTypes&amp;quot;: { &lt;br /&gt;
    &amp;quot;Approve Purchase Requisition&amp;quot;: &amp;quot;OCPM event attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Change PO Quantity&amp;quot;: &amp;quot;OCPM event attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Create Purchase Order&amp;quot;: &amp;quot;OCPM event attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Insert Invoice&amp;quot;: &amp;quot;OCPM event attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Insert Payment&amp;quot;: &amp;quot;OCPM event attributes&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note also that the Objects table (&amp;quot;OCPM: objects&amp;quot; in the above example) must not contain object types which are not specified in the ObjectTypes section (and same for event types in the EventTypes section).&lt;br /&gt;
&lt;br /&gt;
== Import from OCEL 2.0 JSON file ==&lt;br /&gt;
Object-centric model can be import from an OCEL 2.0 JSON file as follows:&lt;br /&gt;
# In the Workspace, open the project where to import the model.&lt;br /&gt;
# Select &#039;&#039;&#039;NEW&#039;&#039;&#039; in top right menu and select &#039;&#039;&#039;Import Model&#039;&#039;&#039;.&lt;br /&gt;
# Select the OCEL 2.0 JSON file from the disk and click &#039;&#039;&#039;Open&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
An object-centric model and a list of datatables is created.&lt;br /&gt;
&lt;br /&gt;
Example OCEL 2.0 eventlogs: https://www.ocel-standard.org/event-logs/overview/ (download the json format supported by QPR ProcessAnalyzer)&lt;br /&gt;
&lt;br /&gt;
== Filtering object-centric model ==&lt;br /&gt;
Object-centric models can be filtered by object attribute values which is similar to filtering by case attribute values in case-centric models. For the object attribute filtering, following settings are defined:&lt;br /&gt;
* Object type to be filtered&lt;br /&gt;
* Object attribute name&lt;br /&gt;
* Object attribute values&lt;br /&gt;
* Include or exclude logic&lt;br /&gt;
* Number of object relation steps&lt;br /&gt;
&lt;br /&gt;
When the number of object relation steps is zero, objects of only the selected type are filtered. For example, when including items, all other object types are excluded (when object relation steps is zero). When excluding items, all other object types are included (when object relation steps is zero).&lt;br /&gt;
&lt;br /&gt;
When object relation steps is one, objects directly related to the filtered object with an object-to-object relation are included or excluded.&lt;br /&gt;
&lt;br /&gt;
When object relation steps is two or more, several object-to-object relations are followed to find the included or excluded objects. The objects are traversed only in the same direction which is either forward or backward. This is based on the notion that in object-centric models, all object-to-object relations have a direction, i.e., starting object and ending object of the relation. When creating an object-centric model, this relation direction needs to be carefully selected to produce desired results in the filtering.&lt;br /&gt;
&lt;br /&gt;
Object attribute filter rules can be created for the entire dashboard by pressing the blue plus button in the dashboard header (requires that an object-centric model is selected for the dashboard). Object attribute filter rules can also be added for an individual chart/flowchart by opening the &#039;&#039;Filter&#039;&#039; tab in the chart/flowchart settings and pressing the &#039;&#039;&#039;Object-centric filter&#039;&#039;&#039; button.&lt;br /&gt;
&lt;br /&gt;
Alternatively, a chart showing object attribute values as dimension or columns, can be selected to start creating object attribute filter rules. When making the selection, user can choose between &#039;&#039;&#039;Include Objects&#039;&#039;&#039; and &#039;&#039;&#039;Exclude Objects&#039;&#039;&#039;. It&#039;s also possible to use the case-centric filtering by selecting &#039;&#039;&#039;Include Cases&#039;&#039;&#039; or &#039;&#039;&#039;Cases Cases&#039;&#039;&#039;. Note that the case-centric attribute filtering doesn&#039;t work with the object-centric flowchart and with other charts having different perspective selected.&lt;br /&gt;
&lt;br /&gt;
=== Changes for QPR ProcessAnalyzer 2025.3 ===&lt;br /&gt;
Starting from QPR ProcessAnalyzer 2025.3, it&#039;s possible to leave the object relation steps setting empty. This means that the object-to-object relations are followed in a way that all the object types are traversed once to find the related objects. This also means that relation direction (forward or backward) doesn&#039;t matter anymore.&lt;br /&gt;
&lt;br /&gt;
== Object-centric model structure ==&lt;br /&gt;
Object-centric model contains datatables described in the table below. Datatables can be named freely, as the model json configuration is used to define the datatable for each type of data. The datatables need to use column names specified in the table below because those are the column names assumed by the object-centric (i.e., column names cannot be selected freely).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Datatable role&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Contained data&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Datatable columns&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||Objects&lt;br /&gt;
||Objects in the model (one row per object).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectId&#039;&#039;&#039;: Unique id for the object (among all objects in the model).&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectType&#039;&#039;&#039;: Object type name (such as Order, Invoice, Delivery). Note that the model json configuration need to use same object type names.&lt;br /&gt;
|-&lt;br /&gt;
||Events&lt;br /&gt;
||Events in the model (one row per event).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelEventId&#039;&#039;&#039;: Unique id for the event (among all events in the model).&lt;br /&gt;
* &#039;&#039;&#039;OcelEventType&#039;&#039;&#039;: Event type name (such as Order created, Invoice sent). Note that the model json configuration need to use same event type names.&lt;br /&gt;
* &#039;&#039;&#039;OcelEventTime&#039;&#039;&#039;: Event timestamp.&lt;br /&gt;
|-&lt;br /&gt;
||Object-object relations&lt;br /&gt;
||Relations between objects (one row per relation).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectToObjectSourceId&#039;&#039;&#039;: Source object id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectToObjectTargetId&#039;&#039;&#039;: Target object id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectToObjectQualifier&#039;&#039;&#039;: Describes the type of the relation as free-form text (not used currently).&lt;br /&gt;
|-&lt;br /&gt;
||Event-object relations&lt;br /&gt;
||Relations between events and objects (one row per relation).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelEventToObjectSourceId&#039;&#039;&#039;: Event id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelEventToObjectTargetId&#039;&#039;&#039;: Object id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelEventToObjectQualifier&#039;&#039;&#039;: Describes the type of the relation as free-form text (not used currently).&lt;br /&gt;
|-&lt;br /&gt;
||Object attributes (several datatables)&lt;br /&gt;
||Object attribute values, each object type in a separate table (one row per object).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectTypeObjectId&#039;&#039;&#039;: Object id. Matches to the objects datatable &#039;&#039;OcelObjectId&#039;&#039; column.&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectTypeTime&#039;&#039;&#039;: Timestamp from which the attribute values are valid.&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectTypeChangedField&#039;&#039;&#039;: Changed object attribute name (not used currently).&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;Object attributes&amp;gt;&#039;&#039;&#039;: Columns for each of the object attribute values (column name is the object attribute name).&lt;br /&gt;
|-&lt;br /&gt;
||Event attributes (several datatables)&lt;br /&gt;
||Event attribute values, each event type in a separate table (one row per event).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelEventTypeEventId&#039;&#039;&#039;: Event id. Matches to the events datatable &#039;&#039;OcelEventId&#039;&#039; column.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;Event attributes&amp;gt;&#039;&#039;&#039;: Columns for each of the event attribute values (column name is the event attribute name).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Object-centric perspectives ==&lt;br /&gt;
Perspectives convert an object-centric model into the traditional case-centric eventlog, allowing to view and analyze object-centric models in analyses provided by charts. A single perspective is not able describe the object-centric model entirely, but just from a certain limited viewpoint. By using analyses with several perspectives, it&#039;s possible to get a more complete picture of the object-centric model. The perspective starts from a certain object type and traverses the object-object relations as many steps as desired.&lt;br /&gt;
&lt;br /&gt;
To define a perspective, the following settings are defined in the chart settings:&lt;br /&gt;
* &#039;&#039;&#039;Base Object type&#039;&#039;&#039;: Object of this type will be cases in the projected case-centric eventlog.&lt;br /&gt;
* &#039;&#039;&#039;Object Relation Steps&#039;&#039;&#039;: Specifies how many object-object relations will be traversed in order to find events connected to the base objects. Value zero means that only those events are returned that are directly connected to the base objects.&lt;br /&gt;
* &#039;&#039;&#039;Show Event Types&#039;&#039;&#039;: List of event type names which are included into the perspective eventlog. If no events are explicitly defined, all events will be included, but their event attributes are not included.&lt;br /&gt;
&lt;br /&gt;
The resulting perspective eventlog will have the following columns:&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectId&#039;&#039;&#039; (mapped to case id)&lt;br /&gt;
* &#039;&#039;&#039;OcelEventType&#039;&#039;&#039; (mapped to event type)&lt;br /&gt;
* &#039;&#039;&#039;OcelEventTime&#039;&#039;&#039; (mapped to timestamp)&lt;br /&gt;
* &#039;&#039;&#039;OcelEventId&#039;&#039;&#039;&lt;br /&gt;
* Object attributes of the base object type. Note that the object attribute values are &amp;quot;repeated&amp;quot; for all events belonging to the same object.&lt;br /&gt;
* Event attributes of the selected event types. Values are null for events that don&#039;t have the attribute.&lt;br /&gt;
&lt;br /&gt;
The base object type attributes are available as case attributes. As the object attribute values may change over time in the OCEL 2.0 data, the last attribute value is used as the case attribute value. Note that other object type&#039;s attributes are not available as case attributes, so the object for which the attributes are used, need to be set as the base object.&lt;br /&gt;
&lt;br /&gt;
== Save perspective to filter ==&lt;br /&gt;
It&#039;s possible to include the object-centric perspective to a stored filter. When a filter is selected, also the perspective in the filter is applied to the dashboard. This allows to quickly change perspectives for the entire dashboard. The chart specific perspective overrides the dashboard level perspective, so the dashboard level perspective is only applied for charts that don&#039;t have the chart specific perspective defined.&lt;br /&gt;
&lt;br /&gt;
Perspective can be added to a filter as follows:&lt;br /&gt;
# Go to the &#039;&#039;Process Discovery&#039;&#039; dashboard.&lt;br /&gt;
# Open the &#039;&#039;Session variables&#039;&#039; dialog in the dots menu on top right.&lt;br /&gt;
# Paste the filter json to the &#039;&#039;Value&#039;&#039; of the &#039;&#039;Filter&#039;&#039; variable (it might be easiest to start with a filter without filter rules, and then add the filter rules using the UI).&lt;br /&gt;
# Click &#039;&#039;Done&#039;&#039; button for the dialog.&lt;br /&gt;
# Save the filter by hovering the &#039;&#039;Unsaved filter&#039;&#039; (filters dropdown list) in the header and click &#039;&#039;Save as new filter&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Example: Filter json without any filter rules:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Items&amp;quot;: [],&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: {&lt;br /&gt;
    &amp;quot;ObjectType&amp;quot;: &amp;quot;Container&amp;quot;,&lt;br /&gt;
    &amp;quot;RecursionDepth&amp;quot;: 0&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Filter json with a filter rule:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Items&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;Type&amp;quot;: &amp;quot;IncludeEvents&amp;quot;,&lt;br /&gt;
      &amp;quot;Items&amp;quot;: [&lt;br /&gt;
        {&lt;br /&gt;
          &amp;quot;Type&amp;quot;: &amp;quot;Attribute&amp;quot;,&lt;br /&gt;
          &amp;quot;Attribute&amp;quot;: &amp;quot;OcelEventId&amp;quot;,&lt;br /&gt;
          &amp;quot;StringifiedValues&amp;quot;: [ &amp;quot;0Event 1&amp;quot; ]&lt;br /&gt;
        }&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  ],&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: {&lt;br /&gt;
    &amp;quot;ObjectType&amp;quot;: &amp;quot;Container&amp;quot;,&lt;br /&gt;
    &amp;quot;RecursionDepth&amp;quot;: 0&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Differences to OCEL 2.0 standard ==&lt;br /&gt;
Object-centric models in QPR ProcessAnalyzer are mainly following the OCEL 2.0 standard, but there are the following differences:&lt;br /&gt;
* Changing of object attributes values over time is not supported.&lt;br /&gt;
* &#039;&#039;ocel_time&#039;&#039; field of each event type table is moved to events datatable (as every event has a timestemp). &lt;br /&gt;
* &#039;&#039;*_map_type&#039;&#039; columns are not needed as the model settings are used for the same purpose. &lt;br /&gt;
* Object type tables: If OcelObjectTypeChangedField is not null, all the other field values are copied from the previous entry except: &lt;br /&gt;
** &#039;&#039;OcelObjectTypeChangedField&#039;&#039; which has the names of the changed fields as a comma separated string. &lt;br /&gt;
** The actual changed field which has the new value. &lt;br /&gt;
** &#039;&#039;OcelObjectTypeTime&#039;&#039; which has the timestamp when the value changed.&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Object-centric_Process_Mining_Model&amp;diff=26040</id>
		<title>Object-centric Process Mining Model</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Object-centric_Process_Mining_Model&amp;diff=26040"/>
		<updated>2025-03-25T14:17:14Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Object-centric model structure */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer supports object-centric process mining (OCPM) based on the OCEL 2.0 standard (https://www.ocel-standard.org). To use object-centric functionality, you need to transform data into the [[#Object-centric_model_structure|suitable format]] for the [[#Create_object-centric_model|object-centric model]]. Object-centric models can be analyzed in the object-centric flowchart and with (case-centric) charts because the object-centric model can be converted into a case-centric eventlog using [[#Object-centric_perspectives|perspectives]]. To use the OCPM functionality, Snowflake needs to be used as the calculation engine.&lt;br /&gt;
&lt;br /&gt;
== Create object-centric model ==&lt;br /&gt;
Create a new object-centric model as follows:&lt;br /&gt;
# In the Workspace, open the project where to create the model.&lt;br /&gt;
# Select &#039;&#039;&#039;NEW&#039;&#039;&#039; in the top right menu and select &#039;&#039;&#039;model&#039;&#039;&#039;.&lt;br /&gt;
# Define a name for the new model.&lt;br /&gt;
# Set &#039;&#039;&#039;Model type&#039;&#039;&#039; as &#039;&#039;&#039;Object-centric&#039;&#039;&#039;.&lt;br /&gt;
# Click &#039;&#039;&#039;Create&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Configure object-centric model datatables ==&lt;br /&gt;
Datatables for the object-centric model need to exist in the same project as the model. Datatables can be set for the model as follows:&lt;br /&gt;
# In the Workspace, select the object-centric model and click &#039;&#039;&#039;Properties&#039;&#039;&#039;.&lt;br /&gt;
# In the model properties dialog, open the &#039;&#039;&#039;Datasource&#039;&#039;&#039; tab.&lt;br /&gt;
# Add a following kind of json configuration to the textbox:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Objects&amp;quot;: &amp;quot;OCPM: objects&amp;quot;,&lt;br /&gt;
  &amp;quot;Events&amp;quot;: &amp;quot;OCPM: events&amp;quot;,&lt;br /&gt;
  &amp;quot;ObjectToObject&amp;quot;: &amp;quot;OCPM: object-object&amp;quot;,&lt;br /&gt;
  &amp;quot;EventToObject&amp;quot;: &amp;quot;OCPM: event-object&amp;quot;,&lt;br /&gt;
  &amp;quot;ObjectTypes&amp;quot;: {&lt;br /&gt;
    &amp;quot;Invoice&amp;quot;: &amp;quot;OCPM object: Invoice&amp;quot;,&lt;br /&gt;
    &amp;quot;Payment&amp;quot;: &amp;quot;OCPM object: Payment&amp;quot;,&lt;br /&gt;
    &amp;quot;Purchase Order&amp;quot;: &amp;quot;OCPM object: Purchase Order&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;EventTypes&amp;quot;: { &lt;br /&gt;
    &amp;quot;Approve Purchase Requisition&amp;quot;: &amp;quot;OCPM event: Approve Purchase Requisition&amp;quot;,&lt;br /&gt;
    &amp;quot;Change PO Quantity&amp;quot;: &amp;quot;OCPM event: Change PO Quantity&amp;quot;,&lt;br /&gt;
    &amp;quot;Create Purchase Order&amp;quot;: &amp;quot;OCPM event: Create Purchase Order&amp;quot;,&lt;br /&gt;
    &amp;quot;Insert Invoice&amp;quot;: &amp;quot;OCPM event: Insert Invoice&amp;quot;,&lt;br /&gt;
    &amp;quot;Insert Payment&amp;quot;: &amp;quot;OCPM event: Insert Payment&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The json configuration needs to have following properties:&lt;br /&gt;
* &#039;&#039;&#039;Objects&#039;&#039;&#039;: Objects datatable name.&lt;br /&gt;
* &#039;&#039;&#039;Events&#039;&#039;&#039;: Events datatable name.&lt;br /&gt;
* &#039;&#039;&#039;ObjectToObject&#039;&#039;&#039;: Object-to-object relation datatable name.&lt;br /&gt;
* &#039;&#039;&#039;EventToObject&#039;&#039;&#039;: Event-to-object relation datatable name.&lt;br /&gt;
* &#039;&#039;&#039;ObjectTypes&#039;&#039;&#039;: Key-value-pairs of object type datatable names. Note that object names need to match with object names in the objects datatable.&lt;br /&gt;
* &#039;&#039;&#039;EventTypes&#039;&#039;&#039;: Key-value-pairs of event type datatable names. Note that event names need to match with event names in the events datatable.&lt;br /&gt;
&lt;br /&gt;
It&#039;s also possible that all object attributes and all event attributes are in the same table:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Objects&amp;quot;: &amp;quot;OCPM: objects&amp;quot;,&lt;br /&gt;
  &amp;quot;Events&amp;quot;: &amp;quot;OCPM: events&amp;quot;,&lt;br /&gt;
  &amp;quot;ObjectToObject&amp;quot;: &amp;quot;OCPM: object-object&amp;quot;,&lt;br /&gt;
  &amp;quot;EventToObject&amp;quot;: &amp;quot;OCPM: event-object&amp;quot;,&lt;br /&gt;
  &amp;quot;ObjectTypes&amp;quot;: {&lt;br /&gt;
    &amp;quot;Invoice&amp;quot;: &amp;quot;OCPM object attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Payment&amp;quot;: &amp;quot;OCPM object attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Purchase Order&amp;quot;: &amp;quot;OCPM object attributes&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;EventTypes&amp;quot;: { &lt;br /&gt;
    &amp;quot;Approve Purchase Requisition&amp;quot;: &amp;quot;OCPM event attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Change PO Quantity&amp;quot;: &amp;quot;OCPM event attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Create Purchase Order&amp;quot;: &amp;quot;OCPM event attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Insert Invoice&amp;quot;: &amp;quot;OCPM event attributes&amp;quot;,&lt;br /&gt;
    &amp;quot;Insert Payment&amp;quot;: &amp;quot;OCPM event attributes&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note also that the Objects table (&amp;quot;OCPM: objects&amp;quot; in the above example) must not contain object types which are not specified in the ObjectTypes section (and same for event types in the EventTypes section).&lt;br /&gt;
&lt;br /&gt;
== Import from OCEL 2.0 JSON file ==&lt;br /&gt;
Object-centric model can be import from an OCEL 2.0 JSON file as follows:&lt;br /&gt;
# In the Workspace, open the project where to import the model.&lt;br /&gt;
# Select &#039;&#039;&#039;NEW&#039;&#039;&#039; in top right menu and select &#039;&#039;&#039;Import Model&#039;&#039;&#039;.&lt;br /&gt;
# Select the OCEL 2.0 JSON file from the disk and click &#039;&#039;&#039;Open&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
An object-centric model and a list of datatables is created.&lt;br /&gt;
&lt;br /&gt;
Example OCEL 2.0 eventlogs: https://www.ocel-standard.org/event-logs/overview/ (download the json format supported by QPR ProcessAnalyzer)&lt;br /&gt;
&lt;br /&gt;
== Filtering object-centric model ==&lt;br /&gt;
Object-centric models can be filtered by object attribute values which is similar to filtering by case attribute values in case-centric models. For the object attribute filtering, following settings are defined:&lt;br /&gt;
* Object type to be filtered&lt;br /&gt;
* Object attribute name&lt;br /&gt;
* Object attribute values&lt;br /&gt;
* Include or exclude logic&lt;br /&gt;
* Number of object relation steps&lt;br /&gt;
&lt;br /&gt;
When the number of object relation steps is zero, objects of only the selected type are filtered. For example, when including items, all other object types are excluded (when object relation steps is zero). When excluding items, all other object types are included (when object relation steps is zero).&lt;br /&gt;
&lt;br /&gt;
When object relation steps is one, objects directly related to the filtered object with an object-to-object relation are included or excluded.&lt;br /&gt;
&lt;br /&gt;
When object relation steps is two or more, several object-to-object relations are followed to find the included or excluded objects. The objects are traversed only in the same direction which is either forward or backward. This is based on the notion that in object-centric models, all object-to-object relations have a direction, i.e., starting object and ending object of the relation. When creating an object-centric model, this relation direction needs to be carefully selected to produce desired results in the filtering.&lt;br /&gt;
&lt;br /&gt;
Object attribute filter rules can be created for the entire dashboard by pressing the blue plus button in the dashboard header (requires that an object-centric model is selected for the dashboard). Object attribute filter rules can also be added for an individual chart/flowchart by opening the &#039;&#039;Filter&#039;&#039; tab in the chart/flowchart settings and pressing the &#039;&#039;&#039;Object-centric filter&#039;&#039;&#039; button.&lt;br /&gt;
&lt;br /&gt;
Alternatively, a chart showing object attribute values as dimension or columns, can be selected to start creating object attribute filter rules. When making the selection, user can choose between &#039;&#039;&#039;Include Objects&#039;&#039;&#039; and &#039;&#039;&#039;Exclude Objects&#039;&#039;&#039;. It&#039;s also possible to use the case-centric filtering by selecting &#039;&#039;&#039;Include Cases&#039;&#039;&#039; or &#039;&#039;&#039;Cases Cases&#039;&#039;&#039;. Note that the case-centric attribute filtering doesn&#039;t work with the object-centric flowchart and with other charts having different perspective selected.&lt;br /&gt;
&lt;br /&gt;
=== Changes for QPR ProcessAnalyzer 2025.3 ===&lt;br /&gt;
Starting from QPR ProcessAnalyzer 2025.3, it&#039;s possible to leave the object relation steps setting empty. This means that the object-to-object relations are followed in a way that all the object types are traversed once to find the related objects. This also means that relation direction (forward or backward) doesn&#039;t matter anymore.&lt;br /&gt;
&lt;br /&gt;
== Object-centric model structure ==&lt;br /&gt;
Object-centric model contains datatables described in the table below. Datatables can be named freely, as the model json configuration is used to define the datatable for each type of data. The datatables need to use column names specified in the table below because those are the column names assumed by the object-centric (i.e., column names cannot be selected freely).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Datatable role&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Contained data&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Datatable columns&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||Objects&lt;br /&gt;
||Objects in the model (one row per object).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectId&#039;&#039;&#039;: Unique id for the object (among all objects in the model).&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectType&#039;&#039;&#039;: Object type name (such as Order, Invoice, Delivery). Note that the model json configuration need to use same object type names.&lt;br /&gt;
|-&lt;br /&gt;
||Events&lt;br /&gt;
||Events in the model (one row per event).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelEventId&#039;&#039;&#039;: Unique id for the event (among all events in the model).&lt;br /&gt;
* &#039;&#039;&#039;OcelEventType&#039;&#039;&#039;: Event type name (such as Order created, Invoice sent). Note that the model json configuration need to use same event type names.&lt;br /&gt;
* &#039;&#039;&#039;OcelEventTime&#039;&#039;&#039;: Event timestamp.&lt;br /&gt;
|-&lt;br /&gt;
||Object-object relations&lt;br /&gt;
||Relations between objects (one row per relation).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectToObjectSourceId&#039;&#039;&#039;: Source object id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectToObjectTargetId&#039;&#039;&#039;: Target object id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectToObjectQualifier&#039;&#039;&#039;: Describes the type of the relation as free-form text (not used currently).&lt;br /&gt;
|-&lt;br /&gt;
||Event-object relations&lt;br /&gt;
||Relations between events and objects (one row per relation).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelEventToObjectSourceId&#039;&#039;&#039;: Event id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelEventToObjectTargetId&#039;&#039;&#039;: Object id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelEventToObjectQualifier&#039;&#039;&#039;: Describes the type of the relation as free-form text (not used currently).&lt;br /&gt;
|-&lt;br /&gt;
||Object attributes (several datatables)&lt;br /&gt;
||Object attribute values, each object type in a separate table (one row per object).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectTypeObjectId&#039;&#039;&#039;: Object id. Matches to the objects datatable &#039;&#039;OcelObjectId&#039;&#039; column.&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectTypeTime&#039;&#039;&#039;: Timestamp which the attribute value is valid from (not used currently).&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectTypeChangedField&#039;&#039;&#039;: Changed object attribute name (not used currently).&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;Object attributes&amp;gt;&#039;&#039;&#039;: Columns for each of the object attribute values (column name is the object attribute name).&lt;br /&gt;
|-&lt;br /&gt;
||Event attributes (several datatables)&lt;br /&gt;
||Event attribute values, each event type in a separate table (one row per event).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelEventTypeEventId&#039;&#039;&#039;: Event id. Matches to the events datatable &#039;&#039;OcelEventId&#039;&#039; column.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;Event attributes&amp;gt;&#039;&#039;&#039;: Columns for each of the event attribute values (column name is the event attribute name).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Object-centric perspectives ==&lt;br /&gt;
Perspectives convert an object-centric model into the traditional case-centric eventlog, allowing to view and analyze object-centric models in analyses provided by charts. A single perspective is not able describe the object-centric model entirely, but just from a certain limited viewpoint. By using analyses with several perspectives, it&#039;s possible to get a more complete picture of the object-centric model. The perspective starts from a certain object type and traverses the object-object relations as many steps as desired.&lt;br /&gt;
&lt;br /&gt;
To define a perspective, the following settings are defined in the chart settings:&lt;br /&gt;
* &#039;&#039;&#039;Base Object type&#039;&#039;&#039;: Object of this type will be cases in the projected case-centric eventlog.&lt;br /&gt;
* &#039;&#039;&#039;Object Relation Steps&#039;&#039;&#039;: Specifies how many object-object relations will be traversed in order to find events connected to the base objects. Value zero means that only those events are returned that are directly connected to the base objects.&lt;br /&gt;
* &#039;&#039;&#039;Show Event Types&#039;&#039;&#039;: List of event type names which are included into the perspective eventlog. If no events are explicitly defined, all events will be included, but their event attributes are not included.&lt;br /&gt;
&lt;br /&gt;
The resulting perspective eventlog will have the following columns:&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectId&#039;&#039;&#039; (mapped to case id)&lt;br /&gt;
* &#039;&#039;&#039;OcelEventType&#039;&#039;&#039; (mapped to event type)&lt;br /&gt;
* &#039;&#039;&#039;OcelEventTime&#039;&#039;&#039; (mapped to timestamp)&lt;br /&gt;
* &#039;&#039;&#039;OcelEventId&#039;&#039;&#039;&lt;br /&gt;
* Object attributes of the base object type. Note that the object attribute values are &amp;quot;repeated&amp;quot; for all events belonging to the same object.&lt;br /&gt;
* Event attributes of the selected event types. Values are null for events that don&#039;t have the attribute.&lt;br /&gt;
&lt;br /&gt;
The base object type attributes are available as case attributes. As the object attribute values may change over time in the OCEL 2.0 data, the last attribute value is used as the case attribute value. Note that other object type&#039;s attributes are not available as case attributes, so the object for which the attributes are used, need to be set as the base object.&lt;br /&gt;
&lt;br /&gt;
== Save perspective to filter ==&lt;br /&gt;
It&#039;s possible to include the object-centric perspective to a stored filter. When a filter is selected, also the perspective in the filter is applied to the dashboard. This allows to quickly change perspectives for the entire dashboard. The chart specific perspective overrides the dashboard level perspective, so the dashboard level perspective is only applied for charts that don&#039;t have the chart specific perspective defined.&lt;br /&gt;
&lt;br /&gt;
Perspective can be added to a filter as follows:&lt;br /&gt;
# Go to the &#039;&#039;Process Discovery&#039;&#039; dashboard.&lt;br /&gt;
# Open the &#039;&#039;Session variables&#039;&#039; dialog in the dots menu on top right.&lt;br /&gt;
# Paste the filter json to the &#039;&#039;Value&#039;&#039; of the &#039;&#039;Filter&#039;&#039; variable (it might be easiest to start with a filter without filter rules, and then add the filter rules using the UI).&lt;br /&gt;
# Click &#039;&#039;Done&#039;&#039; button for the dialog.&lt;br /&gt;
# Save the filter by hovering the &#039;&#039;Unsaved filter&#039;&#039; (filters dropdown list) in the header and click &#039;&#039;Save as new filter&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Example: Filter json without any filter rules:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Items&amp;quot;: [],&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: {&lt;br /&gt;
    &amp;quot;ObjectType&amp;quot;: &amp;quot;Container&amp;quot;,&lt;br /&gt;
    &amp;quot;RecursionDepth&amp;quot;: 0&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Filter json with a filter rule:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Items&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;Type&amp;quot;: &amp;quot;IncludeEvents&amp;quot;,&lt;br /&gt;
      &amp;quot;Items&amp;quot;: [&lt;br /&gt;
        {&lt;br /&gt;
          &amp;quot;Type&amp;quot;: &amp;quot;Attribute&amp;quot;,&lt;br /&gt;
          &amp;quot;Attribute&amp;quot;: &amp;quot;OcelEventId&amp;quot;,&lt;br /&gt;
          &amp;quot;StringifiedValues&amp;quot;: [ &amp;quot;0Event 1&amp;quot; ]&lt;br /&gt;
        }&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  ],&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: {&lt;br /&gt;
    &amp;quot;ObjectType&amp;quot;: &amp;quot;Container&amp;quot;,&lt;br /&gt;
    &amp;quot;RecursionDepth&amp;quot;: 0&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Differences to OCEL 2.0 standard ==&lt;br /&gt;
Object-centric models in QPR ProcessAnalyzer are mainly following the OCEL 2.0 standard, but there are the following differences:&lt;br /&gt;
* Changing of object attributes values over time is not supported.&lt;br /&gt;
* &#039;&#039;ocel_time&#039;&#039; field of each event type table is moved to events datatable (as every event has a timestemp). &lt;br /&gt;
* &#039;&#039;*_map_type&#039;&#039; columns are not needed as the model settings are used for the same purpose. &lt;br /&gt;
* Object type tables: If OcelObjectTypeChangedField is not null, all the other field values are copied from the previous entry except: &lt;br /&gt;
** &#039;&#039;OcelObjectTypeChangedField&#039;&#039; which has the names of the changed fields as a comma separated string. &lt;br /&gt;
** The actual changed field which has the new value. &lt;br /&gt;
** &#039;&#039;OcelObjectTypeTime&#039;&#039; which has the timestamp when the value changed.&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=26025</id>
		<title>Expression Script Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=26025"/>
		<updated>2025-03-19T14:13:25Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Perform a query and send results as E-mail in a HTML table */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains script examples written in the QPR ProcessAnalyzer expression language. See how expression scripts can be created in the [[Managing_Scripts#Creating_Script|Workspace]]. For documentation for the syntax, functions and entities can be found from the main page in the [[QPR_ProcessAnalyzer_Wiki#For_Developers|KPI Expression Language]] section.&lt;br /&gt;
&lt;br /&gt;
== Calling Expression Script from Expression ==&lt;br /&gt;
Expression scripts can be called from an expression using the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Run]] function with the following syntax:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: false,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 123.45&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command waits until the run is completed, and the return value of the called script is returned by the Run function call.&lt;br /&gt;
&lt;br /&gt;
Parameters can be passed to the called script, and the parameters are available as variables in the script. The parameters can contain any type of data.&lt;br /&gt;
&lt;br /&gt;
Expression scripts can also be called from a dashboard. Expressions can be stored to scripts instead of dashboards, which is a way to separate complex expressions from dashboards and allow to reuse expressions across several dashboards.&lt;br /&gt;
&lt;br /&gt;
== Calling SQL Script from Expression ==&lt;br /&gt;
SQL script can be called from an expression using the Run function as follows (similar to calling [[#Calling Expression Script from Expression|expression scripts]]):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 321&lt;br /&gt;
});&lt;br /&gt;
let arrayOfAllReports = result.Keys;&lt;br /&gt;
let report1 = result.Report1;&lt;br /&gt;
let report2 = result.Report2;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL scripts can return multiple &#039;&#039;reports&#039;&#039;, which are combined to a dictionary, where the key is the name of the report (&amp;quot;sheet name&amp;quot;) and value is the report data as a DataFrame. See in the above example, how the reports can be accessed by their name.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Call web service===&lt;br /&gt;
Contact to a web service, fetch some data, and store it to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let datatableName = &amp;quot;Web Service Data&amp;quot;;&lt;br /&gt;
let webServiceData = CallWebService(&lt;br /&gt;
    #{&amp;quot;Address&amp;quot;: &amp;quot;https://processanalyzer.onqpr.com/qprpa/api/serverinfo&amp;quot;}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
let targetDatatable = Project.Datatables.Where(name==datatableName);&lt;br /&gt;
if (Count(targetDatatable) == 0) {&lt;br /&gt;
	targetDatatable = Project.CreateDatatable(datatableName)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting value&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Data read&amp;quot;, &amp;quot;DateTime&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
	targetDatatable = targetDatatable[0];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let currentTime = Now;&lt;br /&gt;
let dataAsDf = ToDataFrame(&lt;br /&gt;
	webServiceData.keys.{&lt;br /&gt;
        let key = _;&lt;br /&gt;
        [key, webServiceData[key], currentTime];&lt;br /&gt;
    },&lt;br /&gt;
	[&amp;quot;Setting name&amp;quot;, &amp;quot;Setting value&amp;quot;, &amp;quot;Data read&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
targetDatatable.Import(dataAsDf, #{&amp;quot;Append&amp;quot;:true});&lt;br /&gt;
WriteLog(`${CountTop(dataAsDf.Rows)} rows written to datatable`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Store data to datatable ===&lt;br /&gt;
Get all models in the system and store them to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let newDatatable = Project&lt;br /&gt;
    .CreateDatatable(&amp;quot;Models list &amp;quot; + ToString(Now, &amp;quot;dd.MM.yyyy HH:mm:ss&amp;quot;))&lt;br /&gt;
    .AddColumn(&amp;quot;Model name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Project name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Created time&amp;quot;, &amp;quot;DateTime&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Cases&amp;quot;, &amp;quot;Integer&amp;quot;);&lt;br /&gt;
let startTime = Now;&lt;br /&gt;
let modelsData = ToDataFrame(&lt;br /&gt;
    Models.([Name, Project.Name, CreatedDate, NCases]),&lt;br /&gt;
    [&amp;quot;Model name&amp;quot;, &amp;quot;Project name&amp;quot;, &amp;quot;Created time&amp;quot;, &amp;quot;Cases&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
WriteLog(`Listing models took ${(Now - startTime).TotalSeconds.Round(2)} seconds.`);&lt;br /&gt;
newDatatable.Import(modelsData);&lt;br /&gt;
WriteLog(`Datatable ${newDatatable.Id} created.`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data ===&lt;br /&gt;
This script can be used to convert a single column into numerical data type. To use the script, you need to setup the following in the beginning of the script:&lt;br /&gt;
* Project name where the datatable is located.&lt;br /&gt;
* Datatable name&lt;br /&gt;
* Name of the column to be converted&lt;br /&gt;
&lt;br /&gt;
Note that the conversion fails, if there is data that cannot be converted into numerical format. The conversion assumes that period (.) is used as the decimal point. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let projectName = &amp;quot;New Project&amp;quot;;&lt;br /&gt;
let datatableName = &amp;quot;qpr processanalyzer events&amp;quot;;&lt;br /&gt;
let columnName = &amp;quot;Event order in case&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let project = (Projects.Where(Name==projectName))[0];&lt;br /&gt;
let datatable = (project.Datatables.Where(Name==datatableName))[0];&lt;br /&gt;
DatatableById(datatable.Id).DataFrame&lt;br /&gt;
.SetColumns([&lt;br /&gt;
	columnName: () =&amp;gt; {&lt;br /&gt;
		let data = Column(columnName);&lt;br /&gt;
		if (data == null) {&lt;br /&gt;
			null;&lt;br /&gt;
		 } else {&lt;br /&gt;
			ToFloat(data);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
])&lt;br /&gt;
.Persist(datatable.Name, [&amp;quot;ProjectId&amp;quot;: project.Id, &amp;quot;Append&amp;quot;: false]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of converting to numeric (with the &#039;&#039;ToFloat&#039;&#039; function), data can be converted into string using the &#039;&#039;ToString&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Show DataFrame as HTML table ===&lt;br /&gt;
&lt;br /&gt;
This script defines a function to show dataframe as a HTML table, and uses the function for a literal dataframe.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function dataframeToHtmlTable(df) {&lt;br /&gt;
	return&lt;br /&gt;
`&amp;lt;table&amp;gt;&lt;br /&gt;
	&amp;lt;tr&amp;gt;&lt;br /&gt;
		${StringJoin(&amp;quot;\r\n\t\t&amp;quot;,  + df.columns.`&amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;`)}&lt;br /&gt;
	&amp;lt;/tr&amp;gt;&lt;br /&gt;
	${StringJoin(&amp;quot;&amp;quot;, df.Rows.(&lt;br /&gt;
		&amp;quot;\r\n\t&amp;lt;tr&amp;gt;&amp;quot; + StringJoin(&amp;quot;&amp;quot;, _.`\r\n\t\t&amp;lt;td&amp;gt;${ToString(_)}&amp;lt;/td&amp;gt;`) + &amp;quot;\r\n\t&amp;lt;/tr&amp;gt;&amp;quot;&lt;br /&gt;
	))}&lt;br /&gt;
&amp;lt;/table&amp;gt;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let data = ToDataFrame(&lt;br /&gt;
	[&lt;br /&gt;
		[&amp;quot;one&amp;quot;, &amp;quot;two&amp;quot;, &amp;quot;three&amp;quot;],&lt;br /&gt;
		[&amp;quot;four&amp;quot;, &amp;quot;five&amp;quot;, &amp;quot;six&amp;quot;],&lt;br /&gt;
		[&amp;quot;seven&amp;quot;, &amp;quot;eight&amp;quot;, &amp;quot;nine&amp;quot;]&lt;br /&gt;
	],&lt;br /&gt;
	[&amp;quot;Column 1&amp;quot;, &amp;quot;Column 2&amp;quot;, &amp;quot;Column 3&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
return dataframeToHtmlTable(data);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy local datatables to Snowflake ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
// Copies all datatables in a project to another project including datatable contents.&lt;br /&gt;
// Usage instructions:&lt;br /&gt;
// 1. Create expression script in the project from where you want to copy the datatables.&lt;br /&gt;
// 2. Create a new project named as &amp;quot;&amp;lt;name of the project to be moved&amp;gt; - Snowflake&amp;quot;. New datatables will be created here. E.g., when moving project named &amp;quot;SAP_OrderToCash&amp;quot;, the target project should be named as &amp;quot;SAP_OrderToCash - Snowflake&amp;quot;.&lt;br /&gt;
// 3. Run the script.&lt;br /&gt;
// NOTE: Columns of type &amp;quot;Any&amp;quot; will be created as &amp;quot;String&amp;quot;-columns in Snowflake, thus it is recommended that actual data types are set for the tables prior to the move.&lt;br /&gt;
&lt;br /&gt;
let sourceProject = Project;&lt;br /&gt;
let sourceProjectName = Project.Name;&lt;br /&gt;
let targetProjectName = `${sourceProjectName} - Snowflake`;&lt;br /&gt;
let targetProject = First(Projects.Where(Name == targetProjectName));&lt;br /&gt;
if (IsNull(targetProject)) {&lt;br /&gt;
  WriteLog(`Unable to find target project named &amp;quot;${targetProjectName}&amp;quot;. Aborting operation.`);&lt;br /&gt;
  return;&lt;br /&gt;
}&lt;br /&gt;
let dts = sourceProject.DataTables;&lt;br /&gt;
WriteLog(`Copying all ${CountTop(dts)} data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
dts.{&lt;br /&gt;
  let sourceDt = _;&lt;br /&gt;
  WriteLog(`Starting to copy data table &amp;quot;${Name}&amp;quot; (id: ${Id}) having ${NRows} rows and ${NColumns} columns.`);&lt;br /&gt;
  let targetDt;&lt;br /&gt;
  targetDt = targetProject.DatatableByName(sourceDt.Name);&lt;br /&gt;
  if (targetDt == null) {&lt;br /&gt;
    targetDt = targetProject.CreateDataTable(sourceDt.Name, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: targetProject.Id})});&lt;br /&gt;
    targetDt.Import(sourceDt.SqlDataFrame);&lt;br /&gt;
    WriteLog(`Finished copying data table &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  } else {&lt;br /&gt;
    WriteLog(`Datatable already exist &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
WriteLog(`Finished copying all the data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to copy the data but only create the Snowflake datatables with columns, you can change the line 22 to&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
targetDt.Import(sourceDt.SqlDataFrame.head(0));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy single datatable to Snowflake ===&lt;br /&gt;
This script creates a copy of a single datatable to Snowflake. Replace the &#039;&#039;&amp;lt;tableId1&amp;gt;&#039;&#039; with the id of the source datatable.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function CopyDataTableToSnowflake(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let sourceDt = DataTableById(dataTableId);&lt;br /&gt;
  sourceDt.SqlDataFrame.Persist(`${sourceDt.Name} - Snowflake`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: sourceDt.Project.Id})});&lt;br /&gt;
}&lt;br /&gt;
CopyDataTableToSnowflake(&amp;lt;tableId1&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a copy of a data table that has all Any-type columns changed to String-type columns ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function ConvertAnyDataTypesToStringsToNewTable(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let dt = DataTableById(dataTableId);&lt;br /&gt;
  let sdf = dt.SqlDataFrame;&lt;br /&gt;
  let cts = dt.ColumnTypes;&lt;br /&gt;
  cts.{&lt;br /&gt;
    let ct = _;&lt;br /&gt;
    if (ct.DataType == &amp;quot;Any&amp;quot;) {&lt;br /&gt;
      let n = ct.Name;&lt;br /&gt;
      sdf = sdf.WithColumn(ct.Name, #sql{Cast(Column(Variable(&amp;quot;n&amp;quot;)), &amp;quot;ShortString&amp;quot;)});&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  sdf.Persist(`${dt.Name} - Converted`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;ProjectId&amp;quot;: dt.Project.Id});&lt;br /&gt;
}&lt;br /&gt;
ConvertAnyDataTypesToStringsToNewTable(&amp;lt;dataTableId&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Query number of rows in given data table having a datetime value in given year grouped by month and return resulting table as CSV ===&lt;br /&gt;
SqlDataFrame is used in order to prevent loading the whole datatable into memory first. Filtering is performed as first operation in order to minimize the amount of required work for the data source of the data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
DataTableById(&amp;lt;data table id&amp;gt;)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .Where(#sql{2014 == Year(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .WithColumn(&amp;quot;Month&amp;quot;, #sql{Month(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .GroupBy([&amp;quot;Month&amp;quot;]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
  .OrderByColumns([&amp;quot;Month&amp;quot;], [true])&lt;br /&gt;
  .Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Function for filtering SqlDataFrame by removing rows having, or replacing, the most infrequently occurring column values ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/***&lt;br /&gt;
 * @name ColumnWithMinUsage&lt;br /&gt;
 * @descripion&lt;br /&gt;
 * Generic function that can be used to filter out the most infrequently occurring attribute values or replace their Values&lt;br /&gt;
 * with given common value.&lt;br /&gt;
 * @param df:&lt;br /&gt;
 * DataFrame to operate on.&lt;br /&gt;
 * @param columnName:&lt;br /&gt;
 * Name of the column to be filtered.&lt;br /&gt;
 * @param newColumnName:&lt;br /&gt;
 * Name of the column that will contain the new value of the original column after filtering (if includeOthers was applied).&lt;br /&gt;
 * @param maxNumUniqueValues:&lt;br /&gt;
 * Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
 * @param minValueUsage:&lt;br /&gt;
 * Minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
 * @param includeOthers:&lt;br /&gt;
 * Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
 * If not empty/null, defines the name used for these other-values.&lt;br /&gt;
 */&lt;br /&gt;
function ColumnWithMinUsage(df, columnName, newColumnName, maxNumUniqueValues, minValueUsage, includeOthers)&lt;br /&gt;
{&lt;br /&gt;
  let all = df&lt;br /&gt;
	.GroupBy([])&lt;br /&gt;
	.Aggregate([&amp;quot;NAllTotal&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
	.WithColumn(&amp;quot;__Join2&amp;quot;, #sql{1});&lt;br /&gt;
  let minValueUsageEnabled = !IsNullTop(minValueUsage);&lt;br /&gt;
  let maxNumUniqueValuesEnabled = !IsNullTop(maxNumUniqueValues);&lt;br /&gt;
  if (minValueUsageEnabled || maxNumUniqueValuesEnabled) {&lt;br /&gt;
	// Perform column value-based filtering if minValueUsageEnabled or maxNumUniqueValuesEnabled is defined.&lt;br /&gt;
    let valueColumnName = &amp;quot;__ValueNew&amp;quot;;&lt;br /&gt;
	let filteredValuesColumns = [valueColumnName: columnName];&lt;br /&gt;
	let filteredValues = df&lt;br /&gt;
	  .GroupBy([columnName]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;]);&lt;br /&gt;
	if (minValueUsageEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithColumn(&amp;quot;__Join&amp;quot;, #sql{1})&lt;br /&gt;
		.Join(all, [&amp;quot;__Join&amp;quot;: &amp;quot;__Join2&amp;quot;], &amp;quot;leftouter&amp;quot;)&lt;br /&gt;
        .WithColumn(&amp;quot;Usage&amp;quot;, #sql{Column(&amp;quot;Count&amp;quot;) / Column(&amp;quot;NAllTotal&amp;quot;)});&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;Usage&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
	if (maxNumUniqueValuesEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithRowNumberColumn(&amp;quot;RowNumber&amp;quot;, [&amp;quot;Count&amp;quot;], null, [false]);&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;RowNumber&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	filteredValues = filteredValues&lt;br /&gt;
	  .Select(filteredValuesColumns);&lt;br /&gt;
&lt;br /&gt;
	// Generate select returning all the accepted values.&lt;br /&gt;
	let allValues = filteredValues&lt;br /&gt;
	  .(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;gt;= #expr{minValueUsage}}) : _)&lt;br /&gt;
	  .(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;lt;= #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
	  .Select([valueColumnName, newColumnName: valueColumnName]);&lt;br /&gt;
&lt;br /&gt;
	if (!IsNullTop(includeOthers)) {&lt;br /&gt;
	  // If includeOthers is defined, replace original values with the variable defined in includeOthers.&lt;br /&gt;
	  let otherValues = filteredValues&lt;br /&gt;
		.(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;lt; #expr{minValueUsage}}) : _)&lt;br /&gt;
		.(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;gt; #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
		.WithColumn(newColumnName, #sql{#expr{includeOthers}})&lt;br /&gt;
		.Select([valueColumnName, newColumnName]);&lt;br /&gt;
	  allValues = allValues.Append(otherValues)&lt;br /&gt;
	}&lt;br /&gt;
	df.Join(allValues, [columnName: valueColumnName], &amp;quot;inner&amp;quot;)&lt;br /&gt;
	  .RemoveColumns([valueColumnName]);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// The following example will return only rows containing two of the most common values for Region-column.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, 2, null, null);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&lt;br /&gt;
// The following example will return all input rows, but will replace the values of rows whose Region-column&lt;br /&gt;
// has a value used by less than 15% of all the rows with a new value: &amp;quot;_Others&amp;quot;.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, null, 0.15, &amp;quot;_Others&amp;quot;);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export model events and cases ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function ExportModelEvents(m) {&lt;br /&gt;
  let attrs = m.EventAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Events.Concat(&lt;br /&gt;
      [Case.Name, Type.Name, ToString(TimeStamp, &amp;quot;yyyy-MM-dd HH:mm:ss.fff&amp;quot;)], &lt;br /&gt;
      {let evt = _; attrs.{let att = _; evt.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;, &amp;quot;EventType&amp;quot;, &amp;quot;TimeStamp&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
function ExportModelCases(m) {&lt;br /&gt;
  let attrs = m.CaseAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Cases.Concat(&lt;br /&gt;
      [Name], &lt;br /&gt;
      {let cas = _; attrs.{let att = _; cas.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).EventsDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).CasesDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Calculate all the value usages of a single column for each event in event data table ===&lt;br /&gt;
This query could be used, e.g., to find out the maximum resource usage for every resource found in the event data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function WithUsageColumns(resourceColumn)&lt;br /&gt;
{&lt;br /&gt;
  function WithTotalUsageColumnOfSingleResource(resourceColumn, resourceValue)&lt;br /&gt;
  {&lt;br /&gt;
    _&lt;br /&gt;
      .WithColumn(&amp;quot;_Prev&amp;quot;, #sql{Lag(Column(resourceColumn), [TimeStamp, EventType], [true, true], [CaseId], 1, null)})&lt;br /&gt;
      .WithColumn(&amp;quot;_UsageDiff&amp;quot;, #sql{&lt;br /&gt;
        CaseWhen(&lt;br /&gt;
          Column(resourceColumn) == Column(&amp;quot;_Prev&amp;quot;), 0, &lt;br /&gt;
          Column(&amp;quot;_Prev&amp;quot;) == #expr{resourceValue}, -1,&lt;br /&gt;
          Column(resourceColumn) == #expr{resourceValue}, 1,&lt;br /&gt;
          0)&lt;br /&gt;
      })&lt;br /&gt;
      .WithColumn(`${resourceValue}_Usage`, #sql{Sum(Column(&amp;quot;_UsageDiff&amp;quot;), [TimeStamp, EventType])})&lt;br /&gt;
      .RemoveColumns([&amp;quot;_Prev&amp;quot;, &amp;quot;_UsageDiff&amp;quot;])&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let sdf = _;&lt;br /&gt;
  let allValues = sdf.SelectDistinct([resourceColumn]).OrderByColumns([resourceColumn], [true]).Collect().Column(resourceColumn);&lt;br /&gt;
  allValues.{&lt;br /&gt;
    let v = _;&lt;br /&gt;
    sdf = sdf.WithTotalUsageColumnOfSingleResource(resourceColumn, v)&lt;br /&gt;
  }&lt;br /&gt;
  sdf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
dt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .WithUsageColumns(&amp;lt;resource column name&amp;gt;)&lt;br /&gt;
  .OrderByColumns([dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]], [true])&lt;br /&gt;
  .Collect().ToCsv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;model id&amp;gt; is the id of the model containing event data to be examined.&lt;br /&gt;
* &amp;lt;resource column name&amp;gt; is the name of the column in the event data table of the specified model containing the resource being used by that event.&lt;br /&gt;
&lt;br /&gt;
NOTE: This expression uses functionalities that are only supported in Snowflake-based data tables.&lt;br /&gt;
&lt;br /&gt;
=== Create new Snowflake model from filter ===&lt;br /&gt;
This script creates a new Snowflake model (and two datatables for cases and events) containing filtered event log from given filter id. The script also works if the model doesn&#039;t have a cases datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let filter = FilterById(1); // filter id&lt;br /&gt;
let model = filter.model;&lt;br /&gt;
let project = model.project;&lt;br /&gt;
let nameSuffix = &amp;quot; - &amp;quot; + filter.name + &amp;quot; - &amp;quot; + ToString(Now, &amp;quot;dd-MM-yyyy HH:mm:ss&amp;quot;);&lt;br /&gt;
let eventsDatatableName = model.EventsDataTable.Name + nameSuffix;&lt;br /&gt;
if (eventsDatatableName.length &amp;gt; 440) {&lt;br /&gt;
  eventsDatatableName = eventsDatatableName.Substring(eventsDatatableName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
let eventsData = model&lt;br /&gt;
  .EventsDataTable&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .ApplyFilter(&lt;br /&gt;
    filter.rules,&lt;br /&gt;
    model.CasesDataTable?.SqlDataFrame&lt;br /&gt;
  );&lt;br /&gt;
project&lt;br /&gt;
  .CreateDatatable(eventsDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
  .Import(eventsData);&lt;br /&gt;
let modelConfiguration = model.Configuration;&lt;br /&gt;
modelConfiguration.DataSource.Events.Set(&amp;quot;DataTableName&amp;quot;, eventsDatatableName);&lt;br /&gt;
if (model.CasesDataTable != null) {&lt;br /&gt;
  let eventsDataCaseIdColumn = &amp;quot;CaseId_&amp;quot; + ToString(Random());&lt;br /&gt;
  let casesDatatableName = model.CasesDataTable.Name + nameSuffix;&lt;br /&gt;
  if (casesDatatableName.length &amp;gt; 440) {&lt;br /&gt;
    casesDatatableName = casesDatatableName.Substring(casesDatatableName.length - 440);&lt;br /&gt;
  }&lt;br /&gt;
  let casesData = model&lt;br /&gt;
    .CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .join(&lt;br /&gt;
	  eventsData.SelectDistinct([eventsDataCaseIdColumn: modelConfiguration.DataSource.Events.Columns.CaseId]),&lt;br /&gt;
      [modelConfiguration.DataSource.Cases.Columns.CaseId: eventsDataCaseIdColumn]&lt;br /&gt;
	).Select(model.CasesDataTable.ColumnNames);&lt;br /&gt;
  project&lt;br /&gt;
    .CreateDatatable(casesDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
    .Import(casesData);&lt;br /&gt;
  modelConfiguration.DataSource.Cases.Set(&amp;quot;DataTableName&amp;quot;, casesDatatableName);&lt;br /&gt;
}&lt;br /&gt;
let modelName = model.Name + nameSuffix;&lt;br /&gt;
if (modelName &amp;gt; 440) {&lt;br /&gt;
  modelName = modelName.Substring(modelName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
project&lt;br /&gt;
  .CreateModel(#{    &lt;br /&gt;
    &amp;quot;Name&amp;quot;: modelName,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: model.Description,&lt;br /&gt;
    &amp;quot;Configuration&amp;quot;: modelConfiguration&lt;br /&gt;
  });&lt;br /&gt;
return modelName;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Creating a model consisting of multiple copies of cases in an existing model ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @name CreateTestModel&lt;br /&gt;
 * @description&lt;br /&gt;
 * Creates a new model (or overwrites an existing) to given target project with given number of &lt;br /&gt;
 * repetitions of given source model.&lt;br /&gt;
 * Each repetition will generate &amp;quot;&amp;lt;N&amp;gt;-&amp;quot;-prefix to CaseId-columns, where N equals to the repeat index.&lt;br /&gt;
 * @param sourceModel&lt;br /&gt;
 * PA model used for the source data and from where the connection is copied for the target model if a &lt;br /&gt;
 * new one has to be created.&lt;br /&gt;
 * @param numRepeats&lt;br /&gt;
 * Number of times the data in the source model should be repeated in the generated model.&lt;br /&gt;
 * @param targetProject&lt;br /&gt;
 * Project in which the target model resides.&lt;br /&gt;
 * @param targetModelName&lt;br /&gt;
 * Specifies the name of the test model in the given target project. If a model already exists with &lt;br /&gt;
 * given name, event and case data in this model will be replaced with the new generated event and &lt;br /&gt;
 * case data.&lt;br /&gt;
 * @returns&lt;br /&gt;
 * Model object of the test model having the newly generated data.&lt;br /&gt;
 */&lt;br /&gt;
function CreateTestModel(sourceModel, numRepeats, targetProject, targetModelName) &lt;br /&gt;
{&lt;br /&gt;
  let eventsColumnMappings = sourceModel.EventsDataTable.ColumnMappings;&lt;br /&gt;
  let casesColumnMappings = sourceModel.CasesDataTable.ColumnMappings;&lt;br /&gt;
  let connection = sourceModel.EventsDataTable.DataSourceConnection;&lt;br /&gt;
&lt;br /&gt;
  function CreateResultModel()&lt;br /&gt;
  {&lt;br /&gt;
    function GetTable(tableName) &lt;br /&gt;
    {&lt;br /&gt;
      let tableConfiguration = #{&lt;br /&gt;
        &amp;quot;Name&amp;quot;: tableName,&lt;br /&gt;
        &amp;quot;Connection&amp;quot;: connection&lt;br /&gt;
      };&lt;br /&gt;
      let resultTable = targetProject.DataTableByName(tableName);&lt;br /&gt;
      if (resultTable == null)&lt;br /&gt;
      {&lt;br /&gt;
        resultTable = targetProject.CreateDataTable(tableConfiguration)&lt;br /&gt;
          .Modify(#{&amp;quot;NameInDataSource&amp;quot;: null})&lt;br /&gt;
          .Synchronize();&lt;br /&gt;
      }&lt;br /&gt;
      return resultTable;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let eventsTableName = `${targetModelName} - events`;&lt;br /&gt;
    let casesTableName = `${targetModelName} - cases`;&lt;br /&gt;
    let targetModel = targetProject.ModelByName(targetModelName);&lt;br /&gt;
    let eventsTable, casesTable = null;&lt;br /&gt;
&lt;br /&gt;
    if (targetModel != null)&lt;br /&gt;
    {&lt;br /&gt;
      eventsTable = targetModel.EventsDataTable;&lt;br /&gt;
      casesTable = targetModel.CasesDataTable;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      eventsTable = GetTable(eventsTableName);&lt;br /&gt;
      if (sourceModel.CasesDataTable != null) {&lt;br /&gt;
        casesTable = GetTable(casesTableName);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      let timestampMapping = eventsColumnMappings[&amp;quot;TimeStamp&amp;quot;];&lt;br /&gt;
      eventsColumnMappings.Remove(&amp;quot;TimeStamp&amp;quot;);&lt;br /&gt;
      eventsColumnMappings.Set(&amp;quot;Timestamp&amp;quot;, timestampMapping);&lt;br /&gt;
&lt;br /&gt;
      let modelConfiguration = #{&lt;br /&gt;
        &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
          &amp;quot;Events&amp;quot;:#{&lt;br /&gt;
            &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
            &amp;quot;DataTableName&amp;quot;: eventsTableName,&lt;br /&gt;
            &amp;quot;Columns&amp;quot;: eventsColumnMappings&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      if (casesColumnMappings != null) {&lt;br /&gt;
        modelConfiguration[&amp;quot;DataSource&amp;quot;].Set(&amp;quot;Cases&amp;quot;, #{&lt;br /&gt;
          &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
          &amp;quot;DataTableName&amp;quot;: casesTableName,&lt;br /&gt;
          &amp;quot;Columns&amp;quot;: casesColumnMappings&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      targetModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: targetModelName, &amp;quot;Configuration&amp;quot;: modelConfiguration});&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    eventsTable.Truncate();&lt;br /&gt;
    casesTable?.Truncate();&lt;br /&gt;
&lt;br /&gt;
    return #{&lt;br /&gt;
      &amp;quot;TargetModel&amp;quot;: targetModel,&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTable,&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: casesTable&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function RepeatNTimes(sourceDf, caseIdColumn, numRepeats)&lt;br /&gt;
  {&lt;br /&gt;
    let resultDf = null;&lt;br /&gt;
    for (let i = 1; i &amp;lt;= numRepeats; ++i) {&lt;br /&gt;
      let iterationDf = sourceDf&lt;br /&gt;
        .WithColumn(caseIdColumn, #sql{Concat(#expr{i}, &amp;quot;-&amp;quot;, Column(#expr{caseIdColumn}))});&lt;br /&gt;
      resultDf = resultDf == null ? iterationDf : resultDf.Append(iterationDf); &lt;br /&gt;
    }&lt;br /&gt;
    resultDf;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let resultModel = CreateResultModel();&lt;br /&gt;
  let sourceEventDataDf = sourceModel.EventsDataTable.SqlDataFrame;&lt;br /&gt;
  let resultEventDataDf = RepeatNTimes(sourceEventDataDf, eventsColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
  resultModel[&amp;quot;Events&amp;quot;].Import(resultEventDataDf);&lt;br /&gt;
&lt;br /&gt;
  let sourceCaseDataDf = sourceModel.CasesDataTable?.SqlDataFrame;&lt;br /&gt;
  if (sourceCaseDataDf != null) {&lt;br /&gt;
    let resultCaseDataDf = RepeatNTimes(sourceCaseDataDf, casesColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
    resultModel[&amp;quot;Cases&amp;quot;].Import(resultCaseDataDf);&lt;br /&gt;
  }&lt;br /&gt;
  resultModel[&amp;quot;TargetModel&amp;quot;];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example usage:&amp;lt;blockquote&amp;gt;CreateTestModel(ProjectByName(&amp;quot;Project&amp;quot;).ModelByName(&amp;quot;SAP_OrderToCash - Snowflake&amp;quot;), 3, ProjectByName(&amp;quot;TestData&amp;quot;), &amp;quot;TestModel&amp;quot;);&amp;lt;/blockquote&amp;gt;Creates a new model named &amp;quot;TestModel&amp;quot; (or overwrites old one) into project named &amp;quot;TestData&amp;quot; containing the data from model &amp;quot;SAP_OrderToCash - Snowflake&amp;quot; in project &amp;quot;Project&amp;quot; repeated three times.&lt;br /&gt;
&lt;br /&gt;
=== Analyzing declare patterns found in event log ===&lt;br /&gt;
&lt;br /&gt;
This is an example expression that shows how POSIX-style regular expressions can be used to search for cases in an event log having certain event type patterns [https://www.researchgate.net/publication/277631859_Generating_Event_Logs_Through_the_Simulation_of_Declare_Models declare patterns].&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
let sdf = dt.SqlDataFrame.Head(1000);&lt;br /&gt;
let caseIdColumn = dt.ColumnMappings[&amp;quot;CaseId&amp;quot;], timeStampColumn = dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;], eventTypeColumn = dt.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let eventTypesDf = sdf&lt;br /&gt;
  .SelectDistinct([eventTypeColumn])&lt;br /&gt;
  .OrderByColumns([eventTypeColumn], [true])&lt;br /&gt;
  .WithRowNumberColumn(&amp;quot;Token&amp;quot;, [eventTypeColumn])&lt;br /&gt;
  .WithColumn(&amp;quot;Token&amp;quot;, #sql{Char(Column(&amp;quot;Token&amp;quot;) + Unicode(&amp;quot;a&amp;quot;) - 1)});&lt;br /&gt;
&lt;br /&gt;
sdf = sdf&lt;br /&gt;
  .Join(eventTypesDf.Select([&amp;quot;_EventType2&amp;quot;: eventTypeColumn, &amp;quot;Token&amp;quot;]), [eventTypeColumn: &amp;quot;_EventType2&amp;quot;], &amp;quot;leftouter&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function RespondedExistencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*((a.*b.*)|(b.*a.*))*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*((${a}.*${b}.*)|(${b}.*${a}.*))*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a.*b)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}.*${b})*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function AlternateResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a[^a]*b[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}[^${a}]*${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ChainResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(ab[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function PrecedencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^b]*(a.*b)*[^b]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${b}]*(${a}.*${b})*[^${b}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let tracesDf = sdf&lt;br /&gt;
  .GroupBy([caseIdColumn])&lt;br /&gt;
  .Aggregate([&amp;quot;Trace&amp;quot;: &amp;quot;Token&amp;quot;], [#{&amp;quot;Function&amp;quot;: &amp;quot;list&amp;quot;, &amp;quot;Ordering&amp;quot;: [timeStampColumn], &amp;quot;Separator&amp;quot;: &amp;quot;&amp;quot;}])&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceNL&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceCA&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternNL&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternCA&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternNL&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternCA&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternNL&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternCA&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternNL&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternCA&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
[tracesDf.Collect().ToCsv(), eventTypesDf.Collect().ToCsv()]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Perform a query and send results as E-mail in a HTML table ===&lt;br /&gt;
This example requires two scripts that are both located in the same project:&lt;br /&gt;
&lt;br /&gt;
# Expression-type script to execute (can be, e.g., scheduled to run daily).&lt;br /&gt;
# Expression-type script containing the query JSON to use as basis for the e-mail. In this example, this script is named as &amp;quot;Send query as E-mail - query JSON&amp;quot;. The contents of this script is just the JSON representation of a query that can be extracted, e.g., from any PA chart view.&lt;br /&gt;
&lt;br /&gt;
Script #1 should contain the following code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let replyToAddress = &amp;quot;noreply@test.com&amp;quot;;&lt;br /&gt;
let recipientsArray = [&amp;quot;testuser@test.com&amp;quot;];&lt;br /&gt;
let queryConfiguration = ParseJson(Project.ScriptByName(&amp;quot;Send query as E-mail - query JSON&amp;quot;).Code)&lt;br /&gt;
  .Set(&amp;quot;IncludeCollect&amp;quot;, true);&lt;br /&gt;
let resultDf = Query(queryConfiguration);&lt;br /&gt;
let mailBodyHtml = resultDf.`&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
  &amp;lt;caption&amp;gt;Example query&amp;lt;/caption&amp;gt;&lt;br /&gt;
  &amp;lt;thead&amp;gt;&lt;br /&gt;
    &amp;lt;tr&amp;gt;&lt;br /&gt;
      ${StringJoin(&amp;quot;&amp;quot;, _.Columns.`&lt;br /&gt;
        &amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;&lt;br /&gt;
      `)}&lt;br /&gt;
    &amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;/thead&amp;gt;&lt;br /&gt;
  &amp;lt;tbody&amp;gt;&lt;br /&gt;
    ${StringJoin(&amp;quot;&amp;quot;, _.Rows.`&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
        ${StringJoin(&amp;quot;&amp;quot;, _.`&lt;br /&gt;
          &amp;lt;td&amp;gt;${_}&amp;lt;/td&amp;gt;&lt;br /&gt;
        `)}&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
    `)}&lt;br /&gt;
  &amp;lt;/tbody&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
`;&lt;br /&gt;
&lt;br /&gt;
SendEmail(#{&lt;br /&gt;
  &amp;quot;ReplyTo&amp;quot;: [replyToAddress],&lt;br /&gt;
  &amp;quot;To&amp;quot;: recipientsArray,&lt;br /&gt;
  &amp;quot;Subject&amp;quot;: &amp;quot;Example query E-mail&amp;quot;,&lt;br /&gt;
  &amp;quot;IsBodyHtml&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Body&amp;quot;: mailBodyHtml&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;NOTE: QPR ProcessAnalyzer Server has to have SMTP server configured. Also, remember to update the values of replyToAddress and recipientsArray before using.&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=26002</id>
		<title>Expression Script Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=26002"/>
		<updated>2025-03-18T14:30:34Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Perform a query and send results as E-mail in a HTML table */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains script examples written in the QPR ProcessAnalyzer expression language. See how expression scripts can be created in the [[Managing_Scripts#Creating_Script|Workspace]]. For documentation for the syntax, functions and entities can be found from the main page in the [[QPR_ProcessAnalyzer_Wiki#For_Developers|KPI Expression Language]] section.&lt;br /&gt;
&lt;br /&gt;
== Calling Expression Script from Expression ==&lt;br /&gt;
Expression scripts can be called from an expression using the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Run]] function with the following syntax:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: false,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 123.45&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command waits until the run is completed, and the return value of the called script is returned by the Run function call.&lt;br /&gt;
&lt;br /&gt;
Parameters can be passed to the called script, and the parameters are available as variables in the script. The parameters can contain any type of data.&lt;br /&gt;
&lt;br /&gt;
Expression scripts can also be called from a dashboard. Expressions can be stored to scripts instead of dashboards, which is a way to separate complex expressions from dashboards and allow to reuse expressions across several dashboards.&lt;br /&gt;
&lt;br /&gt;
== Calling SQL Script from Expression ==&lt;br /&gt;
SQL script can be called from an expression using the Run function as follows (similar to calling [[#Calling Expression Script from Expression|expression scripts]]):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 321&lt;br /&gt;
});&lt;br /&gt;
let arrayOfAllReports = result.Keys;&lt;br /&gt;
let report1 = result.Report1;&lt;br /&gt;
let report2 = result.Report2;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL scripts can return multiple &#039;&#039;reports&#039;&#039;, which are combined to a dictionary, where the key is the name of the report (&amp;quot;sheet name&amp;quot;) and value is the report data as a DataFrame. See in the above example, how the reports can be accessed by their name.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Call web service===&lt;br /&gt;
Contact to a web service, fetch some data, and store it to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let datatableName = &amp;quot;Web Service Data&amp;quot;;&lt;br /&gt;
let webServiceData = CallWebService(&lt;br /&gt;
    #{&amp;quot;Address&amp;quot;: &amp;quot;https://processanalyzer.onqpr.com/qprpa/api/serverinfo&amp;quot;}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
let targetDatatable = Project.Datatables.Where(name==datatableName);&lt;br /&gt;
if (Count(targetDatatable) == 0) {&lt;br /&gt;
	targetDatatable = Project.CreateDatatable(datatableName)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting value&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Data read&amp;quot;, &amp;quot;DateTime&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
	targetDatatable = targetDatatable[0];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let currentTime = Now;&lt;br /&gt;
let dataAsDf = ToDataFrame(&lt;br /&gt;
	webServiceData.keys.{&lt;br /&gt;
        let key = _;&lt;br /&gt;
        [key, webServiceData[key], currentTime];&lt;br /&gt;
    },&lt;br /&gt;
	[&amp;quot;Setting name&amp;quot;, &amp;quot;Setting value&amp;quot;, &amp;quot;Data read&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
targetDatatable.Import(dataAsDf, #{&amp;quot;Append&amp;quot;:true});&lt;br /&gt;
WriteLog(`${CountTop(dataAsDf.Rows)} rows written to datatable`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Store data to datatable ===&lt;br /&gt;
Get all models in the system and store them to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let newDatatable = Project&lt;br /&gt;
    .CreateDatatable(&amp;quot;Models list &amp;quot; + ToString(Now, &amp;quot;dd.MM.yyyy HH:mm:ss&amp;quot;))&lt;br /&gt;
    .AddColumn(&amp;quot;Model name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Project name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Created time&amp;quot;, &amp;quot;DateTime&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Cases&amp;quot;, &amp;quot;Integer&amp;quot;);&lt;br /&gt;
let startTime = Now;&lt;br /&gt;
let modelsData = ToDataFrame(&lt;br /&gt;
    Models.([Name, Project.Name, CreatedDate, NCases]),&lt;br /&gt;
    [&amp;quot;Model name&amp;quot;, &amp;quot;Project name&amp;quot;, &amp;quot;Created time&amp;quot;, &amp;quot;Cases&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
WriteLog(`Listing models took ${(Now - startTime).TotalSeconds.Round(2)} seconds.`);&lt;br /&gt;
newDatatable.Import(modelsData);&lt;br /&gt;
WriteLog(`Datatable ${newDatatable.Id} created.`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data ===&lt;br /&gt;
This script can be used to convert a single column into numerical data type. To use the script, you need to setup the following in the beginning of the script:&lt;br /&gt;
* Project name where the datatable is located.&lt;br /&gt;
* Datatable name&lt;br /&gt;
* Name of the column to be converted&lt;br /&gt;
&lt;br /&gt;
Note that the conversion fails, if there is data that cannot be converted into numerical format. The conversion assumes that period (.) is used as the decimal point. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let projectName = &amp;quot;New Project&amp;quot;;&lt;br /&gt;
let datatableName = &amp;quot;qpr processanalyzer events&amp;quot;;&lt;br /&gt;
let columnName = &amp;quot;Event order in case&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let project = (Projects.Where(Name==projectName))[0];&lt;br /&gt;
let datatable = (project.Datatables.Where(Name==datatableName))[0];&lt;br /&gt;
DatatableById(datatable.Id).DataFrame&lt;br /&gt;
.SetColumns([&lt;br /&gt;
	columnName: () =&amp;gt; {&lt;br /&gt;
		let data = Column(columnName);&lt;br /&gt;
		if (data == null) {&lt;br /&gt;
			null;&lt;br /&gt;
		 } else {&lt;br /&gt;
			ToFloat(data);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
])&lt;br /&gt;
.Persist(datatable.Name, [&amp;quot;ProjectId&amp;quot;: project.Id, &amp;quot;Append&amp;quot;: false]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of converting to numeric (with the &#039;&#039;ToFloat&#039;&#039; function), data can be converted into string using the &#039;&#039;ToString&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Show DataFrame as HTML table ===&lt;br /&gt;
&lt;br /&gt;
This script defines a function to show dataframe as a HTML table, and uses the function for a literal dataframe.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function dataframeToHtmlTable(df) {&lt;br /&gt;
	return&lt;br /&gt;
`&amp;lt;table&amp;gt;&lt;br /&gt;
	&amp;lt;tr&amp;gt;&lt;br /&gt;
		${StringJoin(&amp;quot;\r\n\t\t&amp;quot;,  + df.columns.`&amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;`)}&lt;br /&gt;
	&amp;lt;/tr&amp;gt;&lt;br /&gt;
	${StringJoin(&amp;quot;&amp;quot;, df.Rows.(&lt;br /&gt;
		&amp;quot;\r\n\t&amp;lt;tr&amp;gt;&amp;quot; + StringJoin(&amp;quot;&amp;quot;, _.`\r\n\t\t&amp;lt;td&amp;gt;${ToString(_)}&amp;lt;/td&amp;gt;`) + &amp;quot;\r\n\t&amp;lt;/tr&amp;gt;&amp;quot;&lt;br /&gt;
	))}&lt;br /&gt;
&amp;lt;/table&amp;gt;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let data = ToDataFrame(&lt;br /&gt;
	[&lt;br /&gt;
		[&amp;quot;one&amp;quot;, &amp;quot;two&amp;quot;, &amp;quot;three&amp;quot;],&lt;br /&gt;
		[&amp;quot;four&amp;quot;, &amp;quot;five&amp;quot;, &amp;quot;six&amp;quot;],&lt;br /&gt;
		[&amp;quot;seven&amp;quot;, &amp;quot;eight&amp;quot;, &amp;quot;nine&amp;quot;]&lt;br /&gt;
	],&lt;br /&gt;
	[&amp;quot;Column 1&amp;quot;, &amp;quot;Column 2&amp;quot;, &amp;quot;Column 3&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
return dataframeToHtmlTable(data);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy local datatables to Snowflake ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
// Copies all datatables in a project to another project including datatable contents.&lt;br /&gt;
// Usage instructions:&lt;br /&gt;
// 1. Create expression script in the project from where you want to copy the datatables.&lt;br /&gt;
// 2. Create a new project named as &amp;quot;&amp;lt;name of the project to be moved&amp;gt; - Snowflake&amp;quot;. New datatables will be created here. E.g., when moving project named &amp;quot;SAP_OrderToCash&amp;quot;, the target project should be named as &amp;quot;SAP_OrderToCash - Snowflake&amp;quot;.&lt;br /&gt;
// 3. Run the script.&lt;br /&gt;
// NOTE: Columns of type &amp;quot;Any&amp;quot; will be created as &amp;quot;String&amp;quot;-columns in Snowflake, thus it is recommended that actual data types are set for the tables prior to the move.&lt;br /&gt;
&lt;br /&gt;
let sourceProject = Project;&lt;br /&gt;
let sourceProjectName = Project.Name;&lt;br /&gt;
let targetProjectName = `${sourceProjectName} - Snowflake`;&lt;br /&gt;
let targetProject = First(Projects.Where(Name == targetProjectName));&lt;br /&gt;
if (IsNull(targetProject)) {&lt;br /&gt;
  WriteLog(`Unable to find target project named &amp;quot;${targetProjectName}&amp;quot;. Aborting operation.`);&lt;br /&gt;
  return;&lt;br /&gt;
}&lt;br /&gt;
let dts = sourceProject.DataTables;&lt;br /&gt;
WriteLog(`Copying all ${CountTop(dts)} data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
dts.{&lt;br /&gt;
  let sourceDt = _;&lt;br /&gt;
  WriteLog(`Starting to copy data table &amp;quot;${Name}&amp;quot; (id: ${Id}) having ${NRows} rows and ${NColumns} columns.`);&lt;br /&gt;
  let targetDt;&lt;br /&gt;
  targetDt = targetProject.DatatableByName(sourceDt.Name);&lt;br /&gt;
  if (targetDt == null) {&lt;br /&gt;
    targetDt = targetProject.CreateDataTable(sourceDt.Name, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: targetProject.Id})});&lt;br /&gt;
    targetDt.Import(sourceDt.SqlDataFrame);&lt;br /&gt;
    WriteLog(`Finished copying data table &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  } else {&lt;br /&gt;
    WriteLog(`Datatable already exist &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
WriteLog(`Finished copying all the data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to copy the data but only create the Snowflake datatables with columns, you can change the line 22 to&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
targetDt.Import(sourceDt.SqlDataFrame.head(0));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy single datatable to Snowflake ===&lt;br /&gt;
This script creates a copy of a single datatable to Snowflake. Replace the &#039;&#039;&amp;lt;tableId1&amp;gt;&#039;&#039; with the id of the source datatable.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function CopyDataTableToSnowflake(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let sourceDt = DataTableById(dataTableId);&lt;br /&gt;
  sourceDt.SqlDataFrame.Persist(`${sourceDt.Name} - Snowflake`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: sourceDt.Project.Id})});&lt;br /&gt;
}&lt;br /&gt;
CopyDataTableToSnowflake(&amp;lt;tableId1&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a copy of a data table that has all Any-type columns changed to String-type columns ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function ConvertAnyDataTypesToStringsToNewTable(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let dt = DataTableById(dataTableId);&lt;br /&gt;
  let sdf = dt.SqlDataFrame;&lt;br /&gt;
  let cts = dt.ColumnTypes;&lt;br /&gt;
  cts.{&lt;br /&gt;
    let ct = _;&lt;br /&gt;
    if (ct.DataType == &amp;quot;Any&amp;quot;) {&lt;br /&gt;
      let n = ct.Name;&lt;br /&gt;
      sdf = sdf.WithColumn(ct.Name, #sql{Cast(Column(Variable(&amp;quot;n&amp;quot;)), &amp;quot;ShortString&amp;quot;)});&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  sdf.Persist(`${dt.Name} - Converted`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;ProjectId&amp;quot;: dt.Project.Id});&lt;br /&gt;
}&lt;br /&gt;
ConvertAnyDataTypesToStringsToNewTable(&amp;lt;dataTableId&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Query number of rows in given data table having a datetime value in given year grouped by month and return resulting table as CSV ===&lt;br /&gt;
SqlDataFrame is used in order to prevent loading the whole datatable into memory first. Filtering is performed as first operation in order to minimize the amount of required work for the data source of the data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
DataTableById(&amp;lt;data table id&amp;gt;)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .Where(#sql{2014 == Year(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .WithColumn(&amp;quot;Month&amp;quot;, #sql{Month(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .GroupBy([&amp;quot;Month&amp;quot;]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
  .OrderByColumns([&amp;quot;Month&amp;quot;], [true])&lt;br /&gt;
  .Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Function for filtering SqlDataFrame by removing rows having, or replacing, the most infrequently occurring column values ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/***&lt;br /&gt;
 * @name ColumnWithMinUsage&lt;br /&gt;
 * @descripion&lt;br /&gt;
 * Generic function that can be used to filter out the most infrequently occurring attribute values or replace their Values&lt;br /&gt;
 * with given common value.&lt;br /&gt;
 * @param df:&lt;br /&gt;
 * DataFrame to operate on.&lt;br /&gt;
 * @param columnName:&lt;br /&gt;
 * Name of the column to be filtered.&lt;br /&gt;
 * @param newColumnName:&lt;br /&gt;
 * Name of the column that will contain the new value of the original column after filtering (if includeOthers was applied).&lt;br /&gt;
 * @param maxNumUniqueValues:&lt;br /&gt;
 * Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
 * @param minValueUsage:&lt;br /&gt;
 * Minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
 * @param includeOthers:&lt;br /&gt;
 * Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
 * If not empty/null, defines the name used for these other-values.&lt;br /&gt;
 */&lt;br /&gt;
function ColumnWithMinUsage(df, columnName, newColumnName, maxNumUniqueValues, minValueUsage, includeOthers)&lt;br /&gt;
{&lt;br /&gt;
  let all = df&lt;br /&gt;
	.GroupBy([])&lt;br /&gt;
	.Aggregate([&amp;quot;NAllTotal&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
	.WithColumn(&amp;quot;__Join2&amp;quot;, #sql{1});&lt;br /&gt;
  let minValueUsageEnabled = !IsNullTop(minValueUsage);&lt;br /&gt;
  let maxNumUniqueValuesEnabled = !IsNullTop(maxNumUniqueValues);&lt;br /&gt;
  if (minValueUsageEnabled || maxNumUniqueValuesEnabled) {&lt;br /&gt;
	// Perform column value-based filtering if minValueUsageEnabled or maxNumUniqueValuesEnabled is defined.&lt;br /&gt;
    let valueColumnName = &amp;quot;__ValueNew&amp;quot;;&lt;br /&gt;
	let filteredValuesColumns = [valueColumnName: columnName];&lt;br /&gt;
	let filteredValues = df&lt;br /&gt;
	  .GroupBy([columnName]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;]);&lt;br /&gt;
	if (minValueUsageEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithColumn(&amp;quot;__Join&amp;quot;, #sql{1})&lt;br /&gt;
		.Join(all, [&amp;quot;__Join&amp;quot;: &amp;quot;__Join2&amp;quot;], &amp;quot;leftouter&amp;quot;)&lt;br /&gt;
        .WithColumn(&amp;quot;Usage&amp;quot;, #sql{Column(&amp;quot;Count&amp;quot;) / Column(&amp;quot;NAllTotal&amp;quot;)});&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;Usage&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
	if (maxNumUniqueValuesEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithRowNumberColumn(&amp;quot;RowNumber&amp;quot;, [&amp;quot;Count&amp;quot;], null, [false]);&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;RowNumber&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	filteredValues = filteredValues&lt;br /&gt;
	  .Select(filteredValuesColumns);&lt;br /&gt;
&lt;br /&gt;
	// Generate select returning all the accepted values.&lt;br /&gt;
	let allValues = filteredValues&lt;br /&gt;
	  .(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;gt;= #expr{minValueUsage}}) : _)&lt;br /&gt;
	  .(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;lt;= #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
	  .Select([valueColumnName, newColumnName: valueColumnName]);&lt;br /&gt;
&lt;br /&gt;
	if (!IsNullTop(includeOthers)) {&lt;br /&gt;
	  // If includeOthers is defined, replace original values with the variable defined in includeOthers.&lt;br /&gt;
	  let otherValues = filteredValues&lt;br /&gt;
		.(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;lt; #expr{minValueUsage}}) : _)&lt;br /&gt;
		.(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;gt; #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
		.WithColumn(newColumnName, #sql{#expr{includeOthers}})&lt;br /&gt;
		.Select([valueColumnName, newColumnName]);&lt;br /&gt;
	  allValues = allValues.Append(otherValues)&lt;br /&gt;
	}&lt;br /&gt;
	df.Join(allValues, [columnName: valueColumnName], &amp;quot;inner&amp;quot;)&lt;br /&gt;
	  .RemoveColumns([valueColumnName]);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// The following example will return only rows containing two of the most common values for Region-column.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, 2, null, null);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&lt;br /&gt;
// The following example will return all input rows, but will replace the values of rows whose Region-column&lt;br /&gt;
// has a value used by less than 15% of all the rows with a new value: &amp;quot;_Others&amp;quot;.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, null, 0.15, &amp;quot;_Others&amp;quot;);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export model events and cases ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function ExportModelEvents(m) {&lt;br /&gt;
  let attrs = m.EventAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Events.Concat(&lt;br /&gt;
      [Case.Name, Type.Name, ToString(TimeStamp, &amp;quot;yyyy-MM-dd HH:mm:ss.fff&amp;quot;)], &lt;br /&gt;
      {let evt = _; attrs.{let att = _; evt.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;, &amp;quot;EventType&amp;quot;, &amp;quot;TimeStamp&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
function ExportModelCases(m) {&lt;br /&gt;
  let attrs = m.CaseAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Cases.Concat(&lt;br /&gt;
      [Name], &lt;br /&gt;
      {let cas = _; attrs.{let att = _; cas.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).EventsDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).CasesDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Calculate all the value usages of a single column for each event in event data table ===&lt;br /&gt;
This query could be used, e.g., to find out the maximum resource usage for every resource found in the event data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function WithUsageColumns(resourceColumn)&lt;br /&gt;
{&lt;br /&gt;
  function WithTotalUsageColumnOfSingleResource(resourceColumn, resourceValue)&lt;br /&gt;
  {&lt;br /&gt;
    _&lt;br /&gt;
      .WithColumn(&amp;quot;_Prev&amp;quot;, #sql{Lag(Column(resourceColumn), [TimeStamp, EventType], [true, true], [CaseId], 1, null)})&lt;br /&gt;
      .WithColumn(&amp;quot;_UsageDiff&amp;quot;, #sql{&lt;br /&gt;
        CaseWhen(&lt;br /&gt;
          Column(resourceColumn) == Column(&amp;quot;_Prev&amp;quot;), 0, &lt;br /&gt;
          Column(&amp;quot;_Prev&amp;quot;) == #expr{resourceValue}, -1,&lt;br /&gt;
          Column(resourceColumn) == #expr{resourceValue}, 1,&lt;br /&gt;
          0)&lt;br /&gt;
      })&lt;br /&gt;
      .WithColumn(`${resourceValue}_Usage`, #sql{Sum(Column(&amp;quot;_UsageDiff&amp;quot;), [TimeStamp, EventType])})&lt;br /&gt;
      .RemoveColumns([&amp;quot;_Prev&amp;quot;, &amp;quot;_UsageDiff&amp;quot;])&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let sdf = _;&lt;br /&gt;
  let allValues = sdf.SelectDistinct([resourceColumn]).OrderByColumns([resourceColumn], [true]).Collect().Column(resourceColumn);&lt;br /&gt;
  allValues.{&lt;br /&gt;
    let v = _;&lt;br /&gt;
    sdf = sdf.WithTotalUsageColumnOfSingleResource(resourceColumn, v)&lt;br /&gt;
  }&lt;br /&gt;
  sdf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
dt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .WithUsageColumns(&amp;lt;resource column name&amp;gt;)&lt;br /&gt;
  .OrderByColumns([dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]], [true])&lt;br /&gt;
  .Collect().ToCsv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;model id&amp;gt; is the id of the model containing event data to be examined.&lt;br /&gt;
* &amp;lt;resource column name&amp;gt; is the name of the column in the event data table of the specified model containing the resource being used by that event.&lt;br /&gt;
&lt;br /&gt;
NOTE: This expression uses functionalities that are only supported in Snowflake-based data tables.&lt;br /&gt;
&lt;br /&gt;
=== Create new Snowflake model from filter ===&lt;br /&gt;
This script creates a new Snowflake model (and two datatables for cases and events) containing filtered event log from given filter id. The script also works if the model doesn&#039;t have a cases datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let filter = FilterById(1); // filter id&lt;br /&gt;
let model = filter.model;&lt;br /&gt;
let project = model.project;&lt;br /&gt;
let nameSuffix = &amp;quot; - &amp;quot; + filter.name + &amp;quot; - &amp;quot; + ToString(Now, &amp;quot;dd-MM-yyyy HH:mm:ss&amp;quot;);&lt;br /&gt;
let eventsDatatableName = model.EventsDataTable.Name + nameSuffix;&lt;br /&gt;
if (eventsDatatableName.length &amp;gt; 440) {&lt;br /&gt;
  eventsDatatableName = eventsDatatableName.Substring(eventsDatatableName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
let eventsData = model&lt;br /&gt;
  .EventsDataTable&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .ApplyFilter(&lt;br /&gt;
    filter.rules,&lt;br /&gt;
    model.CasesDataTable?.SqlDataFrame&lt;br /&gt;
  );&lt;br /&gt;
project&lt;br /&gt;
  .CreateDatatable(eventsDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
  .Import(eventsData);&lt;br /&gt;
let modelConfiguration = model.Configuration;&lt;br /&gt;
modelConfiguration.DataSource.Events.Set(&amp;quot;DataTableName&amp;quot;, eventsDatatableName);&lt;br /&gt;
if (model.CasesDataTable != null) {&lt;br /&gt;
  let eventsDataCaseIdColumn = &amp;quot;CaseId_&amp;quot; + ToString(Random());&lt;br /&gt;
  let casesDatatableName = model.CasesDataTable.Name + nameSuffix;&lt;br /&gt;
  if (casesDatatableName.length &amp;gt; 440) {&lt;br /&gt;
    casesDatatableName = casesDatatableName.Substring(casesDatatableName.length - 440);&lt;br /&gt;
  }&lt;br /&gt;
  let casesData = model&lt;br /&gt;
    .CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .join(&lt;br /&gt;
	  eventsData.SelectDistinct([eventsDataCaseIdColumn: modelConfiguration.DataSource.Events.Columns.CaseId]),&lt;br /&gt;
      [modelConfiguration.DataSource.Cases.Columns.CaseId: eventsDataCaseIdColumn]&lt;br /&gt;
	).Select(model.CasesDataTable.ColumnNames);&lt;br /&gt;
  project&lt;br /&gt;
    .CreateDatatable(casesDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
    .Import(casesData);&lt;br /&gt;
  modelConfiguration.DataSource.Cases.Set(&amp;quot;DataTableName&amp;quot;, casesDatatableName);&lt;br /&gt;
}&lt;br /&gt;
let modelName = model.Name + nameSuffix;&lt;br /&gt;
if (modelName &amp;gt; 440) {&lt;br /&gt;
  modelName = modelName.Substring(modelName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
project&lt;br /&gt;
  .CreateModel(#{    &lt;br /&gt;
    &amp;quot;Name&amp;quot;: modelName,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: model.Description,&lt;br /&gt;
    &amp;quot;Configuration&amp;quot;: modelConfiguration&lt;br /&gt;
  });&lt;br /&gt;
return modelName;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Creating a model consisting of multiple copies of cases in an existing model ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @name CreateTestModel&lt;br /&gt;
 * @description&lt;br /&gt;
 * Creates a new model (or overwrites an existing) to given target project with given number of &lt;br /&gt;
 * repetitions of given source model.&lt;br /&gt;
 * Each repetition will generate &amp;quot;&amp;lt;N&amp;gt;-&amp;quot;-prefix to CaseId-columns, where N equals to the repeat index.&lt;br /&gt;
 * @param sourceModel&lt;br /&gt;
 * PA model used for the source data and from where the connection is copied for the target model if a &lt;br /&gt;
 * new one has to be created.&lt;br /&gt;
 * @param numRepeats&lt;br /&gt;
 * Number of times the data in the source model should be repeated in the generated model.&lt;br /&gt;
 * @param targetProject&lt;br /&gt;
 * Project in which the target model resides.&lt;br /&gt;
 * @param targetModelName&lt;br /&gt;
 * Specifies the name of the test model in the given target project. If a model already exists with &lt;br /&gt;
 * given name, event and case data in this model will be replaced with the new generated event and &lt;br /&gt;
 * case data.&lt;br /&gt;
 * @returns&lt;br /&gt;
 * Model object of the test model having the newly generated data.&lt;br /&gt;
 */&lt;br /&gt;
function CreateTestModel(sourceModel, numRepeats, targetProject, targetModelName) &lt;br /&gt;
{&lt;br /&gt;
  let eventsColumnMappings = sourceModel.EventsDataTable.ColumnMappings;&lt;br /&gt;
  let casesColumnMappings = sourceModel.CasesDataTable.ColumnMappings;&lt;br /&gt;
  let connection = sourceModel.EventsDataTable.DataSourceConnection;&lt;br /&gt;
&lt;br /&gt;
  function CreateResultModel()&lt;br /&gt;
  {&lt;br /&gt;
    function GetTable(tableName) &lt;br /&gt;
    {&lt;br /&gt;
      let tableConfiguration = #{&lt;br /&gt;
        &amp;quot;Name&amp;quot;: tableName,&lt;br /&gt;
        &amp;quot;Connection&amp;quot;: connection&lt;br /&gt;
      };&lt;br /&gt;
      let resultTable = targetProject.DataTableByName(tableName);&lt;br /&gt;
      if (resultTable == null)&lt;br /&gt;
      {&lt;br /&gt;
        resultTable = targetProject.CreateDataTable(tableConfiguration)&lt;br /&gt;
          .Modify(#{&amp;quot;NameInDataSource&amp;quot;: null})&lt;br /&gt;
          .Synchronize();&lt;br /&gt;
      }&lt;br /&gt;
      return resultTable;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let eventsTableName = `${targetModelName} - events`;&lt;br /&gt;
    let casesTableName = `${targetModelName} - cases`;&lt;br /&gt;
    let targetModel = targetProject.ModelByName(targetModelName);&lt;br /&gt;
    let eventsTable, casesTable = null;&lt;br /&gt;
&lt;br /&gt;
    if (targetModel != null)&lt;br /&gt;
    {&lt;br /&gt;
      eventsTable = targetModel.EventsDataTable;&lt;br /&gt;
      casesTable = targetModel.CasesDataTable;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      eventsTable = GetTable(eventsTableName);&lt;br /&gt;
      if (sourceModel.CasesDataTable != null) {&lt;br /&gt;
        casesTable = GetTable(casesTableName);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      let timestampMapping = eventsColumnMappings[&amp;quot;TimeStamp&amp;quot;];&lt;br /&gt;
      eventsColumnMappings.Remove(&amp;quot;TimeStamp&amp;quot;);&lt;br /&gt;
      eventsColumnMappings.Set(&amp;quot;Timestamp&amp;quot;, timestampMapping);&lt;br /&gt;
&lt;br /&gt;
      let modelConfiguration = #{&lt;br /&gt;
        &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
          &amp;quot;Events&amp;quot;:#{&lt;br /&gt;
            &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
            &amp;quot;DataTableName&amp;quot;: eventsTableName,&lt;br /&gt;
            &amp;quot;Columns&amp;quot;: eventsColumnMappings&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      if (casesColumnMappings != null) {&lt;br /&gt;
        modelConfiguration[&amp;quot;DataSource&amp;quot;].Set(&amp;quot;Cases&amp;quot;, #{&lt;br /&gt;
          &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
          &amp;quot;DataTableName&amp;quot;: casesTableName,&lt;br /&gt;
          &amp;quot;Columns&amp;quot;: casesColumnMappings&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      targetModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: targetModelName, &amp;quot;Configuration&amp;quot;: modelConfiguration});&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    eventsTable.Truncate();&lt;br /&gt;
    casesTable?.Truncate();&lt;br /&gt;
&lt;br /&gt;
    return #{&lt;br /&gt;
      &amp;quot;TargetModel&amp;quot;: targetModel,&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTable,&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: casesTable&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function RepeatNTimes(sourceDf, caseIdColumn, numRepeats)&lt;br /&gt;
  {&lt;br /&gt;
    let resultDf = null;&lt;br /&gt;
    for (let i = 1; i &amp;lt;= numRepeats; ++i) {&lt;br /&gt;
      let iterationDf = sourceDf&lt;br /&gt;
        .WithColumn(caseIdColumn, #sql{Concat(#expr{i}, &amp;quot;-&amp;quot;, Column(#expr{caseIdColumn}))});&lt;br /&gt;
      resultDf = resultDf == null ? iterationDf : resultDf.Append(iterationDf); &lt;br /&gt;
    }&lt;br /&gt;
    resultDf;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let resultModel = CreateResultModel();&lt;br /&gt;
  let sourceEventDataDf = sourceModel.EventsDataTable.SqlDataFrame;&lt;br /&gt;
  let resultEventDataDf = RepeatNTimes(sourceEventDataDf, eventsColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
  resultModel[&amp;quot;Events&amp;quot;].Import(resultEventDataDf);&lt;br /&gt;
&lt;br /&gt;
  let sourceCaseDataDf = sourceModel.CasesDataTable?.SqlDataFrame;&lt;br /&gt;
  if (sourceCaseDataDf != null) {&lt;br /&gt;
    let resultCaseDataDf = RepeatNTimes(sourceCaseDataDf, casesColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
    resultModel[&amp;quot;Cases&amp;quot;].Import(resultCaseDataDf);&lt;br /&gt;
  }&lt;br /&gt;
  resultModel[&amp;quot;TargetModel&amp;quot;];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example usage:&amp;lt;blockquote&amp;gt;CreateTestModel(ProjectByName(&amp;quot;Project&amp;quot;).ModelByName(&amp;quot;SAP_OrderToCash - Snowflake&amp;quot;), 3, ProjectByName(&amp;quot;TestData&amp;quot;), &amp;quot;TestModel&amp;quot;);&amp;lt;/blockquote&amp;gt;Creates a new model named &amp;quot;TestModel&amp;quot; (or overwrites old one) into project named &amp;quot;TestData&amp;quot; containing the data from model &amp;quot;SAP_OrderToCash - Snowflake&amp;quot; in project &amp;quot;Project&amp;quot; repeated three times.&lt;br /&gt;
&lt;br /&gt;
=== Analyzing declare patterns found in event log ===&lt;br /&gt;
&lt;br /&gt;
This is an example expression that shows how POSIX-style regular expressions can be used to search for cases in an event log having certain event type patterns [https://www.researchgate.net/publication/277631859_Generating_Event_Logs_Through_the_Simulation_of_Declare_Models declare patterns].&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
let sdf = dt.SqlDataFrame.Head(1000);&lt;br /&gt;
let caseIdColumn = dt.ColumnMappings[&amp;quot;CaseId&amp;quot;], timeStampColumn = dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;], eventTypeColumn = dt.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let eventTypesDf = sdf&lt;br /&gt;
  .SelectDistinct([eventTypeColumn])&lt;br /&gt;
  .OrderByColumns([eventTypeColumn], [true])&lt;br /&gt;
  .WithRowNumberColumn(&amp;quot;Token&amp;quot;, [eventTypeColumn])&lt;br /&gt;
  .WithColumn(&amp;quot;Token&amp;quot;, #sql{Char(Column(&amp;quot;Token&amp;quot;) + Unicode(&amp;quot;a&amp;quot;) - 1)});&lt;br /&gt;
&lt;br /&gt;
sdf = sdf&lt;br /&gt;
  .Join(eventTypesDf.Select([&amp;quot;_EventType2&amp;quot;: eventTypeColumn, &amp;quot;Token&amp;quot;]), [eventTypeColumn: &amp;quot;_EventType2&amp;quot;], &amp;quot;leftouter&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function RespondedExistencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*((a.*b.*)|(b.*a.*))*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*((${a}.*${b}.*)|(${b}.*${a}.*))*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a.*b)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}.*${b})*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function AlternateResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a[^a]*b[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}[^${a}]*${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ChainResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(ab[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function PrecedencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^b]*(a.*b)*[^b]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${b}]*(${a}.*${b})*[^${b}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let tracesDf = sdf&lt;br /&gt;
  .GroupBy([caseIdColumn])&lt;br /&gt;
  .Aggregate([&amp;quot;Trace&amp;quot;: &amp;quot;Token&amp;quot;], [#{&amp;quot;Function&amp;quot;: &amp;quot;list&amp;quot;, &amp;quot;Ordering&amp;quot;: [timeStampColumn], &amp;quot;Separator&amp;quot;: &amp;quot;&amp;quot;}])&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceNL&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceCA&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternNL&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternCA&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternNL&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternCA&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternNL&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternCA&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternNL&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternCA&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
[tracesDf.Collect().ToCsv(), eventTypesDf.Collect().ToCsv()]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Perform a query and send results as E-mail in a HTML table ===&lt;br /&gt;
This example requires two scripts that are both located in the same project:&lt;br /&gt;
&lt;br /&gt;
# Expression-type script to execute (can be, e.g., scheduled to run daily).&lt;br /&gt;
# Expression-type script containing the query JSON to use as basis for the e-mail. In this example, this script is named as &amp;quot;Send query as E-mail - query JSON&amp;quot;. The contents of this script is just the JSON representation of a query that can be extracted, e.g., from any PA chart view.&lt;br /&gt;
&lt;br /&gt;
Script #1 should contain the following code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let replyToAddress = &amp;quot;noreply@test.com&amp;quot;;&lt;br /&gt;
let recipientsArray = [&amp;quot;test@test.com&amp;quot;];&lt;br /&gt;
let queryConfiguration = Project.ScriptByName(&amp;quot;Send query as E-mail - query JSON&amp;quot;).Code;&lt;br /&gt;
let resultDf = Query(ParseJson(queryConfiguration));&lt;br /&gt;
let mailBodyHtml = resultDf.Collect().`&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
  &amp;lt;caption&amp;gt;Example query&amp;lt;/caption&amp;gt;&lt;br /&gt;
  &amp;lt;thead&amp;gt;&lt;br /&gt;
    &amp;lt;tr&amp;gt;&lt;br /&gt;
      ${StringJoin(&amp;quot;&amp;quot;, _.Columns.`&lt;br /&gt;
        &amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;&lt;br /&gt;
      `)}&lt;br /&gt;
    &amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;/thead&amp;gt;&lt;br /&gt;
  &amp;lt;tbody&amp;gt;&lt;br /&gt;
    ${StringJoin(&amp;quot;&amp;quot;, _.Rows.`&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
        ${StringJoin(&amp;quot;&amp;quot;, _.`&lt;br /&gt;
          &amp;lt;td&amp;gt;${_}&amp;lt;/td&amp;gt;&lt;br /&gt;
        `)}&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
    `)}&lt;br /&gt;
  &amp;lt;/tbody&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
`;&lt;br /&gt;
&lt;br /&gt;
SendEmail(#{&lt;br /&gt;
  &amp;quot;ReplyTo&amp;quot;: [replyToAddress],&lt;br /&gt;
  &amp;quot;To&amp;quot;: recipientsArray,&lt;br /&gt;
  &amp;quot;Subject&amp;quot;: &amp;quot;Example query E-mail&amp;quot;,&lt;br /&gt;
  &amp;quot;IsBodyHtml&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Body&amp;quot;: mailBodyHtml&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;NOTE: QPR ProcessAnalyzer Server has to have SMTP server configured. Also, remember to update the values of replyToAddress and recipientsArray before using.&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=26001</id>
		<title>Expression Script Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=26001"/>
		<updated>2025-03-18T14:29:35Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Perform a query and send results as E-mail in a HTML table */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains script examples written in the QPR ProcessAnalyzer expression language. See how expression scripts can be created in the [[Managing_Scripts#Creating_Script|Workspace]]. For documentation for the syntax, functions and entities can be found from the main page in the [[QPR_ProcessAnalyzer_Wiki#For_Developers|KPI Expression Language]] section.&lt;br /&gt;
&lt;br /&gt;
== Calling Expression Script from Expression ==&lt;br /&gt;
Expression scripts can be called from an expression using the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Run]] function with the following syntax:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: false,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 123.45&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command waits until the run is completed, and the return value of the called script is returned by the Run function call.&lt;br /&gt;
&lt;br /&gt;
Parameters can be passed to the called script, and the parameters are available as variables in the script. The parameters can contain any type of data.&lt;br /&gt;
&lt;br /&gt;
Expression scripts can also be called from a dashboard. Expressions can be stored to scripts instead of dashboards, which is a way to separate complex expressions from dashboards and allow to reuse expressions across several dashboards.&lt;br /&gt;
&lt;br /&gt;
== Calling SQL Script from Expression ==&lt;br /&gt;
SQL script can be called from an expression using the Run function as follows (similar to calling [[#Calling Expression Script from Expression|expression scripts]]):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 321&lt;br /&gt;
});&lt;br /&gt;
let arrayOfAllReports = result.Keys;&lt;br /&gt;
let report1 = result.Report1;&lt;br /&gt;
let report2 = result.Report2;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL scripts can return multiple &#039;&#039;reports&#039;&#039;, which are combined to a dictionary, where the key is the name of the report (&amp;quot;sheet name&amp;quot;) and value is the report data as a DataFrame. See in the above example, how the reports can be accessed by their name.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Call web service===&lt;br /&gt;
Contact to a web service, fetch some data, and store it to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let datatableName = &amp;quot;Web Service Data&amp;quot;;&lt;br /&gt;
let webServiceData = CallWebService(&lt;br /&gt;
    #{&amp;quot;Address&amp;quot;: &amp;quot;https://processanalyzer.onqpr.com/qprpa/api/serverinfo&amp;quot;}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
let targetDatatable = Project.Datatables.Where(name==datatableName);&lt;br /&gt;
if (Count(targetDatatable) == 0) {&lt;br /&gt;
	targetDatatable = Project.CreateDatatable(datatableName)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting value&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Data read&amp;quot;, &amp;quot;DateTime&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
	targetDatatable = targetDatatable[0];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let currentTime = Now;&lt;br /&gt;
let dataAsDf = ToDataFrame(&lt;br /&gt;
	webServiceData.keys.{&lt;br /&gt;
        let key = _;&lt;br /&gt;
        [key, webServiceData[key], currentTime];&lt;br /&gt;
    },&lt;br /&gt;
	[&amp;quot;Setting name&amp;quot;, &amp;quot;Setting value&amp;quot;, &amp;quot;Data read&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
targetDatatable.Import(dataAsDf, #{&amp;quot;Append&amp;quot;:true});&lt;br /&gt;
WriteLog(`${CountTop(dataAsDf.Rows)} rows written to datatable`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Store data to datatable ===&lt;br /&gt;
Get all models in the system and store them to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let newDatatable = Project&lt;br /&gt;
    .CreateDatatable(&amp;quot;Models list &amp;quot; + ToString(Now, &amp;quot;dd.MM.yyyy HH:mm:ss&amp;quot;))&lt;br /&gt;
    .AddColumn(&amp;quot;Model name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Project name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Created time&amp;quot;, &amp;quot;DateTime&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Cases&amp;quot;, &amp;quot;Integer&amp;quot;);&lt;br /&gt;
let startTime = Now;&lt;br /&gt;
let modelsData = ToDataFrame(&lt;br /&gt;
    Models.([Name, Project.Name, CreatedDate, NCases]),&lt;br /&gt;
    [&amp;quot;Model name&amp;quot;, &amp;quot;Project name&amp;quot;, &amp;quot;Created time&amp;quot;, &amp;quot;Cases&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
WriteLog(`Listing models took ${(Now - startTime).TotalSeconds.Round(2)} seconds.`);&lt;br /&gt;
newDatatable.Import(modelsData);&lt;br /&gt;
WriteLog(`Datatable ${newDatatable.Id} created.`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data ===&lt;br /&gt;
This script can be used to convert a single column into numerical data type. To use the script, you need to setup the following in the beginning of the script:&lt;br /&gt;
* Project name where the datatable is located.&lt;br /&gt;
* Datatable name&lt;br /&gt;
* Name of the column to be converted&lt;br /&gt;
&lt;br /&gt;
Note that the conversion fails, if there is data that cannot be converted into numerical format. The conversion assumes that period (.) is used as the decimal point. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let projectName = &amp;quot;New Project&amp;quot;;&lt;br /&gt;
let datatableName = &amp;quot;qpr processanalyzer events&amp;quot;;&lt;br /&gt;
let columnName = &amp;quot;Event order in case&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let project = (Projects.Where(Name==projectName))[0];&lt;br /&gt;
let datatable = (project.Datatables.Where(Name==datatableName))[0];&lt;br /&gt;
DatatableById(datatable.Id).DataFrame&lt;br /&gt;
.SetColumns([&lt;br /&gt;
	columnName: () =&amp;gt; {&lt;br /&gt;
		let data = Column(columnName);&lt;br /&gt;
		if (data == null) {&lt;br /&gt;
			null;&lt;br /&gt;
		 } else {&lt;br /&gt;
			ToFloat(data);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
])&lt;br /&gt;
.Persist(datatable.Name, [&amp;quot;ProjectId&amp;quot;: project.Id, &amp;quot;Append&amp;quot;: false]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of converting to numeric (with the &#039;&#039;ToFloat&#039;&#039; function), data can be converted into string using the &#039;&#039;ToString&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Show DataFrame as HTML table ===&lt;br /&gt;
&lt;br /&gt;
This script defines a function to show dataframe as a HTML table, and uses the function for a literal dataframe.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function dataframeToHtmlTable(df) {&lt;br /&gt;
	return&lt;br /&gt;
`&amp;lt;table&amp;gt;&lt;br /&gt;
	&amp;lt;tr&amp;gt;&lt;br /&gt;
		${StringJoin(&amp;quot;\r\n\t\t&amp;quot;,  + df.columns.`&amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;`)}&lt;br /&gt;
	&amp;lt;/tr&amp;gt;&lt;br /&gt;
	${StringJoin(&amp;quot;&amp;quot;, df.Rows.(&lt;br /&gt;
		&amp;quot;\r\n\t&amp;lt;tr&amp;gt;&amp;quot; + StringJoin(&amp;quot;&amp;quot;, _.`\r\n\t\t&amp;lt;td&amp;gt;${ToString(_)}&amp;lt;/td&amp;gt;`) + &amp;quot;\r\n\t&amp;lt;/tr&amp;gt;&amp;quot;&lt;br /&gt;
	))}&lt;br /&gt;
&amp;lt;/table&amp;gt;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let data = ToDataFrame(&lt;br /&gt;
	[&lt;br /&gt;
		[&amp;quot;one&amp;quot;, &amp;quot;two&amp;quot;, &amp;quot;three&amp;quot;],&lt;br /&gt;
		[&amp;quot;four&amp;quot;, &amp;quot;five&amp;quot;, &amp;quot;six&amp;quot;],&lt;br /&gt;
		[&amp;quot;seven&amp;quot;, &amp;quot;eight&amp;quot;, &amp;quot;nine&amp;quot;]&lt;br /&gt;
	],&lt;br /&gt;
	[&amp;quot;Column 1&amp;quot;, &amp;quot;Column 2&amp;quot;, &amp;quot;Column 3&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
return dataframeToHtmlTable(data);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy local datatables to Snowflake ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
// Copies all datatables in a project to another project including datatable contents.&lt;br /&gt;
// Usage instructions:&lt;br /&gt;
// 1. Create expression script in the project from where you want to copy the datatables.&lt;br /&gt;
// 2. Create a new project named as &amp;quot;&amp;lt;name of the project to be moved&amp;gt; - Snowflake&amp;quot;. New datatables will be created here. E.g., when moving project named &amp;quot;SAP_OrderToCash&amp;quot;, the target project should be named as &amp;quot;SAP_OrderToCash - Snowflake&amp;quot;.&lt;br /&gt;
// 3. Run the script.&lt;br /&gt;
// NOTE: Columns of type &amp;quot;Any&amp;quot; will be created as &amp;quot;String&amp;quot;-columns in Snowflake, thus it is recommended that actual data types are set for the tables prior to the move.&lt;br /&gt;
&lt;br /&gt;
let sourceProject = Project;&lt;br /&gt;
let sourceProjectName = Project.Name;&lt;br /&gt;
let targetProjectName = `${sourceProjectName} - Snowflake`;&lt;br /&gt;
let targetProject = First(Projects.Where(Name == targetProjectName));&lt;br /&gt;
if (IsNull(targetProject)) {&lt;br /&gt;
  WriteLog(`Unable to find target project named &amp;quot;${targetProjectName}&amp;quot;. Aborting operation.`);&lt;br /&gt;
  return;&lt;br /&gt;
}&lt;br /&gt;
let dts = sourceProject.DataTables;&lt;br /&gt;
WriteLog(`Copying all ${CountTop(dts)} data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
dts.{&lt;br /&gt;
  let sourceDt = _;&lt;br /&gt;
  WriteLog(`Starting to copy data table &amp;quot;${Name}&amp;quot; (id: ${Id}) having ${NRows} rows and ${NColumns} columns.`);&lt;br /&gt;
  let targetDt;&lt;br /&gt;
  targetDt = targetProject.DatatableByName(sourceDt.Name);&lt;br /&gt;
  if (targetDt == null) {&lt;br /&gt;
    targetDt = targetProject.CreateDataTable(sourceDt.Name, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: targetProject.Id})});&lt;br /&gt;
    targetDt.Import(sourceDt.SqlDataFrame);&lt;br /&gt;
    WriteLog(`Finished copying data table &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  } else {&lt;br /&gt;
    WriteLog(`Datatable already exist &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
WriteLog(`Finished copying all the data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to copy the data but only create the Snowflake datatables with columns, you can change the line 22 to&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
targetDt.Import(sourceDt.SqlDataFrame.head(0));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy single datatable to Snowflake ===&lt;br /&gt;
This script creates a copy of a single datatable to Snowflake. Replace the &#039;&#039;&amp;lt;tableId1&amp;gt;&#039;&#039; with the id of the source datatable.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function CopyDataTableToSnowflake(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let sourceDt = DataTableById(dataTableId);&lt;br /&gt;
  sourceDt.SqlDataFrame.Persist(`${sourceDt.Name} - Snowflake`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: sourceDt.Project.Id})});&lt;br /&gt;
}&lt;br /&gt;
CopyDataTableToSnowflake(&amp;lt;tableId1&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a copy of a data table that has all Any-type columns changed to String-type columns ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function ConvertAnyDataTypesToStringsToNewTable(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let dt = DataTableById(dataTableId);&lt;br /&gt;
  let sdf = dt.SqlDataFrame;&lt;br /&gt;
  let cts = dt.ColumnTypes;&lt;br /&gt;
  cts.{&lt;br /&gt;
    let ct = _;&lt;br /&gt;
    if (ct.DataType == &amp;quot;Any&amp;quot;) {&lt;br /&gt;
      let n = ct.Name;&lt;br /&gt;
      sdf = sdf.WithColumn(ct.Name, #sql{Cast(Column(Variable(&amp;quot;n&amp;quot;)), &amp;quot;ShortString&amp;quot;)});&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  sdf.Persist(`${dt.Name} - Converted`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;ProjectId&amp;quot;: dt.Project.Id});&lt;br /&gt;
}&lt;br /&gt;
ConvertAnyDataTypesToStringsToNewTable(&amp;lt;dataTableId&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Query number of rows in given data table having a datetime value in given year grouped by month and return resulting table as CSV ===&lt;br /&gt;
SqlDataFrame is used in order to prevent loading the whole datatable into memory first. Filtering is performed as first operation in order to minimize the amount of required work for the data source of the data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
DataTableById(&amp;lt;data table id&amp;gt;)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .Where(#sql{2014 == Year(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .WithColumn(&amp;quot;Month&amp;quot;, #sql{Month(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .GroupBy([&amp;quot;Month&amp;quot;]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
  .OrderByColumns([&amp;quot;Month&amp;quot;], [true])&lt;br /&gt;
  .Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Function for filtering SqlDataFrame by removing rows having, or replacing, the most infrequently occurring column values ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/***&lt;br /&gt;
 * @name ColumnWithMinUsage&lt;br /&gt;
 * @descripion&lt;br /&gt;
 * Generic function that can be used to filter out the most infrequently occurring attribute values or replace their Values&lt;br /&gt;
 * with given common value.&lt;br /&gt;
 * @param df:&lt;br /&gt;
 * DataFrame to operate on.&lt;br /&gt;
 * @param columnName:&lt;br /&gt;
 * Name of the column to be filtered.&lt;br /&gt;
 * @param newColumnName:&lt;br /&gt;
 * Name of the column that will contain the new value of the original column after filtering (if includeOthers was applied).&lt;br /&gt;
 * @param maxNumUniqueValues:&lt;br /&gt;
 * Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
 * @param minValueUsage:&lt;br /&gt;
 * Minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
 * @param includeOthers:&lt;br /&gt;
 * Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
 * If not empty/null, defines the name used for these other-values.&lt;br /&gt;
 */&lt;br /&gt;
function ColumnWithMinUsage(df, columnName, newColumnName, maxNumUniqueValues, minValueUsage, includeOthers)&lt;br /&gt;
{&lt;br /&gt;
  let all = df&lt;br /&gt;
	.GroupBy([])&lt;br /&gt;
	.Aggregate([&amp;quot;NAllTotal&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
	.WithColumn(&amp;quot;__Join2&amp;quot;, #sql{1});&lt;br /&gt;
  let minValueUsageEnabled = !IsNullTop(minValueUsage);&lt;br /&gt;
  let maxNumUniqueValuesEnabled = !IsNullTop(maxNumUniqueValues);&lt;br /&gt;
  if (minValueUsageEnabled || maxNumUniqueValuesEnabled) {&lt;br /&gt;
	// Perform column value-based filtering if minValueUsageEnabled or maxNumUniqueValuesEnabled is defined.&lt;br /&gt;
    let valueColumnName = &amp;quot;__ValueNew&amp;quot;;&lt;br /&gt;
	let filteredValuesColumns = [valueColumnName: columnName];&lt;br /&gt;
	let filteredValues = df&lt;br /&gt;
	  .GroupBy([columnName]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;]);&lt;br /&gt;
	if (minValueUsageEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithColumn(&amp;quot;__Join&amp;quot;, #sql{1})&lt;br /&gt;
		.Join(all, [&amp;quot;__Join&amp;quot;: &amp;quot;__Join2&amp;quot;], &amp;quot;leftouter&amp;quot;)&lt;br /&gt;
        .WithColumn(&amp;quot;Usage&amp;quot;, #sql{Column(&amp;quot;Count&amp;quot;) / Column(&amp;quot;NAllTotal&amp;quot;)});&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;Usage&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
	if (maxNumUniqueValuesEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithRowNumberColumn(&amp;quot;RowNumber&amp;quot;, [&amp;quot;Count&amp;quot;], null, [false]);&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;RowNumber&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	filteredValues = filteredValues&lt;br /&gt;
	  .Select(filteredValuesColumns);&lt;br /&gt;
&lt;br /&gt;
	// Generate select returning all the accepted values.&lt;br /&gt;
	let allValues = filteredValues&lt;br /&gt;
	  .(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;gt;= #expr{minValueUsage}}) : _)&lt;br /&gt;
	  .(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;lt;= #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
	  .Select([valueColumnName, newColumnName: valueColumnName]);&lt;br /&gt;
&lt;br /&gt;
	if (!IsNullTop(includeOthers)) {&lt;br /&gt;
	  // If includeOthers is defined, replace original values with the variable defined in includeOthers.&lt;br /&gt;
	  let otherValues = filteredValues&lt;br /&gt;
		.(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;lt; #expr{minValueUsage}}) : _)&lt;br /&gt;
		.(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;gt; #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
		.WithColumn(newColumnName, #sql{#expr{includeOthers}})&lt;br /&gt;
		.Select([valueColumnName, newColumnName]);&lt;br /&gt;
	  allValues = allValues.Append(otherValues)&lt;br /&gt;
	}&lt;br /&gt;
	df.Join(allValues, [columnName: valueColumnName], &amp;quot;inner&amp;quot;)&lt;br /&gt;
	  .RemoveColumns([valueColumnName]);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// The following example will return only rows containing two of the most common values for Region-column.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, 2, null, null);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&lt;br /&gt;
// The following example will return all input rows, but will replace the values of rows whose Region-column&lt;br /&gt;
// has a value used by less than 15% of all the rows with a new value: &amp;quot;_Others&amp;quot;.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, null, 0.15, &amp;quot;_Others&amp;quot;);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export model events and cases ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function ExportModelEvents(m) {&lt;br /&gt;
  let attrs = m.EventAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Events.Concat(&lt;br /&gt;
      [Case.Name, Type.Name, ToString(TimeStamp, &amp;quot;yyyy-MM-dd HH:mm:ss.fff&amp;quot;)], &lt;br /&gt;
      {let evt = _; attrs.{let att = _; evt.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;, &amp;quot;EventType&amp;quot;, &amp;quot;TimeStamp&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
function ExportModelCases(m) {&lt;br /&gt;
  let attrs = m.CaseAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Cases.Concat(&lt;br /&gt;
      [Name], &lt;br /&gt;
      {let cas = _; attrs.{let att = _; cas.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).EventsDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).CasesDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Calculate all the value usages of a single column for each event in event data table ===&lt;br /&gt;
This query could be used, e.g., to find out the maximum resource usage for every resource found in the event data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function WithUsageColumns(resourceColumn)&lt;br /&gt;
{&lt;br /&gt;
  function WithTotalUsageColumnOfSingleResource(resourceColumn, resourceValue)&lt;br /&gt;
  {&lt;br /&gt;
    _&lt;br /&gt;
      .WithColumn(&amp;quot;_Prev&amp;quot;, #sql{Lag(Column(resourceColumn), [TimeStamp, EventType], [true, true], [CaseId], 1, null)})&lt;br /&gt;
      .WithColumn(&amp;quot;_UsageDiff&amp;quot;, #sql{&lt;br /&gt;
        CaseWhen(&lt;br /&gt;
          Column(resourceColumn) == Column(&amp;quot;_Prev&amp;quot;), 0, &lt;br /&gt;
          Column(&amp;quot;_Prev&amp;quot;) == #expr{resourceValue}, -1,&lt;br /&gt;
          Column(resourceColumn) == #expr{resourceValue}, 1,&lt;br /&gt;
          0)&lt;br /&gt;
      })&lt;br /&gt;
      .WithColumn(`${resourceValue}_Usage`, #sql{Sum(Column(&amp;quot;_UsageDiff&amp;quot;), [TimeStamp, EventType])})&lt;br /&gt;
      .RemoveColumns([&amp;quot;_Prev&amp;quot;, &amp;quot;_UsageDiff&amp;quot;])&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let sdf = _;&lt;br /&gt;
  let allValues = sdf.SelectDistinct([resourceColumn]).OrderByColumns([resourceColumn], [true]).Collect().Column(resourceColumn);&lt;br /&gt;
  allValues.{&lt;br /&gt;
    let v = _;&lt;br /&gt;
    sdf = sdf.WithTotalUsageColumnOfSingleResource(resourceColumn, v)&lt;br /&gt;
  }&lt;br /&gt;
  sdf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
dt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .WithUsageColumns(&amp;lt;resource column name&amp;gt;)&lt;br /&gt;
  .OrderByColumns([dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]], [true])&lt;br /&gt;
  .Collect().ToCsv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;model id&amp;gt; is the id of the model containing event data to be examined.&lt;br /&gt;
* &amp;lt;resource column name&amp;gt; is the name of the column in the event data table of the specified model containing the resource being used by that event.&lt;br /&gt;
&lt;br /&gt;
NOTE: This expression uses functionalities that are only supported in Snowflake-based data tables.&lt;br /&gt;
&lt;br /&gt;
=== Create new Snowflake model from filter ===&lt;br /&gt;
This script creates a new Snowflake model (and two datatables for cases and events) containing filtered event log from given filter id. The script also works if the model doesn&#039;t have a cases datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let filter = FilterById(1); // filter id&lt;br /&gt;
let model = filter.model;&lt;br /&gt;
let project = model.project;&lt;br /&gt;
let nameSuffix = &amp;quot; - &amp;quot; + filter.name + &amp;quot; - &amp;quot; + ToString(Now, &amp;quot;dd-MM-yyyy HH:mm:ss&amp;quot;);&lt;br /&gt;
let eventsDatatableName = model.EventsDataTable.Name + nameSuffix;&lt;br /&gt;
if (eventsDatatableName.length &amp;gt; 440) {&lt;br /&gt;
  eventsDatatableName = eventsDatatableName.Substring(eventsDatatableName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
let eventsData = model&lt;br /&gt;
  .EventsDataTable&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .ApplyFilter(&lt;br /&gt;
    filter.rules,&lt;br /&gt;
    model.CasesDataTable?.SqlDataFrame&lt;br /&gt;
  );&lt;br /&gt;
project&lt;br /&gt;
  .CreateDatatable(eventsDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
  .Import(eventsData);&lt;br /&gt;
let modelConfiguration = model.Configuration;&lt;br /&gt;
modelConfiguration.DataSource.Events.Set(&amp;quot;DataTableName&amp;quot;, eventsDatatableName);&lt;br /&gt;
if (model.CasesDataTable != null) {&lt;br /&gt;
  let eventsDataCaseIdColumn = &amp;quot;CaseId_&amp;quot; + ToString(Random());&lt;br /&gt;
  let casesDatatableName = model.CasesDataTable.Name + nameSuffix;&lt;br /&gt;
  if (casesDatatableName.length &amp;gt; 440) {&lt;br /&gt;
    casesDatatableName = casesDatatableName.Substring(casesDatatableName.length - 440);&lt;br /&gt;
  }&lt;br /&gt;
  let casesData = model&lt;br /&gt;
    .CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .join(&lt;br /&gt;
	  eventsData.SelectDistinct([eventsDataCaseIdColumn: modelConfiguration.DataSource.Events.Columns.CaseId]),&lt;br /&gt;
      [modelConfiguration.DataSource.Cases.Columns.CaseId: eventsDataCaseIdColumn]&lt;br /&gt;
	).Select(model.CasesDataTable.ColumnNames);&lt;br /&gt;
  project&lt;br /&gt;
    .CreateDatatable(casesDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
    .Import(casesData);&lt;br /&gt;
  modelConfiguration.DataSource.Cases.Set(&amp;quot;DataTableName&amp;quot;, casesDatatableName);&lt;br /&gt;
}&lt;br /&gt;
let modelName = model.Name + nameSuffix;&lt;br /&gt;
if (modelName &amp;gt; 440) {&lt;br /&gt;
  modelName = modelName.Substring(modelName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
project&lt;br /&gt;
  .CreateModel(#{    &lt;br /&gt;
    &amp;quot;Name&amp;quot;: modelName,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: model.Description,&lt;br /&gt;
    &amp;quot;Configuration&amp;quot;: modelConfiguration&lt;br /&gt;
  });&lt;br /&gt;
return modelName;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Creating a model consisting of multiple copies of cases in an existing model ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @name CreateTestModel&lt;br /&gt;
 * @description&lt;br /&gt;
 * Creates a new model (or overwrites an existing) to given target project with given number of &lt;br /&gt;
 * repetitions of given source model.&lt;br /&gt;
 * Each repetition will generate &amp;quot;&amp;lt;N&amp;gt;-&amp;quot;-prefix to CaseId-columns, where N equals to the repeat index.&lt;br /&gt;
 * @param sourceModel&lt;br /&gt;
 * PA model used for the source data and from where the connection is copied for the target model if a &lt;br /&gt;
 * new one has to be created.&lt;br /&gt;
 * @param numRepeats&lt;br /&gt;
 * Number of times the data in the source model should be repeated in the generated model.&lt;br /&gt;
 * @param targetProject&lt;br /&gt;
 * Project in which the target model resides.&lt;br /&gt;
 * @param targetModelName&lt;br /&gt;
 * Specifies the name of the test model in the given target project. If a model already exists with &lt;br /&gt;
 * given name, event and case data in this model will be replaced with the new generated event and &lt;br /&gt;
 * case data.&lt;br /&gt;
 * @returns&lt;br /&gt;
 * Model object of the test model having the newly generated data.&lt;br /&gt;
 */&lt;br /&gt;
function CreateTestModel(sourceModel, numRepeats, targetProject, targetModelName) &lt;br /&gt;
{&lt;br /&gt;
  let eventsColumnMappings = sourceModel.EventsDataTable.ColumnMappings;&lt;br /&gt;
  let casesColumnMappings = sourceModel.CasesDataTable.ColumnMappings;&lt;br /&gt;
  let connection = sourceModel.EventsDataTable.DataSourceConnection;&lt;br /&gt;
&lt;br /&gt;
  function CreateResultModel()&lt;br /&gt;
  {&lt;br /&gt;
    function GetTable(tableName) &lt;br /&gt;
    {&lt;br /&gt;
      let tableConfiguration = #{&lt;br /&gt;
        &amp;quot;Name&amp;quot;: tableName,&lt;br /&gt;
        &amp;quot;Connection&amp;quot;: connection&lt;br /&gt;
      };&lt;br /&gt;
      let resultTable = targetProject.DataTableByName(tableName);&lt;br /&gt;
      if (resultTable == null)&lt;br /&gt;
      {&lt;br /&gt;
        resultTable = targetProject.CreateDataTable(tableConfiguration)&lt;br /&gt;
          .Modify(#{&amp;quot;NameInDataSource&amp;quot;: null})&lt;br /&gt;
          .Synchronize();&lt;br /&gt;
      }&lt;br /&gt;
      return resultTable;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let eventsTableName = `${targetModelName} - events`;&lt;br /&gt;
    let casesTableName = `${targetModelName} - cases`;&lt;br /&gt;
    let targetModel = targetProject.ModelByName(targetModelName);&lt;br /&gt;
    let eventsTable, casesTable = null;&lt;br /&gt;
&lt;br /&gt;
    if (targetModel != null)&lt;br /&gt;
    {&lt;br /&gt;
      eventsTable = targetModel.EventsDataTable;&lt;br /&gt;
      casesTable = targetModel.CasesDataTable;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      eventsTable = GetTable(eventsTableName);&lt;br /&gt;
      if (sourceModel.CasesDataTable != null) {&lt;br /&gt;
        casesTable = GetTable(casesTableName);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      let timestampMapping = eventsColumnMappings[&amp;quot;TimeStamp&amp;quot;];&lt;br /&gt;
      eventsColumnMappings.Remove(&amp;quot;TimeStamp&amp;quot;);&lt;br /&gt;
      eventsColumnMappings.Set(&amp;quot;Timestamp&amp;quot;, timestampMapping);&lt;br /&gt;
&lt;br /&gt;
      let modelConfiguration = #{&lt;br /&gt;
        &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
          &amp;quot;Events&amp;quot;:#{&lt;br /&gt;
            &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
            &amp;quot;DataTableName&amp;quot;: eventsTableName,&lt;br /&gt;
            &amp;quot;Columns&amp;quot;: eventsColumnMappings&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      if (casesColumnMappings != null) {&lt;br /&gt;
        modelConfiguration[&amp;quot;DataSource&amp;quot;].Set(&amp;quot;Cases&amp;quot;, #{&lt;br /&gt;
          &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
          &amp;quot;DataTableName&amp;quot;: casesTableName,&lt;br /&gt;
          &amp;quot;Columns&amp;quot;: casesColumnMappings&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      targetModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: targetModelName, &amp;quot;Configuration&amp;quot;: modelConfiguration});&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    eventsTable.Truncate();&lt;br /&gt;
    casesTable?.Truncate();&lt;br /&gt;
&lt;br /&gt;
    return #{&lt;br /&gt;
      &amp;quot;TargetModel&amp;quot;: targetModel,&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTable,&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: casesTable&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function RepeatNTimes(sourceDf, caseIdColumn, numRepeats)&lt;br /&gt;
  {&lt;br /&gt;
    let resultDf = null;&lt;br /&gt;
    for (let i = 1; i &amp;lt;= numRepeats; ++i) {&lt;br /&gt;
      let iterationDf = sourceDf&lt;br /&gt;
        .WithColumn(caseIdColumn, #sql{Concat(#expr{i}, &amp;quot;-&amp;quot;, Column(#expr{caseIdColumn}))});&lt;br /&gt;
      resultDf = resultDf == null ? iterationDf : resultDf.Append(iterationDf); &lt;br /&gt;
    }&lt;br /&gt;
    resultDf;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let resultModel = CreateResultModel();&lt;br /&gt;
  let sourceEventDataDf = sourceModel.EventsDataTable.SqlDataFrame;&lt;br /&gt;
  let resultEventDataDf = RepeatNTimes(sourceEventDataDf, eventsColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
  resultModel[&amp;quot;Events&amp;quot;].Import(resultEventDataDf);&lt;br /&gt;
&lt;br /&gt;
  let sourceCaseDataDf = sourceModel.CasesDataTable?.SqlDataFrame;&lt;br /&gt;
  if (sourceCaseDataDf != null) {&lt;br /&gt;
    let resultCaseDataDf = RepeatNTimes(sourceCaseDataDf, casesColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
    resultModel[&amp;quot;Cases&amp;quot;].Import(resultCaseDataDf);&lt;br /&gt;
  }&lt;br /&gt;
  resultModel[&amp;quot;TargetModel&amp;quot;];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example usage:&amp;lt;blockquote&amp;gt;CreateTestModel(ProjectByName(&amp;quot;Project&amp;quot;).ModelByName(&amp;quot;SAP_OrderToCash - Snowflake&amp;quot;), 3, ProjectByName(&amp;quot;TestData&amp;quot;), &amp;quot;TestModel&amp;quot;);&amp;lt;/blockquote&amp;gt;Creates a new model named &amp;quot;TestModel&amp;quot; (or overwrites old one) into project named &amp;quot;TestData&amp;quot; containing the data from model &amp;quot;SAP_OrderToCash - Snowflake&amp;quot; in project &amp;quot;Project&amp;quot; repeated three times.&lt;br /&gt;
&lt;br /&gt;
=== Analyzing declare patterns found in event log ===&lt;br /&gt;
&lt;br /&gt;
This is an example expression that shows how POSIX-style regular expressions can be used to search for cases in an event log having certain event type patterns [https://www.researchgate.net/publication/277631859_Generating_Event_Logs_Through_the_Simulation_of_Declare_Models declare patterns].&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
let sdf = dt.SqlDataFrame.Head(1000);&lt;br /&gt;
let caseIdColumn = dt.ColumnMappings[&amp;quot;CaseId&amp;quot;], timeStampColumn = dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;], eventTypeColumn = dt.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let eventTypesDf = sdf&lt;br /&gt;
  .SelectDistinct([eventTypeColumn])&lt;br /&gt;
  .OrderByColumns([eventTypeColumn], [true])&lt;br /&gt;
  .WithRowNumberColumn(&amp;quot;Token&amp;quot;, [eventTypeColumn])&lt;br /&gt;
  .WithColumn(&amp;quot;Token&amp;quot;, #sql{Char(Column(&amp;quot;Token&amp;quot;) + Unicode(&amp;quot;a&amp;quot;) - 1)});&lt;br /&gt;
&lt;br /&gt;
sdf = sdf&lt;br /&gt;
  .Join(eventTypesDf.Select([&amp;quot;_EventType2&amp;quot;: eventTypeColumn, &amp;quot;Token&amp;quot;]), [eventTypeColumn: &amp;quot;_EventType2&amp;quot;], &amp;quot;leftouter&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function RespondedExistencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*((a.*b.*)|(b.*a.*))*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*((${a}.*${b}.*)|(${b}.*${a}.*))*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a.*b)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}.*${b})*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function AlternateResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a[^a]*b[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}[^${a}]*${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ChainResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(ab[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function PrecedencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^b]*(a.*b)*[^b]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${b}]*(${a}.*${b})*[^${b}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let tracesDf = sdf&lt;br /&gt;
  .GroupBy([caseIdColumn])&lt;br /&gt;
  .Aggregate([&amp;quot;Trace&amp;quot;: &amp;quot;Token&amp;quot;], [#{&amp;quot;Function&amp;quot;: &amp;quot;list&amp;quot;, &amp;quot;Ordering&amp;quot;: [timeStampColumn], &amp;quot;Separator&amp;quot;: &amp;quot;&amp;quot;}])&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceNL&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceCA&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternNL&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternCA&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternNL&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternCA&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternNL&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternCA&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternNL&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternCA&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
[tracesDf.Collect().ToCsv(), eventTypesDf.Collect().ToCsv()]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Perform a query and send results as E-mail in a HTML table ===&lt;br /&gt;
This example requires two scripts that are both located in the same project:&lt;br /&gt;
&lt;br /&gt;
# Expression-type script to execute (can be, e.g., scheduled to run daily).&lt;br /&gt;
# Expression-type script containing the query JSON to use as basis for the e-mail. In this example, this script is named as &amp;quot;Send query as E-mail - query JSON&amp;quot;. The contents of this script is just the JSON representation of a query that can be extracted, e.g., from any PA chart view.&lt;br /&gt;
&lt;br /&gt;
Script #1 should contain the following code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let replyToAddress = &amp;quot;noreply@test.com&amp;quot;;&lt;br /&gt;
let recipientsArray = [&amp;quot;test@test.com&amp;quot;];&lt;br /&gt;
let queryConfiguration = Project.ScriptByName(&amp;quot;Send query as E-mail - query JSON&amp;quot;).Code;&lt;br /&gt;
let resultDf = Query(ParseJson(queryConfiguration));&lt;br /&gt;
let mailBodyHtml = resultDf.Collect().`&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
  &amp;lt;caption&amp;gt;Example query&amp;lt;/caption&amp;gt;&lt;br /&gt;
  &amp;lt;thead&amp;gt;&lt;br /&gt;
    &amp;lt;tr&amp;gt;&lt;br /&gt;
      ${StringJoin(&amp;quot;&amp;quot;, _.Columns.`&lt;br /&gt;
        &amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;&lt;br /&gt;
      `)}&lt;br /&gt;
    &amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;/thead&amp;gt;&lt;br /&gt;
  &amp;lt;tbody&amp;gt;&lt;br /&gt;
    ${StringJoin(&amp;quot;&amp;quot;, _.Rows.`&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
        ${StringJoin(&amp;quot;&amp;quot;, _.`&lt;br /&gt;
          &amp;lt;td&amp;gt;${_}&amp;lt;/td&amp;gt;&lt;br /&gt;
        `)}&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
    `)}&lt;br /&gt;
  &amp;lt;/tbody&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
`;&lt;br /&gt;
&lt;br /&gt;
SendEmail(#{&lt;br /&gt;
  &amp;quot;ReplyTo&amp;quot;: [replyToAddress],&lt;br /&gt;
  &amp;quot;To&amp;quot;: recipientsArray,&lt;br /&gt;
  &amp;quot;Subject&amp;quot;: &amp;quot;Example query E-mail&amp;quot;,&lt;br /&gt;
  &amp;quot;IsBodyHtml&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Body&amp;quot;: mailBodyHtml&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;NOTE: PA server has to have SMTP server configured. Also, remember to update the values of replyToAddress and recipientsArray before using.&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=26000</id>
		<title>Expression Script Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=26000"/>
		<updated>2025-03-18T14:26:41Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Perform a query and send results as E-mail in a HTML table */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains script examples written in the QPR ProcessAnalyzer expression language. See how expression scripts can be created in the [[Managing_Scripts#Creating_Script|Workspace]]. For documentation for the syntax, functions and entities can be found from the main page in the [[QPR_ProcessAnalyzer_Wiki#For_Developers|KPI Expression Language]] section.&lt;br /&gt;
&lt;br /&gt;
== Calling Expression Script from Expression ==&lt;br /&gt;
Expression scripts can be called from an expression using the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Run]] function with the following syntax:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: false,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 123.45&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command waits until the run is completed, and the return value of the called script is returned by the Run function call.&lt;br /&gt;
&lt;br /&gt;
Parameters can be passed to the called script, and the parameters are available as variables in the script. The parameters can contain any type of data.&lt;br /&gt;
&lt;br /&gt;
Expression scripts can also be called from a dashboard. Expressions can be stored to scripts instead of dashboards, which is a way to separate complex expressions from dashboards and allow to reuse expressions across several dashboards.&lt;br /&gt;
&lt;br /&gt;
== Calling SQL Script from Expression ==&lt;br /&gt;
SQL script can be called from an expression using the Run function as follows (similar to calling [[#Calling Expression Script from Expression|expression scripts]]):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 321&lt;br /&gt;
});&lt;br /&gt;
let arrayOfAllReports = result.Keys;&lt;br /&gt;
let report1 = result.Report1;&lt;br /&gt;
let report2 = result.Report2;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL scripts can return multiple &#039;&#039;reports&#039;&#039;, which are combined to a dictionary, where the key is the name of the report (&amp;quot;sheet name&amp;quot;) and value is the report data as a DataFrame. See in the above example, how the reports can be accessed by their name.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Call web service===&lt;br /&gt;
Contact to a web service, fetch some data, and store it to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let datatableName = &amp;quot;Web Service Data&amp;quot;;&lt;br /&gt;
let webServiceData = CallWebService(&lt;br /&gt;
    #{&amp;quot;Address&amp;quot;: &amp;quot;https://processanalyzer.onqpr.com/qprpa/api/serverinfo&amp;quot;}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
let targetDatatable = Project.Datatables.Where(name==datatableName);&lt;br /&gt;
if (Count(targetDatatable) == 0) {&lt;br /&gt;
	targetDatatable = Project.CreateDatatable(datatableName)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting value&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Data read&amp;quot;, &amp;quot;DateTime&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
	targetDatatable = targetDatatable[0];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let currentTime = Now;&lt;br /&gt;
let dataAsDf = ToDataFrame(&lt;br /&gt;
	webServiceData.keys.{&lt;br /&gt;
        let key = _;&lt;br /&gt;
        [key, webServiceData[key], currentTime];&lt;br /&gt;
    },&lt;br /&gt;
	[&amp;quot;Setting name&amp;quot;, &amp;quot;Setting value&amp;quot;, &amp;quot;Data read&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
targetDatatable.Import(dataAsDf, #{&amp;quot;Append&amp;quot;:true});&lt;br /&gt;
WriteLog(`${CountTop(dataAsDf.Rows)} rows written to datatable`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Store data to datatable ===&lt;br /&gt;
Get all models in the system and store them to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let newDatatable = Project&lt;br /&gt;
    .CreateDatatable(&amp;quot;Models list &amp;quot; + ToString(Now, &amp;quot;dd.MM.yyyy HH:mm:ss&amp;quot;))&lt;br /&gt;
    .AddColumn(&amp;quot;Model name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Project name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Created time&amp;quot;, &amp;quot;DateTime&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Cases&amp;quot;, &amp;quot;Integer&amp;quot;);&lt;br /&gt;
let startTime = Now;&lt;br /&gt;
let modelsData = ToDataFrame(&lt;br /&gt;
    Models.([Name, Project.Name, CreatedDate, NCases]),&lt;br /&gt;
    [&amp;quot;Model name&amp;quot;, &amp;quot;Project name&amp;quot;, &amp;quot;Created time&amp;quot;, &amp;quot;Cases&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
WriteLog(`Listing models took ${(Now - startTime).TotalSeconds.Round(2)} seconds.`);&lt;br /&gt;
newDatatable.Import(modelsData);&lt;br /&gt;
WriteLog(`Datatable ${newDatatable.Id} created.`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data ===&lt;br /&gt;
This script can be used to convert a single column into numerical data type. To use the script, you need to setup the following in the beginning of the script:&lt;br /&gt;
* Project name where the datatable is located.&lt;br /&gt;
* Datatable name&lt;br /&gt;
* Name of the column to be converted&lt;br /&gt;
&lt;br /&gt;
Note that the conversion fails, if there is data that cannot be converted into numerical format. The conversion assumes that period (.) is used as the decimal point. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let projectName = &amp;quot;New Project&amp;quot;;&lt;br /&gt;
let datatableName = &amp;quot;qpr processanalyzer events&amp;quot;;&lt;br /&gt;
let columnName = &amp;quot;Event order in case&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let project = (Projects.Where(Name==projectName))[0];&lt;br /&gt;
let datatable = (project.Datatables.Where(Name==datatableName))[0];&lt;br /&gt;
DatatableById(datatable.Id).DataFrame&lt;br /&gt;
.SetColumns([&lt;br /&gt;
	columnName: () =&amp;gt; {&lt;br /&gt;
		let data = Column(columnName);&lt;br /&gt;
		if (data == null) {&lt;br /&gt;
			null;&lt;br /&gt;
		 } else {&lt;br /&gt;
			ToFloat(data);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
])&lt;br /&gt;
.Persist(datatable.Name, [&amp;quot;ProjectId&amp;quot;: project.Id, &amp;quot;Append&amp;quot;: false]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of converting to numeric (with the &#039;&#039;ToFloat&#039;&#039; function), data can be converted into string using the &#039;&#039;ToString&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Show DataFrame as HTML table ===&lt;br /&gt;
&lt;br /&gt;
This script defines a function to show dataframe as a HTML table, and uses the function for a literal dataframe.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function dataframeToHtmlTable(df) {&lt;br /&gt;
	return&lt;br /&gt;
`&amp;lt;table&amp;gt;&lt;br /&gt;
	&amp;lt;tr&amp;gt;&lt;br /&gt;
		${StringJoin(&amp;quot;\r\n\t\t&amp;quot;,  + df.columns.`&amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;`)}&lt;br /&gt;
	&amp;lt;/tr&amp;gt;&lt;br /&gt;
	${StringJoin(&amp;quot;&amp;quot;, df.Rows.(&lt;br /&gt;
		&amp;quot;\r\n\t&amp;lt;tr&amp;gt;&amp;quot; + StringJoin(&amp;quot;&amp;quot;, _.`\r\n\t\t&amp;lt;td&amp;gt;${ToString(_)}&amp;lt;/td&amp;gt;`) + &amp;quot;\r\n\t&amp;lt;/tr&amp;gt;&amp;quot;&lt;br /&gt;
	))}&lt;br /&gt;
&amp;lt;/table&amp;gt;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let data = ToDataFrame(&lt;br /&gt;
	[&lt;br /&gt;
		[&amp;quot;one&amp;quot;, &amp;quot;two&amp;quot;, &amp;quot;three&amp;quot;],&lt;br /&gt;
		[&amp;quot;four&amp;quot;, &amp;quot;five&amp;quot;, &amp;quot;six&amp;quot;],&lt;br /&gt;
		[&amp;quot;seven&amp;quot;, &amp;quot;eight&amp;quot;, &amp;quot;nine&amp;quot;]&lt;br /&gt;
	],&lt;br /&gt;
	[&amp;quot;Column 1&amp;quot;, &amp;quot;Column 2&amp;quot;, &amp;quot;Column 3&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
return dataframeToHtmlTable(data);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy local datatables to Snowflake ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
// Copies all datatables in a project to another project including datatable contents.&lt;br /&gt;
// Usage instructions:&lt;br /&gt;
// 1. Create expression script in the project from where you want to copy the datatables.&lt;br /&gt;
// 2. Create a new project named as &amp;quot;&amp;lt;name of the project to be moved&amp;gt; - Snowflake&amp;quot;. New datatables will be created here. E.g., when moving project named &amp;quot;SAP_OrderToCash&amp;quot;, the target project should be named as &amp;quot;SAP_OrderToCash - Snowflake&amp;quot;.&lt;br /&gt;
// 3. Run the script.&lt;br /&gt;
// NOTE: Columns of type &amp;quot;Any&amp;quot; will be created as &amp;quot;String&amp;quot;-columns in Snowflake, thus it is recommended that actual data types are set for the tables prior to the move.&lt;br /&gt;
&lt;br /&gt;
let sourceProject = Project;&lt;br /&gt;
let sourceProjectName = Project.Name;&lt;br /&gt;
let targetProjectName = `${sourceProjectName} - Snowflake`;&lt;br /&gt;
let targetProject = First(Projects.Where(Name == targetProjectName));&lt;br /&gt;
if (IsNull(targetProject)) {&lt;br /&gt;
  WriteLog(`Unable to find target project named &amp;quot;${targetProjectName}&amp;quot;. Aborting operation.`);&lt;br /&gt;
  return;&lt;br /&gt;
}&lt;br /&gt;
let dts = sourceProject.DataTables;&lt;br /&gt;
WriteLog(`Copying all ${CountTop(dts)} data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
dts.{&lt;br /&gt;
  let sourceDt = _;&lt;br /&gt;
  WriteLog(`Starting to copy data table &amp;quot;${Name}&amp;quot; (id: ${Id}) having ${NRows} rows and ${NColumns} columns.`);&lt;br /&gt;
  let targetDt;&lt;br /&gt;
  targetDt = targetProject.DatatableByName(sourceDt.Name);&lt;br /&gt;
  if (targetDt == null) {&lt;br /&gt;
    targetDt = targetProject.CreateDataTable(sourceDt.Name, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: targetProject.Id})});&lt;br /&gt;
    targetDt.Import(sourceDt.SqlDataFrame);&lt;br /&gt;
    WriteLog(`Finished copying data table &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  } else {&lt;br /&gt;
    WriteLog(`Datatable already exist &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
WriteLog(`Finished copying all the data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to copy the data but only create the Snowflake datatables with columns, you can change the line 22 to&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
targetDt.Import(sourceDt.SqlDataFrame.head(0));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy single datatable to Snowflake ===&lt;br /&gt;
This script creates a copy of a single datatable to Snowflake. Replace the &#039;&#039;&amp;lt;tableId1&amp;gt;&#039;&#039; with the id of the source datatable.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function CopyDataTableToSnowflake(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let sourceDt = DataTableById(dataTableId);&lt;br /&gt;
  sourceDt.SqlDataFrame.Persist(`${sourceDt.Name} - Snowflake`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: sourceDt.Project.Id})});&lt;br /&gt;
}&lt;br /&gt;
CopyDataTableToSnowflake(&amp;lt;tableId1&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a copy of a data table that has all Any-type columns changed to String-type columns ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function ConvertAnyDataTypesToStringsToNewTable(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let dt = DataTableById(dataTableId);&lt;br /&gt;
  let sdf = dt.SqlDataFrame;&lt;br /&gt;
  let cts = dt.ColumnTypes;&lt;br /&gt;
  cts.{&lt;br /&gt;
    let ct = _;&lt;br /&gt;
    if (ct.DataType == &amp;quot;Any&amp;quot;) {&lt;br /&gt;
      let n = ct.Name;&lt;br /&gt;
      sdf = sdf.WithColumn(ct.Name, #sql{Cast(Column(Variable(&amp;quot;n&amp;quot;)), &amp;quot;ShortString&amp;quot;)});&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  sdf.Persist(`${dt.Name} - Converted`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;ProjectId&amp;quot;: dt.Project.Id});&lt;br /&gt;
}&lt;br /&gt;
ConvertAnyDataTypesToStringsToNewTable(&amp;lt;dataTableId&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Query number of rows in given data table having a datetime value in given year grouped by month and return resulting table as CSV ===&lt;br /&gt;
SqlDataFrame is used in order to prevent loading the whole datatable into memory first. Filtering is performed as first operation in order to minimize the amount of required work for the data source of the data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
DataTableById(&amp;lt;data table id&amp;gt;)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .Where(#sql{2014 == Year(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .WithColumn(&amp;quot;Month&amp;quot;, #sql{Month(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .GroupBy([&amp;quot;Month&amp;quot;]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
  .OrderByColumns([&amp;quot;Month&amp;quot;], [true])&lt;br /&gt;
  .Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Function for filtering SqlDataFrame by removing rows having, or replacing, the most infrequently occurring column values ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/***&lt;br /&gt;
 * @name ColumnWithMinUsage&lt;br /&gt;
 * @descripion&lt;br /&gt;
 * Generic function that can be used to filter out the most infrequently occurring attribute values or replace their Values&lt;br /&gt;
 * with given common value.&lt;br /&gt;
 * @param df:&lt;br /&gt;
 * DataFrame to operate on.&lt;br /&gt;
 * @param columnName:&lt;br /&gt;
 * Name of the column to be filtered.&lt;br /&gt;
 * @param newColumnName:&lt;br /&gt;
 * Name of the column that will contain the new value of the original column after filtering (if includeOthers was applied).&lt;br /&gt;
 * @param maxNumUniqueValues:&lt;br /&gt;
 * Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
 * @param minValueUsage:&lt;br /&gt;
 * Minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
 * @param includeOthers:&lt;br /&gt;
 * Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
 * If not empty/null, defines the name used for these other-values.&lt;br /&gt;
 */&lt;br /&gt;
function ColumnWithMinUsage(df, columnName, newColumnName, maxNumUniqueValues, minValueUsage, includeOthers)&lt;br /&gt;
{&lt;br /&gt;
  let all = df&lt;br /&gt;
	.GroupBy([])&lt;br /&gt;
	.Aggregate([&amp;quot;NAllTotal&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
	.WithColumn(&amp;quot;__Join2&amp;quot;, #sql{1});&lt;br /&gt;
  let minValueUsageEnabled = !IsNullTop(minValueUsage);&lt;br /&gt;
  let maxNumUniqueValuesEnabled = !IsNullTop(maxNumUniqueValues);&lt;br /&gt;
  if (minValueUsageEnabled || maxNumUniqueValuesEnabled) {&lt;br /&gt;
	// Perform column value-based filtering if minValueUsageEnabled or maxNumUniqueValuesEnabled is defined.&lt;br /&gt;
    let valueColumnName = &amp;quot;__ValueNew&amp;quot;;&lt;br /&gt;
	let filteredValuesColumns = [valueColumnName: columnName];&lt;br /&gt;
	let filteredValues = df&lt;br /&gt;
	  .GroupBy([columnName]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;]);&lt;br /&gt;
	if (minValueUsageEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithColumn(&amp;quot;__Join&amp;quot;, #sql{1})&lt;br /&gt;
		.Join(all, [&amp;quot;__Join&amp;quot;: &amp;quot;__Join2&amp;quot;], &amp;quot;leftouter&amp;quot;)&lt;br /&gt;
        .WithColumn(&amp;quot;Usage&amp;quot;, #sql{Column(&amp;quot;Count&amp;quot;) / Column(&amp;quot;NAllTotal&amp;quot;)});&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;Usage&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
	if (maxNumUniqueValuesEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithRowNumberColumn(&amp;quot;RowNumber&amp;quot;, [&amp;quot;Count&amp;quot;], null, [false]);&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;RowNumber&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	filteredValues = filteredValues&lt;br /&gt;
	  .Select(filteredValuesColumns);&lt;br /&gt;
&lt;br /&gt;
	// Generate select returning all the accepted values.&lt;br /&gt;
	let allValues = filteredValues&lt;br /&gt;
	  .(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;gt;= #expr{minValueUsage}}) : _)&lt;br /&gt;
	  .(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;lt;= #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
	  .Select([valueColumnName, newColumnName: valueColumnName]);&lt;br /&gt;
&lt;br /&gt;
	if (!IsNullTop(includeOthers)) {&lt;br /&gt;
	  // If includeOthers is defined, replace original values with the variable defined in includeOthers.&lt;br /&gt;
	  let otherValues = filteredValues&lt;br /&gt;
		.(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;lt; #expr{minValueUsage}}) : _)&lt;br /&gt;
		.(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;gt; #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
		.WithColumn(newColumnName, #sql{#expr{includeOthers}})&lt;br /&gt;
		.Select([valueColumnName, newColumnName]);&lt;br /&gt;
	  allValues = allValues.Append(otherValues)&lt;br /&gt;
	}&lt;br /&gt;
	df.Join(allValues, [columnName: valueColumnName], &amp;quot;inner&amp;quot;)&lt;br /&gt;
	  .RemoveColumns([valueColumnName]);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// The following example will return only rows containing two of the most common values for Region-column.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, 2, null, null);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&lt;br /&gt;
// The following example will return all input rows, but will replace the values of rows whose Region-column&lt;br /&gt;
// has a value used by less than 15% of all the rows with a new value: &amp;quot;_Others&amp;quot;.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, null, 0.15, &amp;quot;_Others&amp;quot;);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export model events and cases ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function ExportModelEvents(m) {&lt;br /&gt;
  let attrs = m.EventAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Events.Concat(&lt;br /&gt;
      [Case.Name, Type.Name, ToString(TimeStamp, &amp;quot;yyyy-MM-dd HH:mm:ss.fff&amp;quot;)], &lt;br /&gt;
      {let evt = _; attrs.{let att = _; evt.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;, &amp;quot;EventType&amp;quot;, &amp;quot;TimeStamp&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
function ExportModelCases(m) {&lt;br /&gt;
  let attrs = m.CaseAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Cases.Concat(&lt;br /&gt;
      [Name], &lt;br /&gt;
      {let cas = _; attrs.{let att = _; cas.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).EventsDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).CasesDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Calculate all the value usages of a single column for each event in event data table ===&lt;br /&gt;
This query could be used, e.g., to find out the maximum resource usage for every resource found in the event data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function WithUsageColumns(resourceColumn)&lt;br /&gt;
{&lt;br /&gt;
  function WithTotalUsageColumnOfSingleResource(resourceColumn, resourceValue)&lt;br /&gt;
  {&lt;br /&gt;
    _&lt;br /&gt;
      .WithColumn(&amp;quot;_Prev&amp;quot;, #sql{Lag(Column(resourceColumn), [TimeStamp, EventType], [true, true], [CaseId], 1, null)})&lt;br /&gt;
      .WithColumn(&amp;quot;_UsageDiff&amp;quot;, #sql{&lt;br /&gt;
        CaseWhen(&lt;br /&gt;
          Column(resourceColumn) == Column(&amp;quot;_Prev&amp;quot;), 0, &lt;br /&gt;
          Column(&amp;quot;_Prev&amp;quot;) == #expr{resourceValue}, -1,&lt;br /&gt;
          Column(resourceColumn) == #expr{resourceValue}, 1,&lt;br /&gt;
          0)&lt;br /&gt;
      })&lt;br /&gt;
      .WithColumn(`${resourceValue}_Usage`, #sql{Sum(Column(&amp;quot;_UsageDiff&amp;quot;), [TimeStamp, EventType])})&lt;br /&gt;
      .RemoveColumns([&amp;quot;_Prev&amp;quot;, &amp;quot;_UsageDiff&amp;quot;])&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let sdf = _;&lt;br /&gt;
  let allValues = sdf.SelectDistinct([resourceColumn]).OrderByColumns([resourceColumn], [true]).Collect().Column(resourceColumn);&lt;br /&gt;
  allValues.{&lt;br /&gt;
    let v = _;&lt;br /&gt;
    sdf = sdf.WithTotalUsageColumnOfSingleResource(resourceColumn, v)&lt;br /&gt;
  }&lt;br /&gt;
  sdf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
dt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .WithUsageColumns(&amp;lt;resource column name&amp;gt;)&lt;br /&gt;
  .OrderByColumns([dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]], [true])&lt;br /&gt;
  .Collect().ToCsv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;model id&amp;gt; is the id of the model containing event data to be examined.&lt;br /&gt;
* &amp;lt;resource column name&amp;gt; is the name of the column in the event data table of the specified model containing the resource being used by that event.&lt;br /&gt;
&lt;br /&gt;
NOTE: This expression uses functionalities that are only supported in Snowflake-based data tables.&lt;br /&gt;
&lt;br /&gt;
=== Create new Snowflake model from filter ===&lt;br /&gt;
This script creates a new Snowflake model (and two datatables for cases and events) containing filtered event log from given filter id. The script also works if the model doesn&#039;t have a cases datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let filter = FilterById(1); // filter id&lt;br /&gt;
let model = filter.model;&lt;br /&gt;
let project = model.project;&lt;br /&gt;
let nameSuffix = &amp;quot; - &amp;quot; + filter.name + &amp;quot; - &amp;quot; + ToString(Now, &amp;quot;dd-MM-yyyy HH:mm:ss&amp;quot;);&lt;br /&gt;
let eventsDatatableName = model.EventsDataTable.Name + nameSuffix;&lt;br /&gt;
if (eventsDatatableName.length &amp;gt; 440) {&lt;br /&gt;
  eventsDatatableName = eventsDatatableName.Substring(eventsDatatableName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
let eventsData = model&lt;br /&gt;
  .EventsDataTable&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .ApplyFilter(&lt;br /&gt;
    filter.rules,&lt;br /&gt;
    model.CasesDataTable?.SqlDataFrame&lt;br /&gt;
  );&lt;br /&gt;
project&lt;br /&gt;
  .CreateDatatable(eventsDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
  .Import(eventsData);&lt;br /&gt;
let modelConfiguration = model.Configuration;&lt;br /&gt;
modelConfiguration.DataSource.Events.Set(&amp;quot;DataTableName&amp;quot;, eventsDatatableName);&lt;br /&gt;
if (model.CasesDataTable != null) {&lt;br /&gt;
  let eventsDataCaseIdColumn = &amp;quot;CaseId_&amp;quot; + ToString(Random());&lt;br /&gt;
  let casesDatatableName = model.CasesDataTable.Name + nameSuffix;&lt;br /&gt;
  if (casesDatatableName.length &amp;gt; 440) {&lt;br /&gt;
    casesDatatableName = casesDatatableName.Substring(casesDatatableName.length - 440);&lt;br /&gt;
  }&lt;br /&gt;
  let casesData = model&lt;br /&gt;
    .CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .join(&lt;br /&gt;
	  eventsData.SelectDistinct([eventsDataCaseIdColumn: modelConfiguration.DataSource.Events.Columns.CaseId]),&lt;br /&gt;
      [modelConfiguration.DataSource.Cases.Columns.CaseId: eventsDataCaseIdColumn]&lt;br /&gt;
	).Select(model.CasesDataTable.ColumnNames);&lt;br /&gt;
  project&lt;br /&gt;
    .CreateDatatable(casesDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
    .Import(casesData);&lt;br /&gt;
  modelConfiguration.DataSource.Cases.Set(&amp;quot;DataTableName&amp;quot;, casesDatatableName);&lt;br /&gt;
}&lt;br /&gt;
let modelName = model.Name + nameSuffix;&lt;br /&gt;
if (modelName &amp;gt; 440) {&lt;br /&gt;
  modelName = modelName.Substring(modelName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
project&lt;br /&gt;
  .CreateModel(#{    &lt;br /&gt;
    &amp;quot;Name&amp;quot;: modelName,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: model.Description,&lt;br /&gt;
    &amp;quot;Configuration&amp;quot;: modelConfiguration&lt;br /&gt;
  });&lt;br /&gt;
return modelName;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Creating a model consisting of multiple copies of cases in an existing model ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @name CreateTestModel&lt;br /&gt;
 * @description&lt;br /&gt;
 * Creates a new model (or overwrites an existing) to given target project with given number of &lt;br /&gt;
 * repetitions of given source model.&lt;br /&gt;
 * Each repetition will generate &amp;quot;&amp;lt;N&amp;gt;-&amp;quot;-prefix to CaseId-columns, where N equals to the repeat index.&lt;br /&gt;
 * @param sourceModel&lt;br /&gt;
 * PA model used for the source data and from where the connection is copied for the target model if a &lt;br /&gt;
 * new one has to be created.&lt;br /&gt;
 * @param numRepeats&lt;br /&gt;
 * Number of times the data in the source model should be repeated in the generated model.&lt;br /&gt;
 * @param targetProject&lt;br /&gt;
 * Project in which the target model resides.&lt;br /&gt;
 * @param targetModelName&lt;br /&gt;
 * Specifies the name of the test model in the given target project. If a model already exists with &lt;br /&gt;
 * given name, event and case data in this model will be replaced with the new generated event and &lt;br /&gt;
 * case data.&lt;br /&gt;
 * @returns&lt;br /&gt;
 * Model object of the test model having the newly generated data.&lt;br /&gt;
 */&lt;br /&gt;
function CreateTestModel(sourceModel, numRepeats, targetProject, targetModelName) &lt;br /&gt;
{&lt;br /&gt;
  let eventsColumnMappings = sourceModel.EventsDataTable.ColumnMappings;&lt;br /&gt;
  let casesColumnMappings = sourceModel.CasesDataTable.ColumnMappings;&lt;br /&gt;
  let connection = sourceModel.EventsDataTable.DataSourceConnection;&lt;br /&gt;
&lt;br /&gt;
  function CreateResultModel()&lt;br /&gt;
  {&lt;br /&gt;
    function GetTable(tableName) &lt;br /&gt;
    {&lt;br /&gt;
      let tableConfiguration = #{&lt;br /&gt;
        &amp;quot;Name&amp;quot;: tableName,&lt;br /&gt;
        &amp;quot;Connection&amp;quot;: connection&lt;br /&gt;
      };&lt;br /&gt;
      let resultTable = targetProject.DataTableByName(tableName);&lt;br /&gt;
      if (resultTable == null)&lt;br /&gt;
      {&lt;br /&gt;
        resultTable = targetProject.CreateDataTable(tableConfiguration)&lt;br /&gt;
          .Modify(#{&amp;quot;NameInDataSource&amp;quot;: null})&lt;br /&gt;
          .Synchronize();&lt;br /&gt;
      }&lt;br /&gt;
      return resultTable;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let eventsTableName = `${targetModelName} - events`;&lt;br /&gt;
    let casesTableName = `${targetModelName} - cases`;&lt;br /&gt;
    let targetModel = targetProject.ModelByName(targetModelName);&lt;br /&gt;
    let eventsTable, casesTable = null;&lt;br /&gt;
&lt;br /&gt;
    if (targetModel != null)&lt;br /&gt;
    {&lt;br /&gt;
      eventsTable = targetModel.EventsDataTable;&lt;br /&gt;
      casesTable = targetModel.CasesDataTable;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      eventsTable = GetTable(eventsTableName);&lt;br /&gt;
      if (sourceModel.CasesDataTable != null) {&lt;br /&gt;
        casesTable = GetTable(casesTableName);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      let timestampMapping = eventsColumnMappings[&amp;quot;TimeStamp&amp;quot;];&lt;br /&gt;
      eventsColumnMappings.Remove(&amp;quot;TimeStamp&amp;quot;);&lt;br /&gt;
      eventsColumnMappings.Set(&amp;quot;Timestamp&amp;quot;, timestampMapping);&lt;br /&gt;
&lt;br /&gt;
      let modelConfiguration = #{&lt;br /&gt;
        &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
          &amp;quot;Events&amp;quot;:#{&lt;br /&gt;
            &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
            &amp;quot;DataTableName&amp;quot;: eventsTableName,&lt;br /&gt;
            &amp;quot;Columns&amp;quot;: eventsColumnMappings&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      if (casesColumnMappings != null) {&lt;br /&gt;
        modelConfiguration[&amp;quot;DataSource&amp;quot;].Set(&amp;quot;Cases&amp;quot;, #{&lt;br /&gt;
          &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
          &amp;quot;DataTableName&amp;quot;: casesTableName,&lt;br /&gt;
          &amp;quot;Columns&amp;quot;: casesColumnMappings&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      targetModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: targetModelName, &amp;quot;Configuration&amp;quot;: modelConfiguration});&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    eventsTable.Truncate();&lt;br /&gt;
    casesTable?.Truncate();&lt;br /&gt;
&lt;br /&gt;
    return #{&lt;br /&gt;
      &amp;quot;TargetModel&amp;quot;: targetModel,&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTable,&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: casesTable&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function RepeatNTimes(sourceDf, caseIdColumn, numRepeats)&lt;br /&gt;
  {&lt;br /&gt;
    let resultDf = null;&lt;br /&gt;
    for (let i = 1; i &amp;lt;= numRepeats; ++i) {&lt;br /&gt;
      let iterationDf = sourceDf&lt;br /&gt;
        .WithColumn(caseIdColumn, #sql{Concat(#expr{i}, &amp;quot;-&amp;quot;, Column(#expr{caseIdColumn}))});&lt;br /&gt;
      resultDf = resultDf == null ? iterationDf : resultDf.Append(iterationDf); &lt;br /&gt;
    }&lt;br /&gt;
    resultDf;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let resultModel = CreateResultModel();&lt;br /&gt;
  let sourceEventDataDf = sourceModel.EventsDataTable.SqlDataFrame;&lt;br /&gt;
  let resultEventDataDf = RepeatNTimes(sourceEventDataDf, eventsColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
  resultModel[&amp;quot;Events&amp;quot;].Import(resultEventDataDf);&lt;br /&gt;
&lt;br /&gt;
  let sourceCaseDataDf = sourceModel.CasesDataTable?.SqlDataFrame;&lt;br /&gt;
  if (sourceCaseDataDf != null) {&lt;br /&gt;
    let resultCaseDataDf = RepeatNTimes(sourceCaseDataDf, casesColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
    resultModel[&amp;quot;Cases&amp;quot;].Import(resultCaseDataDf);&lt;br /&gt;
  }&lt;br /&gt;
  resultModel[&amp;quot;TargetModel&amp;quot;];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example usage:&amp;lt;blockquote&amp;gt;CreateTestModel(ProjectByName(&amp;quot;Project&amp;quot;).ModelByName(&amp;quot;SAP_OrderToCash - Snowflake&amp;quot;), 3, ProjectByName(&amp;quot;TestData&amp;quot;), &amp;quot;TestModel&amp;quot;);&amp;lt;/blockquote&amp;gt;Creates a new model named &amp;quot;TestModel&amp;quot; (or overwrites old one) into project named &amp;quot;TestData&amp;quot; containing the data from model &amp;quot;SAP_OrderToCash - Snowflake&amp;quot; in project &amp;quot;Project&amp;quot; repeated three times.&lt;br /&gt;
&lt;br /&gt;
=== Analyzing declare patterns found in event log ===&lt;br /&gt;
&lt;br /&gt;
This is an example expression that shows how POSIX-style regular expressions can be used to search for cases in an event log having certain event type patterns [https://www.researchgate.net/publication/277631859_Generating_Event_Logs_Through_the_Simulation_of_Declare_Models declare patterns].&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
let sdf = dt.SqlDataFrame.Head(1000);&lt;br /&gt;
let caseIdColumn = dt.ColumnMappings[&amp;quot;CaseId&amp;quot;], timeStampColumn = dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;], eventTypeColumn = dt.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let eventTypesDf = sdf&lt;br /&gt;
  .SelectDistinct([eventTypeColumn])&lt;br /&gt;
  .OrderByColumns([eventTypeColumn], [true])&lt;br /&gt;
  .WithRowNumberColumn(&amp;quot;Token&amp;quot;, [eventTypeColumn])&lt;br /&gt;
  .WithColumn(&amp;quot;Token&amp;quot;, #sql{Char(Column(&amp;quot;Token&amp;quot;) + Unicode(&amp;quot;a&amp;quot;) - 1)});&lt;br /&gt;
&lt;br /&gt;
sdf = sdf&lt;br /&gt;
  .Join(eventTypesDf.Select([&amp;quot;_EventType2&amp;quot;: eventTypeColumn, &amp;quot;Token&amp;quot;]), [eventTypeColumn: &amp;quot;_EventType2&amp;quot;], &amp;quot;leftouter&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function RespondedExistencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*((a.*b.*)|(b.*a.*))*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*((${a}.*${b}.*)|(${b}.*${a}.*))*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a.*b)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}.*${b})*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function AlternateResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a[^a]*b[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}[^${a}]*${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ChainResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(ab[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function PrecedencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^b]*(a.*b)*[^b]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${b}]*(${a}.*${b})*[^${b}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let tracesDf = sdf&lt;br /&gt;
  .GroupBy([caseIdColumn])&lt;br /&gt;
  .Aggregate([&amp;quot;Trace&amp;quot;: &amp;quot;Token&amp;quot;], [#{&amp;quot;Function&amp;quot;: &amp;quot;list&amp;quot;, &amp;quot;Ordering&amp;quot;: [timeStampColumn], &amp;quot;Separator&amp;quot;: &amp;quot;&amp;quot;}])&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceNL&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceCA&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternNL&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternCA&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternNL&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternCA&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternNL&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternCA&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternNL&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternCA&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
[tracesDf.Collect().ToCsv(), eventTypesDf.Collect().ToCsv()]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Perform a query and send results as E-mail in a HTML table ===&lt;br /&gt;
This example requires two scripts that are both located in the same project:&lt;br /&gt;
&lt;br /&gt;
# Expression-type script to execute (can be, e.g., scheduled to run daily).&lt;br /&gt;
# Expression-type script containing the query JSON to use as basis for the e-mail. In this example, this script is named as &amp;quot;Send query as E-mail - query JSON&amp;quot;. The contents of this script is just the JSON representation of a query that can be extracted, e.g., from any PA chart view.&lt;br /&gt;
&lt;br /&gt;
Script #1 should contain the following code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let replyToAddress = &amp;quot;noreply@test.com&amp;quot;;&lt;br /&gt;
let recipientsArray = [&amp;quot;test@test.com&amp;quot;];&lt;br /&gt;
let queryConfiguration = Project.ScriptByName(&amp;quot;Send query as E-mail - query JSON&amp;quot;).Code;&lt;br /&gt;
let resultDf = Query(ParseJson(queryConfiguration));&lt;br /&gt;
let mailBodyHtml = resultDf.Collect().`&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
  &amp;lt;caption&amp;gt;Example query&amp;lt;/caption&amp;gt;&lt;br /&gt;
  &amp;lt;thead&amp;gt;&lt;br /&gt;
    &amp;lt;tr&amp;gt;&lt;br /&gt;
      ${StringJoin(&amp;quot;&amp;quot;, _.Columns.`&lt;br /&gt;
        &amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;&lt;br /&gt;
      `)}&lt;br /&gt;
    &amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;/thead&amp;gt;&lt;br /&gt;
  &amp;lt;tbody&amp;gt;&lt;br /&gt;
    ${StringJoin(&amp;quot;&amp;quot;, _.Rows.`&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
        ${StringJoin(&amp;quot;&amp;quot;, _.`&lt;br /&gt;
          &amp;lt;td&amp;gt;${_}&amp;lt;/td&amp;gt;&lt;br /&gt;
        `)}&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
    `)}&lt;br /&gt;
  &amp;lt;/tbody&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
`;&lt;br /&gt;
&lt;br /&gt;
SendEmail(#{&lt;br /&gt;
  &amp;quot;ReplyTo&amp;quot;: [replyToAddress],&lt;br /&gt;
  &amp;quot;To&amp;quot;: recipientsArray,&lt;br /&gt;
  &amp;quot;Subject&amp;quot;: &amp;quot;Example query E-mail&amp;quot;,&lt;br /&gt;
  &amp;quot;IsBodyHtml&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Body&amp;quot;: mailBodyHtml&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=25999</id>
		<title>Expression Script Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=25999"/>
		<updated>2025-03-18T14:17:10Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains script examples written in the QPR ProcessAnalyzer expression language. See how expression scripts can be created in the [[Managing_Scripts#Creating_Script|Workspace]]. For documentation for the syntax, functions and entities can be found from the main page in the [[QPR_ProcessAnalyzer_Wiki#For_Developers|KPI Expression Language]] section.&lt;br /&gt;
&lt;br /&gt;
== Calling Expression Script from Expression ==&lt;br /&gt;
Expression scripts can be called from an expression using the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Run]] function with the following syntax:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: false,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 123.45&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command waits until the run is completed, and the return value of the called script is returned by the Run function call.&lt;br /&gt;
&lt;br /&gt;
Parameters can be passed to the called script, and the parameters are available as variables in the script. The parameters can contain any type of data.&lt;br /&gt;
&lt;br /&gt;
Expression scripts can also be called from a dashboard. Expressions can be stored to scripts instead of dashboards, which is a way to separate complex expressions from dashboards and allow to reuse expressions across several dashboards.&lt;br /&gt;
&lt;br /&gt;
== Calling SQL Script from Expression ==&lt;br /&gt;
SQL script can be called from an expression using the Run function as follows (similar to calling [[#Calling Expression Script from Expression|expression scripts]]):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 321&lt;br /&gt;
});&lt;br /&gt;
let arrayOfAllReports = result.Keys;&lt;br /&gt;
let report1 = result.Report1;&lt;br /&gt;
let report2 = result.Report2;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL scripts can return multiple &#039;&#039;reports&#039;&#039;, which are combined to a dictionary, where the key is the name of the report (&amp;quot;sheet name&amp;quot;) and value is the report data as a DataFrame. See in the above example, how the reports can be accessed by their name.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Call web service===&lt;br /&gt;
Contact to a web service, fetch some data, and store it to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let datatableName = &amp;quot;Web Service Data&amp;quot;;&lt;br /&gt;
let webServiceData = CallWebService(&lt;br /&gt;
    #{&amp;quot;Address&amp;quot;: &amp;quot;https://processanalyzer.onqpr.com/qprpa/api/serverinfo&amp;quot;}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
let targetDatatable = Project.Datatables.Where(name==datatableName);&lt;br /&gt;
if (Count(targetDatatable) == 0) {&lt;br /&gt;
	targetDatatable = Project.CreateDatatable(datatableName)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting value&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Data read&amp;quot;, &amp;quot;DateTime&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
	targetDatatable = targetDatatable[0];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let currentTime = Now;&lt;br /&gt;
let dataAsDf = ToDataFrame(&lt;br /&gt;
	webServiceData.keys.{&lt;br /&gt;
        let key = _;&lt;br /&gt;
        [key, webServiceData[key], currentTime];&lt;br /&gt;
    },&lt;br /&gt;
	[&amp;quot;Setting name&amp;quot;, &amp;quot;Setting value&amp;quot;, &amp;quot;Data read&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
targetDatatable.Import(dataAsDf, #{&amp;quot;Append&amp;quot;:true});&lt;br /&gt;
WriteLog(`${CountTop(dataAsDf.Rows)} rows written to datatable`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Store data to datatable ===&lt;br /&gt;
Get all models in the system and store them to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let newDatatable = Project&lt;br /&gt;
    .CreateDatatable(&amp;quot;Models list &amp;quot; + ToString(Now, &amp;quot;dd.MM.yyyy HH:mm:ss&amp;quot;))&lt;br /&gt;
    .AddColumn(&amp;quot;Model name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Project name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Created time&amp;quot;, &amp;quot;DateTime&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Cases&amp;quot;, &amp;quot;Integer&amp;quot;);&lt;br /&gt;
let startTime = Now;&lt;br /&gt;
let modelsData = ToDataFrame(&lt;br /&gt;
    Models.([Name, Project.Name, CreatedDate, NCases]),&lt;br /&gt;
    [&amp;quot;Model name&amp;quot;, &amp;quot;Project name&amp;quot;, &amp;quot;Created time&amp;quot;, &amp;quot;Cases&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
WriteLog(`Listing models took ${(Now - startTime).TotalSeconds.Round(2)} seconds.`);&lt;br /&gt;
newDatatable.Import(modelsData);&lt;br /&gt;
WriteLog(`Datatable ${newDatatable.Id} created.`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data ===&lt;br /&gt;
This script can be used to convert a single column into numerical data type. To use the script, you need to setup the following in the beginning of the script:&lt;br /&gt;
* Project name where the datatable is located.&lt;br /&gt;
* Datatable name&lt;br /&gt;
* Name of the column to be converted&lt;br /&gt;
&lt;br /&gt;
Note that the conversion fails, if there is data that cannot be converted into numerical format. The conversion assumes that period (.) is used as the decimal point. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let projectName = &amp;quot;New Project&amp;quot;;&lt;br /&gt;
let datatableName = &amp;quot;qpr processanalyzer events&amp;quot;;&lt;br /&gt;
let columnName = &amp;quot;Event order in case&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let project = (Projects.Where(Name==projectName))[0];&lt;br /&gt;
let datatable = (project.Datatables.Where(Name==datatableName))[0];&lt;br /&gt;
DatatableById(datatable.Id).DataFrame&lt;br /&gt;
.SetColumns([&lt;br /&gt;
	columnName: () =&amp;gt; {&lt;br /&gt;
		let data = Column(columnName);&lt;br /&gt;
		if (data == null) {&lt;br /&gt;
			null;&lt;br /&gt;
		 } else {&lt;br /&gt;
			ToFloat(data);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
])&lt;br /&gt;
.Persist(datatable.Name, [&amp;quot;ProjectId&amp;quot;: project.Id, &amp;quot;Append&amp;quot;: false]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of converting to numeric (with the &#039;&#039;ToFloat&#039;&#039; function), data can be converted into string using the &#039;&#039;ToString&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Show DataFrame as HTML table ===&lt;br /&gt;
&lt;br /&gt;
This script defines a function to show dataframe as a HTML table, and uses the function for a literal dataframe.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function dataframeToHtmlTable(df) {&lt;br /&gt;
	return&lt;br /&gt;
`&amp;lt;table&amp;gt;&lt;br /&gt;
	&amp;lt;tr&amp;gt;&lt;br /&gt;
		${StringJoin(&amp;quot;\r\n\t\t&amp;quot;,  + df.columns.`&amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;`)}&lt;br /&gt;
	&amp;lt;/tr&amp;gt;&lt;br /&gt;
	${StringJoin(&amp;quot;&amp;quot;, df.Rows.(&lt;br /&gt;
		&amp;quot;\r\n\t&amp;lt;tr&amp;gt;&amp;quot; + StringJoin(&amp;quot;&amp;quot;, _.`\r\n\t\t&amp;lt;td&amp;gt;${ToString(_)}&amp;lt;/td&amp;gt;`) + &amp;quot;\r\n\t&amp;lt;/tr&amp;gt;&amp;quot;&lt;br /&gt;
	))}&lt;br /&gt;
&amp;lt;/table&amp;gt;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let data = ToDataFrame(&lt;br /&gt;
	[&lt;br /&gt;
		[&amp;quot;one&amp;quot;, &amp;quot;two&amp;quot;, &amp;quot;three&amp;quot;],&lt;br /&gt;
		[&amp;quot;four&amp;quot;, &amp;quot;five&amp;quot;, &amp;quot;six&amp;quot;],&lt;br /&gt;
		[&amp;quot;seven&amp;quot;, &amp;quot;eight&amp;quot;, &amp;quot;nine&amp;quot;]&lt;br /&gt;
	],&lt;br /&gt;
	[&amp;quot;Column 1&amp;quot;, &amp;quot;Column 2&amp;quot;, &amp;quot;Column 3&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
return dataframeToHtmlTable(data);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy local datatables to Snowflake ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
// Copies all datatables in a project to another project including datatable contents.&lt;br /&gt;
// Usage instructions:&lt;br /&gt;
// 1. Create expression script in the project from where you want to copy the datatables.&lt;br /&gt;
// 2. Create a new project named as &amp;quot;&amp;lt;name of the project to be moved&amp;gt; - Snowflake&amp;quot;. New datatables will be created here. E.g., when moving project named &amp;quot;SAP_OrderToCash&amp;quot;, the target project should be named as &amp;quot;SAP_OrderToCash - Snowflake&amp;quot;.&lt;br /&gt;
// 3. Run the script.&lt;br /&gt;
// NOTE: Columns of type &amp;quot;Any&amp;quot; will be created as &amp;quot;String&amp;quot;-columns in Snowflake, thus it is recommended that actual data types are set for the tables prior to the move.&lt;br /&gt;
&lt;br /&gt;
let sourceProject = Project;&lt;br /&gt;
let sourceProjectName = Project.Name;&lt;br /&gt;
let targetProjectName = `${sourceProjectName} - Snowflake`;&lt;br /&gt;
let targetProject = First(Projects.Where(Name == targetProjectName));&lt;br /&gt;
if (IsNull(targetProject)) {&lt;br /&gt;
  WriteLog(`Unable to find target project named &amp;quot;${targetProjectName}&amp;quot;. Aborting operation.`);&lt;br /&gt;
  return;&lt;br /&gt;
}&lt;br /&gt;
let dts = sourceProject.DataTables;&lt;br /&gt;
WriteLog(`Copying all ${CountTop(dts)} data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
dts.{&lt;br /&gt;
  let sourceDt = _;&lt;br /&gt;
  WriteLog(`Starting to copy data table &amp;quot;${Name}&amp;quot; (id: ${Id}) having ${NRows} rows and ${NColumns} columns.`);&lt;br /&gt;
  let targetDt;&lt;br /&gt;
  targetDt = targetProject.DatatableByName(sourceDt.Name);&lt;br /&gt;
  if (targetDt == null) {&lt;br /&gt;
    targetDt = targetProject.CreateDataTable(sourceDt.Name, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: targetProject.Id})});&lt;br /&gt;
    targetDt.Import(sourceDt.SqlDataFrame);&lt;br /&gt;
    WriteLog(`Finished copying data table &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  } else {&lt;br /&gt;
    WriteLog(`Datatable already exist &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
WriteLog(`Finished copying all the data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to copy the data but only create the Snowflake datatables with columns, you can change the line 22 to&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
targetDt.Import(sourceDt.SqlDataFrame.head(0));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy single datatable to Snowflake ===&lt;br /&gt;
This script creates a copy of a single datatable to Snowflake. Replace the &#039;&#039;&amp;lt;tableId1&amp;gt;&#039;&#039; with the id of the source datatable.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function CopyDataTableToSnowflake(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let sourceDt = DataTableById(dataTableId);&lt;br /&gt;
  sourceDt.SqlDataFrame.Persist(`${sourceDt.Name} - Snowflake`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: sourceDt.Project.Id})});&lt;br /&gt;
}&lt;br /&gt;
CopyDataTableToSnowflake(&amp;lt;tableId1&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a copy of a data table that has all Any-type columns changed to String-type columns ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function ConvertAnyDataTypesToStringsToNewTable(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let dt = DataTableById(dataTableId);&lt;br /&gt;
  let sdf = dt.SqlDataFrame;&lt;br /&gt;
  let cts = dt.ColumnTypes;&lt;br /&gt;
  cts.{&lt;br /&gt;
    let ct = _;&lt;br /&gt;
    if (ct.DataType == &amp;quot;Any&amp;quot;) {&lt;br /&gt;
      let n = ct.Name;&lt;br /&gt;
      sdf = sdf.WithColumn(ct.Name, #sql{Cast(Column(Variable(&amp;quot;n&amp;quot;)), &amp;quot;ShortString&amp;quot;)});&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  sdf.Persist(`${dt.Name} - Converted`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;ProjectId&amp;quot;: dt.Project.Id});&lt;br /&gt;
}&lt;br /&gt;
ConvertAnyDataTypesToStringsToNewTable(&amp;lt;dataTableId&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Query number of rows in given data table having a datetime value in given year grouped by month and return resulting table as CSV ===&lt;br /&gt;
SqlDataFrame is used in order to prevent loading the whole datatable into memory first. Filtering is performed as first operation in order to minimize the amount of required work for the data source of the data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
DataTableById(&amp;lt;data table id&amp;gt;)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .Where(#sql{2014 == Year(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .WithColumn(&amp;quot;Month&amp;quot;, #sql{Month(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .GroupBy([&amp;quot;Month&amp;quot;]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
  .OrderByColumns([&amp;quot;Month&amp;quot;], [true])&lt;br /&gt;
  .Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Function for filtering SqlDataFrame by removing rows having, or replacing, the most infrequently occurring column values ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/***&lt;br /&gt;
 * @name ColumnWithMinUsage&lt;br /&gt;
 * @descripion&lt;br /&gt;
 * Generic function that can be used to filter out the most infrequently occurring attribute values or replace their Values&lt;br /&gt;
 * with given common value.&lt;br /&gt;
 * @param df:&lt;br /&gt;
 * DataFrame to operate on.&lt;br /&gt;
 * @param columnName:&lt;br /&gt;
 * Name of the column to be filtered.&lt;br /&gt;
 * @param newColumnName:&lt;br /&gt;
 * Name of the column that will contain the new value of the original column after filtering (if includeOthers was applied).&lt;br /&gt;
 * @param maxNumUniqueValues:&lt;br /&gt;
 * Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
 * @param minValueUsage:&lt;br /&gt;
 * Minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
 * @param includeOthers:&lt;br /&gt;
 * Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
 * If not empty/null, defines the name used for these other-values.&lt;br /&gt;
 */&lt;br /&gt;
function ColumnWithMinUsage(df, columnName, newColumnName, maxNumUniqueValues, minValueUsage, includeOthers)&lt;br /&gt;
{&lt;br /&gt;
  let all = df&lt;br /&gt;
	.GroupBy([])&lt;br /&gt;
	.Aggregate([&amp;quot;NAllTotal&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
	.WithColumn(&amp;quot;__Join2&amp;quot;, #sql{1});&lt;br /&gt;
  let minValueUsageEnabled = !IsNullTop(minValueUsage);&lt;br /&gt;
  let maxNumUniqueValuesEnabled = !IsNullTop(maxNumUniqueValues);&lt;br /&gt;
  if (minValueUsageEnabled || maxNumUniqueValuesEnabled) {&lt;br /&gt;
	// Perform column value-based filtering if minValueUsageEnabled or maxNumUniqueValuesEnabled is defined.&lt;br /&gt;
    let valueColumnName = &amp;quot;__ValueNew&amp;quot;;&lt;br /&gt;
	let filteredValuesColumns = [valueColumnName: columnName];&lt;br /&gt;
	let filteredValues = df&lt;br /&gt;
	  .GroupBy([columnName]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;]);&lt;br /&gt;
	if (minValueUsageEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithColumn(&amp;quot;__Join&amp;quot;, #sql{1})&lt;br /&gt;
		.Join(all, [&amp;quot;__Join&amp;quot;: &amp;quot;__Join2&amp;quot;], &amp;quot;leftouter&amp;quot;)&lt;br /&gt;
        .WithColumn(&amp;quot;Usage&amp;quot;, #sql{Column(&amp;quot;Count&amp;quot;) / Column(&amp;quot;NAllTotal&amp;quot;)});&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;Usage&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
	if (maxNumUniqueValuesEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithRowNumberColumn(&amp;quot;RowNumber&amp;quot;, [&amp;quot;Count&amp;quot;], null, [false]);&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;RowNumber&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	filteredValues = filteredValues&lt;br /&gt;
	  .Select(filteredValuesColumns);&lt;br /&gt;
&lt;br /&gt;
	// Generate select returning all the accepted values.&lt;br /&gt;
	let allValues = filteredValues&lt;br /&gt;
	  .(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;gt;= #expr{minValueUsage}}) : _)&lt;br /&gt;
	  .(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;lt;= #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
	  .Select([valueColumnName, newColumnName: valueColumnName]);&lt;br /&gt;
&lt;br /&gt;
	if (!IsNullTop(includeOthers)) {&lt;br /&gt;
	  // If includeOthers is defined, replace original values with the variable defined in includeOthers.&lt;br /&gt;
	  let otherValues = filteredValues&lt;br /&gt;
		.(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;lt; #expr{minValueUsage}}) : _)&lt;br /&gt;
		.(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;gt; #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
		.WithColumn(newColumnName, #sql{#expr{includeOthers}})&lt;br /&gt;
		.Select([valueColumnName, newColumnName]);&lt;br /&gt;
	  allValues = allValues.Append(otherValues)&lt;br /&gt;
	}&lt;br /&gt;
	df.Join(allValues, [columnName: valueColumnName], &amp;quot;inner&amp;quot;)&lt;br /&gt;
	  .RemoveColumns([valueColumnName]);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// The following example will return only rows containing two of the most common values for Region-column.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, 2, null, null);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&lt;br /&gt;
// The following example will return all input rows, but will replace the values of rows whose Region-column&lt;br /&gt;
// has a value used by less than 15% of all the rows with a new value: &amp;quot;_Others&amp;quot;.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, null, 0.15, &amp;quot;_Others&amp;quot;);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export model events and cases ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function ExportModelEvents(m) {&lt;br /&gt;
  let attrs = m.EventAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Events.Concat(&lt;br /&gt;
      [Case.Name, Type.Name, ToString(TimeStamp, &amp;quot;yyyy-MM-dd HH:mm:ss.fff&amp;quot;)], &lt;br /&gt;
      {let evt = _; attrs.{let att = _; evt.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;, &amp;quot;EventType&amp;quot;, &amp;quot;TimeStamp&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
function ExportModelCases(m) {&lt;br /&gt;
  let attrs = m.CaseAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Cases.Concat(&lt;br /&gt;
      [Name], &lt;br /&gt;
      {let cas = _; attrs.{let att = _; cas.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).EventsDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).CasesDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Calculate all the value usages of a single column for each event in event data table ===&lt;br /&gt;
This query could be used, e.g., to find out the maximum resource usage for every resource found in the event data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function WithUsageColumns(resourceColumn)&lt;br /&gt;
{&lt;br /&gt;
  function WithTotalUsageColumnOfSingleResource(resourceColumn, resourceValue)&lt;br /&gt;
  {&lt;br /&gt;
    _&lt;br /&gt;
      .WithColumn(&amp;quot;_Prev&amp;quot;, #sql{Lag(Column(resourceColumn), [TimeStamp, EventType], [true, true], [CaseId], 1, null)})&lt;br /&gt;
      .WithColumn(&amp;quot;_UsageDiff&amp;quot;, #sql{&lt;br /&gt;
        CaseWhen(&lt;br /&gt;
          Column(resourceColumn) == Column(&amp;quot;_Prev&amp;quot;), 0, &lt;br /&gt;
          Column(&amp;quot;_Prev&amp;quot;) == #expr{resourceValue}, -1,&lt;br /&gt;
          Column(resourceColumn) == #expr{resourceValue}, 1,&lt;br /&gt;
          0)&lt;br /&gt;
      })&lt;br /&gt;
      .WithColumn(`${resourceValue}_Usage`, #sql{Sum(Column(&amp;quot;_UsageDiff&amp;quot;), [TimeStamp, EventType])})&lt;br /&gt;
      .RemoveColumns([&amp;quot;_Prev&amp;quot;, &amp;quot;_UsageDiff&amp;quot;])&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let sdf = _;&lt;br /&gt;
  let allValues = sdf.SelectDistinct([resourceColumn]).OrderByColumns([resourceColumn], [true]).Collect().Column(resourceColumn);&lt;br /&gt;
  allValues.{&lt;br /&gt;
    let v = _;&lt;br /&gt;
    sdf = sdf.WithTotalUsageColumnOfSingleResource(resourceColumn, v)&lt;br /&gt;
  }&lt;br /&gt;
  sdf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
dt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .WithUsageColumns(&amp;lt;resource column name&amp;gt;)&lt;br /&gt;
  .OrderByColumns([dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]], [true])&lt;br /&gt;
  .Collect().ToCsv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;model id&amp;gt; is the id of the model containing event data to be examined.&lt;br /&gt;
* &amp;lt;resource column name&amp;gt; is the name of the column in the event data table of the specified model containing the resource being used by that event.&lt;br /&gt;
&lt;br /&gt;
NOTE: This expression uses functionalities that are only supported in Snowflake-based data tables.&lt;br /&gt;
&lt;br /&gt;
=== Create new Snowflake model from filter ===&lt;br /&gt;
This script creates a new Snowflake model (and two datatables for cases and events) containing filtered event log from given filter id. The script also works if the model doesn&#039;t have a cases datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let filter = FilterById(1); // filter id&lt;br /&gt;
let model = filter.model;&lt;br /&gt;
let project = model.project;&lt;br /&gt;
let nameSuffix = &amp;quot; - &amp;quot; + filter.name + &amp;quot; - &amp;quot; + ToString(Now, &amp;quot;dd-MM-yyyy HH:mm:ss&amp;quot;);&lt;br /&gt;
let eventsDatatableName = model.EventsDataTable.Name + nameSuffix;&lt;br /&gt;
if (eventsDatatableName.length &amp;gt; 440) {&lt;br /&gt;
  eventsDatatableName = eventsDatatableName.Substring(eventsDatatableName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
let eventsData = model&lt;br /&gt;
  .EventsDataTable&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .ApplyFilter(&lt;br /&gt;
    filter.rules,&lt;br /&gt;
    model.CasesDataTable?.SqlDataFrame&lt;br /&gt;
  );&lt;br /&gt;
project&lt;br /&gt;
  .CreateDatatable(eventsDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
  .Import(eventsData);&lt;br /&gt;
let modelConfiguration = model.Configuration;&lt;br /&gt;
modelConfiguration.DataSource.Events.Set(&amp;quot;DataTableName&amp;quot;, eventsDatatableName);&lt;br /&gt;
if (model.CasesDataTable != null) {&lt;br /&gt;
  let eventsDataCaseIdColumn = &amp;quot;CaseId_&amp;quot; + ToString(Random());&lt;br /&gt;
  let casesDatatableName = model.CasesDataTable.Name + nameSuffix;&lt;br /&gt;
  if (casesDatatableName.length &amp;gt; 440) {&lt;br /&gt;
    casesDatatableName = casesDatatableName.Substring(casesDatatableName.length - 440);&lt;br /&gt;
  }&lt;br /&gt;
  let casesData = model&lt;br /&gt;
    .CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .join(&lt;br /&gt;
	  eventsData.SelectDistinct([eventsDataCaseIdColumn: modelConfiguration.DataSource.Events.Columns.CaseId]),&lt;br /&gt;
      [modelConfiguration.DataSource.Cases.Columns.CaseId: eventsDataCaseIdColumn]&lt;br /&gt;
	).Select(model.CasesDataTable.ColumnNames);&lt;br /&gt;
  project&lt;br /&gt;
    .CreateDatatable(casesDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
    .Import(casesData);&lt;br /&gt;
  modelConfiguration.DataSource.Cases.Set(&amp;quot;DataTableName&amp;quot;, casesDatatableName);&lt;br /&gt;
}&lt;br /&gt;
let modelName = model.Name + nameSuffix;&lt;br /&gt;
if (modelName &amp;gt; 440) {&lt;br /&gt;
  modelName = modelName.Substring(modelName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
project&lt;br /&gt;
  .CreateModel(#{    &lt;br /&gt;
    &amp;quot;Name&amp;quot;: modelName,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: model.Description,&lt;br /&gt;
    &amp;quot;Configuration&amp;quot;: modelConfiguration&lt;br /&gt;
  });&lt;br /&gt;
return modelName;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Creating a model consisting of multiple copies of cases in an existing model ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @name CreateTestModel&lt;br /&gt;
 * @description&lt;br /&gt;
 * Creates a new model (or overwrites an existing) to given target project with given number of &lt;br /&gt;
 * repetitions of given source model.&lt;br /&gt;
 * Each repetition will generate &amp;quot;&amp;lt;N&amp;gt;-&amp;quot;-prefix to CaseId-columns, where N equals to the repeat index.&lt;br /&gt;
 * @param sourceModel&lt;br /&gt;
 * PA model used for the source data and from where the connection is copied for the target model if a &lt;br /&gt;
 * new one has to be created.&lt;br /&gt;
 * @param numRepeats&lt;br /&gt;
 * Number of times the data in the source model should be repeated in the generated model.&lt;br /&gt;
 * @param targetProject&lt;br /&gt;
 * Project in which the target model resides.&lt;br /&gt;
 * @param targetModelName&lt;br /&gt;
 * Specifies the name of the test model in the given target project. If a model already exists with &lt;br /&gt;
 * given name, event and case data in this model will be replaced with the new generated event and &lt;br /&gt;
 * case data.&lt;br /&gt;
 * @returns&lt;br /&gt;
 * Model object of the test model having the newly generated data.&lt;br /&gt;
 */&lt;br /&gt;
function CreateTestModel(sourceModel, numRepeats, targetProject, targetModelName) &lt;br /&gt;
{&lt;br /&gt;
  let eventsColumnMappings = sourceModel.EventsDataTable.ColumnMappings;&lt;br /&gt;
  let casesColumnMappings = sourceModel.CasesDataTable.ColumnMappings;&lt;br /&gt;
  let connection = sourceModel.EventsDataTable.DataSourceConnection;&lt;br /&gt;
&lt;br /&gt;
  function CreateResultModel()&lt;br /&gt;
  {&lt;br /&gt;
    function GetTable(tableName) &lt;br /&gt;
    {&lt;br /&gt;
      let tableConfiguration = #{&lt;br /&gt;
        &amp;quot;Name&amp;quot;: tableName,&lt;br /&gt;
        &amp;quot;Connection&amp;quot;: connection&lt;br /&gt;
      };&lt;br /&gt;
      let resultTable = targetProject.DataTableByName(tableName);&lt;br /&gt;
      if (resultTable == null)&lt;br /&gt;
      {&lt;br /&gt;
        resultTable = targetProject.CreateDataTable(tableConfiguration)&lt;br /&gt;
          .Modify(#{&amp;quot;NameInDataSource&amp;quot;: null})&lt;br /&gt;
          .Synchronize();&lt;br /&gt;
      }&lt;br /&gt;
      return resultTable;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let eventsTableName = `${targetModelName} - events`;&lt;br /&gt;
    let casesTableName = `${targetModelName} - cases`;&lt;br /&gt;
    let targetModel = targetProject.ModelByName(targetModelName);&lt;br /&gt;
    let eventsTable, casesTable = null;&lt;br /&gt;
&lt;br /&gt;
    if (targetModel != null)&lt;br /&gt;
    {&lt;br /&gt;
      eventsTable = targetModel.EventsDataTable;&lt;br /&gt;
      casesTable = targetModel.CasesDataTable;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      eventsTable = GetTable(eventsTableName);&lt;br /&gt;
      if (sourceModel.CasesDataTable != null) {&lt;br /&gt;
        casesTable = GetTable(casesTableName);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      let timestampMapping = eventsColumnMappings[&amp;quot;TimeStamp&amp;quot;];&lt;br /&gt;
      eventsColumnMappings.Remove(&amp;quot;TimeStamp&amp;quot;);&lt;br /&gt;
      eventsColumnMappings.Set(&amp;quot;Timestamp&amp;quot;, timestampMapping);&lt;br /&gt;
&lt;br /&gt;
      let modelConfiguration = #{&lt;br /&gt;
        &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
          &amp;quot;Events&amp;quot;:#{&lt;br /&gt;
            &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
            &amp;quot;DataTableName&amp;quot;: eventsTableName,&lt;br /&gt;
            &amp;quot;Columns&amp;quot;: eventsColumnMappings&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      if (casesColumnMappings != null) {&lt;br /&gt;
        modelConfiguration[&amp;quot;DataSource&amp;quot;].Set(&amp;quot;Cases&amp;quot;, #{&lt;br /&gt;
          &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
          &amp;quot;DataTableName&amp;quot;: casesTableName,&lt;br /&gt;
          &amp;quot;Columns&amp;quot;: casesColumnMappings&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      targetModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: targetModelName, &amp;quot;Configuration&amp;quot;: modelConfiguration});&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    eventsTable.Truncate();&lt;br /&gt;
    casesTable?.Truncate();&lt;br /&gt;
&lt;br /&gt;
    return #{&lt;br /&gt;
      &amp;quot;TargetModel&amp;quot;: targetModel,&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTable,&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: casesTable&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function RepeatNTimes(sourceDf, caseIdColumn, numRepeats)&lt;br /&gt;
  {&lt;br /&gt;
    let resultDf = null;&lt;br /&gt;
    for (let i = 1; i &amp;lt;= numRepeats; ++i) {&lt;br /&gt;
      let iterationDf = sourceDf&lt;br /&gt;
        .WithColumn(caseIdColumn, #sql{Concat(#expr{i}, &amp;quot;-&amp;quot;, Column(#expr{caseIdColumn}))});&lt;br /&gt;
      resultDf = resultDf == null ? iterationDf : resultDf.Append(iterationDf); &lt;br /&gt;
    }&lt;br /&gt;
    resultDf;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let resultModel = CreateResultModel();&lt;br /&gt;
  let sourceEventDataDf = sourceModel.EventsDataTable.SqlDataFrame;&lt;br /&gt;
  let resultEventDataDf = RepeatNTimes(sourceEventDataDf, eventsColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
  resultModel[&amp;quot;Events&amp;quot;].Import(resultEventDataDf);&lt;br /&gt;
&lt;br /&gt;
  let sourceCaseDataDf = sourceModel.CasesDataTable?.SqlDataFrame;&lt;br /&gt;
  if (sourceCaseDataDf != null) {&lt;br /&gt;
    let resultCaseDataDf = RepeatNTimes(sourceCaseDataDf, casesColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
    resultModel[&amp;quot;Cases&amp;quot;].Import(resultCaseDataDf);&lt;br /&gt;
  }&lt;br /&gt;
  resultModel[&amp;quot;TargetModel&amp;quot;];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example usage:&amp;lt;blockquote&amp;gt;CreateTestModel(ProjectByName(&amp;quot;Project&amp;quot;).ModelByName(&amp;quot;SAP_OrderToCash - Snowflake&amp;quot;), 3, ProjectByName(&amp;quot;TestData&amp;quot;), &amp;quot;TestModel&amp;quot;);&amp;lt;/blockquote&amp;gt;Creates a new model named &amp;quot;TestModel&amp;quot; (or overwrites old one) into project named &amp;quot;TestData&amp;quot; containing the data from model &amp;quot;SAP_OrderToCash - Snowflake&amp;quot; in project &amp;quot;Project&amp;quot; repeated three times.&lt;br /&gt;
&lt;br /&gt;
=== Analyzing declare patterns found in event log ===&lt;br /&gt;
&lt;br /&gt;
This is an example expression that shows how POSIX-style regular expressions can be used to search for cases in an event log having certain event type patterns [https://www.researchgate.net/publication/277631859_Generating_Event_Logs_Through_the_Simulation_of_Declare_Models declare patterns].&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
let sdf = dt.SqlDataFrame.Head(1000);&lt;br /&gt;
let caseIdColumn = dt.ColumnMappings[&amp;quot;CaseId&amp;quot;], timeStampColumn = dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;], eventTypeColumn = dt.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let eventTypesDf = sdf&lt;br /&gt;
  .SelectDistinct([eventTypeColumn])&lt;br /&gt;
  .OrderByColumns([eventTypeColumn], [true])&lt;br /&gt;
  .WithRowNumberColumn(&amp;quot;Token&amp;quot;, [eventTypeColumn])&lt;br /&gt;
  .WithColumn(&amp;quot;Token&amp;quot;, #sql{Char(Column(&amp;quot;Token&amp;quot;) + Unicode(&amp;quot;a&amp;quot;) - 1)});&lt;br /&gt;
&lt;br /&gt;
sdf = sdf&lt;br /&gt;
  .Join(eventTypesDf.Select([&amp;quot;_EventType2&amp;quot;: eventTypeColumn, &amp;quot;Token&amp;quot;]), [eventTypeColumn: &amp;quot;_EventType2&amp;quot;], &amp;quot;leftouter&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function RespondedExistencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*((a.*b.*)|(b.*a.*))*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*((${a}.*${b}.*)|(${b}.*${a}.*))*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a.*b)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}.*${b})*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function AlternateResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a[^a]*b[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}[^${a}]*${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ChainResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(ab[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function PrecedencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^b]*(a.*b)*[^b]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${b}]*(${a}.*${b})*[^${b}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let tracesDf = sdf&lt;br /&gt;
  .GroupBy([caseIdColumn])&lt;br /&gt;
  .Aggregate([&amp;quot;Trace&amp;quot;: &amp;quot;Token&amp;quot;], [#{&amp;quot;Function&amp;quot;: &amp;quot;list&amp;quot;, &amp;quot;Ordering&amp;quot;: [timeStampColumn], &amp;quot;Separator&amp;quot;: &amp;quot;&amp;quot;}])&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceNL&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceCA&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternNL&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternCA&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternNL&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternCA&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternNL&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternCA&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternNL&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternCA&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
[tracesDf.Collect().ToCsv(), eventTypesDf.Collect().ToCsv()]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Perform a query and send results as E-mail in a HTML table ===&lt;br /&gt;
This example requires two scripts:&lt;br /&gt;
&lt;br /&gt;
# Expression-type script to execute (can be, e.g., scheduled to run daily).&lt;br /&gt;
# Expression-type script containing the query JSON to use as basis for the e-mail. In this example, this script is named as &amp;quot;Send query as E-mail - query JSON&amp;quot;. The contents of this script is just the JSON representation of a query that can be extracted, e.g., from any PA chart view.&lt;br /&gt;
&lt;br /&gt;
Script #1 should contain the following code:&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let replyToAddress = &amp;quot;noreply@test.com&amp;quot;;&lt;br /&gt;
let recipientsArray = [&amp;quot;test@test.com&amp;quot;];&lt;br /&gt;
let queryConfiguration = Project.ScriptByName(&amp;quot;Send query as E-mail - query JSON&amp;quot;).Code;&lt;br /&gt;
let resultDf = Query(ParseJson(queryConfiguration));&lt;br /&gt;
let mailBodyHtml = resultDf.Collect().`&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
  &amp;lt;caption&amp;gt;Example query&amp;lt;/caption&amp;gt;&lt;br /&gt;
  &amp;lt;thead&amp;gt;&lt;br /&gt;
    &amp;lt;tr&amp;gt;&lt;br /&gt;
      ${StringJoin(&amp;quot;&amp;quot;, _.Columns.`&lt;br /&gt;
        &amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;&lt;br /&gt;
      `)}&lt;br /&gt;
    &amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;/thead&amp;gt;&lt;br /&gt;
  &amp;lt;tbody&amp;gt;&lt;br /&gt;
    ${StringJoin(&amp;quot;&amp;quot;, _.Rows.`&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
        ${StringJoin(&amp;quot;&amp;quot;, _.`&lt;br /&gt;
          &amp;lt;td&amp;gt;${_}&amp;lt;/td&amp;gt;&lt;br /&gt;
        `)}&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
    `)}&lt;br /&gt;
  &amp;lt;/tbody&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
`;&lt;br /&gt;
&lt;br /&gt;
SendEmail(#{&lt;br /&gt;
  &amp;quot;ReplyTo&amp;quot;: [replyToAddress],&lt;br /&gt;
  &amp;quot;To&amp;quot;: recipientsArray,&lt;br /&gt;
  &amp;quot;Subject&amp;quot;: &amp;quot;Example query E-mail&amp;quot;,&lt;br /&gt;
  &amp;quot;IsBodyHtml&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Body&amp;quot;: mailBodyHtml&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Web_API:_saml2&amp;diff=25989</id>
		<title>Web API: saml2</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Web_API:_saml2&amp;diff=25989"/>
		<updated>2025-03-17T12:30:36Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Saml2&#039;&#039;&#039; method returns the SAML 2.0 service provider (SP) metadata. No authentication is required to fetch the metadata. Usually the service provider metadata url is configured to the identity provider (IdP), which can then read, e.g., the needed public encryption keys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Url: GET qprpa/Saml2&lt;br /&gt;
attachment; filename=&amp;quot;customer.onqpr.com_qprpa_Saml2.xml&amp;quot;&lt;br /&gt;
Content-Type: application/samlmetadata+xml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;EntityDescriptor xmlns=&amp;quot;urn:oasis:names:tc:SAML:2.0:metadata&amp;quot; xmlns:saml2=&amp;quot;urn:oasis:names:tc:SAML:2.0:assertion&amp;quot; cacheDuration=&amp;quot;PT1H&amp;quot; entityID=&amp;quot;https://customer.onqpr.com/qprpa/Saml2&amp;quot; ID=&amp;quot;_76ac281969e84420924d4e25d22b7c4e&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;Signature xmlns=&amp;quot;http://www.w3.org/2000/09/xmldsig#&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;SignedInfo&amp;gt;&lt;br /&gt;
         &amp;lt;CanonicalizationMethod Algorithm=&amp;quot;http://www.w3.org/2001/10/xml-exc-c14n#&amp;quot; /&amp;gt;&lt;br /&gt;
         &amp;lt;SignatureMethod Algorithm=&amp;quot;http://www.w3.org/2001/04/xmldsig-more#rsa-sha256&amp;quot; /&amp;gt;&lt;br /&gt;
         &amp;lt;Reference URI=&amp;quot;...&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;Transforms&amp;gt;&lt;br /&gt;
               &amp;lt;Transform Algorithm=&amp;quot;http://www.w3.org/2000/09/xmldsig#enveloped-signature&amp;quot; /&amp;gt;&lt;br /&gt;
               &amp;lt;Transform Algorithm=&amp;quot;http://www.w3.org/2001/10/xml-exc-c14n#&amp;quot; /&amp;gt;&lt;br /&gt;
            &amp;lt;/Transforms&amp;gt;&lt;br /&gt;
            &amp;lt;DigestMethod Algorithm=&amp;quot;http://www.w3.org/2001/04/xmlenc#sha256&amp;quot; /&amp;gt;&lt;br /&gt;
            &amp;lt;DigestValue&amp;gt;...&amp;lt;/DigestValue&amp;gt;&lt;br /&gt;
         &amp;lt;/Reference&amp;gt;&lt;br /&gt;
      &amp;lt;/SignedInfo&amp;gt;&lt;br /&gt;
      &amp;lt;SignatureValue&amp;gt;...&amp;lt;/SignatureValue&amp;gt;&lt;br /&gt;
      &amp;lt;KeyInfo&amp;gt;&lt;br /&gt;
         &amp;lt;X509Data&amp;gt;&lt;br /&gt;
            &amp;lt;X509Certificate&amp;gt;...&amp;lt;/X509Certificate&amp;gt;&lt;br /&gt;
         &amp;lt;/X509Data&amp;gt;&lt;br /&gt;
      &amp;lt;/KeyInfo&amp;gt;&lt;br /&gt;
   &amp;lt;/Signature&amp;gt;&lt;br /&gt;
   &amp;lt;SPSSODescriptor AuthnRequestsSigned=&amp;quot;false&amp;quot; WantAssertionsSigned=&amp;quot;false&amp;quot; protocolSupportEnumeration=&amp;quot;urn:oasis:names:tc:SAML:2.0:protocol&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;KeyDescriptor use=&amp;quot;signing&amp;quot;&amp;gt;&lt;br /&gt;
         &amp;lt;KeyInfo xmlns=&amp;quot;http://www.w3.org/2000/09/xmldsig#&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;X509Data&amp;gt;&lt;br /&gt;
               &amp;lt;X509Certificate&amp;gt;...&amp;lt;/X509Certificate&amp;gt;&lt;br /&gt;
            &amp;lt;/X509Data&amp;gt;&lt;br /&gt;
         &amp;lt;/KeyInfo&amp;gt;&lt;br /&gt;
      &amp;lt;/KeyDescriptor&amp;gt;&lt;br /&gt;
      &amp;lt;SingleLogoutService Binding=&amp;quot;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect&amp;quot; Location=&amp;quot;https://customer.onqpr.com/QPRPA/Saml2/Logout&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;AssertionConsumerService Binding=&amp;quot;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST&amp;quot; Location=&amp;quot;https://customer.onqpr.com/QPRPA/Saml2/Acs&amp;quot; isDefault=&amp;quot;true&amp;quot; index=&amp;quot;0&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;AssertionConsumerService Binding=&amp;quot;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact&amp;quot; Location=&amp;quot;https://customer.onqpr.com/QPRPA/Saml2/Acs&amp;quot; isDefault=&amp;quot;false&amp;quot; index=&amp;quot;1&amp;quot; /&amp;gt;&lt;br /&gt;
   &amp;lt;/SPSSODescriptor&amp;gt;&lt;br /&gt;
&amp;lt;/EntityDescriptor&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[Category: QPR ProcessAnalyzer]]&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=25971</id>
		<title>Expression Script Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Expression_Script_Examples&amp;diff=25971"/>
		<updated>2025-03-06T09:12:53Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains script examples written in the QPR ProcessAnalyzer expression language. See how expression scripts can be created in the [[Managing_Scripts#Creating_Script|Workspace]]. For documentation for the syntax, functions and entities can be found from the main page in the [[QPR_ProcessAnalyzer_Wiki#For_Developers|KPI Expression Language]] section.&lt;br /&gt;
&lt;br /&gt;
== Calling Expression Script from Expression ==&lt;br /&gt;
Expression scripts can be called from an expression using the [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Run]] function with the following syntax:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: false,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 123.45&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command waits until the run is completed, and the return value of the called script is returned by the Run function call.&lt;br /&gt;
&lt;br /&gt;
Parameters can be passed to the called script, and the parameters are available as variables in the script. The parameters can contain any type of data.&lt;br /&gt;
&lt;br /&gt;
Expression scripts can also be called from a dashboard. Expressions can be stored to scripts instead of dashboards, which is a way to separate complex expressions from dashboards and allow to reuse expressions across several dashboards.&lt;br /&gt;
&lt;br /&gt;
== Calling SQL Script from Expression ==&lt;br /&gt;
SQL script can be called from an expression using the Run function as follows (similar to calling [[#Calling Expression Script from Expression|expression scripts]]):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
let result = ScriptById(123).Run(#{&lt;br /&gt;
  &amp;quot;parameter1&amp;quot;: &amp;quot;value1&amp;quot;,&lt;br /&gt;
  &amp;quot;parameter2&amp;quot;: 321&lt;br /&gt;
});&lt;br /&gt;
let arrayOfAllReports = result.Keys;&lt;br /&gt;
let report1 = result.Report1;&lt;br /&gt;
let report2 = result.Report2;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL scripts can return multiple &#039;&#039;reports&#039;&#039;, which are combined to a dictionary, where the key is the name of the report (&amp;quot;sheet name&amp;quot;) and value is the report data as a DataFrame. See in the above example, how the reports can be accessed by their name.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
=== Call web service===&lt;br /&gt;
Contact to a web service, fetch some data, and store it to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let datatableName = &amp;quot;Web Service Data&amp;quot;;&lt;br /&gt;
let webServiceData = CallWebService(&lt;br /&gt;
    #{&amp;quot;Address&amp;quot;: &amp;quot;https://processanalyzer.onqpr.com/qprpa/api/serverinfo&amp;quot;}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
let targetDatatable = Project.Datatables.Where(name==datatableName);&lt;br /&gt;
if (Count(targetDatatable) == 0) {&lt;br /&gt;
	targetDatatable = Project.CreateDatatable(datatableName)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Setting value&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
	.AddColumn(&amp;quot;Data read&amp;quot;, &amp;quot;DateTime&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
	targetDatatable = targetDatatable[0];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let currentTime = Now;&lt;br /&gt;
let dataAsDf = ToDataFrame(&lt;br /&gt;
	webServiceData.keys.{&lt;br /&gt;
        let key = _;&lt;br /&gt;
        [key, webServiceData[key], currentTime];&lt;br /&gt;
    },&lt;br /&gt;
	[&amp;quot;Setting name&amp;quot;, &amp;quot;Setting value&amp;quot;, &amp;quot;Data read&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
targetDatatable.Import(dataAsDf, #{&amp;quot;Append&amp;quot;:true});&lt;br /&gt;
WriteLog(`${CountTop(dataAsDf.Rows)} rows written to datatable`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Store data to datatable ===&lt;br /&gt;
Get all models in the system and store them to a datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let newDatatable = Project&lt;br /&gt;
    .CreateDatatable(&amp;quot;Models list &amp;quot; + ToString(Now, &amp;quot;dd.MM.yyyy HH:mm:ss&amp;quot;))&lt;br /&gt;
    .AddColumn(&amp;quot;Model name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Project name&amp;quot;, &amp;quot;String&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Created time&amp;quot;, &amp;quot;DateTime&amp;quot;)&lt;br /&gt;
    .AddColumn(&amp;quot;Cases&amp;quot;, &amp;quot;Integer&amp;quot;);&lt;br /&gt;
let startTime = Now;&lt;br /&gt;
let modelsData = ToDataFrame(&lt;br /&gt;
    Models.([Name, Project.Name, CreatedDate, NCases]),&lt;br /&gt;
    [&amp;quot;Model name&amp;quot;, &amp;quot;Project name&amp;quot;, &amp;quot;Created time&amp;quot;, &amp;quot;Cases&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
WriteLog(`Listing models took ${(Now - startTime).TotalSeconds.Round(2)} seconds.`);&lt;br /&gt;
newDatatable.Import(modelsData);&lt;br /&gt;
WriteLog(`Datatable ${newDatatable.Id} created.`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert datatable column data ===&lt;br /&gt;
This script can be used to convert a single column into numerical data type. To use the script, you need to setup the following in the beginning of the script:&lt;br /&gt;
* Project name where the datatable is located.&lt;br /&gt;
* Datatable name&lt;br /&gt;
* Name of the column to be converted&lt;br /&gt;
&lt;br /&gt;
Note that the conversion fails, if there is data that cannot be converted into numerical format. The conversion assumes that period (.) is used as the decimal point. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
let projectName = &amp;quot;New Project&amp;quot;;&lt;br /&gt;
let datatableName = &amp;quot;qpr processanalyzer events&amp;quot;;&lt;br /&gt;
let columnName = &amp;quot;Event order in case&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
let project = (Projects.Where(Name==projectName))[0];&lt;br /&gt;
let datatable = (project.Datatables.Where(Name==datatableName))[0];&lt;br /&gt;
DatatableById(datatable.Id).DataFrame&lt;br /&gt;
.SetColumns([&lt;br /&gt;
	columnName: () =&amp;gt; {&lt;br /&gt;
		let data = Column(columnName);&lt;br /&gt;
		if (data == null) {&lt;br /&gt;
			null;&lt;br /&gt;
		 } else {&lt;br /&gt;
			ToFloat(data);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
])&lt;br /&gt;
.Persist(datatable.Name, [&amp;quot;ProjectId&amp;quot;: project.Id, &amp;quot;Append&amp;quot;: false]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Instead of converting to numeric (with the &#039;&#039;ToFloat&#039;&#039; function), data can be converted into string using the &#039;&#039;ToString&#039;&#039; function.&lt;br /&gt;
&lt;br /&gt;
=== Show DataFrame as HTML table ===&lt;br /&gt;
&lt;br /&gt;
This script defines a function to show dataframe as a HTML table, and uses the function for a literal dataframe.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function dataframeToHtmlTable(df) {&lt;br /&gt;
	return&lt;br /&gt;
`&amp;lt;table&amp;gt;&lt;br /&gt;
	&amp;lt;tr&amp;gt;&lt;br /&gt;
		${StringJoin(&amp;quot;\r\n\t\t&amp;quot;,  + df.columns.`&amp;lt;th&amp;gt;${_}&amp;lt;/th&amp;gt;`)}&lt;br /&gt;
	&amp;lt;/tr&amp;gt;&lt;br /&gt;
	${StringJoin(&amp;quot;&amp;quot;, df.Rows.(&lt;br /&gt;
		&amp;quot;\r\n\t&amp;lt;tr&amp;gt;&amp;quot; + StringJoin(&amp;quot;&amp;quot;, _.`\r\n\t\t&amp;lt;td&amp;gt;${ToString(_)}&amp;lt;/td&amp;gt;`) + &amp;quot;\r\n\t&amp;lt;/tr&amp;gt;&amp;quot;&lt;br /&gt;
	))}&lt;br /&gt;
&amp;lt;/table&amp;gt;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let data = ToDataFrame(&lt;br /&gt;
	[&lt;br /&gt;
		[&amp;quot;one&amp;quot;, &amp;quot;two&amp;quot;, &amp;quot;three&amp;quot;],&lt;br /&gt;
		[&amp;quot;four&amp;quot;, &amp;quot;five&amp;quot;, &amp;quot;six&amp;quot;],&lt;br /&gt;
		[&amp;quot;seven&amp;quot;, &amp;quot;eight&amp;quot;, &amp;quot;nine&amp;quot;]&lt;br /&gt;
	],&lt;br /&gt;
	[&amp;quot;Column 1&amp;quot;, &amp;quot;Column 2&amp;quot;, &amp;quot;Column 3&amp;quot;]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
return dataframeToHtmlTable(data);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy local datatables to Snowflake ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
// Copies all datatables in a project to another project including datatable contents.&lt;br /&gt;
// Usage instructions:&lt;br /&gt;
// 1. Create expression script in the project from where you want to copy the datatables.&lt;br /&gt;
// 2. Create a new project named as &amp;quot;&amp;lt;name of the project to be moved&amp;gt; - Snowflake&amp;quot;. New datatables will be created here. E.g., when moving project named &amp;quot;SAP_OrderToCash&amp;quot;, the target project should be named as &amp;quot;SAP_OrderToCash - Snowflake&amp;quot;.&lt;br /&gt;
// 3. Run the script.&lt;br /&gt;
// NOTE: Columns of type &amp;quot;Any&amp;quot; will be created as &amp;quot;String&amp;quot;-columns in Snowflake, thus it is recommended that actual data types are set for the tables prior to the move.&lt;br /&gt;
&lt;br /&gt;
let sourceProject = Project;&lt;br /&gt;
let sourceProjectName = Project.Name;&lt;br /&gt;
let targetProjectName = `${sourceProjectName} - Snowflake`;&lt;br /&gt;
let targetProject = First(Projects.Where(Name == targetProjectName));&lt;br /&gt;
if (IsNull(targetProject)) {&lt;br /&gt;
  WriteLog(`Unable to find target project named &amp;quot;${targetProjectName}&amp;quot;. Aborting operation.`);&lt;br /&gt;
  return;&lt;br /&gt;
}&lt;br /&gt;
let dts = sourceProject.DataTables;&lt;br /&gt;
WriteLog(`Copying all ${CountTop(dts)} data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
dts.{&lt;br /&gt;
  let sourceDt = _;&lt;br /&gt;
  WriteLog(`Starting to copy data table &amp;quot;${Name}&amp;quot; (id: ${Id}) having ${NRows} rows and ${NColumns} columns.`);&lt;br /&gt;
  let targetDt;&lt;br /&gt;
  targetDt = targetProject.DatatableByName(sourceDt.Name);&lt;br /&gt;
  if (targetDt == null) {&lt;br /&gt;
    targetDt = targetProject.CreateDataTable(sourceDt.Name, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: targetProject.Id})});&lt;br /&gt;
    targetDt.Import(sourceDt.SqlDataFrame);&lt;br /&gt;
    WriteLog(`Finished copying data table &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  } else {&lt;br /&gt;
    WriteLog(`Datatable already exist &amp;quot;${Name}&amp;quot; (id: ${Id}) to table &amp;quot;${targetDt.Name}&amp;quot; (id: ${targetDt.Id})`);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
WriteLog(`Finished copying all the data tables found in project &amp;quot;${sourceProject.Name}&amp;quot; (id: ${sourceProject.Id}) to Snowflake in project &amp;quot;${targetProject.Name}&amp;quot; (id: ${targetProject.Id})`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to copy the data but only create the Snowflake datatables with columns, you can change the line 22 to&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
targetDt.Import(sourceDt.SqlDataFrame.head(0));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Copy single datatable to Snowflake ===&lt;br /&gt;
This script creates a copy of a single datatable to Snowflake. Replace the &#039;&#039;&amp;lt;tableId1&amp;gt;&#039;&#039; with the id of the source datatable.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function CopyDataTableToSnowflake(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let sourceDt = DataTableById(dataTableId);&lt;br /&gt;
  sourceDt.SqlDataFrame.Persist(`${sourceDt.Name} - Snowflake`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: sourceDt.Project.Id})});&lt;br /&gt;
}&lt;br /&gt;
CopyDataTableToSnowflake(&amp;lt;tableId1&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a copy of a data table that has all Any-type columns changed to String-type columns ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line&amp;gt;&lt;br /&gt;
function ConvertAnyDataTypesToStringsToNewTable(dataTableId)&lt;br /&gt;
{&lt;br /&gt;
  let dt = DataTableById(dataTableId);&lt;br /&gt;
  let sdf = dt.SqlDataFrame;&lt;br /&gt;
  let cts = dt.ColumnTypes;&lt;br /&gt;
  cts.{&lt;br /&gt;
    let ct = _;&lt;br /&gt;
    if (ct.DataType == &amp;quot;Any&amp;quot;) {&lt;br /&gt;
      let n = ct.Name;&lt;br /&gt;
      sdf = sdf.WithColumn(ct.Name, #sql{Cast(Column(Variable(&amp;quot;n&amp;quot;)), &amp;quot;ShortString&amp;quot;)});&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  sdf.Persist(`${dt.Name} - Converted`, #{&amp;quot;Append&amp;quot;: false, &amp;quot;ProjectId&amp;quot;: dt.Project.Id});&lt;br /&gt;
}&lt;br /&gt;
ConvertAnyDataTypesToStringsToNewTable(&amp;lt;dataTableId&amp;gt;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Query number of rows in given data table having a datetime value in given year grouped by month and return resulting table as CSV ===&lt;br /&gt;
SqlDataFrame is used in order to prevent loading the whole datatable into memory first. Filtering is performed as first operation in order to minimize the amount of required work for the data source of the data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
DataTableById(&amp;lt;data table id&amp;gt;)&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .Where(#sql{2014 == Year(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .WithColumn(&amp;quot;Month&amp;quot;, #sql{Month(Column(&amp;quot;Start Time&amp;quot;))})&lt;br /&gt;
  .GroupBy([&amp;quot;Month&amp;quot;]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
  .OrderByColumns([&amp;quot;Month&amp;quot;], [true])&lt;br /&gt;
  .Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Function for filtering SqlDataFrame by removing rows having, or replacing, the most infrequently occurring column values ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/***&lt;br /&gt;
 * @name ColumnWithMinUsage&lt;br /&gt;
 * @descripion&lt;br /&gt;
 * Generic function that can be used to filter out the most infrequently occurring attribute values or replace their Values&lt;br /&gt;
 * with given common value.&lt;br /&gt;
 * @param df:&lt;br /&gt;
 * DataFrame to operate on.&lt;br /&gt;
 * @param columnName:&lt;br /&gt;
 * Name of the column to be filtered.&lt;br /&gt;
 * @param newColumnName:&lt;br /&gt;
 * Name of the column that will contain the new value of the original column after filtering (if includeOthers was applied).&lt;br /&gt;
 * @param maxNumUniqueValues:&lt;br /&gt;
 * Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
 * @param minValueUsage:&lt;br /&gt;
 * Minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
 * @param includeOthers:&lt;br /&gt;
 * Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
 * If not empty/null, defines the name used for these other-values.&lt;br /&gt;
 */&lt;br /&gt;
function ColumnWithMinUsage(df, columnName, newColumnName, maxNumUniqueValues, minValueUsage, includeOthers)&lt;br /&gt;
{&lt;br /&gt;
  let all = df&lt;br /&gt;
	.GroupBy([])&lt;br /&gt;
	.Aggregate([&amp;quot;NAllTotal&amp;quot;], [&amp;quot;Count&amp;quot;])&lt;br /&gt;
	.WithColumn(&amp;quot;__Join2&amp;quot;, #sql{1});&lt;br /&gt;
  let minValueUsageEnabled = !IsNullTop(minValueUsage);&lt;br /&gt;
  let maxNumUniqueValuesEnabled = !IsNullTop(maxNumUniqueValues);&lt;br /&gt;
  if (minValueUsageEnabled || maxNumUniqueValuesEnabled) {&lt;br /&gt;
	// Perform column value-based filtering if minValueUsageEnabled or maxNumUniqueValuesEnabled is defined.&lt;br /&gt;
    let valueColumnName = &amp;quot;__ValueNew&amp;quot;;&lt;br /&gt;
	let filteredValuesColumns = [valueColumnName: columnName];&lt;br /&gt;
	let filteredValues = df&lt;br /&gt;
	  .GroupBy([columnName]).Aggregate([&amp;quot;Count&amp;quot;], [&amp;quot;Count&amp;quot;]);&lt;br /&gt;
	if (minValueUsageEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithColumn(&amp;quot;__Join&amp;quot;, #sql{1})&lt;br /&gt;
		.Join(all, [&amp;quot;__Join&amp;quot;: &amp;quot;__Join2&amp;quot;], &amp;quot;leftouter&amp;quot;)&lt;br /&gt;
        .WithColumn(&amp;quot;Usage&amp;quot;, #sql{Column(&amp;quot;Count&amp;quot;) / Column(&amp;quot;NAllTotal&amp;quot;)});&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;Usage&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
	if (maxNumUniqueValuesEnabled) {&lt;br /&gt;
	  filteredValues = filteredValues&lt;br /&gt;
		.WithRowNumberColumn(&amp;quot;RowNumber&amp;quot;, [&amp;quot;Count&amp;quot;], null, [false]);&lt;br /&gt;
	  filteredValuesColumns = Concat(filteredValuesColumns, [&amp;quot;RowNumber&amp;quot;]);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	filteredValues = filteredValues&lt;br /&gt;
	  .Select(filteredValuesColumns);&lt;br /&gt;
&lt;br /&gt;
	// Generate select returning all the accepted values.&lt;br /&gt;
	let allValues = filteredValues&lt;br /&gt;
	  .(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;gt;= #expr{minValueUsage}}) : _)&lt;br /&gt;
	  .(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;lt;= #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
	  .Select([valueColumnName, newColumnName: valueColumnName]);&lt;br /&gt;
&lt;br /&gt;
	if (!IsNullTop(includeOthers)) {&lt;br /&gt;
	  // If includeOthers is defined, replace original values with the variable defined in includeOthers.&lt;br /&gt;
	  let otherValues = filteredValues&lt;br /&gt;
		.(minValueUsageEnabled ? Where(#sql{Column(&amp;quot;Usage&amp;quot;) &amp;lt; #expr{minValueUsage}}) : _)&lt;br /&gt;
		.(maxNumUniqueValuesEnabled ? Where(#sql{Column(&amp;quot;RowNumber&amp;quot;) &amp;gt; #expr{maxNumUniqueValues}}) : _)&lt;br /&gt;
		.WithColumn(newColumnName, #sql{#expr{includeOthers}})&lt;br /&gt;
		.Select([valueColumnName, newColumnName]);&lt;br /&gt;
	  allValues = allValues.Append(otherValues)&lt;br /&gt;
	}&lt;br /&gt;
	df.Join(allValues, [columnName: valueColumnName], &amp;quot;inner&amp;quot;)&lt;br /&gt;
	  .RemoveColumns([valueColumnName]);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// The following example will return only rows containing two of the most common values for Region-column.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, 2, null, null);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&lt;br /&gt;
// The following example will return all input rows, but will replace the values of rows whose Region-column&lt;br /&gt;
// has a value used by less than 15% of all the rows with a new value: &amp;quot;_Others&amp;quot;.&lt;br /&gt;
//let df = DataTableById(&amp;lt;data table id&amp;gt;).SqlDataFrame;&lt;br /&gt;
//df = ColumnWithMinUsage(df, &amp;quot;Region&amp;quot;, &amp;quot;_Filtered&amp;quot;, null, 0.15, &amp;quot;_Others&amp;quot;);&lt;br /&gt;
//df.Collect().ToCsv();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export model events and cases ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function ExportModelEvents(m) {&lt;br /&gt;
  let attrs = m.EventAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Events.Concat(&lt;br /&gt;
      [Case.Name, Type.Name, ToString(TimeStamp, &amp;quot;yyyy-MM-dd HH:mm:ss.fff&amp;quot;)], &lt;br /&gt;
      {let evt = _; attrs.{let att = _; evt.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;, &amp;quot;EventType&amp;quot;, &amp;quot;TimeStamp&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
function ExportModelCases(m) {&lt;br /&gt;
  let attrs = m.CaseAttributes;&lt;br /&gt;
  ToDataFrame(&lt;br /&gt;
    m.EventLog.Cases.Concat(&lt;br /&gt;
      [Name], &lt;br /&gt;
      {let cas = _; attrs.{let att = _; cas.Attribute(att)}}&lt;br /&gt;
    ), &lt;br /&gt;
    Concat(&lt;br /&gt;
      [&amp;quot;CaseId&amp;quot;], &lt;br /&gt;
      attrs.Name&lt;br /&gt;
    )&lt;br /&gt;
  ).ToCsv(true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).EventsDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
First(Models.Where(Name==&amp;quot;SAP OtC Extended&amp;quot;)).CasesDataTable.DataFrame.ToCsv(true)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Calculate all the value usages of a single column for each event in event data table ===&lt;br /&gt;
This query could be used, e.g., to find out the maximum resource usage for every resource found in the event data table.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
function WithUsageColumns(resourceColumn)&lt;br /&gt;
{&lt;br /&gt;
  function WithTotalUsageColumnOfSingleResource(resourceColumn, resourceValue)&lt;br /&gt;
  {&lt;br /&gt;
    _&lt;br /&gt;
      .WithColumn(&amp;quot;_Prev&amp;quot;, #sql{Lag(Column(resourceColumn), [TimeStamp, EventType], [true, true], [CaseId], 1, null)})&lt;br /&gt;
      .WithColumn(&amp;quot;_UsageDiff&amp;quot;, #sql{&lt;br /&gt;
        CaseWhen(&lt;br /&gt;
          Column(resourceColumn) == Column(&amp;quot;_Prev&amp;quot;), 0, &lt;br /&gt;
          Column(&amp;quot;_Prev&amp;quot;) == #expr{resourceValue}, -1,&lt;br /&gt;
          Column(resourceColumn) == #expr{resourceValue}, 1,&lt;br /&gt;
          0)&lt;br /&gt;
      })&lt;br /&gt;
      .WithColumn(`${resourceValue}_Usage`, #sql{Sum(Column(&amp;quot;_UsageDiff&amp;quot;), [TimeStamp, EventType])})&lt;br /&gt;
      .RemoveColumns([&amp;quot;_Prev&amp;quot;, &amp;quot;_UsageDiff&amp;quot;])&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let sdf = _;&lt;br /&gt;
  let allValues = sdf.SelectDistinct([resourceColumn]).OrderByColumns([resourceColumn], [true]).Collect().Column(resourceColumn);&lt;br /&gt;
  allValues.{&lt;br /&gt;
    let v = _;&lt;br /&gt;
    sdf = sdf.WithTotalUsageColumnOfSingleResource(resourceColumn, v)&lt;br /&gt;
  }&lt;br /&gt;
  sdf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
dt&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .WithUsageColumns(&amp;lt;resource column name&amp;gt;)&lt;br /&gt;
  .OrderByColumns([dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;]], [true])&lt;br /&gt;
  .Collect().ToCsv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;model id&amp;gt; is the id of the model containing event data to be examined.&lt;br /&gt;
* &amp;lt;resource column name&amp;gt; is the name of the column in the event data table of the specified model containing the resource being used by that event.&lt;br /&gt;
&lt;br /&gt;
NOTE: This expression uses functionalities that are only supported in Snowflake-based data tables.&lt;br /&gt;
&lt;br /&gt;
=== Create new Snowflake model from filter ===&lt;br /&gt;
This script creates a new Snowflake model (and two datatables for cases and events) containing filtered event log from given filter id. The script also works if the model doesn&#039;t have a cases datatable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let filter = FilterById(1); // filter id&lt;br /&gt;
let model = filter.model;&lt;br /&gt;
let project = model.project;&lt;br /&gt;
let nameSuffix = &amp;quot; - &amp;quot; + filter.name + &amp;quot; - &amp;quot; + ToString(Now, &amp;quot;dd-MM-yyyy HH:mm:ss&amp;quot;);&lt;br /&gt;
let eventsDatatableName = model.EventsDataTable.Name + nameSuffix;&lt;br /&gt;
if (eventsDatatableName.length &amp;gt; 440) {&lt;br /&gt;
  eventsDatatableName = eventsDatatableName.Substring(eventsDatatableName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
let eventsData = model&lt;br /&gt;
  .EventsDataTable&lt;br /&gt;
  .SqlDataFrame&lt;br /&gt;
  .ApplyFilter(&lt;br /&gt;
    filter.rules,&lt;br /&gt;
    model.CasesDataTable?.SqlDataFrame&lt;br /&gt;
  );&lt;br /&gt;
project&lt;br /&gt;
  .CreateDatatable(eventsDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
  .Import(eventsData);&lt;br /&gt;
let modelConfiguration = model.Configuration;&lt;br /&gt;
modelConfiguration.DataSource.Events.Set(&amp;quot;DataTableName&amp;quot;, eventsDatatableName);&lt;br /&gt;
if (model.CasesDataTable != null) {&lt;br /&gt;
  let eventsDataCaseIdColumn = &amp;quot;CaseId_&amp;quot; + ToString(Random());&lt;br /&gt;
  let casesDatatableName = model.CasesDataTable.Name + nameSuffix;&lt;br /&gt;
  if (casesDatatableName.length &amp;gt; 440) {&lt;br /&gt;
    casesDatatableName = casesDatatableName.Substring(casesDatatableName.length - 440);&lt;br /&gt;
  }&lt;br /&gt;
  let casesData = model&lt;br /&gt;
    .CasesDataTable&lt;br /&gt;
    .SqlDataFrame&lt;br /&gt;
    .join(&lt;br /&gt;
	  eventsData.SelectDistinct([eventsDataCaseIdColumn: modelConfiguration.DataSource.Events.Columns.CaseId]),&lt;br /&gt;
      [modelConfiguration.DataSource.Cases.Columns.CaseId: eventsDataCaseIdColumn]&lt;br /&gt;
	).Select(model.CasesDataTable.ColumnNames);&lt;br /&gt;
  project&lt;br /&gt;
    .CreateDatatable(casesDatatableName, #{&amp;quot;Connection&amp;quot;: CreateSnowflakeConnection()})&lt;br /&gt;
    .Import(casesData);&lt;br /&gt;
  modelConfiguration.DataSource.Cases.Set(&amp;quot;DataTableName&amp;quot;, casesDatatableName);&lt;br /&gt;
}&lt;br /&gt;
let modelName = model.Name + nameSuffix;&lt;br /&gt;
if (modelName &amp;gt; 440) {&lt;br /&gt;
  modelName = modelName.Substring(modelName.length - 440);&lt;br /&gt;
}&lt;br /&gt;
project&lt;br /&gt;
  .CreateModel(#{    &lt;br /&gt;
    &amp;quot;Name&amp;quot;: modelName,&lt;br /&gt;
    &amp;quot;Description&amp;quot;: model.Description,&lt;br /&gt;
    &amp;quot;Configuration&amp;quot;: modelConfiguration&lt;br /&gt;
  });&lt;br /&gt;
return modelName;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Creating a model consisting of multiple copies of cases in an existing model ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @name CreateTestModel&lt;br /&gt;
 * @description&lt;br /&gt;
 * Creates a new model (or overwrites an existing) to given target project with given number of &lt;br /&gt;
 * repetitions of given source model.&lt;br /&gt;
 * Each repetition will generate &amp;quot;&amp;lt;N&amp;gt;-&amp;quot;-prefix to CaseId-columns, where N equals to the repeat index.&lt;br /&gt;
 * @param sourceModel&lt;br /&gt;
 * PA model used for the source data and from where the connection is copied for the target model if a &lt;br /&gt;
 * new one has to be created.&lt;br /&gt;
 * @param numRepeats&lt;br /&gt;
 * Number of times the data in the source model should be repeated in the generated model.&lt;br /&gt;
 * @param targetProject&lt;br /&gt;
 * Project in which the target model resides.&lt;br /&gt;
 * @param targetModelName&lt;br /&gt;
 * Specifies the name of the test model in the given target project. If a model already exists with &lt;br /&gt;
 * given name, event and case data in this model will be replaced with the new generated event and &lt;br /&gt;
 * case data.&lt;br /&gt;
 * @returns&lt;br /&gt;
 * Model object of the test model having the newly generated data.&lt;br /&gt;
 */&lt;br /&gt;
function CreateTestModel(sourceModel, numRepeats, targetProject, targetModelName) &lt;br /&gt;
{&lt;br /&gt;
  let eventsColumnMappings = sourceModel.EventsDataTable.ColumnMappings;&lt;br /&gt;
  let casesColumnMappings = sourceModel.CasesDataTable.ColumnMappings;&lt;br /&gt;
  let connection = sourceModel.EventsDataTable.DataSourceConnection;&lt;br /&gt;
&lt;br /&gt;
  function CreateResultModel()&lt;br /&gt;
  {&lt;br /&gt;
    function GetTable(tableName) &lt;br /&gt;
    {&lt;br /&gt;
      let tableConfiguration = #{&lt;br /&gt;
        &amp;quot;Name&amp;quot;: tableName,&lt;br /&gt;
        &amp;quot;Connection&amp;quot;: connection&lt;br /&gt;
      };&lt;br /&gt;
      let resultTable = targetProject.DataTableByName(tableName);&lt;br /&gt;
      if (resultTable == null)&lt;br /&gt;
      {&lt;br /&gt;
        resultTable = targetProject.CreateDataTable(tableConfiguration)&lt;br /&gt;
          .Modify(#{&amp;quot;NameInDataSource&amp;quot;: null})&lt;br /&gt;
          .Synchronize();&lt;br /&gt;
      }&lt;br /&gt;
      return resultTable;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let eventsTableName = `${targetModelName} - events`;&lt;br /&gt;
    let casesTableName = `${targetModelName} - cases`;&lt;br /&gt;
    let targetModel = targetProject.ModelByName(targetModelName);&lt;br /&gt;
    let eventsTable, casesTable = null;&lt;br /&gt;
&lt;br /&gt;
    if (targetModel != null)&lt;br /&gt;
    {&lt;br /&gt;
      eventsTable = targetModel.EventsDataTable;&lt;br /&gt;
      casesTable = targetModel.CasesDataTable;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      eventsTable = GetTable(eventsTableName);&lt;br /&gt;
      if (sourceModel.CasesDataTable != null) {&lt;br /&gt;
        casesTable = GetTable(casesTableName);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      let timestampMapping = eventsColumnMappings[&amp;quot;TimeStamp&amp;quot;];&lt;br /&gt;
      eventsColumnMappings.Remove(&amp;quot;TimeStamp&amp;quot;);&lt;br /&gt;
      eventsColumnMappings.Set(&amp;quot;Timestamp&amp;quot;, timestampMapping);&lt;br /&gt;
&lt;br /&gt;
      let modelConfiguration = #{&lt;br /&gt;
        &amp;quot;DataSource&amp;quot;: #{&lt;br /&gt;
          &amp;quot;Events&amp;quot;:#{&lt;br /&gt;
            &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
            &amp;quot;DataTableName&amp;quot;: eventsTableName,&lt;br /&gt;
            &amp;quot;Columns&amp;quot;: eventsColumnMappings&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      if (casesColumnMappings != null) {&lt;br /&gt;
        modelConfiguration[&amp;quot;DataSource&amp;quot;].Set(&amp;quot;Cases&amp;quot;, #{&lt;br /&gt;
          &amp;quot;DataSourceType&amp;quot;: &amp;quot;datatable&amp;quot;,&lt;br /&gt;
          &amp;quot;DataTableName&amp;quot;: casesTableName,&lt;br /&gt;
          &amp;quot;Columns&amp;quot;: casesColumnMappings&lt;br /&gt;
        });&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      targetModel = targetProject.CreateModel(#{&amp;quot;Name&amp;quot;: targetModelName, &amp;quot;Configuration&amp;quot;: modelConfiguration});&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    eventsTable.Truncate();&lt;br /&gt;
    casesTable?.Truncate();&lt;br /&gt;
&lt;br /&gt;
    return #{&lt;br /&gt;
      &amp;quot;TargetModel&amp;quot;: targetModel,&lt;br /&gt;
      &amp;quot;Events&amp;quot;: eventsTable,&lt;br /&gt;
      &amp;quot;Cases&amp;quot;: casesTable&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function RepeatNTimes(sourceDf, caseIdColumn, numRepeats)&lt;br /&gt;
  {&lt;br /&gt;
    let resultDf = null;&lt;br /&gt;
    for (let i = 1; i &amp;lt;= numRepeats; ++i) {&lt;br /&gt;
      let iterationDf = sourceDf&lt;br /&gt;
        .WithColumn(caseIdColumn, #sql{Concat(#expr{i}, &amp;quot;-&amp;quot;, Column(#expr{caseIdColumn}))});&lt;br /&gt;
      resultDf = resultDf == null ? iterationDf : resultDf.Append(iterationDf); &lt;br /&gt;
    }&lt;br /&gt;
    resultDf;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let resultModel = CreateResultModel();&lt;br /&gt;
  let sourceEventDataDf = sourceModel.EventsDataTable.SqlDataFrame;&lt;br /&gt;
  let resultEventDataDf = RepeatNTimes(sourceEventDataDf, eventsColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
  resultModel[&amp;quot;Events&amp;quot;].Import(resultEventDataDf);&lt;br /&gt;
&lt;br /&gt;
  let sourceCaseDataDf = sourceModel.CasesDataTable?.SqlDataFrame;&lt;br /&gt;
  if (sourceCaseDataDf != null) {&lt;br /&gt;
    let resultCaseDataDf = RepeatNTimes(sourceCaseDataDf, casesColumnMappings[&amp;quot;CaseId&amp;quot;], numRepeats);&lt;br /&gt;
    resultModel[&amp;quot;Cases&amp;quot;].Import(resultCaseDataDf);&lt;br /&gt;
  }&lt;br /&gt;
  resultModel[&amp;quot;TargetModel&amp;quot;];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example usage:&amp;lt;blockquote&amp;gt;CreateTestModel(ProjectByName(&amp;quot;Project&amp;quot;).ModelByName(&amp;quot;SAP_OrderToCash - Snowflake&amp;quot;), 3, ProjectByName(&amp;quot;TestData&amp;quot;), &amp;quot;TestModel&amp;quot;);&amp;lt;/blockquote&amp;gt;Creates a new model named &amp;quot;TestModel&amp;quot; (or overwrites old one) into project named &amp;quot;TestData&amp;quot; containing the data from model &amp;quot;SAP_OrderToCash - Snowflake&amp;quot; in project &amp;quot;Project&amp;quot; repeated three times.&lt;br /&gt;
&lt;br /&gt;
=== Analyzing declare patterns found in event log ===&lt;br /&gt;
&lt;br /&gt;
This is an example expression that shows how POSIX-style regular expressions can be used to search for cases in an event log having certain event type patterns [https://www.researchgate.net/publication/277631859_Generating_Event_Logs_Through_the_Simulation_of_Declare_Models declare patterns].&lt;br /&gt;
&lt;br /&gt;
Note: Before use, replace &amp;lt;model id&amp;gt; with a valid model identifier having event data.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
let dt = ModelById(&amp;lt;model id&amp;gt;).EventsDataTable;&lt;br /&gt;
let sdf = dt.SqlDataFrame.Head(1000);&lt;br /&gt;
let caseIdColumn = dt.ColumnMappings[&amp;quot;CaseId&amp;quot;], timeStampColumn = dt.ColumnMappings[&amp;quot;TimeStamp&amp;quot;], eventTypeColumn = dt.ColumnMappings[&amp;quot;EventType&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
let eventTypesDf = sdf&lt;br /&gt;
  .SelectDistinct([eventTypeColumn])&lt;br /&gt;
  .OrderByColumns([eventTypeColumn], [true])&lt;br /&gt;
  .WithRowNumberColumn(&amp;quot;Token&amp;quot;, [eventTypeColumn])&lt;br /&gt;
  .WithColumn(&amp;quot;Token&amp;quot;, #sql{Char(Column(&amp;quot;Token&amp;quot;) + Unicode(&amp;quot;a&amp;quot;) - 1)});&lt;br /&gt;
&lt;br /&gt;
sdf = sdf&lt;br /&gt;
  .Join(eventTypesDf.Select([&amp;quot;_EventType2&amp;quot;: eventTypeColumn, &amp;quot;Token&amp;quot;]), [eventTypeColumn: &amp;quot;_EventType2&amp;quot;], &amp;quot;leftouter&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function RespondedExistencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*((a.*b.*)|(b.*a.*))*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*((${a}.*${b}.*)|(${b}.*${a}.*))*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a.*b)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}.*${b})*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function AlternateResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(a[^a]*b[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}[^${a}]*${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ChainResponsePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^a]*(ab[^a]*)*[^a]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${a}]*(${a}${b}[^${a}]*)*[^${a}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function PrecedencePattern(a, b)&lt;br /&gt;
{&lt;br /&gt;
  // [^b]*(a.*b)*[^b]*&lt;br /&gt;
  ToSqlExpression(`Regexp(Column(&amp;quot;Trace&amp;quot;), &amp;quot;[^${b}]*(${a}.*${b})*[^${b}]&amp;quot;)`)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let tracesDf = sdf&lt;br /&gt;
  .GroupBy([caseIdColumn])&lt;br /&gt;
  .Aggregate([&amp;quot;Trace&amp;quot;: &amp;quot;Token&amp;quot;], [#{&amp;quot;Function&amp;quot;: &amp;quot;list&amp;quot;, &amp;quot;Ordering&amp;quot;: [timeStampColumn], &amp;quot;Separator&amp;quot;: &amp;quot;&amp;quot;}])&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceNL&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;RespondedExistenceCA&amp;quot;, #sql{#expr{RespondedExistencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternNL&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ResponsePatternCA&amp;quot;, #sql{#expr{ResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternNL&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;AlternateResponsePatternCA&amp;quot;, #sql{#expr{AlternateResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternNL&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;ChainResponsePatternCA&amp;quot;, #sql{#expr{ChainResponsePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternNL&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;n&amp;quot;, &amp;quot;l&amp;quot;)}})&lt;br /&gt;
  .WithColumn(&amp;quot;PrecedencePatternCA&amp;quot;, #sql{#expr{PrecedencePattern(&amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;)}})&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
[tracesDf.Collect().ToCsv(), eventTypesDf.Collect().ToCsv()]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=SQL_Scripting_Commands&amp;diff=25670</id>
		<title>SQL Scripting Commands</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=SQL_Scripting_Commands&amp;diff=25670"/>
		<updated>2025-01-14T15:49:52Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* --#ImportSapQuery */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page lists QPR ProcessAnalyzer commands that can be used in the SQL scripts. Each command precedes one or two SQL queries, which sets parameters for the command or defines the data used by the command.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display: flex;flex-wrap: wrap;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 230px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
=== Data Extraction ===&lt;br /&gt;
* [[#--.23CallWebService|CallWebService]]&lt;br /&gt;
* [[#--.23ImportOdbcQuery|ImportOdbcQuery]]&lt;br /&gt;
* [[#--.23ImportOleDbQuery|ImportOleDbQuery]]&lt;br /&gt;
* [[#--.23ImportSalesforceQuery|ImportSalesforceQuery]]&lt;br /&gt;
* [[#--.23ImportSapQuery|ImportSapQuery]]&lt;br /&gt;
* [[#--.23ImportSqlQuery|ImportSqlQuery]] (ADO.Net)&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 230px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Data Output ===&lt;br /&gt;
* [[#--.23ImportDataTable|ImportDataTable]]&lt;br /&gt;
* [[#--.23SendEmail|SendEmail]]&lt;br /&gt;
* [[#--.23ShowReport|ShowReport]]&lt;br /&gt;
* [[#--.23WriteLog|WriteLog]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 230px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Script Flow ===&lt;br /&gt;
* [[#--.23RunQuery|RunQuery]] ([[RunQuery Script Examples|examples]])&lt;br /&gt;
* [[#--.23Commit|Commit]]&lt;br /&gt;
* [[#--.23Exit|Exit]]&lt;br /&gt;
* [[#--.23GetAnalysis|GetAnalysis]]&lt;br /&gt;
* [[#--.23Run|Run]]&lt;br /&gt;
* [[#--.23StartBackground|StartBackground]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= --#CallWebService =&lt;br /&gt;
Extracts data via Web Service. This command takes one SELECT query as parameter.&lt;br /&gt;
&lt;br /&gt;
== Query ==&lt;br /&gt;
Configures the command using a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&lt;br /&gt;
; Address&lt;br /&gt;
: Defines the URI of the service to call. Mandatory.&lt;br /&gt;
; Method&lt;br /&gt;
: Defines the HTTP method to use for the call. Must be any of the following: GET (default), POST, PUT, DELETE. Optional.&lt;br /&gt;
; Body&lt;br /&gt;
: Defines the message body text to send to the service. Default value is empty. Optional.&lt;br /&gt;
; Encoding&lt;br /&gt;
: Defines the encoding method to use. The supported options are listed in [https://msdn.microsoft.com/en-us/library/system.text.encoding%28v=vs.110%29.aspx https://msdn.microsoft.com/en-us/library/system.text.encoding%28v=vs.110%29.aspx]. Default value is UTF8. Optional.&lt;br /&gt;
; Timeout&lt;br /&gt;
: Number of milliseconds to wait before the request times out. Default value is 60000. Optional.&lt;br /&gt;
; ExecuteInClientSide&lt;br /&gt;
: Defines whether the web service call is made from the QPR ScriptLauncher or from the server. TRUE or 1, the call is executed in the ScriptLauncher. FALSE or 0, the call is executed in the server. Default value is FALSE. Optional.&lt;br /&gt;
; DefaultNetworkCredentials&lt;br /&gt;
: Optional. Defines the possibility to use default network credentials in web service calls:&lt;br /&gt;
: 1 = use the default network credentials.&lt;br /&gt;
: 0 = don&#039;t use the default network credentials.&lt;br /&gt;
: If CallWebService command is run in the server side (ExecuteInClientSide=False), the default network credentials can be used only if in the server configuration AllowForwardingNetworkCredentials is true (it is false by default). Otherwise, if the CallWebService command is run in the client side (ExecuteInClientSide=True), the default network credentials can always be used. &lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT.&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
;&amp;lt;nowiki&amp;gt;&amp;lt;other parameters&amp;gt;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
: All the rest of the passed parameters not listed above are added as extra headers to the request. For example, &#039;&#039;Content-Type&#039;&#039; and &#039;&#039;Accept&#039;&#039; HTTP headers can be added. Optional.&lt;br /&gt;
&lt;br /&gt;
== Result ==&lt;br /&gt;
The result of the request is passed to the script following the CallWebService operation in the following variables:&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ResponseText&amp;lt;/code&amp;gt; The response text received from the remote server. If there was an error in processing the request, this will contain the received error message. NVARCHAR(MAX). &lt;br /&gt;
: &amp;lt;code&amp;gt;@_ResponseStatusCode&amp;lt;/code&amp;gt; The numeric status code received from the remote server. INT. &lt;br /&gt;
: &amp;lt;code&amp;gt;@_ResponseSuccess&amp;lt;/code&amp;gt; True only if the request returned status code that represents a success. BIT.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[CallWebService Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#Commit =&lt;br /&gt;
[https://docs.microsoft.com/en-us/sql/t-sql/language-elements/commit-transaction-transact-sql?view=sql-server-ver15 Commits] the currently open SQL transaction in the sandbox database and starts a new transaction. The commit command can be executed at any point in the script. Note that the command does not have any parameters, i.e. there is no preceding SELECT statement before the --#Commit statement.&lt;br /&gt;
&lt;br /&gt;
If the commit command is not used, the database transaction in the sandbox database is committed when the script is completed. On the other hand, if the script execution encounters an error, the SQL transaction is rolled back. &lt;br /&gt;
&lt;br /&gt;
The commit command is useful in following circumstances:&lt;br /&gt;
* If the sandbox database is configured to allow storing permanent objects, commit can be used to preserve changes even if the script execution encounters an error.&lt;br /&gt;
* When the scripting is handling large amount of data, it&#039;s better to make commits during the script run, so that the database transaction log doesn&#039;t grow too large.&lt;br /&gt;
* Committing changes makes them visible for other users in the database.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
--#Commit&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= --#Exit =&lt;br /&gt;
Stops the execution of the script and gives a message to the user. This command takes one SELECT query as its parameter.&lt;br /&gt;
&lt;br /&gt;
Configures the command using a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&amp;lt;br/&amp;gt;&lt;br /&gt;
; Exit&lt;br /&gt;
: Defines whether to stop the script execution:&lt;br /&gt;
: 1 = stop execution of the current script and call the script defined by the RunScriptId parameter if it is given.&lt;br /&gt;
: 0 = if a value for the RunScriptId parameter is given, pause the execution of the current script and call the given script, then resume running the current script after the given script ends. If a value for RunScriptId is not given, do not pause or stop execution of the current script.&lt;br /&gt;
; MessageText&lt;br /&gt;
: Text to be shown to the user after the script execution is finished if the script finished because of the Exit command, i.e. when Exit=1. The default value is &amp;quot;Script execution finished.&amp;quot;, which is shown also when the script finished normally, i.e. when Exit=0. The text is also written to the script log.&lt;br /&gt;
; RunScriptId&lt;br /&gt;
: Optional. The Id of the script to be run. Can be empty. Note that the script can call itself, so be careful not to create a looping script.&lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[Exit Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#GetAnalysis =&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border:1px solid #dfdfdf;padding:0.5em 1em 0.5em 1em;background-color:#E7EAEC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
--#GetAnalysis command is deprecated and it will be removed in a future release. Use the more flexible [[SQL_Scripting_Commands#--.23RunQuery|--#RunQuery]] command instead.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creates an analysis from the data which the preceding SQL statements given as parameters provide. This command can take several queries, one for every analysis to be performed. These queries and analysis results are independent from one another. Contains information about the scripts that are running and have been run.​&lt;br /&gt;
&lt;br /&gt;
Configures the command using a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&amp;lt;br/&amp;gt;&lt;br /&gt;
; &amp;lt;Analysis Parameter&amp;gt;&lt;br /&gt;
: The --#GetAnalysis command supports the following analysis types:&lt;br /&gt;
* DataTableAnalysis=18: Reads a data table from SQL server and stores it in temporary table​&lt;br /&gt;
* Etl=19&lt;br /&gt;
* EtlReport=20&lt;br /&gt;
* RunScript=25&lt;br /&gt;
* ExpressionAnalysis=33&lt;br /&gt;
; TargetTable&lt;br /&gt;
: The temporary table to which the analysis is to be stored. When the TargetTable parameter is used, the &amp;quot;Table&amp;quot; result type of the ForceAnalysisResultType parameter is also automatically used. If the specified temporary table already exists in the database then its contents are deleted before storing analysis.&lt;br /&gt;
; Show&lt;br /&gt;
: Optional. If TRUE or 1, the analysis is opened after the script is run. If the Show parameter is set to TRUE or 1 and the TargetTable parameter is used in the same GetAnalysis command, the analysis result is stored in the target table in tabular format.&lt;br /&gt;
; Title&lt;br /&gt;
: Optional. Name of the CSV file created when Show is TRUE or 1. Default value is the name of the analysis type.&lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
; MaximumCount&lt;br /&gt;
: Used with Operation Log Analysis analysis type. Integer. The maximum amount of rows returned. Optional. Default value is 1000.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[GetAnalysis Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#ImportDataTable =&lt;br /&gt;
Imports data from an SQL query to a datatable. This command takes two SELECT queries as parameters.&lt;br /&gt;
&lt;br /&gt;
== First Query ==&lt;br /&gt;
Configures the command using a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&amp;lt;br/&amp;gt;&lt;br /&gt;
; ProjectId or ProjectName&lt;br /&gt;
: The id or the name of the project in which the target data table exists.&lt;br /&gt;
; DataTableId or DataTableName&lt;br /&gt;
: The id or the name of the existing/new target data table.&lt;br /&gt;
; Append&lt;br /&gt;
: Defines what to do with an existing contents of the target datatable. When value is 1, existing rows in the target datatable are not deleted (also new columns in the imported data are created to the datatable). When value is 0, existing rows in the target datatable are deleted before the import (columns are still preserved). Not used when creating a new data table.&lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
&lt;br /&gt;
== Second Query ==&lt;br /&gt;
; &amp;lt;nowiki&amp;gt;&amp;lt;data&amp;gt;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
: The database query whose results are to be imported. Note that if the query doesn&#039;t return any data, the datatable is not created.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[ImportDataTable Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#ImportOdbcQuery =&lt;br /&gt;
Extracts data from an ODBC data source and imports it to QPR ProcessAnalyzer datatable or temporary table. Column names from the query result as used. If a column name contains illegal characters for table names, the illegal characters are converted to be underscore characters. Columns are extracted as text data. To use ImportOdbcQuery, define a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&lt;br /&gt;
&lt;br /&gt;
; TargetTable: The temporary table to which the data is to be imported. If not used, define the target using the ProjectId/ProjectName, DataTableId/DataTableName, and Append parameters described below. &lt;br /&gt;
; ProjectId / ProjectName&lt;br /&gt;
: The id or the name of the project in which the target data table exists.&lt;br /&gt;
; DataTableId / DataTableName&lt;br /&gt;
: The id or the name of the existing/new target data table.&lt;br /&gt;
; Append&lt;br /&gt;
: Defines what to do with an existing target data table and its contents. TRUE or any other Integer than &amp;quot;0&amp;quot; = the target data table and its existing contents are not deleted before import. If a user imports into a data table with &#039;Append&#039; = FALSE or &amp;quot;0&amp;quot;, the contents of the data table are deleted before the import. If a user imports into a temporary table (i.e. TargetTable) with &#039;Append&#039; = FALSE or &amp;quot;0&amp;quot;, then the whole temporary table is deleted before the import. Not used when creating a new data table.&lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
; OdbcConnectionString&lt;br /&gt;
: The ODBC driver connection string that includes the settings needed to establish the initial connection. Mandatory. See [http://msdn.microsoft.com/en-us/library/system.data.odbc.odbcconnection.connectionstring%28v=vs.110%29.aspx?cs-save-lang=1&amp;amp;cs-lang=csharp#code-snippet-1 OdbcConnection.ConnectionString Property in Microsoft Development Network] for more information on the possible connection strings.&lt;br /&gt;
; OdbcConnectionStringKey&lt;br /&gt;
: [[Storing_Secrets_for_Scripts|Secret name]] for the connection string. Alternative for the OdbcConnectionString property.&lt;br /&gt;
; OdbcQueryString&lt;br /&gt;
: The SQL query string. Mandatory. Note that if the query doesn&#039;t return any data, the target data table or temporary table is not created.&lt;br /&gt;
; QueryExecutionTimeout&lt;br /&gt;
: Defines timeout in seconds for the ODBC command execution. If not specified, default value is 600 seconds.&lt;br /&gt;
; ExecuteInClientSide&lt;br /&gt;
: Defines whether the command is executed from the QPR ScriptLauncher or from the server. This parameter is used when there is no server connection available, for example. TRUE or 1, the query is executed in the QPR ScriptLauncher. FALSE or 0, the query is executed in the server. Supports only data table as the import destination. If &#039;TargetTable&#039; has been defined as the import destination and the value of this parameter is given as TRUE or 1, you will receive an error message. Optional. Default value is FALSE.&lt;br /&gt;
&lt;br /&gt;
See examples in the [[ImportOdbcQuery Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#ImportOleDbQuery =&lt;br /&gt;
Extracts data from an OLE DB data source and imports it to QPR ProcessAnalyzer datatable or a temporary table. Column names from the query result are used. It is possible to both create new datatables as well as modify existing datatables with this command. To use the ImportOleDbQuery, define a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&lt;br /&gt;
&lt;br /&gt;
; TargetTable&lt;br /&gt;
: The temporary table to which the data is to be imported. If not used, define the target using the ProjectId/ProjectName, DataTableId/DataTableName, and Append parameters described below.&lt;br /&gt;
; ProjectId / ProjectName&lt;br /&gt;
: The id or the name of the project in which the target data table exists.&lt;br /&gt;
; DataTableId / DataTableName&lt;br /&gt;
: The id or the name of the existing/new target data table.&lt;br /&gt;
; Append&lt;br /&gt;
: Defines what to do with an existing target data table and its contents. TRUE or any other Integer than &amp;quot;0&amp;quot; = the target data table and its existing contents are not deleted before import. If a user imports into a data table with &#039;Append&#039; = FALSE or &amp;quot;0&amp;quot;, the contents of the data table are deleted before the import. If a user imports into a temporary table(i.e. TargetTable) with &#039;Append&#039; = FALSE or &amp;quot;0&amp;quot;, then the whole temporary table is deleted before the import. Not used when creating a new data table.&lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
; OleDbConnectionString&lt;br /&gt;
: The OLE DB connection string that includes the settings needed to establish the initial connection. Mandatory. See [http://msdn.microsoft.com/en-us/library/system.data.oledb.oledbconnection.connectionstring%28v=vs.110%29.aspx OleDbConnection.ConnectionString Property in Microsoft Development Network] for more information on the possible connection strings.&lt;br /&gt;
; OleDbQueryString&lt;br /&gt;
: The SQL query string. Mandatory. Note that if the query doesn&#039;t return any data, the target data table or temporary table is not created.&lt;br /&gt;
; QueryExecutionTimeout&lt;br /&gt;
: Defines timeout in seconds for the OLE DB command execution. If not specified, default value is 600 seconds.&lt;br /&gt;
; ExecuteInClientSide&lt;br /&gt;
: Defines whether the command is executed from the QPR ScriptLauncher or from the server. This parameter is used when there is no server connection available, for example. TRUE or 1, the query is executed in the QPR ScriptLauncher. FALSE or 0, the query is executed in the server. Supports only data table as the import destination. If &#039;TargetTable&#039; has been defined as the import destination and the value of this parameter is given as TRUE or any other Integer than &amp;quot;0&amp;quot;, you will receive an error message. Optional. Default value is FALSE.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[ImportOleDbQuery Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#ImportSalesforceQuery =&lt;br /&gt;
Extracts data from the Salesforce cloud using its REST API and imports the data to a datatable. The command takes one SELECT query as its parameter. If the query doesn&#039;t return any data, the target data table or temporary table is not created.&lt;br /&gt;
&lt;br /&gt;
More information about the Salesforce REST API: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_rest.htm.&lt;br /&gt;
&lt;br /&gt;
== Query ==&lt;br /&gt;
Configures the command using a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&amp;lt;br/&amp;gt;&lt;br /&gt;
; TargetTable&lt;br /&gt;
: Temporary table to which the data is imported. If not used, define the target using the ProjectId/ProjectName, DataTableId/DataTableName, and Append parameters described below.&lt;br /&gt;
; ProjectId / ProjectName&lt;br /&gt;
: Id or the name of the project in which the target datatable is located.&lt;br /&gt;
; DataTableId / DataTableName&lt;br /&gt;
: Id or the name of the target data table. If DataTableName is used, the ProjectId or ProjectName can also be used to define the project where the datatable is located.&lt;br /&gt;
; Append&lt;br /&gt;
: Defines what to do with an existing target data table contents. TRUE or 1, existing contents of the target datatable is not deleted in the import. When FALSE or 0, existing contents of the target datatable are deleted before importing new data. Not used when creating a new data table.&lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
; SalesforceUser&lt;br /&gt;
: Username for the Salesforce cloud.&lt;br /&gt;
; SalesforcePW&lt;br /&gt;
: Password for the Salesforce cloud.&lt;br /&gt;
; SalesforcePWKey&lt;br /&gt;
: [[Storing_Secrets_for_Scripts|Secret name]] for the stored Salesforce password. Alternative for the SalesforcePW property.&lt;br /&gt;
; SalesforceUrl&lt;br /&gt;
: Optional. Salesforce web service url.&lt;br /&gt;
; SalesforceQueryMode&lt;br /&gt;
: Optional. Determines which Salesforce query function to use. One of the following values (1, 2 or 3) can be used:&lt;br /&gt;
: 1: &#039;&#039;&#039;QueryAll&#039;&#039;&#039; (default): Executes specified SOQL query, except unlike &#039;&#039;Query&#039;&#039;, &#039;&#039;QueryAll&#039;&#039; returns records that are deleted because of a merge or delete. More information: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_queryall.htm.&lt;br /&gt;
: 2: &#039;&#039;&#039;Query&#039;&#039;&#039;: Executes the specified SOQL query. More information: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_query.htm)&lt;br /&gt;
: 3: &#039;&#039;&#039;sObject Describe&#039;&#039;&#039;: Completely describes the individual metadata at all levels for the specified object. For example, this can be used to retrieve the fields, URLs, and child relationships for the Account object. More information: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_describe.htm).&lt;br /&gt;
; SalesforceQuery&lt;br /&gt;
: Query to run in the Salesforce cloud to fetch the data, defined as SOQL (Salesforce Object Query Language). More information: https://developer.salesforce.com/docs/atlas.en-us.236.0.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_sosl_intro.htm.&lt;br /&gt;
; SalesforceQueryRetries&lt;br /&gt;
: Optional. Number of retries to attempt if the Salesforce query doesn&#039;t succeed. Default value is 3.&lt;br /&gt;
; SalesforceQueryRetryWait&lt;br /&gt;
: Optional. Number of milliseconds to wait between query retries. Default is 3000 ms.&lt;br /&gt;
; SalesforceBatchSize&lt;br /&gt;
: Optional. Data is queried from Salesforce in batches, and this setting determines the batch size. The value can be between 200 and 2000, and the default value is 500.&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
If you get error &#039;&#039;INVALID_TYPE sObject type &#039;Objectname&#039; is not supported&#039;&#039;:&lt;br /&gt;
* Check that the object in question exists or that the object name is correct.&lt;br /&gt;
* Verify that the Salesforce user has rights to the object. &lt;br /&gt;
** You have to give access to the new custom objects and VisualForce pages from the user&#039;s profile, and you have to check the &amp;quot;Customize Application&amp;quot; checkbox under the same profile (https://developer.salesforce.com/forums/?id=906F00000008qG6IAI). Contact your Salesforce administrator.&lt;br /&gt;
* The Salesforce user may need extra license to access the object. Special 3rd party custom objects may need separate license. Contact your Salesforce application administrator.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[ImportSalesforceQuery Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#ImportSapQuery =&lt;br /&gt;
Extracts data from an SAP system and imports it to QPR ProcessAnalyzer datatable or a temporary table. Column names from the query result are used. If a column name contains illegal characters for table names, the illegal characters are converted to be underscore characters (e.g. &amp;quot;sap:Owner&amp;quot; -&amp;gt; &amp;quot;sap_Owner&amp;quot;). Columns are extracted as text data. Note that using this command requires [[QPR_ProcessAnalyzer_ScriptLauncher#Installing_SAP_NetWeaver_RFC_Library|installing SAP NetWeaver RFC Library]].&lt;br /&gt;
&lt;br /&gt;
To use the ImportSapQuery command, define a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&lt;br /&gt;
; TargetTable&lt;br /&gt;
: If this parameter is given, store the results into a temporary SQL table in the ETL sandbox. If the TargetTable parameter is not given, use either the ProjectId or ProjectName parameters.&lt;br /&gt;
; ProjectId / ProjectName&lt;br /&gt;
: The id or the name of the project in which the target data table exists.&lt;br /&gt;
; DataTableId / DataTableName&lt;br /&gt;
: The id or the name of the existing/new target data table.&lt;br /&gt;
; Append&lt;br /&gt;
: Defines what to do with an existing target data table and its contents. TRUE or any other Integer than &amp;quot;0&amp;quot; = the target data table and its existing contents are not deleted before import. If a user imports into a data table with &#039;Append&#039; = FALSE or &amp;quot;0&amp;quot;, the contents of the data table are deleted before the import. If a user imports into a temporary table (i.e. TargetTable) with &#039;Append&#039; = FALSE or &amp;quot;0&amp;quot;, then the whole temporary table is deleted before the import. Not used when creating a new data table.&lt;br /&gt;
; ConvertDataTypes&lt;br /&gt;
: List of SAP data types that are converted into respective data types supported by SQL Server instead of using NVARCHAR. Defined by listing the data type identifier characters in any order. Available data type identifying characters are &#039;&#039;&#039;IFPCDTNX&#039;&#039;&#039;. If not defined, all data is converted to NVARCHAR. Example: &#039;&#039;IFP&#039;&#039; (convert only numeric data types: Integer, Float, Packed number) ([[Importing_Data_from_SAP|more information]]).&lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
;ImportChunkSize&lt;br /&gt;
:Specifies the size of the chunks of data used when importing the data from SAP to PA Server. The value represents the approximate maximum number of data cells in each chunk (consisting tables of &amp;lt;number of rows&amp;gt; * &amp;lt;number of columns&amp;gt; data cells. Default value is 200000. Smaller value causes big imports to be split into more chunks taking more time in total, but it makes importing of each chunk faster possibly helping in some timeout situations.&lt;br /&gt;
; SapUser&lt;br /&gt;
: SAP username used to connect to SAP. Mandatory. Corresponds to the &amp;quot;USER&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapPW&lt;br /&gt;
: Password of the SAP user used to connect to SAP. Mandatory. Corresponds to the &amp;quot;PASSWD&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapPWKey&lt;br /&gt;
: [[Storing_Secrets_for_Scripts|Secret name]] for the stored SAP password. Alternative for the SapPW property.&lt;br /&gt;
; SapClient&lt;br /&gt;
: The SAP backend client. Mandatory. Corresponds to the &amp;quot;CLIENT&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapAppServerHost&lt;br /&gt;
: The hostname or IP of the specific SAP application server, to which all connections shall be opened. Mandatory if SapMessageServerHost is not defined. Corresponds to the &amp;quot;ASHOST&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapMessageServerHost&lt;br /&gt;
: The hostname or IP of the SAP system’s message server (central instance). Mandatory if SapAppServerHost is not defined. Corresponds to the &amp;quot;MSHOST&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapSystemNumber&lt;br /&gt;
: The SAP system’s system number. Mandatory if SapSystemID is not defined. Corresponds to the &amp;quot;SYSNR&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapSystemID&lt;br /&gt;
: The SAP system’s three-letter system ID. Mandatory if SapSystemNumber is not defined. Corresponds to the &amp;quot;SYSID&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; ExecuteInClientSide&lt;br /&gt;
: Defines whether the command is executed from the QPR ScriptLauncher or from the server. This parameter is used when there is no server connection available, for example. TRUE or 1, the query is executed in the QPR ScriptLauncher. FALSE or 0, the query is executed in the server. Supports only data table as the import destination. If &#039;TargetTable&#039; has been defined as the import destination and the value of this parameter is given as TRUE or any other Integer than &amp;quot;0&amp;quot;, you will receive an error message. Optional. Default value is FALSE.&lt;br /&gt;
; SapLanguage&lt;br /&gt;
: SAP language used. Default value is &amp;quot;EN&amp;quot;. Optional. Corresponds to the &amp;quot;LANG&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapPoolSize&lt;br /&gt;
: The maximum number of RFC connections that this destination will keep in its pool. Default value is &amp;quot;5&amp;quot;. Optional. Corresponds to the &amp;quot;POOL_SIZE&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapMaxPoolSize&lt;br /&gt;
: In order to prevent an unlimited number of connections to be opened, you can use this parameter. Default value is &amp;quot;10&amp;quot;. Optional. Corresponds to the &amp;quot;MAX_POOL_SIZE&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapConnectionIdleTimeout&lt;br /&gt;
: If a connection has been idle for more than SapIdleTimeout seconds, it will be closed and removed from the connection pool upon checking for idle connections or pools. Default value is &amp;quot;600&amp;quot;. Optional. Corresponds to the &amp;quot;IDLE_TIMEOUT&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapRouter&lt;br /&gt;
: List of host names and service names / port numbers for the SAPRouter in the following format: /H/hostname/S/portnumber. Optional. Corresponds to the &amp;quot;SAPROUTER&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapLogonGroup&lt;br /&gt;
: The logon group from which the message server shall select an application server. Optional. Corresponds to the &amp;quot;GROUP&amp;quot; constant on SAP side. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapQueryMode&lt;br /&gt;
: If this number is set to &amp;quot;1&amp;quot;, then the query result will have the SAP Table field names as data table column names and actual data rows as rows. If this is set to &amp;quot;3&amp;quot;, the query result will get the field descriptions from the SAP query using NO_DATA parameter, i.e. the returned columns are the following (in this order): Field, Type, Description, Length, Offset. Default value is &amp;quot;1&amp;quot;. Optional. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapQueryTable&lt;br /&gt;
: Name of the SAP table to be extracted. Specifies the value for the parameter QUERY_TABLE in tab: &#039;Import&#039; or function module &#039;rfc_read_table&#039; in SAP. Mandatory. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info. Note that if the query doesn&#039;t return any data, the target data table or temporary table is not created.&lt;br /&gt;
; SapRowcount&lt;br /&gt;
: The maximum amount of rows to fetch. Specifies the value for parameter ROWCOUNT in tab: &#039;Import&#039; or function module &#039;rfc_read_table&#039; in SAP. Optional. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapRowskips&lt;br /&gt;
: The number of rows to skip. Specifies the value for parameter ROWSKIPS in tab: &#039;Import&#039; or function module &#039;rfc_read_table&#039;. in SAP. Optional. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapWhereClause&lt;br /&gt;
: A comma separated list of WHERE clause elements passed for the SapQueryTable. Can be used with or without the SapWhereClauseSelect parameter. If used together with the SapWhereClauseSelect parameter, use the SapWhereClause parameter first. NOTE: The default maximum length for the Where Clause string is 72 characters in SAP, so the recommended maximum length of the SapWhereClause value is also 72 characters. In effect, specifies the value for parameter OPTIONS in tab: &#039;Import&#039; or function module &#039;rfc_read_table&#039; in SAP. Optional. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapWhereClauseSelect&lt;br /&gt;
: The SELECT query to be executed in QPR ProcessAnalyzer sandbox. Used with or without the SapWhereClause parameter to pass WHERE clauses to SapQueryTable. If used together with the SapWhereClause parameter, use the SapWhereClause parameter first. The query is expected to return a table with at least one column, as the contents from the rows in the first column of the table are concatenated together to form the WHERE clause in SAP RFC_ReadTable. Therefore, it&#039;s recommended to first create the table with the WHERE clauses into a temporary table. In addition, it&#039;s recommended to have an order number column in the table and use that in the SELECT query to make sure the WHERE clause elements are concatenated in the correct order. The default maximum length for Where Clause string is 72 characters in SAP, so the recommended maximum length for the WHERE clause string in each row of the table is also 72. In effect, specifies the value for parameter OPTIONS in tab: &#039;Import&#039; or function module &#039;rfc_read_table&#039; in SAP. Optional. The contents up to the first 10 rows in the first column of the SELECT query are shown in the QPR ProcessAnalyzer [[QPR_ProcessAnalyzer_Logs#Script_Log|Script Log]]. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&amp;lt;br/&amp;gt;&lt;br /&gt;
; SapFieldNames&lt;br /&gt;
: A comma separated list of field names for columns to be imported. Default value is empty, resulting in all columns being imported. Specifies the value for parameter FIELDNAME in tab: &#039;Tables&#039; for table &#039;FIELDS&#039; for function module &#039;rfc_read_table&#039; in SAP. Optional. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; SapFunction&lt;br /&gt;
: If you define a value for this parameter, then the new value specifies the SAP function that is called inside the #ImportSapQuery command. Optional. The default value is RFC_READ_TABLE. Another possible value is BBP_RFC_READ_TABLE. See the [http://help.sap.com/saphelp_nw04/helpdata/en/e9/23c80d66d08c4c8c044a3ea11ca90f/content.htm SAP .NET Connector documentation] for more info.&lt;br /&gt;
; UseAnyAsColumnType&lt;br /&gt;
: Determines datatable column data types for the created columns. When &#039;&#039;true&#039;&#039;, &amp;quot;Any&amp;quot; type of columns are created (resulting into SQL_variant columns in SQL server), and when &#039;&#039;false&#039;&#039;, data types depend on the ConvertDataTypes parameter. Default value is true when running the import in SQL script, and otherwise default value is false.&lt;br /&gt;
;AliasUser&lt;br /&gt;
:&lt;br /&gt;
;AppServerService&lt;br /&gt;
:&lt;br /&gt;
;CharacterFaultIndicatorToken&lt;br /&gt;
:&lt;br /&gt;
;Codepage&lt;br /&gt;
:&lt;br /&gt;
;GatewayHost&lt;br /&gt;
:&lt;br /&gt;
;GatewayService&lt;br /&gt;
:&lt;br /&gt;
;IdleCheckTime&lt;br /&gt;
:&lt;br /&gt;
;LogonCheck&lt;br /&gt;
:&lt;br /&gt;
;MaxPoolWaitTime&lt;br /&gt;
:&lt;br /&gt;
;MessageServerService&lt;br /&gt;
:&lt;br /&gt;
;Name&lt;br /&gt;
:&lt;br /&gt;
;NoCompression&lt;br /&gt;
:&lt;br /&gt;
;OnCharacterConversionError&lt;br /&gt;
:&lt;br /&gt;
;PartnerCharSize&lt;br /&gt;
:&lt;br /&gt;
;PasswordChangeEnforced&lt;br /&gt;
:&lt;br /&gt;
;ProgramId&lt;br /&gt;
:&lt;br /&gt;
;R3Name&lt;br /&gt;
:&lt;br /&gt;
;RegistrationCount&lt;br /&gt;
:&lt;br /&gt;
;RepositoryDestination&lt;br /&gt;
:&lt;br /&gt;
;RepositoryPassword&lt;br /&gt;
:&lt;br /&gt;
;RepositorySncMyName&lt;br /&gt;
:&lt;br /&gt;
;RepositoryUser&lt;br /&gt;
:&lt;br /&gt;
;RepositoryX509Certificate&lt;br /&gt;
:&lt;br /&gt;
;SapSso2Ticket&lt;br /&gt;
:&lt;br /&gt;
;SncLibraryPath&lt;br /&gt;
:Full path including file name of the [[Importing_Data_from_SAP#SNC_encrypted_connection|SNC]] shared library to be used.&lt;br /&gt;
;SncMode&lt;br /&gt;
: Determines whether connections will be secured with [[Importing_Data_from_SAP#SNC_encrypted_connection|SNC]]. Value &#039;&#039;&#039;0&#039;&#039;&#039; doesn&#039;t use SNC (default) and value &#039;&#039;&#039;1&#039;&#039;&#039; uses SNC.&lt;br /&gt;
;SncMyName&lt;br /&gt;
:Token/identifier representing the external RFC program. In most cases this can be omitted. The installed [[Importing_Data_from_SAP#SNC_encrypted_connection|SNC]] solution usually knows its own SNC name. Only for solutions supporting “multiple identities”, you may Varies depending on the installed SNC solution (Secude, Kerberos, NTLM, etc). Example for Secude: p/secude:CN=ALEREMOT SAP Online Help 09.09.2014 SAP .NET Connector 3.0 41 need to specify the identity to be used for this particular destination/server. E, O=Mustermann-AG, C=DE&lt;br /&gt;
;SncPartnerName&lt;br /&gt;
:The backend&#039;s [[Importing_Data_from_SAP#SNC_encrypted_connection|SNC]]name.&lt;br /&gt;
;SncPartnerNames&lt;br /&gt;
:&lt;br /&gt;
;SncQop&lt;br /&gt;
:Quality of service to be used for SNC communication of this particular destination/server. One of the following values:&lt;br /&gt;
* 1: Digital signature&lt;br /&gt;
* 2: Digital signature and encryption&lt;br /&gt;
* 3: Digital signature, encryption, and user authentication&lt;br /&gt;
* 8: Default value defined by back-end system&lt;br /&gt;
* 9: Maximum value that the current security product supports&lt;br /&gt;
;SystemIds&lt;br /&gt;
:&lt;br /&gt;
;UseSapGui&lt;br /&gt;
:&lt;br /&gt;
;X509Certificate&lt;br /&gt;
:&lt;br /&gt;
&lt;br /&gt;
See examples at the [[ImportSapQuery Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#ImportSqlQuery =&lt;br /&gt;
Extracts data from an ADO.Net source (which usually is an SQL Server database) and imports it to QPR ProcessAnalyzer datatable or a temporary table. Column names from the query result are used. It is possible to both create new Data Tables as well as modify existing datatables with this command. To use the ImportSqlQuery command, a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&lt;br /&gt;
&lt;br /&gt;
; TargetTable&lt;br /&gt;
: The temporary table to which the data is to be imported. If not used, define the target using the ProjectId/ProjectName, DataTableId/DataTableName, and Append parameters described below.&lt;br /&gt;
; ProjectId / ProjectName&lt;br /&gt;
: The id or the name of the project in which the target data table exists.&lt;br /&gt;
; DataTableId / DataTableName&lt;br /&gt;
: The id or the name of the existing/new target data table.&lt;br /&gt;
; Append&lt;br /&gt;
: Defines what to do with an existing target data table and its contents. TRUE or any other Integer than &amp;quot;0&amp;quot; = the target data table and its existing contents are not deleted before import. If a user imports into a data table with &#039;Append&#039; = FALSE or &amp;quot;0&amp;quot;, the contents of the data table are deleted before the import. If a user imports into a temporary table (i.e. TargetTable) with &#039;Append&#039; = FALSE or &amp;quot;0&amp;quot;, then the whole temporary table is deleted before the import. Not used when creating a new data table.&lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
; SqlConnectionString&lt;br /&gt;
: The SQL connection string that includes the settings needed to establish the initial connection. Mandatory. See [http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.connectionstring%28v=vs.110%29.aspx SqlConnection.ConnectionString Property in Microsoft Development Network] for more information on the connection parameters.&lt;br /&gt;
; SqlQueryString&lt;br /&gt;
: The SQL query string. Mandatory. Note that if the query doesn&#039;t return any data, the target data table or temporary table is not created.&lt;br /&gt;
; QueryExecutionTimeout&lt;br /&gt;
: Defines timeout in seconds for the SQL command execution. If not specified, default value is 600 seconds.&lt;br /&gt;
; ExecuteInClientSide&lt;br /&gt;
: Defines whether the command is executed from the QPR ScriptLauncher or from the server. This parameter is used when there is no server connection available, for example. TRUE or 1, the query is executed in the client side. FALSE or 0, the query is executed in the server side. Supports only data table as the import destination. If &#039;TargetTable&#039; has been defined as the import destination and the value of this parameter is given as TRUE or any other Integer than &amp;quot;0&amp;quot;, you will receive an error message. Optional. Default value is FALSE.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[ImportSqlQuery Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#Run =&lt;br /&gt;
Runs another script with specified parameters. This command can take multiple SELECT queries which are passed as parameters to the called script. The first SELECT configures the script call by defining the script id to be called.&lt;br /&gt;
&lt;br /&gt;
== First Query ==&lt;br /&gt;
Configures the command using a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&amp;lt;br/&amp;gt;&lt;br /&gt;
; ScriptId&lt;br /&gt;
: Mandatory. The Id of the called script.&lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
: &amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
&lt;br /&gt;
== Following Queries ==&lt;br /&gt;
Subsequent queries are optional and they are used for passing parameters to the called script. Maximum number of arguments is 10.  Each argument is created as a temporary table with names &#039;&#039;&#039;#_Arg1&#039;&#039;&#039;, &#039;&#039;&#039;#_Arg2&#039;&#039;&#039;, &#039;&#039;&#039;#_Arg10&#039;&#039;&#039;. In the created temporary tables, all columns are of the type SQL_VARIANT. If the column names have not been specified, then &#039;&#039;&#039;Value_0&#039;&#039;&#039;, &#039;&#039;&#039;Value_1&#039;&#039;&#039; etc. are used.&lt;br /&gt;
The possible arguments are as follows:&lt;br /&gt;
* &#039;&#039;&#039;@_Argv&#039;&#039;&#039;: Number of provided parameters (between 0 to 10) (type iNT)&lt;br /&gt;
* &#039;&#039;&#039;#_Arg1&#039;&#039;&#039;, &#039;&#039;&#039;#_Arg2&#039;&#039;&#039;, ... &#039;&#039;&#039;#_Arg10&#039;&#039;&#039;: arguments passed to that script&lt;br /&gt;
&lt;br /&gt;
Each argument exists in the called script until the next --#Run command is executed in that script. After the called script has finished, the main script continues its execution.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[Run Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#RunQuery =&lt;br /&gt;
Runs an [[Web_API:_Expression/query|expression language query]], and stores results to a [[QPR_ProcessAnalyzer_Project_Workspace#Datatables|datatable]] or to a temporary table in the scripting database. Following parameters can be used in the command:&lt;br /&gt;
* &#039;&#039;&#039;Configuration&#039;&#039;&#039;: Expression language query to run, written in JSON as specified in [[Web_API:_Expression/query|Web API: Expression/query]]. Queries can be created by using a [[QPR_ProcessAnalyzer_Chart|chart]] where to open the &#039;&#039;&#039;Query&#039;&#039;&#039; (in the &#039;&#039;&#039;Advanced&#039;&#039;&#039; tab). It will show the query made by chart that&#039;s compatible with what can be specified in the &#039;&#039;Configuration&#039;&#039; parameter.&lt;br /&gt;
* &#039;&#039;&#039;TargetTable&#039;&#039;&#039;: When specified, results are stored to a temporary table with that name in the scripting sandbox. The temporary table can be read using the subsequent commands. When the script ends, temporary tables are automatically removed.&lt;br /&gt;
* &#039;&#039;&#039;DatatableId&#039;&#039;&#039;: When specified, data is stored to the defined existing datatable. When using datatable id, ProjectName or ProjectId parameter don&#039;t need to be defined.&lt;br /&gt;
* &#039;&#039;&#039;DataTableName&#039;&#039;&#039;: When specified, data is stored to the datatable with that name, located in the same project as the script. If you want to use different project, specify either the ProjectName or ProjectId parameter.&lt;br /&gt;
* &#039;&#039;&#039;ProjectName&#039;&#039;&#039;: Specifies a project by name where the results datatable is stored. Used together with the DataTableName parameter.&lt;br /&gt;
* &#039;&#039;&#039;ProjectId&#039;&#039;&#039;: Specifies a project by id where the results datatable is stored. Used together with the DataTableName parameter.&lt;br /&gt;
&lt;br /&gt;
See [[RunQuery Script Examples]].&lt;br /&gt;
&lt;br /&gt;
= --#SendEmail =&lt;br /&gt;
Sends an e-mail and writes a message to script log whether sending the email was successful or not. Script execution continues even when the sending isn&#039;t successful. &lt;br /&gt;
&lt;br /&gt;
Configures the command using a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&amp;lt;br/&amp;gt;&lt;br /&gt;
; CatchOperationExceptions&lt;br /&gt;
: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
&#039;&#039;&#039;E-mail Parameters&#039;&#039;&#039;&lt;br /&gt;
; EmailFrom&lt;br /&gt;
: Defines the from address for this e-mail message. Mandatory.&lt;br /&gt;
; EmailTo&lt;br /&gt;
: Defines the recipient(s) for this e-mail message given in a list separated by comma. Mandatory.&lt;br /&gt;
; EmailSubject&lt;br /&gt;
: Defines the subject of the email. Default value is empty. Optional.&lt;br /&gt;
; EmailBody&lt;br /&gt;
: Defines the message body. Default value is empty. Optional.&lt;br /&gt;
; EmailCc&lt;br /&gt;
: Defines the carbon copy recipient(s) for this e-mail message given in a list separated by comma. Optional.&lt;br /&gt;
; EmailBcc&lt;br /&gt;
: Defines the blind carbon copy recipient(s) for this e-mail message given in a list separated by comma. Optional.&lt;br /&gt;
; EmailIsBodyHtml&lt;br /&gt;
: Defines whether the e-mail message body is in HTML. TRUE or any other Integer than &amp;quot;0&amp;quot; = body is in HTML, FALSE or &amp;quot;0&amp;quot; = body is not in HTML. Default value is FALSE. Optional.&lt;br /&gt;
; EmailSender&lt;br /&gt;
: Defines the sender&#039;s address for this e-mail message. Default value is empty. Optional.&lt;br /&gt;
; EmailReplyTo&lt;br /&gt;
: Defines the ReplyTo address(es) for the mail message given in a list separated by comma. Optional.&lt;br /&gt;
; EmailPriority&lt;br /&gt;
: Defines the priority of this e-mail message. Possible values are &amp;quot;High&amp;quot;, &amp;quot;Normal&amp;quot;, and &amp;quot;Low&amp;quot;. Default value is &amp;quot;Normal&amp;quot;. Optional.&lt;br /&gt;
; EmailDeliveryNotification&lt;br /&gt;
: Defines the delivery notifications for this e-mail message. Possible values are &amp;quot;Delay&amp;quot;, &amp;quot;Never&amp;quot;, &amp;quot;None&amp;quot;, &amp;quot;OnFailure&amp;quot;, and &amp;quot;OnSuccess&amp;quot;. Default value is &amp;quot;None&amp;quot;. Optional.&lt;br /&gt;
; EmailBodyEncoding&lt;br /&gt;
: Defines the encoding used to encode the message body. Supported encodings are listed in the &amp;quot;Remarks&amp;quot; section at http://msdn.microsoft.com/en-us/library/System.Text.Encoding.aspx. UTF8 is used by default. Optional.&lt;br /&gt;
; EmailSubjectEncoding&lt;br /&gt;
: Defines the encoding used for the subject content for this e-mail message. Supported encodings are listed in the &amp;quot;Remarks&amp;quot; section at http://msdn.microsoft.com/en-us/library/System.Text.Encoding.aspx. UTF8 is used by default. Optional.&lt;br /&gt;
; EmailAttachmentQuery&lt;br /&gt;
: Defines a query to fetch the parameters for adding attachments to the email. Each row (except the header row)  in the query result corresponds to one attachment. The result must contain the following columns in this order: Name of the attachment, Content for the attachment (Sent as-is without any modifications. Supports binary values.), Media type (supported types are text/plain, text/html, text/xml, and image/jpeg), and Creation time (SQL datetime). Names of the columns do not matter. If the result doesn&#039;t contain some of the columns, an error is written into the Progress log, and the email is not sent. Optional.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;SMTP Server Parameters&#039;&#039;&#039;&lt;br /&gt;
; SmtpServer&lt;br /&gt;
: Defines the hostname or the IP address of the server. Mandatory for the first occurrence of the SendEmail command during script execution.&lt;br /&gt;
; SmtpPort&lt;br /&gt;
: Defines the port of the SMTP server. Default value is &amp;quot;25&amp;quot;. Optional.&lt;br /&gt;
; SmtpAuthenticationUsername&lt;br /&gt;
: Defines the user name for the SMTP server. Note that the user name is in plain text and visible to all users who have access to the script. Optional.&lt;br /&gt;
; SmtpAuthenticationPassword&lt;br /&gt;
: Defines the password for the SMTP server. Note that the password is in plain text and visible to all users who have access to the script. Optional.&lt;br /&gt;
; SmtpEnableSSL&lt;br /&gt;
: Defines whether SSL should be enabled for the SMTP connection. TRUE or any other Integer than &amp;quot;0&amp;quot; = SSL is enabled, FALSE or &amp;quot;0&amp;quot; = SSL is not enabled. Default value is &amp;quot;FALSE&amp;quot;. Optional.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[SendEmail Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#ShowReport =&lt;br /&gt;
Outputs result of an SQL query to a CSV file when running script from [[QPR_ProcessAnalyzer_ScriptLauncher|QPR ProcessAnalyzer ScriptLauncher]]. This command takes two SELECT queries as parameters.&lt;br /&gt;
== First Query ==&lt;br /&gt;
SQL query which results are shown. &lt;br /&gt;
; &amp;lt;nowiki&amp;gt;&amp;lt;data&amp;gt;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
: Mandatory. The database query whose results are to be returned.&lt;br /&gt;
&lt;br /&gt;
== Second Query ==&lt;br /&gt;
Configures the command using a SELECT statement returning two columns: the first column is for a key and the second one is for a value of that key. The values in both the key column and in the value column are of type NVARCHAR. The supported keys for this command are:&amp;lt;br/&amp;gt;&lt;br /&gt;
; &amp;lt;Analysis Parameter&amp;gt;&lt;br /&gt;
: Optional. The analysis parameters given for the operation. Some suggested parameters to be used:&lt;br /&gt;
:; Title&lt;br /&gt;
:: The name of the created CSV file.&lt;br /&gt;
:; MaximumCount&lt;br /&gt;
:: The maximum number of rows to show (0 = all, default = 1000).&lt;br /&gt;
&lt;br /&gt;
; CatchOperationExceptions: Optional. Defines whether to stop the script execution or to continue to run the script from the next statement if an exception occurs when running the script:&lt;br /&gt;
: 1 = don&#039;t stop execution of the script, continue running the script from the next statement.&lt;br /&gt;
: 0 = stop execution of the current script and show the exception.&lt;br /&gt;
: The following script variables will be set and are shown in the script log:&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionOccurred&amp;lt;/code&amp;gt; If there was an exception, then this value is 1, otherwise 0. INT&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionType&amp;lt;/code&amp;gt; If there was an exception, shows the C# class name for the exception, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionMessage&amp;lt;/code&amp;gt; If there was an exception, contains a message that would have been displayed, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
:&amp;lt;code&amp;gt;@_ExceptionDetails&amp;lt;/code&amp;gt; If there was an exception, contains the details that would have been displayed, including the system stack trace, NVARCHAR(MAX), otherwise NULL.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[ShowReport Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#StartBackground =&lt;br /&gt;
Continues the script run in background, i.e. the parent script execution completes and the rest of the script execution continues. When running a script in the background, it cannot output any results using the ShowReport command or GetAnalysis with the Show parameter. It&#039;s possible to terminate scripts that run in the background via the [[QPR_ProcessAnalyzer_Logs#Task_Log|Task log]]. No also that a script running in the background cannot execute in the client side mode.&lt;br /&gt;
&lt;br /&gt;
Takes one SELECT query as a parameter. Following parameter is supported:&lt;br /&gt;
&lt;br /&gt;
; Enabled&lt;br /&gt;
: Boolean value defining whether the script is run in background starting from this command. TRUE = run in background, FALSE = don&#039;t run in background. Default value is TRUE.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[StartBackground Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
= --#WriteLog =&lt;br /&gt;
Adds the first column values from the preceding SQL statements to the log that is shown after the whole script execution is completed. In addition to the WriteLog command, you can also use the [https://docs.microsoft.com/en-us/sql/t-sql/language-elements/print-transact-sql?view=sql-server-ver15 Print SQL statement] to generate log entries into the script execution log. The difference to the WriteLog command is that the Print statement can use also variables.&lt;br /&gt;
&lt;br /&gt;
See examples at the [[WriteLog Script Examples]] page.&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category: QPR ProcessAnalyzer]]&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=ExtractSap_Function&amp;diff=25669</id>
		<title>ExtractSap Function</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=ExtractSap_Function&amp;diff=25669"/>
		<updated>2025-01-14T15:41:10Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;ExtractSap function imports data from an SAP R/3 system using its RFC interface and returns a DataFlow. The function is in the generic context and also in the project context. When using the secure strings, the function need to be called in the project context as the secure strings are project specific.  Using the ExtractSap function requires installing [[QPR_ProcessAnalyzer_ScriptLauncher#Installing_SAP_NetWeaver_RFC_Library|SAP NetWeaver RFC Library]]. More information about [[Importing_Data_from_SAP|connecting to SAP]].&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
* &#039;&#039;&#039;UseGateway&#039;&#039;&#039;: Boolean value indicating whether data extraction should be performed through the gateway (&#039;&#039;true&#039;&#039;) or by QPR ProcessAnalyzer Server (&#039;&#039;false&#039;&#039;, default value). The gateway may be needed to access on-premise systems that are not available in the public network.&lt;br /&gt;
* &#039;&#039;&#039;User&#039;&#039;&#039;: SAP username used to connect to SAP. Mandatory. Corresponds to the &amp;quot;USER&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;Password&#039;&#039;&#039;: Password for the SAP user used to connect to SAP. Mandatory. Corresponds to the &amp;quot;PASSWD&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;PasswordKey&#039;&#039;&#039;: [[Storing_Secrets_for_Scripts|Secret name]] for the stored SAP password. Alternative for the Password property.&lt;br /&gt;
* &#039;&#039;&#039;Client&#039;&#039;&#039;: The SAP backend client. Mandatory. Corresponds to the &amp;quot;CLIENT&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;AppServerHost&#039;&#039;&#039;: Hostname or IP of the specific SAP application server, to which all connections shall be opened. Mandatory if MessageServerHost is not defined. Corresponds to the &amp;quot;ASHOST&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;MessageServerHost&#039;&#039;&#039;: Hostname or IP of the SAP system’s message server (central instance). Mandatory if AppServerHost is not defined. Corresponds to the &amp;quot;MSHOST&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;SystemNumber&#039;&#039;&#039;: SAP system’s system number. Mandatory if SystemID is not defined. Corresponds to the &amp;quot;SYSNR&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;SystemID&#039;&#039;&#039;: SAP system’s three-letter system ID. Mandatory if SystemNumber is not defined. Corresponds to the &amp;quot;SYSID&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;Options&#039;&#039;&#039;: Array of clause elements. Specifies the value for parameter OPTIONS in tab &#039;Import&#039; or function module &#039;rfc_read_table&#039; in SAP. NOTE: maximum length for one string is 72 characters(SAP limit). Optional. Example: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0000017450&#039;&amp;quot;].&lt;br /&gt;
* &#039;&#039;&#039;OptionString&#039;&#039;&#039;: String of clause elements. Specifies the value for parameter OPTIONS in tab &#039;Import&#039; or function module &#039;rfc_read_table&#039; in SAP. Maximum length for one string is 72 characters(SAP limit). Over 72 character length OptionString is splited by &#039; AND &#039; and &#039; OR &#039; to several OPTIONS parameter to avoid SAP 72 characters limit. Optional. Example: &amp;quot;VBELN BETWEEN &#039;0000017448&#039; AND &#039;0000017450&#039;&amp;quot;.&lt;br /&gt;
* &#039;&#039;&#039;FieldNames&#039;&#039;&#039;: Comma separated list of field names for columns to be extracted. Default value is empty, which will extract all columns. Specifies the value for parameter FIELDNAME in tab &#039;Tables&#039; for table &#039;FIELDS&#039; for function module &#039;rfc_read_table&#039; in SAP. Optional.&lt;br /&gt;
* &#039;&#039;&#039;Function&#039;&#039;&#039;: SAP function name that is called in SAP. Optional. The default value is RFC_READ_TABLE. Another possible function name is BBP_RFC_READ_TABLE.&lt;br /&gt;
* &#039;&#039;&#039;Language&#039;&#039;&#039;: SAP language used. Default value is &amp;quot;EN&amp;quot;. Optional. Corresponds to the &amp;quot;LANG&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;Rowcount&#039;&#039;&#039;: Maximum amount of rows to fetch. Specifies the value for parameter ROWCOUNT in tab &#039;Import&#039; or function module &#039;rfc_read_table&#039; in SAP. Optional.&lt;br /&gt;
* &#039;&#039;&#039;Rowskips&#039;&#039;&#039;: Number of rows to skip. Specifies the value for parameter ROWSKIPS in tab &#039;Import&#039; or function module &#039;rfc_read_table&#039;. in SAP. Optional.&lt;br /&gt;
* &#039;&#039;&#039;PoolSize&#039;&#039;&#039;: Maximum number of RFC connections that this destination will keep in its pool. Default value is &amp;quot;5&amp;quot;. Optional. Corresponds to the &amp;quot;POOL_SIZE&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;IdleTimeout&#039;&#039;&#039;: If a connection has been idle for more than the defined time in seconds, it will be closed and removed from the connection pool. Default value is &amp;quot;600&amp;quot;. Optional. Corresponds to the &amp;quot;IDLE_TIMEOUT&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;ImportChunkSize&#039;&#039;&#039;: Specifies the size of the chunks of data used when importing the data from SAP to PA Server. The value represents the approximate maximum number of data cells in each chunk (consisting tables of &amp;lt;number of rows&amp;gt; * &amp;lt;number of columns&amp;gt; data cells. Default value is 200000. Smaller value causes big imports to be split into more chunks taking more time in total, but it makes importing of each chunk faster possibly helping in some timeout situations.&lt;br /&gt;
* &#039;&#039;&#039;Router&#039;&#039;&#039;: List of host names and service names or port numbers for the SAPRouter in the following format: /H/hostname/S/portnumber. Optional. Corresponds to the &amp;quot;SAPROUTER&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;LogonGroup&#039;&#039;&#039;: The logon group from which the message server shall select an application server. Optional. Corresponds to the &amp;quot;GROUP&amp;quot; constant in SAP.&lt;br /&gt;
* &#039;&#039;&#039;Mode&#039;&#039;&#039;: If this number is set to &amp;quot;1&amp;quot;, then the query result will have the SAP Table field names as data table column names and actual data rows as rows. If this is set to &amp;quot;3&amp;quot;, the query result will get the field descriptions from the SAP query using NO_DATA parameter, i.e. the returned columns are the following (in this order): Field, Type, Description, Length, Offset. Default value is &amp;quot;1&amp;quot;. Optional.&lt;br /&gt;
* &#039;&#039;&#039;QueryTable&#039;&#039;&#039;: Name of the SAP table to be extracted. Specifies the value for the parameter QUERY_TABLE in tab &#039;Import&#039; or function module &#039;rfc_read_table&#039; in SAP. Mandatory. Note that if the query doesn&#039;t return any data, the target data table or temporary table is not created.&lt;br /&gt;
* &#039;&#039;&#039;[[Importing_Data_from_SAP|ConvertDataTypes]]&#039;&#039;&#039;: List of SAP data types that are converted into respective expression language data types. Defined by listing the data type identifier characters in any order. Available data type identifying characters are IFPCDTNX. If not defined, all data types are converted. Example: IFP (convert only numeric data types: Integer, Float, Packed number).&lt;br /&gt;
* &#039;&#039;&#039;Ping&#039;&#039;&#039;: If this is set to true, SAP server is pinged before SAP query execution. Purpose is help troubleshooting SAP connection issues. Optional.&lt;br /&gt;
* &#039;&#039;&#039;TraceLevel&#039;&#039;&#039;: Sets SAP trace level, which can be 0, 1, 2, 3 or 4. Setting higher trace level helps to troubleshoot SAP connection issues. &#039;&#039;sapnco.dll&#039;&#039; writes log file to the current working folder with for example name&#039;nco_rfc_5484_2.trc&#039;. Optional.&lt;br /&gt;
* &#039;&#039;&#039;AliasUser&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;AppServerService&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;CharacterFaultIndicatorToken&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Codepage&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;GatewayHost&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;GatewayService&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;IdleCheckTime&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;LogonCheck&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;MaxPoolWaitTime&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;MessageServerService&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Name&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;NoCompression&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;OnCharacterConversionError&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;PartnerCharSize&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;PasswordChangeEnforced&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;ProgramId&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;R3Name&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;RegistrationCount&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;RepositoryDestination&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;RepositoryPassword&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;RepositorySncMyName&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;RepositoryUser&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;RepositoryX509Certificate&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;SapSso2Ticket&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;SncLibraryPath&#039;&#039;&#039;: Full path including file name of the SNC shared library to be used.&lt;br /&gt;
* &#039;&#039;&#039;SncMode&#039;&#039;&#039;: Determines whether connections will be secured with SNC. Value 0 doesn&#039;t use SNC (default) and value 1 uses SNC.&lt;br /&gt;
* &#039;&#039;&#039;SncMyName&#039;&#039;&#039;: Token/identifier representing the external RFC program. In most cases this can be omitted. The installed SNC solution usually knows its own SNC name. Only for solutions supporting “multiple identities”, you may Varies depending on the installed SNC solution (Secude, Kerberos, NTLM, etc). Example for Secude: p/secude:CN=ALEREMOT SAP Online Help 09.09.2014 SAP .NET Connector 3.0 41 need to specify the identity to be used for this particular destination/server. E, O=Mustermann-AG, C=DE&lt;br /&gt;
* &#039;&#039;&#039;SncPartnerName&#039;&#039;&#039;: The backend&#039;s SNCname.&lt;br /&gt;
* &#039;&#039;&#039;SncPartnerNames&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;SncQop&#039;&#039;&#039;: Quality of service to be used for SNC communication of this particular destination/server. One of the following values:&lt;br /&gt;
** 1: Digital signature&lt;br /&gt;
** 2: Digital signature and encryption&lt;br /&gt;
** 3: Digital signature, encryption, and user authentication&lt;br /&gt;
** 8: Default value defined by back-end system&lt;br /&gt;
** 9: Maximum value that the current security product supports&lt;br /&gt;
* &#039;&#039;&#039;SystemIds&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;UseSapGui&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;X509Certificate&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
An example of performing a SAP extraction and persisting the extracted data to a table having id 1. Note: Data table must exist already when using this approach.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sapConnection = #{ &lt;br /&gt;
  &amp;quot;AppServerHost&amp;quot;: &amp;quot;sap01&amp;quot;,&lt;br /&gt;
  &amp;quot;SystemNumber&amp;quot;: &amp;quot;00&amp;quot;,&lt;br /&gt;
  &amp;quot;User&amp;quot;: &amp;quot;user1&amp;quot;,&lt;br /&gt;
  &amp;quot;PasswordKey&amp;quot;: &amp;quot;SapPW1&amp;quot;,&lt;br /&gt;
  &amp;quot;Router&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
  &amp;quot;SystemID&amp;quot;: &amp;quot;QPR&amp;quot;,&lt;br /&gt;
  &amp;quot;Client&amp;quot;: &amp;quot;800&amp;quot;,&lt;br /&gt;
  &amp;quot;Language&amp;quot;: &amp;quot;EN&amp;quot;,&lt;br /&gt;
  &amp;quot;PoolSize&amp;quot;: 5,&lt;br /&gt;
  &amp;quot;PoolSizeMax&amp;quot;: 10,&lt;br /&gt;
  &amp;quot;IdleTimeout&amp;quot;: 600 &lt;br /&gt;
 };&lt;br /&gt;
let queryParameters = sapConnection.Extend(#{&lt;br /&gt;
  &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
  &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
  &amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0000017450&#039;&amp;quot;]&lt;br /&gt;
});&lt;br /&gt;
let resultsFlow = ProjectById(1)&lt;br /&gt;
  .ExtractSap(queryParameters);&lt;br /&gt;
DatatableById(1)&lt;br /&gt;
  .Import(resultsFlow);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example of performing a SAP extraction and persisting the extracted data to a table named &amp;quot;SAPData&amp;quot; in project having id 1. If the data table does not yet exist, a new one is created into Snowflake.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let sapConnection = #{ &lt;br /&gt;
  &amp;quot;AppServerHost&amp;quot;: &amp;quot;sap01&amp;quot;,&lt;br /&gt;
  &amp;quot;SystemNumber&amp;quot;: &amp;quot;10&amp;quot;,&lt;br /&gt;
  &amp;quot;User&amp;quot;: &amp;quot;exampleuser&amp;quot;,&lt;br /&gt;
  &amp;quot;Password&amp;quot;: &amp;quot;examplepassword&amp;quot;,&lt;br /&gt;
  &amp;quot;Router&amp;quot;: &amp;quot;/H/127.0.0.1/A/1234/H/&amp;quot;,&lt;br /&gt;
  &amp;quot;SystemID&amp;quot;: &amp;quot;QPR&amp;quot;,&lt;br /&gt;
  &amp;quot;Client&amp;quot;: &amp;quot;200&amp;quot;,&lt;br /&gt;
  &amp;quot;Language&amp;quot;: &amp;quot;EN&amp;quot;,&lt;br /&gt;
  &amp;quot;PoolSize&amp;quot;: 5,&lt;br /&gt;
  &amp;quot;PoolSizeMax&amp;quot;: 10,&lt;br /&gt;
  &amp;quot;IdleTimeout&amp;quot;: 600,&lt;br /&gt;
  &amp;quot;AppServerHost&amp;quot;: &amp;quot;127.0.0.1&amp;quot;,&lt;br /&gt;
  &amp;quot;LogonGroup&amp;quot;: &amp;quot;GROUPXNAME&amp;quot;,&lt;br /&gt;
  &amp;quot;Mode&amp;quot;: &amp;quot;1&amp;quot;&lt;br /&gt;
 };&lt;br /&gt;
let queryParameters = sapConnection.Extend(#{&lt;br /&gt;
  &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
  &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
  &amp;quot;Options&amp;quot;: [&amp;quot;VBELN EQ &#039;0060000039&#039;&amp;quot;, &amp;quot;OR VBELN EQ &#039;0060000040&#039;&amp;quot;]&lt;br /&gt;
});&lt;br /&gt;
ProjectById(1)&lt;br /&gt;
  .ExtractSap(queryParameters)&lt;br /&gt;
  .Persist(&amp;quot;SAPData&amp;quot;, #{&amp;quot;ProjectId&amp;quot;: 1, &amp;quot;Connection&amp;quot;: CreateSnowflakeConnection(#{&amp;quot;ProjectId&amp;quot;: 1})})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example of performing a SAP extract, a simple transformation for extracted data and import to a table named &amp;quot;TransformedSAPData&amp;quot; in project having id 1.&lt;br /&gt;
&lt;br /&gt;
Note: The example uses ScriptLauncher as a gateway and works only with ScriptLauncher version 2023.1 or later with &amp;quot;UseLegacyClientSideImport&amp;quot;-setting in appsettings.json set to false (if the setting is available in the used version).&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ExtractTransformAndLoad(extractFunc, transformFunc, loadFunc)&lt;br /&gt;
{&lt;br /&gt;
  let rawDataFlow = extractFunc();&lt;br /&gt;
  let transformedDataFlow = ToDataFlow();&lt;br /&gt;
&lt;br /&gt;
  _system.Parallel.Run([&lt;br /&gt;
    () =&amp;gt; Catch({&lt;br /&gt;
      let df;&lt;br /&gt;
      while (!IsNullTop(df = rawDataFlow.Collect(#{&amp;quot;CollectChunk&amp;quot;: true}))) {&lt;br /&gt;
        transformedDataFlow.Append(transformFunc(df));&lt;br /&gt;
        WriteLog(`A chunk having ${df.NRows} rows has been transformed.`);&lt;br /&gt;
      }&lt;br /&gt;
      if (rawDataFlow.HasError) {&lt;br /&gt;
        transformedDataFlow.Fail(&amp;quot;Error occurred during data extraction.&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        transformedDataFlow.Complete();&lt;br /&gt;
      }&lt;br /&gt;
    }, {&lt;br /&gt;
      transformedDataFlow.Fail(&amp;quot;Error occurred during transformation calculation.&amp;quot;);&lt;br /&gt;
    }),&lt;br /&gt;
    () =&amp;gt; {&lt;br /&gt;
      loadFunc(transformedDataFlow);&lt;br /&gt;
    }&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let sapConnection = #{ &lt;br /&gt;
  &amp;quot;AppServerHost&amp;quot;: &amp;quot;sap01&amp;quot;,&lt;br /&gt;
  &amp;quot;SystemNumber&amp;quot;: &amp;quot;10&amp;quot;,&lt;br /&gt;
  &amp;quot;User&amp;quot;: &amp;quot;exampleuser&amp;quot;,&lt;br /&gt;
  &amp;quot;Password&amp;quot;: &amp;quot;examplepassword&amp;quot;,&lt;br /&gt;
  &amp;quot;Router&amp;quot;: &amp;quot;/H/127.0.0.1/A/1234/H/&amp;quot;,&lt;br /&gt;
  &amp;quot;SystemID&amp;quot;: &amp;quot;QPR&amp;quot;,&lt;br /&gt;
  &amp;quot;Client&amp;quot;: &amp;quot;200&amp;quot;,&lt;br /&gt;
  &amp;quot;Language&amp;quot;: &amp;quot;EN&amp;quot;,&lt;br /&gt;
  &amp;quot;PoolSize&amp;quot;: 5,&lt;br /&gt;
  &amp;quot;PoolSizeMax&amp;quot;: 10,&lt;br /&gt;
  &amp;quot;IdleTimeout&amp;quot;: 600,&lt;br /&gt;
  &amp;quot;AppServerHost&amp;quot;: &amp;quot;127.0.0.1&amp;quot;,&lt;br /&gt;
  &amp;quot;LogonGroup&amp;quot;: &amp;quot;GROUPXNAME&amp;quot;,&lt;br /&gt;
  &amp;quot;Mode&amp;quot;: &amp;quot;1&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
ExtractTransformAndLoad(&lt;br /&gt;
  () =&amp;gt; ExtractSap(&lt;br /&gt;
    sapConnection.Extend([&lt;br /&gt;
      &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
      &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
      &amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0060000042&#039;&amp;quot;],&lt;br /&gt;
      &amp;quot;UseGateway&amp;quot;: true&lt;br /&gt;
    ])&lt;br /&gt;
  ),&lt;br /&gt;
  df =&amp;gt; df.SetColumns([&amp;quot;Test&amp;quot;: () =&amp;gt; `${Column(&amp;quot;NETWR&amp;quot;)} ${Column(&amp;quot;WAERK&amp;quot;)}`]),&lt;br /&gt;
  dataFlow =&amp;gt; {&lt;br /&gt;
    dataFlow.Persist(&amp;quot;TransformedSAPData&amp;quot;, [&amp;quot;ProjectName&amp;quot;: &amp;quot;TestData&amp;quot;, &amp;quot;Append&amp;quot;: 0]);&lt;br /&gt;
  }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=Object-centric_Process_Mining_Model&amp;diff=25552</id>
		<title>Object-centric Process Mining Model</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=Object-centric_Process_Mining_Model&amp;diff=25552"/>
		<updated>2024-12-10T09:02:27Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Object-centric model structure */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;QPR ProcessAnalyzer supports object-centric process mining (OCPM) based on the OCEL 2.0 standard (https://www.ocel-standard.org). To use object-centric functionality, you need to transform data into the [[#Object-centric_model_structure|suitable format]] for the [[#Create_object-centric_model|object-centric model]]. Object-centric models can be analyzed in the object-centric flowchart and with (case-centric) charts because the object-centric model can be converted into a case-centric eventlog using [[#Object-centric_perspectives|perspectives]]. To use the OCPM functionality, Snowflake needs to be used as the calculation engine.&lt;br /&gt;
&lt;br /&gt;
== Create object-centric model ==&lt;br /&gt;
Create a new object-centric model as follows:&lt;br /&gt;
# In the Workspace, open the project where to create the model.&lt;br /&gt;
# Select &#039;&#039;&#039;NEW&amp;quot;&#039;&#039;&#039; in the top right menu and select &#039;&#039;&#039;model&#039;&#039;&#039;.&lt;br /&gt;
# Define a name for the new model.&lt;br /&gt;
# Set &#039;&#039;&#039;Model type&#039;&#039;&#039; as &#039;&#039;&#039;Object-centric&#039;&#039;&#039;.&lt;br /&gt;
# Click &#039;&#039;&#039;Create&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Configure object-centric model datatables ==&lt;br /&gt;
Datatables for the object-centric model need to exist in the same project as the model. Datatables can be set for the model as follows:&lt;br /&gt;
# In the Workspace, select the object-centric model and click &#039;&#039;&#039;Properties&#039;&#039;&#039;.&lt;br /&gt;
# In the model properties dialog, open the &#039;&#039;&#039;Datasource&#039;&#039;&#039; tab.&lt;br /&gt;
# Add a following kind of json configuration to the textbox:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Objects&amp;quot;: &amp;quot;OCPM: objects&amp;quot;,&lt;br /&gt;
  &amp;quot;Events&amp;quot;: &amp;quot;OCPM: events&amp;quot;,&lt;br /&gt;
  &amp;quot;ObjectToObject&amp;quot;: &amp;quot;OCPM: object-object&amp;quot;,&lt;br /&gt;
  &amp;quot;EventToObject&amp;quot;: &amp;quot;OCPM: event-object&amp;quot;,&lt;br /&gt;
  &amp;quot;ObjectTypes&amp;quot;: {&lt;br /&gt;
    &amp;quot;Invoice&amp;quot;: &amp;quot;OCPM object: Invoice&amp;quot;,&lt;br /&gt;
    &amp;quot;Payment&amp;quot;: &amp;quot;OCPM object: Payment&amp;quot;,&lt;br /&gt;
    &amp;quot;Purchase Order&amp;quot;: &amp;quot;OCPM object: Purchase Order&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;EventTypes&amp;quot;: { &lt;br /&gt;
    &amp;quot;Approve Purchase Requisition&amp;quot;: &amp;quot;OCPM event: Approve Purchase Requisition&amp;quot;,&lt;br /&gt;
    &amp;quot;Change PO Quantity&amp;quot;: &amp;quot;OCPM event: Change PO Quantity&amp;quot;,&lt;br /&gt;
    &amp;quot;Create Purchase Order&amp;quot;: &amp;quot;OCPM event: Create Purchase Order&amp;quot;,&lt;br /&gt;
    &amp;quot;Insert Invoice&amp;quot;: &amp;quot;OCPM event: Insert Invoice&amp;quot;,&lt;br /&gt;
    &amp;quot;Insert Payment&amp;quot;: &amp;quot;OCPM event: Insert Payment&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The json configuration needs to have following properties:&lt;br /&gt;
* &#039;&#039;&#039;Objects&#039;&#039;&#039;: Objects datatable name.&lt;br /&gt;
* &#039;&#039;&#039;Events&#039;&#039;&#039;: Events datatable name.&lt;br /&gt;
* &#039;&#039;&#039;ObjectToObject&#039;&#039;&#039;: Object-to-object relation datatable name.&lt;br /&gt;
* &#039;&#039;&#039;EventToObject&#039;&#039;&#039;: Event-to-object relation datatable name.&lt;br /&gt;
* &#039;&#039;&#039;ObjectTypes&#039;&#039;&#039;: Key-value-pairs of object type datatable names. Note that object names need to match with object names in the objects datatable.&lt;br /&gt;
* &#039;&#039;&#039;EventTypes&#039;&#039;&#039;: Key-value-pairs of event type datatable names. Note that event names need to match with event names in the events datatable.&lt;br /&gt;
&lt;br /&gt;
== Import from OCEL 2.0 JSON file ==&lt;br /&gt;
Object-centric model can be import from an OCEL 2.0 JSON file as follows:&lt;br /&gt;
# In the Workspace, open the project where to import the model.&lt;br /&gt;
# Select &#039;&#039;&#039;NEW&#039;&#039;&#039; in top right menu and select &#039;&#039;&#039;Import Model&#039;&#039;&#039;.&lt;br /&gt;
# Select the OCEL 2.0 JSON file from the disk and click &#039;&#039;&#039;Open&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
An object-centric model and a list of datatables is created.&lt;br /&gt;
&lt;br /&gt;
Example OCEL 2.0 eventlogs: https://www.ocel-standard.org/event-logs/overview/ (download the json format supported by QPR ProcessAnalyzer)&lt;br /&gt;
&lt;br /&gt;
== Object-centric model structure ==&lt;br /&gt;
Object-centric model contains datatables described in the table below. Datatables can be named freely, as the model json configuration is used to define the datatable for each type of data. The datatables need to use column names specified in the table below because those are the column names assumed by the object-centric (i.e., column names cannot be selected freely).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Datatable role&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Contained data&#039;&#039;&#039;&lt;br /&gt;
! &#039;&#039;&#039;Datatable columns&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
||Objects&lt;br /&gt;
||Objects in the model (one row per object).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectId&#039;&#039;&#039;: Unique id for the object (among all objects in the model).&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectType&#039;&#039;&#039;: Object type name (such as Order, Invoice, Delivery). Note that the model json configuration need to use same object type names.&lt;br /&gt;
|-&lt;br /&gt;
||Events&lt;br /&gt;
||Events in the model (one row per event).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelEventId&#039;&#039;&#039;: Unique id for the event (among all events in the model).&lt;br /&gt;
* &#039;&#039;&#039;OcelEventType&#039;&#039;&#039;: Event type name (such as Order created, Invoice sent). Note that the model json configuration need to use same event type names.&lt;br /&gt;
* &#039;&#039;&#039;OcelEventTime&#039;&#039;&#039;: Event timestamp.&lt;br /&gt;
|-&lt;br /&gt;
||Object-object relations&lt;br /&gt;
||Relations between objects (one row per relation).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectToObjectSourceId&#039;&#039;&#039;: Source object id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectToObjectTargetId&#039;&#039;&#039;: Target object id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectToObjectQualifier&#039;&#039;&#039;: Describes the type of the relation as free-form text (not used currently).&lt;br /&gt;
|-&lt;br /&gt;
||Event-object relations&lt;br /&gt;
||Relations between events and objects (one row per relation).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelEventToObjectSourceId&#039;&#039;&#039;: Object id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelEventToObjectTargetId&#039;&#039;&#039;: Event id in the relation.&lt;br /&gt;
* &#039;&#039;&#039;OcelEventToObjectQualifier&#039;&#039;&#039;: Describes the type of the relation as free-form text (not used currently).&lt;br /&gt;
|-&lt;br /&gt;
||Object attributes (several datatables)&lt;br /&gt;
||Object attribute values, each object type in a separate table (one row per object).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectTypeObjectId&#039;&#039;&#039;: Object id. Matches to the objects datatable &#039;&#039;OcelObjectId&#039;&#039; column.&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectTypeTime&#039;&#039;&#039;: Timestamp which the attribute value is valid from (not used currently).&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectTypeChangedField&#039;&#039;&#039;: Changed object attribute name (not used currently).&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;Object attributes&amp;gt;&#039;&#039;&#039;: Columns for each of the object attribute values (column name is the object attribute name).&lt;br /&gt;
|-&lt;br /&gt;
||Event attributes (several datatables)&lt;br /&gt;
||Event attribute values, each event type in a separate table (one row per event).&lt;br /&gt;
||&lt;br /&gt;
* &#039;&#039;&#039;OcelEventTypeEventId&#039;&#039;&#039;: Event id. Matches to the events datatable &#039;&#039;OcelEventId&#039;&#039; column.&lt;br /&gt;
* &#039;&#039;&#039;&amp;lt;Event attributes&amp;gt;&#039;&#039;&#039;: Columns for each of the event attribute values (column name is the event attribute name).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Object-centric perspectives ==&lt;br /&gt;
Perspectives convert an object-centric model into the traditional case-centric eventlog, allowing to view and analyze object-centric models in analyses provided by charts. A single perspective is not able describe the object-centric model entirely, but just from a certain limited viewpoint. By using analyses with several perspectives, it&#039;s possible to get a more complete picture of the object-centric model. The perspective starts from a certain object type and traverses the object-object relations as many steps as desired.&lt;br /&gt;
&lt;br /&gt;
To define a perspective, the following settings are defined in the chart settings:&lt;br /&gt;
* &#039;&#039;&#039;Base Object type&#039;&#039;&#039;: Object of this type will be cases in the projected case-centric eventlog.&lt;br /&gt;
* &#039;&#039;&#039;Object Relation Steps&#039;&#039;&#039;: Specifies how many object-object relations will be traversed in order to find events connected to the base objects. Value zero means that only those events are returned that are directly connected to the base objects.&lt;br /&gt;
* &#039;&#039;&#039;Show Event Types&#039;&#039;&#039;: List of event type names which are included into the perspective eventlog. If no events are explicitly defined, all events will be included, but their event attributes are not included.&lt;br /&gt;
&lt;br /&gt;
The resulting perspective eventlog will have the following columns:&lt;br /&gt;
* &#039;&#039;&#039;OcelObjectId&#039;&#039;&#039; (mapped to case id)&lt;br /&gt;
* &#039;&#039;&#039;OcelEventType&#039;&#039;&#039; (mapped to event type)&lt;br /&gt;
* &#039;&#039;&#039;OcelEventTime&#039;&#039;&#039; (mapped to timestamp)&lt;br /&gt;
* &#039;&#039;&#039;OcelEventId&#039;&#039;&#039;&lt;br /&gt;
* Object attributes of the base object type. Note that the object attribute values are &amp;quot;repeated&amp;quot; for all events belonging to the same object.&lt;br /&gt;
* Event attributes of the selected event types. Values are null for events that don&#039;t have the attribute.&lt;br /&gt;
&lt;br /&gt;
The base object type attributes are available as case attributes. As the object attribute values may change over time in the OCEL 2.0 data, the last attribute value is used as the case attribute value. Note that other object type&#039;s attributes are not available as case attributes, so the object for which the attributes are used, need to be set as the base object.&lt;br /&gt;
&lt;br /&gt;
== Save perspective to filter ==&lt;br /&gt;
It&#039;s possible to include the object-centric perspective to a stored filter. When a filter is selected, also the perspective in the filter is applied to the dashboard. This allows to quickly change perspectives for the entire dashboard. The chart specific perspective overrides the dashboard level perspective, so the dashboard level perspective is only applied for charts that don&#039;t have the chart specific perspective defined.&lt;br /&gt;
&lt;br /&gt;
Perspective can be added to a filter as follows:&lt;br /&gt;
# Go to the &#039;&#039;Process Discovery&#039;&#039; dashboard.&lt;br /&gt;
# Open the &#039;&#039;Session variables&#039;&#039; dialog in the dots menu on top right.&lt;br /&gt;
# Paste the filter json to the &#039;&#039;Value&#039;&#039; of the &#039;&#039;Filter&#039;&#039; variable (it might be easiest to start with a filter without filter rules, and then add the filter rules using the UI).&lt;br /&gt;
# Click &#039;&#039;Done&#039;&#039; button for the dialog.&lt;br /&gt;
# Save the filter by hovering the &#039;&#039;Unsaved filter&#039;&#039; (filters dropdown list) in the header and click &#039;&#039;Save as new filter&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Example: Filter json without any filter rules:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Items&amp;quot;: [],&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: {&lt;br /&gt;
    &amp;quot;ObjectType&amp;quot;: &amp;quot;Container&amp;quot;,&lt;br /&gt;
    &amp;quot;RecursionDepth&amp;quot;: 0&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: Filter json with a filter rule:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Items&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;Type&amp;quot;: &amp;quot;IncludeEvents&amp;quot;,&lt;br /&gt;
      &amp;quot;Items&amp;quot;: [&lt;br /&gt;
        {&lt;br /&gt;
          &amp;quot;Type&amp;quot;: &amp;quot;Attribute&amp;quot;,&lt;br /&gt;
          &amp;quot;Attribute&amp;quot;: &amp;quot;OcelEventId&amp;quot;,&lt;br /&gt;
          &amp;quot;StringifiedValues&amp;quot;: [ &amp;quot;0Event 1&amp;quot; ]&lt;br /&gt;
        }&lt;br /&gt;
      ]&lt;br /&gt;
    }&lt;br /&gt;
  ],&lt;br /&gt;
  &amp;quot;Perspective&amp;quot;: {&lt;br /&gt;
    &amp;quot;ObjectType&amp;quot;:  &amp;quot;Container&amp;quot;,&lt;br /&gt;
    &amp;quot;RecursionDepth&amp;quot;: 0&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Differences to OCEL 2.0 standard ==&lt;br /&gt;
Object-centric models in QPR ProcessAnalyzer are mainly following the OCEL 2.0 standard, but there are the following differences:&lt;br /&gt;
* Changing of object attributes values over time is not supported.&lt;br /&gt;
* &#039;&#039;ocel_time&#039;&#039; field of each event type table is moved to events datatable (as every event has a timestemp). &lt;br /&gt;
* &#039;&#039;*_map_type&#039;&#039; columns are not needed as the model settings are used for the same purpose. &lt;br /&gt;
* Object type tables: If OcelObjectTypeChangedField is not null, all the other field values are copied from the previous entry except: &lt;br /&gt;
** &#039;&#039;OcelObjectTypeChangedField&#039;&#039; which has the names of the changed fields as a comma separated string. &lt;br /&gt;
** The actual changed field which has the new value. &lt;br /&gt;
** &#039;&#039;OcelObjectTypeTime&#039;&#039; which has the timestamp when the value changed.&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25481</id>
		<title>System Library</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25481"/>
		<updated>2024-11-20T10:24:06Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Example */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;System library is a collection of Expression Language functions and properties that provide additional tools for scripting Process Analyzer functionalities. System library is referenced in scripts via &#039;&#039;_system&#039;&#039;-property, which provides additional properties dedicated for different areas of interests for scripting. &lt;br /&gt;
&lt;br /&gt;
==ML.GeneratePredictionModel==&lt;br /&gt;
Documentation [[Create_Predicted_Eventlog#Create_prediction_script_in_QPR_ProcessAnalyzer|here]].&lt;br /&gt;
&lt;br /&gt;
==ML.ApplyTransformations==&lt;br /&gt;
Documentation [[Create_Simulated_Eventlog#Create_simulation_script_in_QPR_ProcessAnalyzer|here]].&lt;br /&gt;
&lt;br /&gt;
==Parallel.Run==&lt;br /&gt;
Runs given functions in parallel.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;functions&#039;&#039;&#039;:&lt;br /&gt;
**An array of functions to run in parallel.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
An array of results returned by the called functions, in the same order as the function generating them in the &#039;&#039;functions&#039;&#039;-parameter.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
The following script uses _system.Parallel.Run to run three functions:&lt;br /&gt;
&lt;br /&gt;
* SAP-extraction from VBAK-table in SAP (connection parameters defined in connectionParametersDict-dictionary).&lt;br /&gt;
*Transform the extracted data by adding a new column.&lt;br /&gt;
*Load the data into data table identified by dataTableId.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ExtractTransformAndLoad(extractFunc, transformFunc, loadFunc)&lt;br /&gt;
{&lt;br /&gt;
  let rawDataFlow = extractFunc();&lt;br /&gt;
  let transformedDataFlow = ToDataFlow();&lt;br /&gt;
&lt;br /&gt;
  _system.Parallel.Run([&lt;br /&gt;
    () =&amp;gt; Catch({&lt;br /&gt;
      let df;&lt;br /&gt;
      while (!IsNullTop(df = rawDataFlow.Collect(#{&amp;quot;CollectChunk&amp;quot;: true}))) {&lt;br /&gt;
        transformedDataFlow.Append(transformFunc(df));&lt;br /&gt;
        WriteLog(`A chunk having ${df.NRows} rows has been transformed.`);&lt;br /&gt;
      }&lt;br /&gt;
      if (rawDataFlow.HasError) {&lt;br /&gt;
        transformedDataFlow.Fail(&amp;quot;Error occurred during data extraction.&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        transformedDataFlow.Complete();&lt;br /&gt;
      }&lt;br /&gt;
    }, {&lt;br /&gt;
      transformedDataFlow.Fail(&amp;quot;Error occurred during transformation calculation.&amp;quot;);&lt;br /&gt;
    }),&lt;br /&gt;
    () =&amp;gt; {&lt;br /&gt;
      loadFunc(transformedDataFlow);&lt;br /&gt;
    }&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ExtractTransformAndLoad(&lt;br /&gt;
  () =&amp;gt; ExtractSap(connectionParametersDict.Extend(&lt;br /&gt;
    [&lt;br /&gt;
    &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
    &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
	&amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0060000042&#039;&amp;quot;],&lt;br /&gt;
    &amp;quot;UseGateway&amp;quot;: true&lt;br /&gt;
    ])&lt;br /&gt;
  ),&lt;br /&gt;
  df =&amp;gt; df.SetColumns([&amp;quot;Test&amp;quot;: () =&amp;gt; `${Column(&amp;quot;NETWR&amp;quot;)} ${Column(&amp;quot;WAERK&amp;quot;)}`]),&lt;br /&gt;
  dataFlow =&amp;gt; {&lt;br /&gt;
    DataTableById(dataTableId).Import(dataFlow, [&amp;quot;Append&amp;quot;: 0]);&lt;br /&gt;
  }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
DataTableById(dataTableId).SqlDataFrame.OrderByColumns([&amp;quot;VBELN&amp;quot;], [true]).Collect()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==RootCauses.FindForDataFrame==&lt;br /&gt;
Finds root causes for a particular process phenomenon by comparing properties of selected cases against those of all cases in given model.&lt;br /&gt;
&lt;br /&gt;
Based on the similar in-memory function: [[FindRootCauses Function|EventLog.FindRootCauses]].&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;model&#039;&#039;&#039;:&lt;br /&gt;
**Model object of a model whose data tables are used to calculate the root causes.&lt;br /&gt;
*&#039;&#039;&#039;parameters&#039;&#039;&#039;:&lt;br /&gt;
**A parameter convertible to a StringDictionary object with the following supported key-values: &lt;br /&gt;
*** &#039;&#039;&#039;Filter&#039;&#039;&#039;: &lt;br /&gt;
****Filter json (#30921#) that is applied to the event log before calculating root causes.&lt;br /&gt;
*** &#039;&#039;&#039;Selection&#039;&#039;&#039;: &lt;br /&gt;
**** Selection json (#30927#) that defines selected cases to find root causes for. Is applied on top of the filtered event log (specified by Filter parameter).&lt;br /&gt;
****If nothing is selected, 100% of cases are counted as selected.&lt;br /&gt;
*** &#039;&#039;&#039;CaseAttributeTypes&#039;&#039;&#039;: &lt;br /&gt;
****An array of strings with the names of case attributes included into the root causes.&lt;br /&gt;
****Only case attributes of type string, integer or boolean are included.&lt;br /&gt;
****If CaseAttributeTypes is null or empty string or not specified, all case attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If the model doesn&#039;t have a case attribute with the specified name, an error message is shown.&lt;br /&gt;
*** &#039;&#039;&#039;EventAttributeTypes&#039;&#039;&#039;:&lt;br /&gt;
****An array of strings with the names of event attributes included into the root causes.&lt;br /&gt;
****Only event attributes of type string are included.&lt;br /&gt;
****If EventAttributeTypes is null, all event attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If EventAttributeTypes is empty array or not specified, event attributes not applied to the root causes.&lt;br /&gt;
****If the model doesn&#039;t have an event attribute with the specified name, an error message is shown.&lt;br /&gt;
****Analysis column Name should contain Event Attribute Name.&lt;br /&gt;
****Analysis column Value should contain Event Attribute Value and number of occurrences in case: &amp;lt;value&amp;gt; (count).&lt;br /&gt;
****Analysis column Type should have &amp;quot;EventAttributeValue&amp;quot; as its value.&lt;br /&gt;
***&#039;&#039;&#039;WeightingExpression&#039;&#039;&#039;: &lt;br /&gt;
**** Expression providing weights for each case.&lt;br /&gt;
****Expression, if defined, must be any of the following types:&lt;br /&gt;
***** A string containing the [[SQL Expressions|SqlExpression]] to evaluate.&lt;br /&gt;
*****A SqlExpression object, created e.g., using ToSqlExpression-function (see also: [[QPR_ProcessAnalyzer_Expressions#In-memory_expression_blocks_in_SQL_expressions|In-memory expression blocks in SQL expressions]]).&lt;br /&gt;
****A row is filtered out of result if expression result is null.&lt;br /&gt;
***&#039;&#039;&#039;MaximumRowCount&#039;&#039;&#039;:&lt;br /&gt;
****The maximum number of the most and least contributing root causes to return. Thus, the actual number of returned rows can be at most two times this value (if specified).&lt;br /&gt;
****If undefined, 200 is used.&lt;br /&gt;
**** If set to 0, all rows are returned.&lt;br /&gt;
***&#039;&#039;&#039;MinValueUsage&#039;&#039;&#039;: &lt;br /&gt;
****The minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
*****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;MaxNumUniqueValues&#039;&#039;&#039;: &lt;br /&gt;
****Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;IncludeOthers&#039;&#039;&#039;:  &lt;br /&gt;
****Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
****If given any string value, that value is used as the value for all the aggregated values.&lt;br /&gt;
****Default = undefined =&amp;gt; others values will not be included into the results.&lt;br /&gt;
***&#039;&#039;&#039;ValueIfNull&#039;&#039;&#039;: Value used to indicate null-values.&lt;br /&gt;
****Default = &amp;quot;(blank)&amp;quot;.&lt;br /&gt;
****Must be not null.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] object with the following columns:&lt;br /&gt;
&lt;br /&gt;
*Common columns:&lt;br /&gt;
**&#039;&#039;&#039;Type&#039;&#039;&#039;: &lt;br /&gt;
***Type of the root cause:&lt;br /&gt;
****&amp;quot;CaseAttributeValue&amp;quot; for case attributes.&lt;br /&gt;
****&amp;quot;EventAttributeValue&amp;quot; for event attributes.&lt;br /&gt;
**&#039;&#039;&#039;Name&#039;&#039;&#039;:  &lt;br /&gt;
***When type is CaseAttributeValue, case attribute name.&lt;br /&gt;
***When type is EventAttributeValue, event attribute name.&lt;br /&gt;
**&#039;&#039;&#039;Value&#039;&#039;&#039;: &lt;br /&gt;
***When type is CaseAttributeValue, case attribute value.&lt;br /&gt;
***When type is EventAttributeValue, event attribute value and number of occurrences in case.&lt;br /&gt;
** &#039;&#039;&#039;Total&#039;&#039;&#039;:  &lt;br /&gt;
***The total number of cases having the found root cause.&lt;br /&gt;
** &#039;&#039;&#039;Selected&#039;&#039;&#039;: &lt;br /&gt;
***The number of cases that have the found root cause and belong to the selected cases.&lt;br /&gt;
**&#039;&#039;&#039;Compared&#039;&#039;&#039;:&lt;br /&gt;
*** The number of cases that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
**Columns when WeightingExpression not have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The number of cases which contribute to the deviation from the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of cases which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected cases with the found root cause and the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of selected cases that have the found root cause out of all cases with that root cause.&lt;br /&gt;
**Columns when WeightingExpression have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The sum of case weights which contribute to the deviation from the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of case weights which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected case weights with the found root cause and the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of selected case weights that have the found root cause out of all case weights with that root cause.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;ComparedWeight&#039;&#039;&#039;: &lt;br /&gt;
****The sum of weights that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;TotalWeight&#039;&#039;&#039;:&lt;br /&gt;
****The sum of weights of all cases with that root cause.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Calculate root cause analysis for given model using parameters read from the [[Web API: Expression/query|query configuration]].&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;ProcessingMethod&amp;quot;: &amp;quot;DataFrame&amp;quot;,&lt;br /&gt;
  &amp;quot;Root&amp;quot;:&amp;quot;let m = _; _system.RootCauses.FindForDataFrame(m, _query.Configuration.Parameters.FindRootCausesParameters.Clone().Extend(#{\&amp;quot;Filter\&amp;quot;: _query.Configuration.Filter}))&amp;quot;,&lt;br /&gt;
  &amp;quot;Parameters&amp;quot;: {&lt;br /&gt;
    &amp;quot;FindRootCausesParameters&amp;quot;: {&lt;br /&gt;
      &amp;quot;CaseAttributeTypes&amp;quot;: [&amp;quot;Account Manager&amp;quot;,&amp;quot;Customer Group&amp;quot;,&amp;quot;Product Group&amp;quot;,&amp;quot;Region&amp;quot;],&lt;br /&gt;
      &amp;quot;Selection&amp;quot;: {&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]}]}]},&lt;br /&gt;
      &amp;quot;MaxNumUniqueValues&amp;quot;: 2,&lt;br /&gt;
      &amp;quot;MaximumRowCount&amp;quot;: 1000,&lt;br /&gt;
      &amp;quot;MinValueUsage&amp;quot;: 0.20,&lt;br /&gt;
      &amp;quot;WeightingExpression&amp;quot;: &amp;quot;Column(\&amp;quot;Cost\&amp;quot;)&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;Ordering&amp;quot;: [&lt;br /&gt;
    {&amp;quot;Name&amp;quot;: &amp;quot;Contribution&amp;quot;, &amp;quot;Direction&amp;quot;: &amp;quot;Descending&amp;quot;}&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.GetSampledEvents==&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied. &lt;br /&gt;
&lt;br /&gt;
===Parameters ===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;sourceModel&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;sampledCaseCount&#039;&#039;&#039;:&lt;br /&gt;
**The maximum number of cases to return (or null if all cases should be returned).&lt;br /&gt;
* &#039;&#039;&#039;filter&#039;&#039;&#039;:&lt;br /&gt;
**JSON filter to be applied on the event data of the source model prior to performing the sampling.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
[[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Get a sample of all the events of at most 1000 cases having &amp;quot;Hats&amp;quot; as &amp;quot;Product Group&amp;quot; case attribute value from model identified by modelId.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let eventDataSampleSdf = _system.Utils.GetSampledEvents(modelId, 1000, #{&lt;br /&gt;
  &amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
    &amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
	&amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
	  &amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
	  &amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&lt;br /&gt;
	  &amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]&lt;br /&gt;
	}]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.ModifyColumnTypes==&lt;br /&gt;
In-place modifies the column types of given columns in given data table.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;dataTable&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;columnTypesToSet&#039;&#039;&#039;:&lt;br /&gt;
**Array of column name/type definitions to set.&lt;br /&gt;
***Only columns that are to be changed are required to be listed.&lt;br /&gt;
***Columns that don&#039;t exist in the data table will be skipped.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
Returns the modified data table object.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Modify column &amp;quot;CaseId&amp;quot; to be of type string.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.ModifyColumnTypes(&lt;br /&gt;
  eventsTable, &lt;br /&gt;
  [#{&amp;quot;Name&amp;quot;: &amp;quot;CaseId&amp;quot;, &amp;quot;DataType&amp;quot;: &amp;quot;String&amp;quot;}]&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.RunFunctionWithParallelLogging==&lt;br /&gt;
Runs given function that generates logging information into given data table in a way that all the logging will be included into the generated script run log as well (if run inside a script).&lt;br /&gt;
&lt;br /&gt;
Internally polls the table every 5 seconds for new rows and adds all the newly added rows to script log.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;logTable&#039;&#039;&#039;:&lt;br /&gt;
**A DataTable used for logging.&lt;br /&gt;
*&#039;&#039;&#039;callbackFunc&#039;&#039;&#039;:&lt;br /&gt;
** Function that uses given data table for logging it&#039;s current status.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
The result of the callback function.&lt;br /&gt;
&lt;br /&gt;
=== Example===&lt;br /&gt;
Run stored procedure named &amp;quot;StoredProcedureTest&amp;quot; in Snowflake, that generates new rows to log table identified by logTableId and log the generated rows into the script run log.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.RunFunctionWithParallelLogging(DataTableById(logTableId), () =&amp;gt; {&lt;br /&gt;
    CreateSnowflakeConnection().CallStoredProcedure(&amp;quot;StoredProcedureTest&amp;quot;, #{})&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25480</id>
		<title>System Library</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25480"/>
		<updated>2024-11-19T13:53:09Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;System library is a collection of Expression Language functions and properties that provide additional tools for scripting Process Analyzer functionalities. System library is referenced in scripts via &#039;&#039;_system&#039;&#039;-property, which provides additional properties dedicated for different areas of interests for scripting. &lt;br /&gt;
&lt;br /&gt;
==ML.GeneratePredictionModel==&lt;br /&gt;
Documentation [[Create_Predicted_Eventlog#Create_prediction_script_in_QPR_ProcessAnalyzer|here]].&lt;br /&gt;
&lt;br /&gt;
==ML.ApplyTransformations==&lt;br /&gt;
Documentation [[Create_Simulated_Eventlog#Create_simulation_script_in_QPR_ProcessAnalyzer|here]].&lt;br /&gt;
&lt;br /&gt;
==Parallel.Run==&lt;br /&gt;
Runs given functions in parallel.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;functions&#039;&#039;&#039;:&lt;br /&gt;
**An array of functions to run in parallel.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
An array of results returned by the called functions, in the same order as the function generating them in the &#039;&#039;functions&#039;&#039;-parameter.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
The following script uses _system.Parallel.Run to run three functions:&lt;br /&gt;
&lt;br /&gt;
* SAP-extraction from VBAK-table in SAP (connection parameters defined in connectionParametersDict-dictionary).&lt;br /&gt;
*Transform the extracted data by adding a new column.&lt;br /&gt;
*Load the data into data table identified by dataTableId.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ExtractTransformAndLoad(extractFunc, transformFunc, loadFunc)&lt;br /&gt;
{&lt;br /&gt;
  let rawDataFlow = extractFunc();&lt;br /&gt;
  let transformedDataFlow = ToDataFlow();&lt;br /&gt;
&lt;br /&gt;
  _system.Parallel.Run([&lt;br /&gt;
    () =&amp;gt; Catch({&lt;br /&gt;
      let df;&lt;br /&gt;
      while (!IsNullTop(df = rawDataFlow.Collect(#{&amp;quot;CollectChunk&amp;quot;: true}))) {&lt;br /&gt;
        transformedDataFlow.Append(transformFunc(df));&lt;br /&gt;
        WriteLog(`A chunk having ${df.NRows} rows has been transformed.`);&lt;br /&gt;
      }&lt;br /&gt;
      if (rawDataFlow.HasError) {&lt;br /&gt;
        transformedDataFlow.Fail(&amp;quot;Error occurred during data extraction.&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        transformedDataFlow.Complete();&lt;br /&gt;
      }&lt;br /&gt;
    }, {&lt;br /&gt;
      transformedDataFlow.Fail(&amp;quot;Error occurred during transformation calculation.&amp;quot;);&lt;br /&gt;
    }),&lt;br /&gt;
    () =&amp;gt; {&lt;br /&gt;
      loadFunc(transformedDataFlow);&lt;br /&gt;
    }&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ExtractTransformAndLoad(&lt;br /&gt;
  () =&amp;gt; ExtractSap(connectionParametersDict.Extend(&lt;br /&gt;
    [&lt;br /&gt;
    &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
    &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
	&amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0060000042&#039;&amp;quot;],&lt;br /&gt;
    &amp;quot;UseGateway&amp;quot;: true&lt;br /&gt;
    ])&lt;br /&gt;
  ),&lt;br /&gt;
  df =&amp;gt; df.SetColumns([&amp;quot;Test&amp;quot;: () =&amp;gt; `${Column(&amp;quot;NETWR&amp;quot;)} ${Column(&amp;quot;WAERK&amp;quot;)}`]),&lt;br /&gt;
  dataFlow =&amp;gt; {&lt;br /&gt;
    DataTableById(dataTableId).Import(dataFlow, [&amp;quot;Append&amp;quot;: 0]);&lt;br /&gt;
  }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
DataTableById(dataTableId).SqlDataFrame.OrderByColumns([&amp;quot;VBELN&amp;quot;], [true]).Collect()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==RootCauses.FindForDataFrame==&lt;br /&gt;
Finds root causes for a particular process phenomenon by comparing properties of selected cases against those of all cases in given model.&lt;br /&gt;
&lt;br /&gt;
Based on the similar in-memory function: [[FindRootCauses Function|EventLog.FindRootCauses]].&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;model&#039;&#039;&#039;:&lt;br /&gt;
**Model object of a model whose data tables are used to calculate the root causes.&lt;br /&gt;
*&#039;&#039;&#039;parameters&#039;&#039;&#039;:&lt;br /&gt;
**A parameter convertible to a StringDictionary object with the following supported key-values: &lt;br /&gt;
*** &#039;&#039;&#039;Filter&#039;&#039;&#039;: &lt;br /&gt;
****Filter json (#30921#) that is applied to the event log before calculating root causes.&lt;br /&gt;
*** &#039;&#039;&#039;Selection&#039;&#039;&#039;: &lt;br /&gt;
**** Selection json (#30927#) that defines selected cases to find root causes for. Is applied on top of the filtered event log (specified by Filter parameter).&lt;br /&gt;
****If nothing is selected, 100% of cases are counted as selected.&lt;br /&gt;
*** &#039;&#039;&#039;CaseAttributeTypes&#039;&#039;&#039;: &lt;br /&gt;
****An array of strings with the names of case attributes included into the root causes.&lt;br /&gt;
****Only case attributes of type string, integer or boolean are included.&lt;br /&gt;
****If CaseAttributeTypes is null or empty string or not specified, all case attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If the model doesn&#039;t have a case attribute with the specified name, an error message is shown.&lt;br /&gt;
*** &#039;&#039;&#039;EventAttributeTypes&#039;&#039;&#039;:&lt;br /&gt;
****An array of strings with the names of event attributes included into the root causes.&lt;br /&gt;
****Only event attributes of type string are included.&lt;br /&gt;
****If EventAttributeTypes is null, all event attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If EventAttributeTypes is empty array or not specified, event attributes not applied to the root causes.&lt;br /&gt;
****If the model doesn&#039;t have an event attribute with the specified name, an error message is shown.&lt;br /&gt;
****Analysis column Name should contain Event Attribute Name.&lt;br /&gt;
****Analysis column Value should contain Event Attribute Value and number of occurrences in case: &amp;lt;value&amp;gt; (count).&lt;br /&gt;
****Analysis column Type should have &amp;quot;EventAttributeValue&amp;quot; as its value.&lt;br /&gt;
***&#039;&#039;&#039;WeightingExpression&#039;&#039;&#039;: &lt;br /&gt;
**** Expression providing weights for each case.&lt;br /&gt;
****Expression, if defined, must be any of the following types:&lt;br /&gt;
***** A string containing the [[SQL Expressions|SqlExpression]] to evaluate.&lt;br /&gt;
*****A SqlExpression object, created e.g., using ToSqlExpression-function (see also: [[QPR_ProcessAnalyzer_Expressions#In-memory_expression_blocks_in_SQL_expressions|In-memory expression blocks in SQL expressions]]).&lt;br /&gt;
****A row is filtered out of result if expression result is null.&lt;br /&gt;
***&#039;&#039;&#039;MaximumRowCount&#039;&#039;&#039;:&lt;br /&gt;
****The maximum number of the most and least contributing root causes to return. Thus, the actual number of returned rows can be at most two times this value (if specified).&lt;br /&gt;
****If undefined, 200 is used.&lt;br /&gt;
**** If set to 0, all rows are returned.&lt;br /&gt;
***&#039;&#039;&#039;MinValueUsage&#039;&#039;&#039;: &lt;br /&gt;
****The minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
*****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;MaxNumUniqueValues&#039;&#039;&#039;: &lt;br /&gt;
****Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;IncludeOthers&#039;&#039;&#039;:  &lt;br /&gt;
****Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
****If given any string value, that value is used as the value for all the aggregated values.&lt;br /&gt;
****Default = undefined =&amp;gt; others values will not be included into the results.&lt;br /&gt;
***&#039;&#039;&#039;ValueIfNull&#039;&#039;&#039;: Value used to indicate null-values.&lt;br /&gt;
****Default = &amp;quot;(blank)&amp;quot;.&lt;br /&gt;
****Must be not null.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] object with the following columns:&lt;br /&gt;
&lt;br /&gt;
*Common columns:&lt;br /&gt;
**&#039;&#039;&#039;Type&#039;&#039;&#039;: &lt;br /&gt;
***Type of the root cause:&lt;br /&gt;
****&amp;quot;CaseAttributeValue&amp;quot; for case attributes.&lt;br /&gt;
****&amp;quot;EventAttributeValue&amp;quot; for event attributes.&lt;br /&gt;
**&#039;&#039;&#039;Name&#039;&#039;&#039;:  &lt;br /&gt;
***When type is CaseAttributeValue, case attribute name.&lt;br /&gt;
***When type is EventAttributeValue, event attribute name.&lt;br /&gt;
**&#039;&#039;&#039;Value&#039;&#039;&#039;: &lt;br /&gt;
***When type is CaseAttributeValue, case attribute value.&lt;br /&gt;
***When type is EventAttributeValue, event attribute value and number of occurrences in case.&lt;br /&gt;
** &#039;&#039;&#039;Total&#039;&#039;&#039;:  &lt;br /&gt;
***The total number of cases having the found root cause.&lt;br /&gt;
** &#039;&#039;&#039;Selected&#039;&#039;&#039;: &lt;br /&gt;
***The number of cases that have the found root cause and belong to the selected cases.&lt;br /&gt;
**&#039;&#039;&#039;Compared&#039;&#039;&#039;:&lt;br /&gt;
*** The number of cases that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
**Columns when WeightingExpression not have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The number of cases which contribute to the deviation from the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of cases which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected cases with the found root cause and the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of selected cases that have the found root cause out of all cases with that root cause.&lt;br /&gt;
**Columns when WeightingExpression have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The sum of case weights which contribute to the deviation from the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of case weights which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected case weights with the found root cause and the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of selected case weights that have the found root cause out of all case weights with that root cause.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;ComparedWeight&#039;&#039;&#039;: &lt;br /&gt;
****The sum of weights that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;TotalWeight&#039;&#039;&#039;:&lt;br /&gt;
****The sum of weights of all cases with that root cause.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Calculate root cause analysis for given model using parameters read from the [[Web API: Expression/query|query configuration]].&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;ProcessingMethod&amp;quot;: &amp;quot;DataFrame&amp;quot;,&lt;br /&gt;
  &amp;quot;Root&amp;quot;:&amp;quot;let m = _; _system.RootCauses.FindForDataFrame(m, _query.Configuration.Parameters.FindRootCausesParameters.Clone().Extend(#{\&amp;quot;Filter\&amp;quot;: _query.Configuration.Filter}))&amp;quot;,&lt;br /&gt;
  &amp;quot;Parameters&amp;quot;: {&lt;br /&gt;
    &amp;quot;FindRootCausesParameters&amp;quot;: {&lt;br /&gt;
      &amp;quot;CaseAttributeTypes&amp;quot;: [&amp;quot;Account Manager&amp;quot;,&amp;quot;Customer Group&amp;quot;,&amp;quot;Product Group&amp;quot;,&amp;quot;Region&amp;quot;],&lt;br /&gt;
      &amp;quot;Selection&amp;quot;: {&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]}]}]},&lt;br /&gt;
      &amp;quot;MaxNumUniqueValues&amp;quot;: 2,&lt;br /&gt;
      &amp;quot;MaximumRowCount&amp;quot;: 1000,&lt;br /&gt;
      &amp;quot;MinValueUsage&amp;quot;: 0.20,&lt;br /&gt;
      &amp;quot;WeightingExpression&amp;quot;: &amp;quot;Column(\&amp;quot;Cost\&amp;quot;)&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;Ordering&amp;quot;: [&lt;br /&gt;
    {&amp;quot;Name&amp;quot;: &amp;quot;Contribution&amp;quot;, &amp;quot;Direction&amp;quot;: &amp;quot;Descending&amp;quot;}&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.GetSampledEvents==&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied. &lt;br /&gt;
&lt;br /&gt;
===Parameters ===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;sourceModel&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;sampledCaseCount&#039;&#039;&#039;:&lt;br /&gt;
**The maximum number of cases to return (or null if all cases should be returned).&lt;br /&gt;
* &#039;&#039;&#039;filter&#039;&#039;&#039;:&lt;br /&gt;
**JSON filter to be applied on the event data of the source model prior to performing the sampling.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
[[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Get a sample of all the events of at most 1000 cases having &amp;quot;Hats&amp;quot; as &amp;quot;Product Group&amp;quot; case attribute value from model identified by modelId.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let eventDataSampleSdf = _system.Utils.GetSampledEvents(modelId, 1000, #{&lt;br /&gt;
  &amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
    &amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
	&amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
	  &amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
	  &amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&lt;br /&gt;
	  &amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]&lt;br /&gt;
	}]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.ModifyColumnTypes==&lt;br /&gt;
In-place modifies the column types of given columns in given data table.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;dataTable&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;columnTypesToSet&#039;&#039;&#039;:&lt;br /&gt;
**Array of column name/type definitions to set.&lt;br /&gt;
***Only columns that are to be changed are required to be listed.&lt;br /&gt;
***Columns that don&#039;t exist in the data table will be skipped.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
Returns the modified data table object.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Modify column &amp;quot;CaseId&amp;quot; to be of type string.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.ModifyColumnTypes(&lt;br /&gt;
  eventsTable, &lt;br /&gt;
  #{#{&amp;quot;Name&amp;quot;: &amp;quot;CaseId&amp;quot;, &amp;quot;DataType&amp;quot;: &amp;quot;String&amp;quot;}}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.RunFunctionWithParallelLogging==&lt;br /&gt;
Runs given function that generates logging information into given data table in a way that all the logging will be included into the generated script run log as well (if run inside a script).&lt;br /&gt;
&lt;br /&gt;
Internally polls the table every 5 seconds for new rows and adds all the newly added rows to script log.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;logTable&#039;&#039;&#039;:&lt;br /&gt;
**A DataTable used for logging.&lt;br /&gt;
*&#039;&#039;&#039;callbackFunc&#039;&#039;&#039;:&lt;br /&gt;
** Function that uses given data table for logging it&#039;s current status.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
The result of the callback function.&lt;br /&gt;
&lt;br /&gt;
=== Example===&lt;br /&gt;
Run stored procedure named &amp;quot;StoredProcedureTest&amp;quot; in Snowflake, that generates new rows to log table identified by logTableId and log the generated rows into the script run log.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.RunFunctionWithParallelLogging(DataTableById(logTableId), () =&amp;gt; {&lt;br /&gt;
    CreateSnowflakeConnection().CallStoredProcedure(&amp;quot;StoredProcedureTest&amp;quot;, #{})&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25479</id>
		<title>System Library</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25479"/>
		<updated>2024-11-19T13:52:05Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* ML.ApplyTransformations */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;System library is a collection of Expression Language functions and properties that provide additional tools for scripting Process Analyzer functionalities. System library is referenced in scripts via &#039;&#039;_system&#039;&#039;-property, which provides additional properties dedicated for different areas of interests for scripting. &lt;br /&gt;
&lt;br /&gt;
The following hierarchy shows the properties and functions available in System Library:&lt;br /&gt;
&lt;br /&gt;
* ML&lt;br /&gt;
** GeneratePredictionModel&lt;br /&gt;
**ApplyTransformations&lt;br /&gt;
*Parallel&lt;br /&gt;
**Run&lt;br /&gt;
*RootCauses&lt;br /&gt;
**FindRootCausesDataFrame&lt;br /&gt;
*Utils&lt;br /&gt;
**GetSampledEvents&lt;br /&gt;
**ModifyColumnTypes&lt;br /&gt;
**RunFunctionWithParallelLogging&lt;br /&gt;
&lt;br /&gt;
==ML.GeneratePredictionModel==&lt;br /&gt;
Documentation [[Create_Predicted_Eventlog#Create_prediction_script_in_QPR_ProcessAnalyzer|here]].&lt;br /&gt;
&lt;br /&gt;
==ML.ApplyTransformations==&lt;br /&gt;
Documentation [[Create_Simulated_Eventlog#Create_simulation_script_in_QPR_ProcessAnalyzer|here]].&lt;br /&gt;
&lt;br /&gt;
==Parallel.Run==&lt;br /&gt;
Runs given functions in parallel.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;functions&#039;&#039;&#039;:&lt;br /&gt;
**An array of functions to run in parallel.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
An array of results returned by the called functions, in the same order as the function generating them in the &#039;&#039;functions&#039;&#039;-parameter.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
The following script uses _system.Parallel.Run to run three functions:&lt;br /&gt;
&lt;br /&gt;
* SAP-extraction from VBAK-table in SAP (connection parameters defined in connectionParametersDict-dictionary).&lt;br /&gt;
*Transform the extracted data by adding a new column.&lt;br /&gt;
*Load the data into data table identified by dataTableId.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ExtractTransformAndLoad(extractFunc, transformFunc, loadFunc)&lt;br /&gt;
{&lt;br /&gt;
  let rawDataFlow = extractFunc();&lt;br /&gt;
  let transformedDataFlow = ToDataFlow();&lt;br /&gt;
&lt;br /&gt;
  _system.Parallel.Run([&lt;br /&gt;
    () =&amp;gt; Catch({&lt;br /&gt;
      let df;&lt;br /&gt;
      while (!IsNullTop(df = rawDataFlow.Collect(#{&amp;quot;CollectChunk&amp;quot;: true}))) {&lt;br /&gt;
        transformedDataFlow.Append(transformFunc(df));&lt;br /&gt;
        WriteLog(`A chunk having ${df.NRows} rows has been transformed.`);&lt;br /&gt;
      }&lt;br /&gt;
      if (rawDataFlow.HasError) {&lt;br /&gt;
        transformedDataFlow.Fail(&amp;quot;Error occurred during data extraction.&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        transformedDataFlow.Complete();&lt;br /&gt;
      }&lt;br /&gt;
    }, {&lt;br /&gt;
      transformedDataFlow.Fail(&amp;quot;Error occurred during transformation calculation.&amp;quot;);&lt;br /&gt;
    }),&lt;br /&gt;
    () =&amp;gt; {&lt;br /&gt;
      loadFunc(transformedDataFlow);&lt;br /&gt;
    }&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ExtractTransformAndLoad(&lt;br /&gt;
  () =&amp;gt; ExtractSap(connectionParametersDict.Extend(&lt;br /&gt;
    [&lt;br /&gt;
    &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
    &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
	&amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0060000042&#039;&amp;quot;],&lt;br /&gt;
    &amp;quot;UseGateway&amp;quot;: true&lt;br /&gt;
    ])&lt;br /&gt;
  ),&lt;br /&gt;
  df =&amp;gt; df.SetColumns([&amp;quot;Test&amp;quot;: () =&amp;gt; `${Column(&amp;quot;NETWR&amp;quot;)} ${Column(&amp;quot;WAERK&amp;quot;)}`]),&lt;br /&gt;
  dataFlow =&amp;gt; {&lt;br /&gt;
    DataTableById(dataTableId).Import(dataFlow, [&amp;quot;Append&amp;quot;: 0]);&lt;br /&gt;
  }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
DataTableById(dataTableId).SqlDataFrame.OrderByColumns([&amp;quot;VBELN&amp;quot;], [true]).Collect()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==RootCauses.FindForDataFrame==&lt;br /&gt;
Finds root causes for a particular process phenomenon by comparing properties of selected cases against those of all cases in given model.&lt;br /&gt;
&lt;br /&gt;
Based on the similar in-memory function: [[FindRootCauses Function|EventLog.FindRootCauses]].&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;model&#039;&#039;&#039;:&lt;br /&gt;
**Model object of a model whose data tables are used to calculate the root causes.&lt;br /&gt;
*&#039;&#039;&#039;parameters&#039;&#039;&#039;:&lt;br /&gt;
**A parameter convertible to a StringDictionary object with the following supported key-values: &lt;br /&gt;
*** &#039;&#039;&#039;Filter&#039;&#039;&#039;: &lt;br /&gt;
****Filter json (#30921#) that is applied to the event log before calculating root causes.&lt;br /&gt;
*** &#039;&#039;&#039;Selection&#039;&#039;&#039;: &lt;br /&gt;
**** Selection json (#30927#) that defines selected cases to find root causes for. Is applied on top of the filtered event log (specified by Filter parameter).&lt;br /&gt;
****If nothing is selected, 100% of cases are counted as selected.&lt;br /&gt;
*** &#039;&#039;&#039;CaseAttributeTypes&#039;&#039;&#039;: &lt;br /&gt;
****An array of strings with the names of case attributes included into the root causes.&lt;br /&gt;
****Only case attributes of type string, integer or boolean are included.&lt;br /&gt;
****If CaseAttributeTypes is null or empty string or not specified, all case attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If the model doesn&#039;t have a case attribute with the specified name, an error message is shown.&lt;br /&gt;
*** &#039;&#039;&#039;EventAttributeTypes&#039;&#039;&#039;:&lt;br /&gt;
****An array of strings with the names of event attributes included into the root causes.&lt;br /&gt;
****Only event attributes of type string are included.&lt;br /&gt;
****If EventAttributeTypes is null, all event attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If EventAttributeTypes is empty array or not specified, event attributes not applied to the root causes.&lt;br /&gt;
****If the model doesn&#039;t have an event attribute with the specified name, an error message is shown.&lt;br /&gt;
****Analysis column Name should contain Event Attribute Name.&lt;br /&gt;
****Analysis column Value should contain Event Attribute Value and number of occurrences in case: &amp;lt;value&amp;gt; (count).&lt;br /&gt;
****Analysis column Type should have &amp;quot;EventAttributeValue&amp;quot; as its value.&lt;br /&gt;
***&#039;&#039;&#039;WeightingExpression&#039;&#039;&#039;: &lt;br /&gt;
**** Expression providing weights for each case.&lt;br /&gt;
****Expression, if defined, must be any of the following types:&lt;br /&gt;
***** A string containing the [[SQL Expressions|SqlExpression]] to evaluate.&lt;br /&gt;
*****A SqlExpression object, created e.g., using ToSqlExpression-function (see also: [[QPR_ProcessAnalyzer_Expressions#In-memory_expression_blocks_in_SQL_expressions|In-memory expression blocks in SQL expressions]]).&lt;br /&gt;
****A row is filtered out of result if expression result is null.&lt;br /&gt;
***&#039;&#039;&#039;MaximumRowCount&#039;&#039;&#039;:&lt;br /&gt;
****The maximum number of the most and least contributing root causes to return. Thus, the actual number of returned rows can be at most two times this value (if specified).&lt;br /&gt;
****If undefined, 200 is used.&lt;br /&gt;
**** If set to 0, all rows are returned.&lt;br /&gt;
***&#039;&#039;&#039;MinValueUsage&#039;&#039;&#039;: &lt;br /&gt;
****The minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
*****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;MaxNumUniqueValues&#039;&#039;&#039;: &lt;br /&gt;
****Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;IncludeOthers&#039;&#039;&#039;:  &lt;br /&gt;
****Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
****If given any string value, that value is used as the value for all the aggregated values.&lt;br /&gt;
****Default = undefined =&amp;gt; others values will not be included into the results.&lt;br /&gt;
***&#039;&#039;&#039;ValueIfNull&#039;&#039;&#039;: Value used to indicate null-values.&lt;br /&gt;
****Default = &amp;quot;(blank)&amp;quot;.&lt;br /&gt;
****Must be not null.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] object with the following columns:&lt;br /&gt;
&lt;br /&gt;
*Common columns:&lt;br /&gt;
**&#039;&#039;&#039;Type&#039;&#039;&#039;: &lt;br /&gt;
***Type of the root cause:&lt;br /&gt;
****&amp;quot;CaseAttributeValue&amp;quot; for case attributes.&lt;br /&gt;
****&amp;quot;EventAttributeValue&amp;quot; for event attributes.&lt;br /&gt;
**&#039;&#039;&#039;Name&#039;&#039;&#039;:  &lt;br /&gt;
***When type is CaseAttributeValue, case attribute name.&lt;br /&gt;
***When type is EventAttributeValue, event attribute name.&lt;br /&gt;
**&#039;&#039;&#039;Value&#039;&#039;&#039;: &lt;br /&gt;
***When type is CaseAttributeValue, case attribute value.&lt;br /&gt;
***When type is EventAttributeValue, event attribute value and number of occurrences in case.&lt;br /&gt;
** &#039;&#039;&#039;Total&#039;&#039;&#039;:  &lt;br /&gt;
***The total number of cases having the found root cause.&lt;br /&gt;
** &#039;&#039;&#039;Selected&#039;&#039;&#039;: &lt;br /&gt;
***The number of cases that have the found root cause and belong to the selected cases.&lt;br /&gt;
**&#039;&#039;&#039;Compared&#039;&#039;&#039;:&lt;br /&gt;
*** The number of cases that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
**Columns when WeightingExpression not have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The number of cases which contribute to the deviation from the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of cases which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected cases with the found root cause and the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of selected cases that have the found root cause out of all cases with that root cause.&lt;br /&gt;
**Columns when WeightingExpression have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The sum of case weights which contribute to the deviation from the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of case weights which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected case weights with the found root cause and the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of selected case weights that have the found root cause out of all case weights with that root cause.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;ComparedWeight&#039;&#039;&#039;: &lt;br /&gt;
****The sum of weights that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;TotalWeight&#039;&#039;&#039;:&lt;br /&gt;
****The sum of weights of all cases with that root cause.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Calculate root cause analysis for given model using parameters read from the [[Web API: Expression/query|query configuration]].&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;ProcessingMethod&amp;quot;: &amp;quot;DataFrame&amp;quot;,&lt;br /&gt;
  &amp;quot;Root&amp;quot;:&amp;quot;let m = _; _system.RootCauses.FindForDataFrame(m, _query.Configuration.Parameters.FindRootCausesParameters.Clone().Extend(#{\&amp;quot;Filter\&amp;quot;: _query.Configuration.Filter}))&amp;quot;,&lt;br /&gt;
  &amp;quot;Parameters&amp;quot;: {&lt;br /&gt;
    &amp;quot;FindRootCausesParameters&amp;quot;: {&lt;br /&gt;
      &amp;quot;CaseAttributeTypes&amp;quot;: [&amp;quot;Account Manager&amp;quot;,&amp;quot;Customer Group&amp;quot;,&amp;quot;Product Group&amp;quot;,&amp;quot;Region&amp;quot;],&lt;br /&gt;
      &amp;quot;Selection&amp;quot;: {&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]}]}]},&lt;br /&gt;
      &amp;quot;MaxNumUniqueValues&amp;quot;: 2,&lt;br /&gt;
      &amp;quot;MaximumRowCount&amp;quot;: 1000,&lt;br /&gt;
      &amp;quot;MinValueUsage&amp;quot;: 0.20,&lt;br /&gt;
      &amp;quot;WeightingExpression&amp;quot;: &amp;quot;Column(\&amp;quot;Cost\&amp;quot;)&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;Ordering&amp;quot;: [&lt;br /&gt;
    {&amp;quot;Name&amp;quot;: &amp;quot;Contribution&amp;quot;, &amp;quot;Direction&amp;quot;: &amp;quot;Descending&amp;quot;}&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.GetSampledEvents==&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied. &lt;br /&gt;
&lt;br /&gt;
===Parameters ===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;sourceModel&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;sampledCaseCount&#039;&#039;&#039;:&lt;br /&gt;
**The maximum number of cases to return (or null if all cases should be returned).&lt;br /&gt;
* &#039;&#039;&#039;filter&#039;&#039;&#039;:&lt;br /&gt;
**JSON filter to be applied on the event data of the source model prior to performing the sampling.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
[[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Get a sample of all the events of at most 1000 cases having &amp;quot;Hats&amp;quot; as &amp;quot;Product Group&amp;quot; case attribute value from model identified by modelId.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let eventDataSampleSdf = _system.Utils.GetSampledEvents(modelId, 1000, #{&lt;br /&gt;
  &amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
    &amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
	&amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
	  &amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
	  &amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&lt;br /&gt;
	  &amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]&lt;br /&gt;
	}]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.ModifyColumnTypes==&lt;br /&gt;
In-place modifies the column types of given columns in given data table.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;dataTable&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;columnTypesToSet&#039;&#039;&#039;:&lt;br /&gt;
**Array of column name/type definitions to set.&lt;br /&gt;
***Only columns that are to be changed are required to be listed.&lt;br /&gt;
***Columns that don&#039;t exist in the data table will be skipped.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
Returns the modified data table object.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Modify column &amp;quot;CaseId&amp;quot; to be of type string.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.ModifyColumnTypes(&lt;br /&gt;
  eventsTable, &lt;br /&gt;
  #{#{&amp;quot;Name&amp;quot;: &amp;quot;CaseId&amp;quot;, &amp;quot;DataType&amp;quot;: &amp;quot;String&amp;quot;}}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.RunFunctionWithParallelLogging==&lt;br /&gt;
Runs given function that generates logging information into given data table in a way that all the logging will be included into the generated script run log as well (if run inside a script).&lt;br /&gt;
&lt;br /&gt;
Internally polls the table every 5 seconds for new rows and adds all the newly added rows to script log.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;logTable&#039;&#039;&#039;:&lt;br /&gt;
**A DataTable used for logging.&lt;br /&gt;
*&#039;&#039;&#039;callbackFunc&#039;&#039;&#039;:&lt;br /&gt;
** Function that uses given data table for logging it&#039;s current status.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
The result of the callback function.&lt;br /&gt;
&lt;br /&gt;
=== Example===&lt;br /&gt;
Run stored procedure named &amp;quot;StoredProcedureTest&amp;quot; in Snowflake, that generates new rows to log table identified by logTableId and log the generated rows into the script run log.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.RunFunctionWithParallelLogging(DataTableById(logTableId), () =&amp;gt; {&lt;br /&gt;
    CreateSnowflakeConnection().CallStoredProcedure(&amp;quot;StoredProcedureTest&amp;quot;, #{})&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25478</id>
		<title>System Library</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25478"/>
		<updated>2024-11-19T13:50:46Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* ML.GeneratePredictionModel */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;System library is a collection of Expression Language functions and properties that provide additional tools for scripting Process Analyzer functionalities. System library is referenced in scripts via &#039;&#039;_system&#039;&#039;-property, which provides additional properties dedicated for different areas of interests for scripting. &lt;br /&gt;
&lt;br /&gt;
The following hierarchy shows the properties and functions available in System Library:&lt;br /&gt;
&lt;br /&gt;
* ML&lt;br /&gt;
** GeneratePredictionModel&lt;br /&gt;
**ApplyTransformations&lt;br /&gt;
*Parallel&lt;br /&gt;
**Run&lt;br /&gt;
*RootCauses&lt;br /&gt;
**FindRootCausesDataFrame&lt;br /&gt;
*Utils&lt;br /&gt;
**GetSampledEvents&lt;br /&gt;
**ModifyColumnTypes&lt;br /&gt;
**RunFunctionWithParallelLogging&lt;br /&gt;
&lt;br /&gt;
==ML.GeneratePredictionModel==&lt;br /&gt;
Documentation [[Create_Predicted_Eventlog#Create_prediction_script_in_QPR_ProcessAnalyzer|here]].&lt;br /&gt;
&lt;br /&gt;
==ML.ApplyTransformations==&lt;br /&gt;
Documentation here: [[Create Simulated Eventlog|ApplyTransformations]]&lt;br /&gt;
&lt;br /&gt;
==Parallel.Run==&lt;br /&gt;
Runs given functions in parallel.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;functions&#039;&#039;&#039;:&lt;br /&gt;
**An array of functions to run in parallel.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
An array of results returned by the called functions, in the same order as the function generating them in the &#039;&#039;functions&#039;&#039;-parameter.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
The following script uses _system.Parallel.Run to run three functions:&lt;br /&gt;
&lt;br /&gt;
* SAP-extraction from VBAK-table in SAP (connection parameters defined in connectionParametersDict-dictionary).&lt;br /&gt;
*Transform the extracted data by adding a new column.&lt;br /&gt;
*Load the data into data table identified by dataTableId.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ExtractTransformAndLoad(extractFunc, transformFunc, loadFunc)&lt;br /&gt;
{&lt;br /&gt;
  let rawDataFlow = extractFunc();&lt;br /&gt;
  let transformedDataFlow = ToDataFlow();&lt;br /&gt;
&lt;br /&gt;
  _system.Parallel.Run([&lt;br /&gt;
    () =&amp;gt; Catch({&lt;br /&gt;
      let df;&lt;br /&gt;
      while (!IsNullTop(df = rawDataFlow.Collect(#{&amp;quot;CollectChunk&amp;quot;: true}))) {&lt;br /&gt;
        transformedDataFlow.Append(transformFunc(df));&lt;br /&gt;
        WriteLog(`A chunk having ${df.NRows} rows has been transformed.`);&lt;br /&gt;
      }&lt;br /&gt;
      if (rawDataFlow.HasError) {&lt;br /&gt;
        transformedDataFlow.Fail(&amp;quot;Error occurred during data extraction.&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        transformedDataFlow.Complete();&lt;br /&gt;
      }&lt;br /&gt;
    }, {&lt;br /&gt;
      transformedDataFlow.Fail(&amp;quot;Error occurred during transformation calculation.&amp;quot;);&lt;br /&gt;
    }),&lt;br /&gt;
    () =&amp;gt; {&lt;br /&gt;
      loadFunc(transformedDataFlow);&lt;br /&gt;
    }&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ExtractTransformAndLoad(&lt;br /&gt;
  () =&amp;gt; ExtractSap(connectionParametersDict.Extend(&lt;br /&gt;
    [&lt;br /&gt;
    &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
    &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
	&amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0060000042&#039;&amp;quot;],&lt;br /&gt;
    &amp;quot;UseGateway&amp;quot;: true&lt;br /&gt;
    ])&lt;br /&gt;
  ),&lt;br /&gt;
  df =&amp;gt; df.SetColumns([&amp;quot;Test&amp;quot;: () =&amp;gt; `${Column(&amp;quot;NETWR&amp;quot;)} ${Column(&amp;quot;WAERK&amp;quot;)}`]),&lt;br /&gt;
  dataFlow =&amp;gt; {&lt;br /&gt;
    DataTableById(dataTableId).Import(dataFlow, [&amp;quot;Append&amp;quot;: 0]);&lt;br /&gt;
  }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
DataTableById(dataTableId).SqlDataFrame.OrderByColumns([&amp;quot;VBELN&amp;quot;], [true]).Collect()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==RootCauses.FindForDataFrame==&lt;br /&gt;
Finds root causes for a particular process phenomenon by comparing properties of selected cases against those of all cases in given model.&lt;br /&gt;
&lt;br /&gt;
Based on the similar in-memory function: [[FindRootCauses Function|EventLog.FindRootCauses]].&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;model&#039;&#039;&#039;:&lt;br /&gt;
**Model object of a model whose data tables are used to calculate the root causes.&lt;br /&gt;
*&#039;&#039;&#039;parameters&#039;&#039;&#039;:&lt;br /&gt;
**A parameter convertible to a StringDictionary object with the following supported key-values: &lt;br /&gt;
*** &#039;&#039;&#039;Filter&#039;&#039;&#039;: &lt;br /&gt;
****Filter json (#30921#) that is applied to the event log before calculating root causes.&lt;br /&gt;
*** &#039;&#039;&#039;Selection&#039;&#039;&#039;: &lt;br /&gt;
**** Selection json (#30927#) that defines selected cases to find root causes for. Is applied on top of the filtered event log (specified by Filter parameter).&lt;br /&gt;
****If nothing is selected, 100% of cases are counted as selected.&lt;br /&gt;
*** &#039;&#039;&#039;CaseAttributeTypes&#039;&#039;&#039;: &lt;br /&gt;
****An array of strings with the names of case attributes included into the root causes.&lt;br /&gt;
****Only case attributes of type string, integer or boolean are included.&lt;br /&gt;
****If CaseAttributeTypes is null or empty string or not specified, all case attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If the model doesn&#039;t have a case attribute with the specified name, an error message is shown.&lt;br /&gt;
*** &#039;&#039;&#039;EventAttributeTypes&#039;&#039;&#039;:&lt;br /&gt;
****An array of strings with the names of event attributes included into the root causes.&lt;br /&gt;
****Only event attributes of type string are included.&lt;br /&gt;
****If EventAttributeTypes is null, all event attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If EventAttributeTypes is empty array or not specified, event attributes not applied to the root causes.&lt;br /&gt;
****If the model doesn&#039;t have an event attribute with the specified name, an error message is shown.&lt;br /&gt;
****Analysis column Name should contain Event Attribute Name.&lt;br /&gt;
****Analysis column Value should contain Event Attribute Value and number of occurrences in case: &amp;lt;value&amp;gt; (count).&lt;br /&gt;
****Analysis column Type should have &amp;quot;EventAttributeValue&amp;quot; as its value.&lt;br /&gt;
***&#039;&#039;&#039;WeightingExpression&#039;&#039;&#039;: &lt;br /&gt;
**** Expression providing weights for each case.&lt;br /&gt;
****Expression, if defined, must be any of the following types:&lt;br /&gt;
***** A string containing the [[SQL Expressions|SqlExpression]] to evaluate.&lt;br /&gt;
*****A SqlExpression object, created e.g., using ToSqlExpression-function (see also: [[QPR_ProcessAnalyzer_Expressions#In-memory_expression_blocks_in_SQL_expressions|In-memory expression blocks in SQL expressions]]).&lt;br /&gt;
****A row is filtered out of result if expression result is null.&lt;br /&gt;
***&#039;&#039;&#039;MaximumRowCount&#039;&#039;&#039;:&lt;br /&gt;
****The maximum number of the most and least contributing root causes to return. Thus, the actual number of returned rows can be at most two times this value (if specified).&lt;br /&gt;
****If undefined, 200 is used.&lt;br /&gt;
**** If set to 0, all rows are returned.&lt;br /&gt;
***&#039;&#039;&#039;MinValueUsage&#039;&#039;&#039;: &lt;br /&gt;
****The minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
*****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;MaxNumUniqueValues&#039;&#039;&#039;: &lt;br /&gt;
****Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;IncludeOthers&#039;&#039;&#039;:  &lt;br /&gt;
****Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
****If given any string value, that value is used as the value for all the aggregated values.&lt;br /&gt;
****Default = undefined =&amp;gt; others values will not be included into the results.&lt;br /&gt;
***&#039;&#039;&#039;ValueIfNull&#039;&#039;&#039;: Value used to indicate null-values.&lt;br /&gt;
****Default = &amp;quot;(blank)&amp;quot;.&lt;br /&gt;
****Must be not null.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] object with the following columns:&lt;br /&gt;
&lt;br /&gt;
*Common columns:&lt;br /&gt;
**&#039;&#039;&#039;Type&#039;&#039;&#039;: &lt;br /&gt;
***Type of the root cause:&lt;br /&gt;
****&amp;quot;CaseAttributeValue&amp;quot; for case attributes.&lt;br /&gt;
****&amp;quot;EventAttributeValue&amp;quot; for event attributes.&lt;br /&gt;
**&#039;&#039;&#039;Name&#039;&#039;&#039;:  &lt;br /&gt;
***When type is CaseAttributeValue, case attribute name.&lt;br /&gt;
***When type is EventAttributeValue, event attribute name.&lt;br /&gt;
**&#039;&#039;&#039;Value&#039;&#039;&#039;: &lt;br /&gt;
***When type is CaseAttributeValue, case attribute value.&lt;br /&gt;
***When type is EventAttributeValue, event attribute value and number of occurrences in case.&lt;br /&gt;
** &#039;&#039;&#039;Total&#039;&#039;&#039;:  &lt;br /&gt;
***The total number of cases having the found root cause.&lt;br /&gt;
** &#039;&#039;&#039;Selected&#039;&#039;&#039;: &lt;br /&gt;
***The number of cases that have the found root cause and belong to the selected cases.&lt;br /&gt;
**&#039;&#039;&#039;Compared&#039;&#039;&#039;:&lt;br /&gt;
*** The number of cases that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
**Columns when WeightingExpression not have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The number of cases which contribute to the deviation from the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of cases which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected cases with the found root cause and the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of selected cases that have the found root cause out of all cases with that root cause.&lt;br /&gt;
**Columns when WeightingExpression have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The sum of case weights which contribute to the deviation from the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of case weights which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected case weights with the found root cause and the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of selected case weights that have the found root cause out of all case weights with that root cause.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;ComparedWeight&#039;&#039;&#039;: &lt;br /&gt;
****The sum of weights that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;TotalWeight&#039;&#039;&#039;:&lt;br /&gt;
****The sum of weights of all cases with that root cause.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Calculate root cause analysis for given model using parameters read from the [[Web API: Expression/query|query configuration]].&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;ProcessingMethod&amp;quot;: &amp;quot;DataFrame&amp;quot;,&lt;br /&gt;
  &amp;quot;Root&amp;quot;:&amp;quot;let m = _; _system.RootCauses.FindForDataFrame(m, _query.Configuration.Parameters.FindRootCausesParameters.Clone().Extend(#{\&amp;quot;Filter\&amp;quot;: _query.Configuration.Filter}))&amp;quot;,&lt;br /&gt;
  &amp;quot;Parameters&amp;quot;: {&lt;br /&gt;
    &amp;quot;FindRootCausesParameters&amp;quot;: {&lt;br /&gt;
      &amp;quot;CaseAttributeTypes&amp;quot;: [&amp;quot;Account Manager&amp;quot;,&amp;quot;Customer Group&amp;quot;,&amp;quot;Product Group&amp;quot;,&amp;quot;Region&amp;quot;],&lt;br /&gt;
      &amp;quot;Selection&amp;quot;: {&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]}]}]},&lt;br /&gt;
      &amp;quot;MaxNumUniqueValues&amp;quot;: 2,&lt;br /&gt;
      &amp;quot;MaximumRowCount&amp;quot;: 1000,&lt;br /&gt;
      &amp;quot;MinValueUsage&amp;quot;: 0.20,&lt;br /&gt;
      &amp;quot;WeightingExpression&amp;quot;: &amp;quot;Column(\&amp;quot;Cost\&amp;quot;)&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;Ordering&amp;quot;: [&lt;br /&gt;
    {&amp;quot;Name&amp;quot;: &amp;quot;Contribution&amp;quot;, &amp;quot;Direction&amp;quot;: &amp;quot;Descending&amp;quot;}&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.GetSampledEvents==&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied. &lt;br /&gt;
&lt;br /&gt;
===Parameters ===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;sourceModel&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;sampledCaseCount&#039;&#039;&#039;:&lt;br /&gt;
**The maximum number of cases to return (or null if all cases should be returned).&lt;br /&gt;
* &#039;&#039;&#039;filter&#039;&#039;&#039;:&lt;br /&gt;
**JSON filter to be applied on the event data of the source model prior to performing the sampling.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
[[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Get a sample of all the events of at most 1000 cases having &amp;quot;Hats&amp;quot; as &amp;quot;Product Group&amp;quot; case attribute value from model identified by modelId.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let eventDataSampleSdf = _system.Utils.GetSampledEvents(modelId, 1000, #{&lt;br /&gt;
  &amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
    &amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
	&amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
	  &amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
	  &amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&lt;br /&gt;
	  &amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]&lt;br /&gt;
	}]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.ModifyColumnTypes==&lt;br /&gt;
In-place modifies the column types of given columns in given data table.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;dataTable&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;columnTypesToSet&#039;&#039;&#039;:&lt;br /&gt;
**Array of column name/type definitions to set.&lt;br /&gt;
***Only columns that are to be changed are required to be listed.&lt;br /&gt;
***Columns that don&#039;t exist in the data table will be skipped.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
Returns the modified data table object.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Modify column &amp;quot;CaseId&amp;quot; to be of type string.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.ModifyColumnTypes(&lt;br /&gt;
  eventsTable, &lt;br /&gt;
  #{#{&amp;quot;Name&amp;quot;: &amp;quot;CaseId&amp;quot;, &amp;quot;DataType&amp;quot;: &amp;quot;String&amp;quot;}}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.RunFunctionWithParallelLogging==&lt;br /&gt;
Runs given function that generates logging information into given data table in a way that all the logging will be included into the generated script run log as well (if run inside a script).&lt;br /&gt;
&lt;br /&gt;
Internally polls the table every 5 seconds for new rows and adds all the newly added rows to script log.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;logTable&#039;&#039;&#039;:&lt;br /&gt;
**A DataTable used for logging.&lt;br /&gt;
*&#039;&#039;&#039;callbackFunc&#039;&#039;&#039;:&lt;br /&gt;
** Function that uses given data table for logging it&#039;s current status.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
The result of the callback function.&lt;br /&gt;
&lt;br /&gt;
=== Example===&lt;br /&gt;
Run stored procedure named &amp;quot;StoredProcedureTest&amp;quot; in Snowflake, that generates new rows to log table identified by logTableId and log the generated rows into the script run log.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.RunFunctionWithParallelLogging(DataTableById(logTableId), () =&amp;gt; {&lt;br /&gt;
    CreateSnowflakeConnection().CallStoredProcedure(&amp;quot;StoredProcedureTest&amp;quot;, #{})&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25477</id>
		<title>System Library</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25477"/>
		<updated>2024-11-19T13:49:54Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* ML.GeneratePredictionModel */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;System library is a collection of Expression Language functions and properties that provide additional tools for scripting Process Analyzer functionalities. System library is referenced in scripts via &#039;&#039;_system&#039;&#039;-property, which provides additional properties dedicated for different areas of interests for scripting. &lt;br /&gt;
&lt;br /&gt;
The following hierarchy shows the properties and functions available in System Library:&lt;br /&gt;
&lt;br /&gt;
* ML&lt;br /&gt;
** GeneratePredictionModel&lt;br /&gt;
**ApplyTransformations&lt;br /&gt;
*Parallel&lt;br /&gt;
**Run&lt;br /&gt;
*RootCauses&lt;br /&gt;
**FindRootCausesDataFrame&lt;br /&gt;
*Utils&lt;br /&gt;
**GetSampledEvents&lt;br /&gt;
**ModifyColumnTypes&lt;br /&gt;
**RunFunctionWithParallelLogging&lt;br /&gt;
&lt;br /&gt;
==ML.GeneratePredictionModel==&lt;br /&gt;
Documentation here: [[Create Predicted Eventlog|Create_Predicted_Eventlog#Create_prediction_script_in_QPR_ProcessAnalyzer]]&lt;br /&gt;
&lt;br /&gt;
==ML.ApplyTransformations==&lt;br /&gt;
Documentation here: [[Create Simulated Eventlog|ApplyTransformations]]&lt;br /&gt;
&lt;br /&gt;
==Parallel.Run==&lt;br /&gt;
Runs given functions in parallel.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;functions&#039;&#039;&#039;:&lt;br /&gt;
**An array of functions to run in parallel.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
An array of results returned by the called functions, in the same order as the function generating them in the &#039;&#039;functions&#039;&#039;-parameter.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
The following script uses _system.Parallel.Run to run three functions:&lt;br /&gt;
&lt;br /&gt;
* SAP-extraction from VBAK-table in SAP (connection parameters defined in connectionParametersDict-dictionary).&lt;br /&gt;
*Transform the extracted data by adding a new column.&lt;br /&gt;
*Load the data into data table identified by dataTableId.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ExtractTransformAndLoad(extractFunc, transformFunc, loadFunc)&lt;br /&gt;
{&lt;br /&gt;
  let rawDataFlow = extractFunc();&lt;br /&gt;
  let transformedDataFlow = ToDataFlow();&lt;br /&gt;
&lt;br /&gt;
  _system.Parallel.Run([&lt;br /&gt;
    () =&amp;gt; Catch({&lt;br /&gt;
      let df;&lt;br /&gt;
      while (!IsNullTop(df = rawDataFlow.Collect(#{&amp;quot;CollectChunk&amp;quot;: true}))) {&lt;br /&gt;
        transformedDataFlow.Append(transformFunc(df));&lt;br /&gt;
        WriteLog(`A chunk having ${df.NRows} rows has been transformed.`);&lt;br /&gt;
      }&lt;br /&gt;
      if (rawDataFlow.HasError) {&lt;br /&gt;
        transformedDataFlow.Fail(&amp;quot;Error occurred during data extraction.&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        transformedDataFlow.Complete();&lt;br /&gt;
      }&lt;br /&gt;
    }, {&lt;br /&gt;
      transformedDataFlow.Fail(&amp;quot;Error occurred during transformation calculation.&amp;quot;);&lt;br /&gt;
    }),&lt;br /&gt;
    () =&amp;gt; {&lt;br /&gt;
      loadFunc(transformedDataFlow);&lt;br /&gt;
    }&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ExtractTransformAndLoad(&lt;br /&gt;
  () =&amp;gt; ExtractSap(connectionParametersDict.Extend(&lt;br /&gt;
    [&lt;br /&gt;
    &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
    &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
	&amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0060000042&#039;&amp;quot;],&lt;br /&gt;
    &amp;quot;UseGateway&amp;quot;: true&lt;br /&gt;
    ])&lt;br /&gt;
  ),&lt;br /&gt;
  df =&amp;gt; df.SetColumns([&amp;quot;Test&amp;quot;: () =&amp;gt; `${Column(&amp;quot;NETWR&amp;quot;)} ${Column(&amp;quot;WAERK&amp;quot;)}`]),&lt;br /&gt;
  dataFlow =&amp;gt; {&lt;br /&gt;
    DataTableById(dataTableId).Import(dataFlow, [&amp;quot;Append&amp;quot;: 0]);&lt;br /&gt;
  }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
DataTableById(dataTableId).SqlDataFrame.OrderByColumns([&amp;quot;VBELN&amp;quot;], [true]).Collect()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==RootCauses.FindForDataFrame==&lt;br /&gt;
Finds root causes for a particular process phenomenon by comparing properties of selected cases against those of all cases in given model.&lt;br /&gt;
&lt;br /&gt;
Based on the similar in-memory function: [[FindRootCauses Function|EventLog.FindRootCauses]].&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;model&#039;&#039;&#039;:&lt;br /&gt;
**Model object of a model whose data tables are used to calculate the root causes.&lt;br /&gt;
*&#039;&#039;&#039;parameters&#039;&#039;&#039;:&lt;br /&gt;
**A parameter convertible to a StringDictionary object with the following supported key-values: &lt;br /&gt;
*** &#039;&#039;&#039;Filter&#039;&#039;&#039;: &lt;br /&gt;
****Filter json (#30921#) that is applied to the event log before calculating root causes.&lt;br /&gt;
*** &#039;&#039;&#039;Selection&#039;&#039;&#039;: &lt;br /&gt;
**** Selection json (#30927#) that defines selected cases to find root causes for. Is applied on top of the filtered event log (specified by Filter parameter).&lt;br /&gt;
****If nothing is selected, 100% of cases are counted as selected.&lt;br /&gt;
*** &#039;&#039;&#039;CaseAttributeTypes&#039;&#039;&#039;: &lt;br /&gt;
****An array of strings with the names of case attributes included into the root causes.&lt;br /&gt;
****Only case attributes of type string, integer or boolean are included.&lt;br /&gt;
****If CaseAttributeTypes is null or empty string or not specified, all case attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If the model doesn&#039;t have a case attribute with the specified name, an error message is shown.&lt;br /&gt;
*** &#039;&#039;&#039;EventAttributeTypes&#039;&#039;&#039;:&lt;br /&gt;
****An array of strings with the names of event attributes included into the root causes.&lt;br /&gt;
****Only event attributes of type string are included.&lt;br /&gt;
****If EventAttributeTypes is null, all event attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If EventAttributeTypes is empty array or not specified, event attributes not applied to the root causes.&lt;br /&gt;
****If the model doesn&#039;t have an event attribute with the specified name, an error message is shown.&lt;br /&gt;
****Analysis column Name should contain Event Attribute Name.&lt;br /&gt;
****Analysis column Value should contain Event Attribute Value and number of occurrences in case: &amp;lt;value&amp;gt; (count).&lt;br /&gt;
****Analysis column Type should have &amp;quot;EventAttributeValue&amp;quot; as its value.&lt;br /&gt;
***&#039;&#039;&#039;WeightingExpression&#039;&#039;&#039;: &lt;br /&gt;
**** Expression providing weights for each case.&lt;br /&gt;
****Expression, if defined, must be any of the following types:&lt;br /&gt;
***** A string containing the [[SQL Expressions|SqlExpression]] to evaluate.&lt;br /&gt;
*****A SqlExpression object, created e.g., using ToSqlExpression-function (see also: [[QPR_ProcessAnalyzer_Expressions#In-memory_expression_blocks_in_SQL_expressions|In-memory expression blocks in SQL expressions]]).&lt;br /&gt;
****A row is filtered out of result if expression result is null.&lt;br /&gt;
***&#039;&#039;&#039;MaximumRowCount&#039;&#039;&#039;:&lt;br /&gt;
****The maximum number of the most and least contributing root causes to return. Thus, the actual number of returned rows can be at most two times this value (if specified).&lt;br /&gt;
****If undefined, 200 is used.&lt;br /&gt;
**** If set to 0, all rows are returned.&lt;br /&gt;
***&#039;&#039;&#039;MinValueUsage&#039;&#039;&#039;: &lt;br /&gt;
****The minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
*****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;MaxNumUniqueValues&#039;&#039;&#039;: &lt;br /&gt;
****Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;IncludeOthers&#039;&#039;&#039;:  &lt;br /&gt;
****Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
****If given any string value, that value is used as the value for all the aggregated values.&lt;br /&gt;
****Default = undefined =&amp;gt; others values will not be included into the results.&lt;br /&gt;
***&#039;&#039;&#039;ValueIfNull&#039;&#039;&#039;: Value used to indicate null-values.&lt;br /&gt;
****Default = &amp;quot;(blank)&amp;quot;.&lt;br /&gt;
****Must be not null.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] object with the following columns:&lt;br /&gt;
&lt;br /&gt;
*Common columns:&lt;br /&gt;
**&#039;&#039;&#039;Type&#039;&#039;&#039;: &lt;br /&gt;
***Type of the root cause:&lt;br /&gt;
****&amp;quot;CaseAttributeValue&amp;quot; for case attributes.&lt;br /&gt;
****&amp;quot;EventAttributeValue&amp;quot; for event attributes.&lt;br /&gt;
**&#039;&#039;&#039;Name&#039;&#039;&#039;:  &lt;br /&gt;
***When type is CaseAttributeValue, case attribute name.&lt;br /&gt;
***When type is EventAttributeValue, event attribute name.&lt;br /&gt;
**&#039;&#039;&#039;Value&#039;&#039;&#039;: &lt;br /&gt;
***When type is CaseAttributeValue, case attribute value.&lt;br /&gt;
***When type is EventAttributeValue, event attribute value and number of occurrences in case.&lt;br /&gt;
** &#039;&#039;&#039;Total&#039;&#039;&#039;:  &lt;br /&gt;
***The total number of cases having the found root cause.&lt;br /&gt;
** &#039;&#039;&#039;Selected&#039;&#039;&#039;: &lt;br /&gt;
***The number of cases that have the found root cause and belong to the selected cases.&lt;br /&gt;
**&#039;&#039;&#039;Compared&#039;&#039;&#039;:&lt;br /&gt;
*** The number of cases that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
**Columns when WeightingExpression not have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The number of cases which contribute to the deviation from the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of cases which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected cases with the found root cause and the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of selected cases that have the found root cause out of all cases with that root cause.&lt;br /&gt;
**Columns when WeightingExpression have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The sum of case weights which contribute to the deviation from the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of case weights which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected case weights with the found root cause and the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of selected case weights that have the found root cause out of all case weights with that root cause.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;ComparedWeight&#039;&#039;&#039;: &lt;br /&gt;
****The sum of weights that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;TotalWeight&#039;&#039;&#039;:&lt;br /&gt;
****The sum of weights of all cases with that root cause.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Calculate root cause analysis for given model using parameters read from the [[Web API: Expression/query|query configuration]].&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;ProcessingMethod&amp;quot;: &amp;quot;DataFrame&amp;quot;,&lt;br /&gt;
  &amp;quot;Root&amp;quot;:&amp;quot;let m = _; _system.RootCauses.FindForDataFrame(m, _query.Configuration.Parameters.FindRootCausesParameters.Clone().Extend(#{\&amp;quot;Filter\&amp;quot;: _query.Configuration.Filter}))&amp;quot;,&lt;br /&gt;
  &amp;quot;Parameters&amp;quot;: {&lt;br /&gt;
    &amp;quot;FindRootCausesParameters&amp;quot;: {&lt;br /&gt;
      &amp;quot;CaseAttributeTypes&amp;quot;: [&amp;quot;Account Manager&amp;quot;,&amp;quot;Customer Group&amp;quot;,&amp;quot;Product Group&amp;quot;,&amp;quot;Region&amp;quot;],&lt;br /&gt;
      &amp;quot;Selection&amp;quot;: {&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]}]}]},&lt;br /&gt;
      &amp;quot;MaxNumUniqueValues&amp;quot;: 2,&lt;br /&gt;
      &amp;quot;MaximumRowCount&amp;quot;: 1000,&lt;br /&gt;
      &amp;quot;MinValueUsage&amp;quot;: 0.20,&lt;br /&gt;
      &amp;quot;WeightingExpression&amp;quot;: &amp;quot;Column(\&amp;quot;Cost\&amp;quot;)&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;Ordering&amp;quot;: [&lt;br /&gt;
    {&amp;quot;Name&amp;quot;: &amp;quot;Contribution&amp;quot;, &amp;quot;Direction&amp;quot;: &amp;quot;Descending&amp;quot;}&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.GetSampledEvents==&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied. &lt;br /&gt;
&lt;br /&gt;
===Parameters ===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;sourceModel&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;sampledCaseCount&#039;&#039;&#039;:&lt;br /&gt;
**The maximum number of cases to return (or null if all cases should be returned).&lt;br /&gt;
* &#039;&#039;&#039;filter&#039;&#039;&#039;:&lt;br /&gt;
**JSON filter to be applied on the event data of the source model prior to performing the sampling.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
[[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Get a sample of all the events of at most 1000 cases having &amp;quot;Hats&amp;quot; as &amp;quot;Product Group&amp;quot; case attribute value from model identified by modelId.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let eventDataSampleSdf = _system.Utils.GetSampledEvents(modelId, 1000, #{&lt;br /&gt;
  &amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
    &amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
	&amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
	  &amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
	  &amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&lt;br /&gt;
	  &amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]&lt;br /&gt;
	}]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.ModifyColumnTypes==&lt;br /&gt;
In-place modifies the column types of given columns in given data table.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;dataTable&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;columnTypesToSet&#039;&#039;&#039;:&lt;br /&gt;
**Array of column name/type definitions to set.&lt;br /&gt;
***Only columns that are to be changed are required to be listed.&lt;br /&gt;
***Columns that don&#039;t exist in the data table will be skipped.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
Returns the modified data table object.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Modify column &amp;quot;CaseId&amp;quot; to be of type string.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.ModifyColumnTypes(&lt;br /&gt;
  eventsTable, &lt;br /&gt;
  #{#{&amp;quot;Name&amp;quot;: &amp;quot;CaseId&amp;quot;, &amp;quot;DataType&amp;quot;: &amp;quot;String&amp;quot;}}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.RunFunctionWithParallelLogging==&lt;br /&gt;
Runs given function that generates logging information into given data table in a way that all the logging will be included into the generated script run log as well (if run inside a script).&lt;br /&gt;
&lt;br /&gt;
Internally polls the table every 5 seconds for new rows and adds all the newly added rows to script log.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;logTable&#039;&#039;&#039;:&lt;br /&gt;
**A DataTable used for logging.&lt;br /&gt;
*&#039;&#039;&#039;callbackFunc&#039;&#039;&#039;:&lt;br /&gt;
** Function that uses given data table for logging it&#039;s current status.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
The result of the callback function.&lt;br /&gt;
&lt;br /&gt;
=== Example===&lt;br /&gt;
Run stored procedure named &amp;quot;StoredProcedureTest&amp;quot; in Snowflake, that generates new rows to log table identified by logTableId and log the generated rows into the script run log.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.RunFunctionWithParallelLogging(DataTableById(logTableId), () =&amp;gt; {&lt;br /&gt;
    CreateSnowflakeConnection().CallStoredProcedure(&amp;quot;StoredProcedureTest&amp;quot;, #{})&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25476</id>
		<title>System Library</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25476"/>
		<updated>2024-11-19T13:48:49Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;System library is a collection of Expression Language functions and properties that provide additional tools for scripting Process Analyzer functionalities. System library is referenced in scripts via &#039;&#039;_system&#039;&#039;-property, which provides additional properties dedicated for different areas of interests for scripting. &lt;br /&gt;
&lt;br /&gt;
The following hierarchy shows the properties and functions available in System Library:&lt;br /&gt;
&lt;br /&gt;
* ML&lt;br /&gt;
** GeneratePredictionModel&lt;br /&gt;
**ApplyTransformations&lt;br /&gt;
*Parallel&lt;br /&gt;
**Run&lt;br /&gt;
*RootCauses&lt;br /&gt;
**FindRootCausesDataFrame&lt;br /&gt;
*Utils&lt;br /&gt;
**GetSampledEvents&lt;br /&gt;
**ModifyColumnTypes&lt;br /&gt;
**RunFunctionWithParallelLogging&lt;br /&gt;
&lt;br /&gt;
==ML.GeneratePredictionModel==&lt;br /&gt;
Documentation here: [[Create Predicted Eventlog|GeneratePredictionModel]]&lt;br /&gt;
&lt;br /&gt;
==ML.ApplyTransformations==&lt;br /&gt;
Documentation here: [[Create Simulated Eventlog|ApplyTransformations]]&lt;br /&gt;
&lt;br /&gt;
==Parallel.Run==&lt;br /&gt;
Runs given functions in parallel.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;functions&#039;&#039;&#039;:&lt;br /&gt;
**An array of functions to run in parallel.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
An array of results returned by the called functions, in the same order as the function generating them in the &#039;&#039;functions&#039;&#039;-parameter.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
The following script uses _system.Parallel.Run to run three functions:&lt;br /&gt;
&lt;br /&gt;
* SAP-extraction from VBAK-table in SAP (connection parameters defined in connectionParametersDict-dictionary).&lt;br /&gt;
*Transform the extracted data by adding a new column.&lt;br /&gt;
*Load the data into data table identified by dataTableId.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ExtractTransformAndLoad(extractFunc, transformFunc, loadFunc)&lt;br /&gt;
{&lt;br /&gt;
  let rawDataFlow = extractFunc();&lt;br /&gt;
  let transformedDataFlow = ToDataFlow();&lt;br /&gt;
&lt;br /&gt;
  _system.Parallel.Run([&lt;br /&gt;
    () =&amp;gt; Catch({&lt;br /&gt;
      let df;&lt;br /&gt;
      while (!IsNullTop(df = rawDataFlow.Collect(#{&amp;quot;CollectChunk&amp;quot;: true}))) {&lt;br /&gt;
        transformedDataFlow.Append(transformFunc(df));&lt;br /&gt;
        WriteLog(`A chunk having ${df.NRows} rows has been transformed.`);&lt;br /&gt;
      }&lt;br /&gt;
      if (rawDataFlow.HasError) {&lt;br /&gt;
        transformedDataFlow.Fail(&amp;quot;Error occurred during data extraction.&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        transformedDataFlow.Complete();&lt;br /&gt;
      }&lt;br /&gt;
    }, {&lt;br /&gt;
      transformedDataFlow.Fail(&amp;quot;Error occurred during transformation calculation.&amp;quot;);&lt;br /&gt;
    }),&lt;br /&gt;
    () =&amp;gt; {&lt;br /&gt;
      loadFunc(transformedDataFlow);&lt;br /&gt;
    }&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ExtractTransformAndLoad(&lt;br /&gt;
  () =&amp;gt; ExtractSap(connectionParametersDict.Extend(&lt;br /&gt;
    [&lt;br /&gt;
    &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
    &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
	&amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0060000042&#039;&amp;quot;],&lt;br /&gt;
    &amp;quot;UseGateway&amp;quot;: true&lt;br /&gt;
    ])&lt;br /&gt;
  ),&lt;br /&gt;
  df =&amp;gt; df.SetColumns([&amp;quot;Test&amp;quot;: () =&amp;gt; `${Column(&amp;quot;NETWR&amp;quot;)} ${Column(&amp;quot;WAERK&amp;quot;)}`]),&lt;br /&gt;
  dataFlow =&amp;gt; {&lt;br /&gt;
    DataTableById(dataTableId).Import(dataFlow, [&amp;quot;Append&amp;quot;: 0]);&lt;br /&gt;
  }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
DataTableById(dataTableId).SqlDataFrame.OrderByColumns([&amp;quot;VBELN&amp;quot;], [true]).Collect()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==RootCauses.FindForDataFrame==&lt;br /&gt;
Finds root causes for a particular process phenomenon by comparing properties of selected cases against those of all cases in given model.&lt;br /&gt;
&lt;br /&gt;
Based on the similar in-memory function: [[FindRootCauses Function|EventLog.FindRootCauses]].&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;model&#039;&#039;&#039;:&lt;br /&gt;
**Model object of a model whose data tables are used to calculate the root causes.&lt;br /&gt;
*&#039;&#039;&#039;parameters&#039;&#039;&#039;:&lt;br /&gt;
**A parameter convertible to a StringDictionary object with the following supported key-values: &lt;br /&gt;
*** &#039;&#039;&#039;Filter&#039;&#039;&#039;: &lt;br /&gt;
****Filter json (#30921#) that is applied to the event log before calculating root causes.&lt;br /&gt;
*** &#039;&#039;&#039;Selection&#039;&#039;&#039;: &lt;br /&gt;
**** Selection json (#30927#) that defines selected cases to find root causes for. Is applied on top of the filtered event log (specified by Filter parameter).&lt;br /&gt;
****If nothing is selected, 100% of cases are counted as selected.&lt;br /&gt;
*** &#039;&#039;&#039;CaseAttributeTypes&#039;&#039;&#039;: &lt;br /&gt;
****An array of strings with the names of case attributes included into the root causes.&lt;br /&gt;
****Only case attributes of type string, integer or boolean are included.&lt;br /&gt;
****If CaseAttributeTypes is null or empty string or not specified, all case attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If the model doesn&#039;t have a case attribute with the specified name, an error message is shown.&lt;br /&gt;
*** &#039;&#039;&#039;EventAttributeTypes&#039;&#039;&#039;:&lt;br /&gt;
****An array of strings with the names of event attributes included into the root causes.&lt;br /&gt;
****Only event attributes of type string are included.&lt;br /&gt;
****If EventAttributeTypes is null, all event attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
****If EventAttributeTypes is empty array or not specified, event attributes not applied to the root causes.&lt;br /&gt;
****If the model doesn&#039;t have an event attribute with the specified name, an error message is shown.&lt;br /&gt;
****Analysis column Name should contain Event Attribute Name.&lt;br /&gt;
****Analysis column Value should contain Event Attribute Value and number of occurrences in case: &amp;lt;value&amp;gt; (count).&lt;br /&gt;
****Analysis column Type should have &amp;quot;EventAttributeValue&amp;quot; as its value.&lt;br /&gt;
***&#039;&#039;&#039;WeightingExpression&#039;&#039;&#039;: &lt;br /&gt;
**** Expression providing weights for each case.&lt;br /&gt;
****Expression, if defined, must be any of the following types:&lt;br /&gt;
***** A string containing the [[SQL Expressions|SqlExpression]] to evaluate.&lt;br /&gt;
*****A SqlExpression object, created e.g., using ToSqlExpression-function (see also: [[QPR_ProcessAnalyzer_Expressions#In-memory_expression_blocks_in_SQL_expressions|In-memory expression blocks in SQL expressions]]).&lt;br /&gt;
****A row is filtered out of result if expression result is null.&lt;br /&gt;
***&#039;&#039;&#039;MaximumRowCount&#039;&#039;&#039;:&lt;br /&gt;
****The maximum number of the most and least contributing root causes to return. Thus, the actual number of returned rows can be at most two times this value (if specified).&lt;br /&gt;
****If undefined, 200 is used.&lt;br /&gt;
**** If set to 0, all rows are returned.&lt;br /&gt;
***&#039;&#039;&#039;MinValueUsage&#039;&#039;&#039;: &lt;br /&gt;
****The minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
*****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;MaxNumUniqueValues&#039;&#039;&#039;: &lt;br /&gt;
****Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
****If not defined or null, all values are included (=default).&lt;br /&gt;
***&#039;&#039;&#039;IncludeOthers&#039;&#039;&#039;:  &lt;br /&gt;
****Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
****If given any string value, that value is used as the value for all the aggregated values.&lt;br /&gt;
****Default = undefined =&amp;gt; others values will not be included into the results.&lt;br /&gt;
***&#039;&#039;&#039;ValueIfNull&#039;&#039;&#039;: Value used to indicate null-values.&lt;br /&gt;
****Default = &amp;quot;(blank)&amp;quot;.&lt;br /&gt;
****Must be not null.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] object with the following columns:&lt;br /&gt;
&lt;br /&gt;
*Common columns:&lt;br /&gt;
**&#039;&#039;&#039;Type&#039;&#039;&#039;: &lt;br /&gt;
***Type of the root cause:&lt;br /&gt;
****&amp;quot;CaseAttributeValue&amp;quot; for case attributes.&lt;br /&gt;
****&amp;quot;EventAttributeValue&amp;quot; for event attributes.&lt;br /&gt;
**&#039;&#039;&#039;Name&#039;&#039;&#039;:  &lt;br /&gt;
***When type is CaseAttributeValue, case attribute name.&lt;br /&gt;
***When type is EventAttributeValue, event attribute name.&lt;br /&gt;
**&#039;&#039;&#039;Value&#039;&#039;&#039;: &lt;br /&gt;
***When type is CaseAttributeValue, case attribute value.&lt;br /&gt;
***When type is EventAttributeValue, event attribute value and number of occurrences in case.&lt;br /&gt;
** &#039;&#039;&#039;Total&#039;&#039;&#039;:  &lt;br /&gt;
***The total number of cases having the found root cause.&lt;br /&gt;
** &#039;&#039;&#039;Selected&#039;&#039;&#039;: &lt;br /&gt;
***The number of cases that have the found root cause and belong to the selected cases.&lt;br /&gt;
**&#039;&#039;&#039;Compared&#039;&#039;&#039;:&lt;br /&gt;
*** The number of cases that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
**Columns when WeightingExpression not have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The number of cases which contribute to the deviation from the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of cases which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected cases with the found root cause and the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of selected cases that have the found root cause out of all cases with that root cause.&lt;br /&gt;
**Columns when WeightingExpression have value:&lt;br /&gt;
***&#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
****The sum of case weights which contribute to the deviation from the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of case weights which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
****The deviation in percentage between selected case weights with the found root cause and the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
***&#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
****The percent of selected case weights that have the found root cause out of all case weights with that root cause.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;ComparedWeight&#039;&#039;&#039;: &lt;br /&gt;
****The sum of weights that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;TotalWeight&#039;&#039;&#039;:&lt;br /&gt;
****The sum of weights of all cases with that root cause.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Calculate root cause analysis for given model using parameters read from the [[Web API: Expression/query|query configuration]].&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;ProcessingMethod&amp;quot;: &amp;quot;DataFrame&amp;quot;,&lt;br /&gt;
  &amp;quot;Root&amp;quot;:&amp;quot;let m = _; _system.RootCauses.FindForDataFrame(m, _query.Configuration.Parameters.FindRootCausesParameters.Clone().Extend(#{\&amp;quot;Filter\&amp;quot;: _query.Configuration.Filter}))&amp;quot;,&lt;br /&gt;
  &amp;quot;Parameters&amp;quot;: {&lt;br /&gt;
    &amp;quot;FindRootCausesParameters&amp;quot;: {&lt;br /&gt;
      &amp;quot;CaseAttributeTypes&amp;quot;: [&amp;quot;Account Manager&amp;quot;,&amp;quot;Customer Group&amp;quot;,&amp;quot;Product Group&amp;quot;,&amp;quot;Region&amp;quot;],&lt;br /&gt;
      &amp;quot;Selection&amp;quot;: {&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]}]}]},&lt;br /&gt;
      &amp;quot;MaxNumUniqueValues&amp;quot;: 2,&lt;br /&gt;
      &amp;quot;MaximumRowCount&amp;quot;: 1000,&lt;br /&gt;
      &amp;quot;MinValueUsage&amp;quot;: 0.20,&lt;br /&gt;
      &amp;quot;WeightingExpression&amp;quot;: &amp;quot;Column(\&amp;quot;Cost\&amp;quot;)&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;Ordering&amp;quot;: [&lt;br /&gt;
    {&amp;quot;Name&amp;quot;: &amp;quot;Contribution&amp;quot;, &amp;quot;Direction&amp;quot;: &amp;quot;Descending&amp;quot;}&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.GetSampledEvents==&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied. &lt;br /&gt;
&lt;br /&gt;
===Parameters ===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;sourceModel&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;sampledCaseCount&#039;&#039;&#039;:&lt;br /&gt;
**The maximum number of cases to return (or null if all cases should be returned).&lt;br /&gt;
* &#039;&#039;&#039;filter&#039;&#039;&#039;:&lt;br /&gt;
**JSON filter to be applied on the event data of the source model prior to performing the sampling.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
[[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Get a sample of all the events of at most 1000 cases having &amp;quot;Hats&amp;quot; as &amp;quot;Product Group&amp;quot; case attribute value from model identified by modelId.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let eventDataSampleSdf = _system.Utils.GetSampledEvents(modelId, 1000, #{&lt;br /&gt;
  &amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
    &amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
	&amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
	  &amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
	  &amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&lt;br /&gt;
	  &amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]&lt;br /&gt;
	}]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.ModifyColumnTypes==&lt;br /&gt;
In-place modifies the column types of given columns in given data table.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
*&#039;&#039;&#039;dataTable&#039;&#039;&#039;: &lt;br /&gt;
**ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
*&#039;&#039;&#039;columnTypesToSet&#039;&#039;&#039;:&lt;br /&gt;
**Array of column name/type definitions to set.&lt;br /&gt;
***Only columns that are to be changed are required to be listed.&lt;br /&gt;
***Columns that don&#039;t exist in the data table will be skipped.&lt;br /&gt;
&lt;br /&gt;
===Return value ===&lt;br /&gt;
Returns the modified data table object.&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
Modify column &amp;quot;CaseId&amp;quot; to be of type string.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.ModifyColumnTypes(&lt;br /&gt;
  eventsTable, &lt;br /&gt;
  #{#{&amp;quot;Name&amp;quot;: &amp;quot;CaseId&amp;quot;, &amp;quot;DataType&amp;quot;: &amp;quot;String&amp;quot;}}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Utils.RunFunctionWithParallelLogging==&lt;br /&gt;
Runs given function that generates logging information into given data table in a way that all the logging will be included into the generated script run log as well (if run inside a script).&lt;br /&gt;
&lt;br /&gt;
Internally polls the table every 5 seconds for new rows and adds all the newly added rows to script log.&lt;br /&gt;
&lt;br /&gt;
===Parameters===&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;logTable&#039;&#039;&#039;:&lt;br /&gt;
**A DataTable used for logging.&lt;br /&gt;
*&#039;&#039;&#039;callbackFunc&#039;&#039;&#039;:&lt;br /&gt;
** Function that uses given data table for logging it&#039;s current status.&lt;br /&gt;
&lt;br /&gt;
===Return value===&lt;br /&gt;
The result of the callback function.&lt;br /&gt;
&lt;br /&gt;
=== Example===&lt;br /&gt;
Run stored procedure named &amp;quot;StoredProcedureTest&amp;quot; in Snowflake, that generates new rows to log table identified by logTableId and log the generated rows into the script run log.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.RunFunctionWithParallelLogging(DataTableById(logTableId), () =&amp;gt; {&lt;br /&gt;
    CreateSnowflakeConnection().CallStoredProcedure(&amp;quot;StoredProcedureTest&amp;quot;, #{})&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_Wiki&amp;diff=25475</id>
		<title>QPR ProcessAnalyzer Wiki</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=QPR_ProcessAnalyzer_Wiki&amp;diff=25475"/>
		<updated>2024-11-19T13:42:43Z</updated>

		<summary type="html">&lt;p&gt;MarHink: Added system library link&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;downloadButton&amp;quot; style=&amp;quot;width:190px;float:right;margin: 3px 12px 0px 15px;&amp;quot;&amp;gt;[[Online_Learning_Platform|Online Learning&amp;lt;br /&amp;gt;Platform]]&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Welcome to QPR ProcessAnalyzer Wiki! QPR ProcessAnalyzer is a software for turning event and transactional data into visual process analysis and intelligence. Topics in this documentation are divided based on user roles for process analysts, developers and administrators.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;height:5px;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
== For Process Analysts ==&lt;br /&gt;
This section contains information how to get started with QPR ProcessAnalyzer and how to create your first dashboards! This section also describes how to use filters and how to make different kinds of analyses with QPR ProcessAnalyzer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display: flex;flex-wrap: wrap;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 210px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
=== Getting Started ===&lt;br /&gt;
* [[Getting Started with QPR ProcessAnalyzer]]&lt;br /&gt;
* [[QPR_ProcessAnalyzer_Native_App_in_Snowflake|Snowflake Native App]]&lt;br /&gt;
* [[Introduction to Process Mining|Introduction to Process Mining]]&lt;br /&gt;
* [[Process_Mining_Concepts|Process Mining Concepts]]&lt;br /&gt;
* [[Log_in_QPR_ProcessAnalyzer|Log in QPR ProcessAnalyzer]]&lt;br /&gt;
* [[Languages and Localization|Language and Localization Settings]]&lt;br /&gt;
* [[User Settings|User Settings]]&lt;br /&gt;
* [[Navigation_Menu|Navigation Menu Functions]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 210px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Working with Dashboards ===&lt;br /&gt;
* [[QPR_ProcessAnalyzer_Project_Workspace|Project Workspace]]: [[QPR_ProcessAnalyzer_Project_Workspace#Projects|Projects]], [[QPR_ProcessAnalyzer_Project_Workspace#Dashboards|Dashboards]], [[QPR_ProcessAnalyzer_Project_Workspace#Models|Models]], [[QPR_ProcessAnalyzer_Project_Workspace#Datatables|Datatables]], [[Managing_Scripts|Scripts]], [[QPR_ProcessAnalyzer_Project_Workspace#Recycle_Bin|Recycle Bin]]&lt;br /&gt;
* [[Filtering_in_QPR_ProcessAnalyzer|Using Filters]]&lt;br /&gt;
* [[QPR ProcessAnalyzer Dashboard Designer|Creating Dashboards]]&lt;br /&gt;
* [[AI Assistant for QPR ProcessAnalyzer|AI Assistant]] (powered by generative AI)&lt;br /&gt;
* [[Dashboard Variables|Dynamic Variables in Dashboards]]&lt;br /&gt;
* [[Business Calendar|Business Calendar to Calculate Durations]]&lt;br /&gt;
* [[Best Practices for Designing Dashboards|Best Practices for Designing Dashboards]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 370px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Analyses and Visualizations ===&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; width:50%;&amp;quot;&amp;gt;&lt;br /&gt;
* [[Process Flowchart|Process Flowchart]]&lt;br /&gt;
* [[Object-Centric_Flowchart|Object-Centric Flowchart]]&lt;br /&gt;
* [[QPR ProcessAnalyzer Chart|Chart]] / [[Snowflake Chart|Snowflake Chart]]&lt;br /&gt;
** [[QPR ProcessAnalyzer Graphs|Graphs]]&lt;br /&gt;
** [[QPR_ProcessAnalyzer_Table|Table]]&lt;br /&gt;
** [[QPR_ProcessAnalyzer_Pivot_Table|Pivot Table]]&lt;br /&gt;
** [[QPR_ProcessAnalyzer_KPI_Card|KPI Card]]&lt;br /&gt;
** [[Measure,_Dimension_and_Column_Settings|Measure Settings]]&lt;br /&gt;
** [[Chart_On-screen_Settings|On-screen Settings]]&lt;br /&gt;
** [[Chart_Linked_Settings|Linked Settings]]&lt;br /&gt;
** [[Actions_to_Run_Script_in_Table|Run Script Actions]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; width:50%;&amp;quot;&amp;gt;&lt;br /&gt;
* [[Root Causes|Root Causes Analysis]]&lt;br /&gt;
* [[Clustering Analysis|Clustering Analysis]]&lt;br /&gt;
* [[Conformance Analysis|Conformance Analysis]]&lt;br /&gt;
* [[Design Diagram|Design Diagram]] / [[QPR ProcessAnalyzer BPMN Editor|BPMN Editor]]&lt;br /&gt;
* [[Gantt_Chart|Gantt Chart]]&lt;br /&gt;
* [[Sankey_Chart|Sankey Chart]]&lt;br /&gt;
* [[Label and Link]]&lt;br /&gt;
* [[Image|Image]]&lt;br /&gt;
* [[Filter_Selectors|Filter Selectors]] / [[Dropdown_List_Selector|Dropdown List Selector]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== For Citizen Developers ==&lt;br /&gt;
This section describes how to build ETL scripts that transform the source data into process mining models. There is also a detailed description how the process mining models can be configured so that they are optimal for the desired analyses. Finally, there is reference documentation for all expression language related functionality, that can be used both when writing custom KPI&#039;s in dashboards and in the ETL scripts.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display: flex;flex-wrap: wrap;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 370px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
=== Expression Language Reference ===&lt;br /&gt;
* [[SQL_Expressions|SQL Expressions for Snowflake]]&lt;br /&gt;
* In-memory expressions: [[QPR_ProcessAnalyzer_Expressions|Basic Syntax and Operations]] / [[Generic Functions in QPR ProcessAnalyzer|Generic Functions]]&lt;br /&gt;
* [[Generic_Objects_in_Expression_Language|Generic types]] ([[Generic_Properties_in_Expression_Language|Generic Properties]], [[Generic_Objects_in_Expression_Language#Array|Array]], [[Generic_Objects_in_Expression_Language#DateTime|DateTime]], [[Generic_Objects_in_Expression_Language#String|String]], [[Generic_Objects_in_Expression_Language#Timespan|Timespan]],  [[Generic_Objects_in_Expression_Language#Dictionary|Dictionary]])&lt;br /&gt;
* [[Process_Mining_Objects_in_Expression_Language|In-memory models API]] ([[Process_Mining_Objects_in_Expression_Language#AttributeType|AttributeType]], [[Process_Mining_Objects_in_Expression_Language#Case|Case]], [[Process_Mining_Objects_in_Expression_Language#Event|Event]], [[Process_Mining_Objects_in_Expression_Language#EventLog|EventLog]], [[Process_Mining_Objects_in_Expression_Language#EventType|EventType]], [[Process_Mining_Objects_in_Expression_Language#Flow|Flow]], [[Process_Mining_Objects_in_Expression_Language#FlowOccurrence|FlowOccurrence]], [[Process_Mining_Objects_in_Expression_Language#Variation|Variation]])&lt;br /&gt;
* Configuration objects ([[Dashboard_in_Expression_Language|Dashboard]], [[Datatable_in_Expression_Language|Datatable]], [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Model|Model]], [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Filter|Filter]], [[Diagram_in_Expression_Language|Diagram]], [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Script|Script]], [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#Project|Project]], [[QPR_ProcessAnalyzer_Objects_in_Expression_Language#User.2FGroup|User/Group]])&lt;br /&gt;
* Tabular data ([[DataFrame in Expression Language|DataFrame]], [[SqlDataFrame in Expression Language|SqlDataFrame]],  [[DataFlow_in_Expression_Language|DataFlow]])&lt;br /&gt;
* [[Machine_Learning_Functions_in_Expression_Language|Machine Learning API]] / [[Conformance_Checking|Conformance Checking]]&lt;br /&gt;
* [[Filtering_in_QPR_ProcessAnalyzer_Queries|Filter Rules JSON]]&lt;br /&gt;
* [[System Library]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 250px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Process Mining Models ===&lt;br /&gt;
* [[Creating Process Mining Model|Walkthrough: Creating Process Mining Model]]&lt;br /&gt;
* [[Exporting_and_Importing_Data_in_QPR_ProcessAnalyzer|Importing Data from CSV, XES and PACM files]]&lt;br /&gt;
* [[Object-centric_Process_Mining_Model|Object-centric Process Mining]]&lt;br /&gt;
* [[Event Ordering for Identical Timestamps|Event Ordering for Identical Timestamps]]&lt;br /&gt;
* [[Managing Time Zones and Local Time|Time Zones and Local Time]]&lt;br /&gt;
* [[Best_Practices_for_Designing_Models|Best Practices for Designing Models]]&lt;br /&gt;
* [[Create_Predicted_Eventlog|Create Predicted Eventlog]]&lt;br /&gt;
* [[Create Simulated Eventlog]]&lt;br /&gt;
==== For In-memory Models ====&lt;br /&gt;
* [[Calculated Attributes in QPR ProcessAnalyzer|Calculated Case and Event Attributes]]&lt;br /&gt;
* [[Email Notifications|Email Notifications]]&lt;br /&gt;
* [[QPR ProcessAnalyzer Model Datasources|Model datasources]] ([[QPR ProcessAnalyzer Model Datasources#Loading Data from Datatables|Datatable]], [[QPR ProcessAnalyzer Model Datasources#Loading Script|Loading Script]], [[QPR ProcessAnalyzer Model Datasources#ODBC_Datasource|ODBC]])&lt;br /&gt;
* [[Case Level Permissions|Case Level Permissions]]&lt;br /&gt;
* [[Automatic Model Loading on Server Startup|Keeping Models Always Available]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 250px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Data Integrations and Connectors ===&lt;br /&gt;
* [[Managing Scripts|Managing Scripts in Workspace]]&lt;br /&gt;
* [[Datatable_Properties_Dialog|Managing Datatables in Workspace]]&lt;br /&gt;
* [[SQL Scripting for ETL|Writing SQL Scripts]]&lt;br /&gt;
* [[SQL Scripting Commands|SQL Scripting Commands Reference]]&lt;br /&gt;
* [[Storing Secrets for Scripts|Storing Secrets]]&lt;br /&gt;
* [[QPR ProcessAnalyzer ScriptLauncher|Installing and using QPR ScriptLauncher]]&lt;br /&gt;
* [[Importing_Data_from_SAP|How to Import Data from SAP]]&lt;br /&gt;
* [[Anonymize data|Anonymize data]]&lt;br /&gt;
* [[Expression Script Examples|Expression Script Examples]]&lt;br /&gt;
* [[QPR ProcessAnalyzer API|QPR ProcessAnalyzer REST API]]&lt;br /&gt;
* [[Sample Eventlog Files|Sample Eventlogs]]&lt;br /&gt;
* [[QPR TaskRecorder]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== For System Administrators ==&lt;br /&gt;
This section starts with the planning of QPR ProcessAnalyzer installation. After all requirements have been fulfilled, you can continue with the installation and configuration of QPR ProcessAnalyzer. Finally, learn how to manage users and perform other administrative tasks.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display: flex;flex-wrap: wrap;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 290px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
=== Planning Installation ===&lt;br /&gt;
* [[QPR ProcessAnalyzer System Requirements|System Requirements]]&lt;br /&gt;
* [[QPR ProcessAnalyzer System Architecture|System Architecture]]&lt;br /&gt;
* [[User Session Management|User Session Management]]&lt;br /&gt;
* [[QPR ProcessAnalyzer Release Notes|QPR ProcessAnalyzer Release Notes]]&lt;br /&gt;
* [[QPR_TaskRecorder_Release_Notes|QPR TaskRecorder Release Notes]]&lt;br /&gt;
* [[QPR_ProcessAnalyzer_Downloads|Downloads Page]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 290px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Installing and Configuring ===&lt;br /&gt;
* [[Installing QPR ProcessAnalyzer Server|Install QPR ProcessAnalyzer Server]] (or [[Updating_QPR_ProcessAnalyzer_Server|update existing]])&lt;br /&gt;
* [[Snowflake_Connection_Configuration|Configure Snowflake Connection]]&lt;br /&gt;
* [[Setting_up_Scripting_Sandbox|Setting up SQL Scripting Sandbox]]&lt;br /&gt;
* [[SAML_2.0_Federated_Authentication|SAML 2.0 Authentication]]&lt;br /&gt;
* [[QPR ProcessAnalyzer Security Hardening|Security Hardening]]&lt;br /&gt;
* [[Activate_QPR_ProcessAnalyzer_using_ActivationUtility|Activation without Internet Connection]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;flex: 1 0 290px;border:1px solid #dfdfdf;padding:0 1em 1em 1.5em;background-color:#F7FAFC;margin:10px 0px 0px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Administrating System ===&lt;br /&gt;
* [[Roles and Permissions|User Roles and Permissions]]&lt;br /&gt;
* [[Manage Users and Groups|Managing Users]]&lt;br /&gt;
* [[PA_Configuration_database_table|System Configurations]]&lt;br /&gt;
* [[QPR ProcessAnalyzer Logs|Logs for Audit and Troubleshooting]]&lt;br /&gt;
* [[In-memory_Models_Management|In-memory Models Management]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Agreements ==&lt;br /&gt;
See the [[QPR End User Software License Agreement]] and [[QPR Software as a Service Agreement]].&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25474</id>
		<title>System Library</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25474"/>
		<updated>2024-11-19T13:35:36Z</updated>

		<summary type="html">&lt;p&gt;MarHink: /* Example */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;System library is a collection of Expression Language functions and properties that provide additional tools for scripting Process Analyzer functionalities. System library is referenced in scripts via &#039;&#039;_system&#039;&#039;-property, which provides additional properties dedicated for different areas of interests for scripting. &lt;br /&gt;
&lt;br /&gt;
The following hierarchy shows the properties and functions available in System Library:&lt;br /&gt;
&lt;br /&gt;
* ML&lt;br /&gt;
** GeneratePredictionModel (documentation here: [[Create Predicted Eventlog|GeneratePredictionModel]])&lt;br /&gt;
** ApplyTransformations (documentation here: [[Create Simulated Eventlog|ApplyTransformations]])&lt;br /&gt;
* Parallel&lt;br /&gt;
** Run&lt;br /&gt;
* RootCauses&lt;br /&gt;
** FindRootCausesDataFrame&lt;br /&gt;
* Utils&lt;br /&gt;
** GetSampledEvents&lt;br /&gt;
** ModifyColumnTypes&lt;br /&gt;
** RunFunctionWithParallelLogging&lt;br /&gt;
&lt;br /&gt;
== Parallel.Run ==&lt;br /&gt;
Runs given functions in parallel.&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
* &#039;&#039;&#039;functions&#039;&#039;&#039;:&lt;br /&gt;
** An array of functions to run in parallel.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
An array of results returned by the called functions, in the same order as the function generating them in the &#039;&#039;functions&#039;&#039;-parameter.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The following script uses _system.Parallel.Run to run three functions:&lt;br /&gt;
&lt;br /&gt;
* SAP-extraction from VBAK-table in SAP (connection parameters defined in connectionParametersDict-dictionary).&lt;br /&gt;
* Transform the extracted data by adding a new column.&lt;br /&gt;
* Load the data into data table identified by dataTableId.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ExtractTransformAndLoad(extractFunc, transformFunc, loadFunc)&lt;br /&gt;
{&lt;br /&gt;
  let rawDataFlow = extractFunc();&lt;br /&gt;
  let transformedDataFlow = ToDataFlow();&lt;br /&gt;
&lt;br /&gt;
  _system.Parallel.Run([&lt;br /&gt;
    () =&amp;gt; Catch({&lt;br /&gt;
      let df;&lt;br /&gt;
      while (!IsNullTop(df = rawDataFlow.Collect(#{&amp;quot;CollectChunk&amp;quot;: true}))) {&lt;br /&gt;
        transformedDataFlow.Append(transformFunc(df));&lt;br /&gt;
        WriteLog(`A chunk having ${df.NRows} rows has been transformed.`);&lt;br /&gt;
      }&lt;br /&gt;
      if (rawDataFlow.HasError) {&lt;br /&gt;
        transformedDataFlow.Fail(&amp;quot;Error occurred during data extraction.&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        transformedDataFlow.Complete();&lt;br /&gt;
      }&lt;br /&gt;
    }, {&lt;br /&gt;
      transformedDataFlow.Fail(&amp;quot;Error occurred during transformation calculation.&amp;quot;);&lt;br /&gt;
    }),&lt;br /&gt;
    () =&amp;gt; {&lt;br /&gt;
      loadFunc(transformedDataFlow);&lt;br /&gt;
    }&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ExtractTransformAndLoad(&lt;br /&gt;
  () =&amp;gt; ExtractSap(connectionParametersDict.Extend(&lt;br /&gt;
    [&lt;br /&gt;
    &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
    &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
	&amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0060000042&#039;&amp;quot;],&lt;br /&gt;
    &amp;quot;UseGateway&amp;quot;: true&lt;br /&gt;
    ])&lt;br /&gt;
  ),&lt;br /&gt;
  df =&amp;gt; df.SetColumns([&amp;quot;Test&amp;quot;: () =&amp;gt; `${Column(&amp;quot;NETWR&amp;quot;)} ${Column(&amp;quot;WAERK&amp;quot;)}`]),&lt;br /&gt;
  dataFlow =&amp;gt; {&lt;br /&gt;
    DataTableById(dataTableId).Import(dataFlow, [&amp;quot;Append&amp;quot;: 0]);&lt;br /&gt;
  }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
DataTableById(dataTableId).SqlDataFrame.OrderByColumns([&amp;quot;VBELN&amp;quot;], [true]).Collect()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== RootCauses.FindForDataFrame ==&lt;br /&gt;
Finds root causes for a particular process phenomenon by comparing properties of selected cases against those of all cases in given model.&lt;br /&gt;
&lt;br /&gt;
Based on the similar in-memory function: [[FindRootCauses Function|EventLog.FindRootCauses]].&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;model&#039;&#039;&#039;:&lt;br /&gt;
** Model object of a model whose data tables are used to calculate the root causes.&lt;br /&gt;
* &#039;&#039;&#039;parameters&#039;&#039;&#039;:&lt;br /&gt;
** A parameter convertible to a StringDictionary object with the following supported key-values:&lt;br /&gt;
*** &#039;&#039;&#039;Filter&#039;&#039;&#039;: &lt;br /&gt;
**** Filter json (#30921#) that is applied to the event log before calculating root causes.&lt;br /&gt;
*** &#039;&#039;&#039;Selection&#039;&#039;&#039;: &lt;br /&gt;
**** Selection json (#30927#) that defines selected cases to find root causes for. Is applied on top of the filtered event log (specified by Filter parameter).&lt;br /&gt;
**** If nothing is selected, 100% of cases are counted as selected.&lt;br /&gt;
*** &#039;&#039;&#039;CaseAttributeTypes&#039;&#039;&#039;: &lt;br /&gt;
**** An array of strings with the names of case attributes included into the root causes.&lt;br /&gt;
**** Only case attributes of type string, integer or boolean are included.&lt;br /&gt;
**** If CaseAttributeTypes is null or empty string or not specified, all case attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
**** If the model doesn&#039;t have a case attribute with the specified name, an error message is shown.&lt;br /&gt;
*** &#039;&#039;&#039;EventAttributeTypes&#039;&#039;&#039;:&lt;br /&gt;
**** An array of strings with the names of event attributes included into the root causes.&lt;br /&gt;
**** Only event attributes of type string are included.&lt;br /&gt;
**** If EventAttributeTypes is null, all event attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
**** If EventAttributeTypes is empty array or not specified, event attributes not applied to the root causes.&lt;br /&gt;
**** If the model doesn&#039;t have an event attribute with the specified name, an error message is shown.&lt;br /&gt;
**** Analysis column Name should contain Event Attribute Name.&lt;br /&gt;
**** Analysis column Value should contain Event Attribute Value and number of occurrences in case: &amp;lt;value&amp;gt; (count).&lt;br /&gt;
**** Analysis column Type should have &amp;quot;EventAttributeValue&amp;quot; as its value.&lt;br /&gt;
*** &#039;&#039;&#039;WeightingExpression&#039;&#039;&#039;: &lt;br /&gt;
**** Expression providing weights for each case.&lt;br /&gt;
**** Expression, if defined, must be any of the following types:&lt;br /&gt;
***** A string containing the [[SQL Expressions|SqlExpression]] to evaluate.&lt;br /&gt;
***** A SqlExpression object, created e.g., using ToSqlExpression-function (see also: [[QPR_ProcessAnalyzer_Expressions#In-memory_expression_blocks_in_SQL_expressions|In-memory expression blocks in SQL expressions]]).&lt;br /&gt;
**** A row is filtered out of result if expression result is null.&lt;br /&gt;
*** &#039;&#039;&#039;MaximumRowCount&#039;&#039;&#039;:&lt;br /&gt;
**** The maximum number of the most and least contributing root causes to return. Thus, the actual number of returned rows can be at most two times this value (if specified).&lt;br /&gt;
**** If undefined, 200 is used.&lt;br /&gt;
**** If set to 0, all rows are returned.&lt;br /&gt;
*** &#039;&#039;&#039;MinValueUsage&#039;&#039;&#039;: &lt;br /&gt;
**** The minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
***** If not defined or null, all values are included (=default).&lt;br /&gt;
*** &#039;&#039;&#039;MaxNumUniqueValues&#039;&#039;&#039;: &lt;br /&gt;
**** Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
**** If not defined or null, all values are included (=default).&lt;br /&gt;
*** &#039;&#039;&#039;IncludeOthers&#039;&#039;&#039;: &lt;br /&gt;
**** Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
**** If given any string value, that value is used as the value for all the aggregated values.&lt;br /&gt;
**** Default = undefined =&amp;gt; others values will not be included into the results.&lt;br /&gt;
*** &#039;&#039;&#039;ValueIfNull&#039;&#039;&#039;: Value used to indicate null-values.&lt;br /&gt;
**** Default = &amp;quot;(blank)&amp;quot;.&lt;br /&gt;
**** Must be not null.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] object with the following columns:&lt;br /&gt;
&lt;br /&gt;
* Common columns:&lt;br /&gt;
** &#039;&#039;&#039;Type&#039;&#039;&#039;: &lt;br /&gt;
*** Type of the root cause:&lt;br /&gt;
**** &amp;quot;CaseAttributeValue&amp;quot; for case attributes.&lt;br /&gt;
**** &amp;quot;EventAttributeValue&amp;quot; for event attributes.&lt;br /&gt;
** &#039;&#039;&#039;Name&#039;&#039;&#039;: &lt;br /&gt;
*** When type is CaseAttributeValue, case attribute name.&lt;br /&gt;
*** When type is EventAttributeValue, event attribute name.&lt;br /&gt;
** &#039;&#039;&#039;Value&#039;&#039;&#039;: &lt;br /&gt;
*** When type is CaseAttributeValue, case attribute value.&lt;br /&gt;
*** When type is EventAttributeValue, event attribute value and number of occurrences in case.&lt;br /&gt;
** &#039;&#039;&#039;Total&#039;&#039;&#039;: &lt;br /&gt;
*** The total number of cases having the found root cause.&lt;br /&gt;
** &#039;&#039;&#039;Selected&#039;&#039;&#039;: &lt;br /&gt;
*** The number of cases that have the found root cause and belong to the selected cases.&lt;br /&gt;
** &#039;&#039;&#039;Compared&#039;&#039;&#039;:&lt;br /&gt;
*** The number of cases that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
** Columns when WeightingExpression not have value:&lt;br /&gt;
*** &#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
**** The number of cases which contribute to the deviation from the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of cases which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
**** The deviation in percentage between selected cases with the found root cause and the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of selected cases that have the found root cause out of all cases with that root cause.&lt;br /&gt;
** Columns when WeightingExpression have value:&lt;br /&gt;
*** &#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of case weights which contribute to the deviation from the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of case weights which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
**** The deviation in percentage between selected case weights with the found root cause and the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;:&lt;br /&gt;
**** The percent of selected case weights that have the found root cause out of all case weights with that root cause.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;ComparedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;TotalWeight&#039;&#039;&#039;:&lt;br /&gt;
**** The sum of weights of all cases with that root cause.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Calculate root cause analysis for given model using parameters read from the [[Web API: Expression/query|query configuration]].&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;ProcessingMethod&amp;quot;: &amp;quot;DataFrame&amp;quot;,&lt;br /&gt;
  &amp;quot;Root&amp;quot;:&amp;quot;let m = _; _system.RootCauses.FindForDataFrame(m, _query.Configuration.Parameters.FindRootCausesParameters.Clone().Extend(#{\&amp;quot;Filter\&amp;quot;: _query.Configuration.Filter}))&amp;quot;,&lt;br /&gt;
  &amp;quot;Parameters&amp;quot;: {&lt;br /&gt;
    &amp;quot;FindRootCausesParameters&amp;quot;: {&lt;br /&gt;
      &amp;quot;CaseAttributeTypes&amp;quot;: [&amp;quot;Account Manager&amp;quot;,&amp;quot;Customer Group&amp;quot;,&amp;quot;Product Group&amp;quot;,&amp;quot;Region&amp;quot;],&lt;br /&gt;
      &amp;quot;Selection&amp;quot;: {&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]}]}]},&lt;br /&gt;
      &amp;quot;MaxNumUniqueValues&amp;quot;: 2,&lt;br /&gt;
      &amp;quot;MaximumRowCount&amp;quot;: 1000,&lt;br /&gt;
      &amp;quot;MinValueUsage&amp;quot;: 0.20,&lt;br /&gt;
      &amp;quot;WeightingExpression&amp;quot;: &amp;quot;Column(\&amp;quot;Cost\&amp;quot;)&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;Ordering&amp;quot;: [&lt;br /&gt;
    {&amp;quot;Name&amp;quot;: &amp;quot;Contribution&amp;quot;, &amp;quot;Direction&amp;quot;: &amp;quot;Descending&amp;quot;}&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Utils.GetSampledEvents ==&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied. &lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;sourceModel&#039;&#039;&#039;:&lt;br /&gt;
** ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
* &#039;&#039;&#039;sampledCaseCount&#039;&#039;&#039;:&lt;br /&gt;
** The maximum number of cases to return (or null if all cases should be returned).&lt;br /&gt;
* &#039;&#039;&#039;filter&#039;&#039;&#039;:&lt;br /&gt;
** JSON filter to be applied on the event data of the source model prior to performing the sampling.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
[[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Get a sample of all the events of at most 1000 cases having &amp;quot;Hats&amp;quot; as &amp;quot;Product Group&amp;quot; case attribute value from model identified by modelId.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let eventDataSampleSdf = _system.Utils.GetSampledEvents(modelId, 1000, #{&lt;br /&gt;
  &amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
    &amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
	&amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
	  &amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
	  &amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&lt;br /&gt;
	  &amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]&lt;br /&gt;
	}]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Utils.ModifyColumnTypes ==&lt;br /&gt;
In-place modifies the column types of given columns in given data table.&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
* &#039;&#039;&#039;dataTable&#039;&#039;&#039;:&lt;br /&gt;
** ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
* &#039;&#039;&#039;columnTypesToSet&#039;&#039;&#039;:&lt;br /&gt;
** Array of column name/type definitions to set.&lt;br /&gt;
*** Only columns that are to be changed are required to be listed.&lt;br /&gt;
*** Columns that don&#039;t exist in the data table will be skipped.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
Returns the modified data table object.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Modify column &amp;quot;CaseId&amp;quot; to be of type string.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.ModifyColumnTypes(&lt;br /&gt;
  eventsTable, &lt;br /&gt;
  #{#{&amp;quot;Name&amp;quot;: &amp;quot;CaseId&amp;quot;, &amp;quot;DataType&amp;quot;: &amp;quot;String&amp;quot;}}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Utils.RunFunctionWithParallelLogging ==&lt;br /&gt;
Runs given function that generates logging information into given data table in a way that all the logging will be included into the generated script run log as well (if run inside a script).&lt;br /&gt;
&lt;br /&gt;
Internally polls the table every 5 seconds for new rows and adds all the newly added rows to script log.&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;logTable&#039;&#039;&#039;:&lt;br /&gt;
** A DataTable used for logging.&lt;br /&gt;
* &#039;&#039;&#039;callbackFunc&#039;&#039;&#039;:&lt;br /&gt;
** Function that uses given data table for logging it&#039;s current status.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
The result of the callback function.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Run stored procedure named &amp;quot;StoredProcedureTest&amp;quot; in Snowflake, that generates new rows to log table identified by logTableId and log the generated rows into the script run log.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.RunFunctionWithParallelLogging(DataTableById(logTableId), () =&amp;gt; {&lt;br /&gt;
    CreateSnowflakeConnection().CallStoredProcedure(&amp;quot;StoredProcedureTest&amp;quot;, #{})&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25473</id>
		<title>System Library</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25473"/>
		<updated>2024-11-19T13:26:55Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;System library is a collection of Expression Language functions and properties that provide additional tools for scripting Process Analyzer functionalities. System library is referenced in scripts via &#039;&#039;_system&#039;&#039;-property, which provides additional properties dedicated for different areas of interests for scripting. &lt;br /&gt;
&lt;br /&gt;
The following hierarchy shows the properties and functions available in System Library:&lt;br /&gt;
&lt;br /&gt;
* ML&lt;br /&gt;
** GeneratePredictionModel (documentation here: [[Create Predicted Eventlog|GeneratePredictionModel]])&lt;br /&gt;
** ApplyTransformations (documentation here: [[Create Simulated Eventlog|ApplyTransformations]])&lt;br /&gt;
* Parallel&lt;br /&gt;
** Run&lt;br /&gt;
* RootCauses&lt;br /&gt;
** FindRootCausesDataFrame&lt;br /&gt;
* Utils&lt;br /&gt;
** GetSampledEvents&lt;br /&gt;
** ModifyColumnTypes&lt;br /&gt;
** RunFunctionWithParallelLogging&lt;br /&gt;
&lt;br /&gt;
== Parallel.Run ==&lt;br /&gt;
Runs given functions in parallel.&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
* &#039;&#039;&#039;functions&#039;&#039;&#039;:&lt;br /&gt;
** An array of functions to run in parallel.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
An array of results returned by the called functions, in the same order as the function generating them in the &#039;&#039;functions&#039;&#039;-parameter.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The following script uses _system.Parallel.Run to run three functions:&lt;br /&gt;
&lt;br /&gt;
* SAP-extraction from VBAK-table in SAP (connection parameters defined in connectionParametersDict-dictionary).&lt;br /&gt;
* Transform the extracted data by adding a new column.&lt;br /&gt;
* Load the data into data table identified by dataTableId.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ExtractTransformAndLoad(extractFunc, transformFunc, loadFunc)&lt;br /&gt;
{&lt;br /&gt;
  let rawDataFlow = extractFunc();&lt;br /&gt;
  let transformedDataFlow = ToDataFlow();&lt;br /&gt;
&lt;br /&gt;
  _system.Parallel.Run([&lt;br /&gt;
    () =&amp;gt; Catch({&lt;br /&gt;
      let df;&lt;br /&gt;
      while (!IsNullTop(df = rawDataFlow.Collect(#{&amp;quot;CollectChunk&amp;quot;: true}))) {&lt;br /&gt;
        transformedDataFlow.Append(transformFunc(df));&lt;br /&gt;
        WriteLog(`A chunk having ${df.NRows} rows has been transformed.`);&lt;br /&gt;
      }&lt;br /&gt;
      if (rawDataFlow.HasError) {&lt;br /&gt;
        transformedDataFlow.Fail(&amp;quot;Error occurred during data extraction.&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        transformedDataFlow.Complete();&lt;br /&gt;
      }&lt;br /&gt;
    }, {&lt;br /&gt;
      transformedDataFlow.Fail(&amp;quot;Error occurred during transformation calculation.&amp;quot;);&lt;br /&gt;
    }),&lt;br /&gt;
    () =&amp;gt; {&lt;br /&gt;
      loadFunc(transformedDataFlow);&lt;br /&gt;
    }&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ExtractTransformAndLoad(&lt;br /&gt;
  () =&amp;gt; ExtractSap(connectionParametersDict.Extend(&lt;br /&gt;
    [&lt;br /&gt;
    &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
    &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
	&amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0060000042&#039;&amp;quot;],&lt;br /&gt;
    &amp;quot;UseGateway&amp;quot;: true&lt;br /&gt;
    ])&lt;br /&gt;
  ),&lt;br /&gt;
  df =&amp;gt; df.SetColumns([&amp;quot;Test&amp;quot;: () =&amp;gt; `${Column(&amp;quot;NETWR&amp;quot;)} ${Column(&amp;quot;WAERK&amp;quot;)}`]),&lt;br /&gt;
  dataFlow =&amp;gt; {&lt;br /&gt;
    DataTableById(dataTableId).Import(dataFlow, [&amp;quot;Append&amp;quot;: 0]);&lt;br /&gt;
  }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
DataTableById(dataTableId).SqlDataFrame.OrderByColumns([&amp;quot;VBELN&amp;quot;], [true]).Collect()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== RootCauses.FindForDataFrame ==&lt;br /&gt;
Finds root causes for a particular process phenomenon by comparing properties of selected cases against those of all cases in given model.&lt;br /&gt;
&lt;br /&gt;
Based on the similar in-memory function: [[FindRootCauses Function|EventLog.FindRootCauses]].&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;model&#039;&#039;&#039;:&lt;br /&gt;
** Model object of a model whose data tables are used to calculate the root causes.&lt;br /&gt;
* &#039;&#039;&#039;parameters&#039;&#039;&#039;:&lt;br /&gt;
** A parameter convertible to a StringDictionary object with the following supported key-values:&lt;br /&gt;
*** &#039;&#039;&#039;Filter&#039;&#039;&#039;: &lt;br /&gt;
**** Filter json (#30921#) that is applied to the event log before calculating root causes.&lt;br /&gt;
*** &#039;&#039;&#039;Selection&#039;&#039;&#039;: &lt;br /&gt;
**** Selection json (#30927#) that defines selected cases to find root causes for. Is applied on top of the filtered event log (specified by Filter parameter).&lt;br /&gt;
**** If nothing is selected, 100% of cases are counted as selected.&lt;br /&gt;
*** &#039;&#039;&#039;CaseAttributeTypes&#039;&#039;&#039;: &lt;br /&gt;
**** An array of strings with the names of case attributes included into the root causes.&lt;br /&gt;
**** Only case attributes of type string, integer or boolean are included.&lt;br /&gt;
**** If CaseAttributeTypes is null or empty string or not specified, all case attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
**** If the model doesn&#039;t have a case attribute with the specified name, an error message is shown.&lt;br /&gt;
*** &#039;&#039;&#039;EventAttributeTypes&#039;&#039;&#039;:&lt;br /&gt;
**** An array of strings with the names of event attributes included into the root causes.&lt;br /&gt;
**** Only event attributes of type string are included.&lt;br /&gt;
**** If EventAttributeTypes is null, all event attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
**** If EventAttributeTypes is empty array or not specified, event attributes not applied to the root causes.&lt;br /&gt;
**** If the model doesn&#039;t have an event attribute with the specified name, an error message is shown.&lt;br /&gt;
**** Analysis column Name should contain Event Attribute Name.&lt;br /&gt;
**** Analysis column Value should contain Event Attribute Value and number of occurrences in case: &amp;lt;value&amp;gt; (count).&lt;br /&gt;
**** Analysis column Type should have &amp;quot;EventAttributeValue&amp;quot; as its value.&lt;br /&gt;
*** &#039;&#039;&#039;WeightingExpression&#039;&#039;&#039;: &lt;br /&gt;
**** Expression providing weights for each case.&lt;br /&gt;
**** Expression, if defined, must be any of the following types:&lt;br /&gt;
***** A string containing the [[SQL Expressions|SqlExpression]] to evaluate.&lt;br /&gt;
***** A SqlExpression object, created e.g., using ToSqlExpression-function (see also: [[QPR_ProcessAnalyzer_Expressions#In-memory_expression_blocks_in_SQL_expressions|In-memory expression blocks in SQL expressions]]).&lt;br /&gt;
**** A row is filtered out of result if expression result is null.&lt;br /&gt;
*** &#039;&#039;&#039;MaximumRowCount&#039;&#039;&#039;:&lt;br /&gt;
**** The maximum number of the most and least contributing root causes to return. Thus, the actual number of returned rows can be at most two times this value (if specified).&lt;br /&gt;
**** If undefined, 200 is used.&lt;br /&gt;
**** If set to 0, all rows are returned.&lt;br /&gt;
*** &#039;&#039;&#039;MinValueUsage&#039;&#039;&#039;: &lt;br /&gt;
**** The minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
***** If not defined or null, all values are included (=default).&lt;br /&gt;
*** &#039;&#039;&#039;MaxNumUniqueValues&#039;&#039;&#039;: &lt;br /&gt;
**** Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
**** If not defined or null, all values are included (=default).&lt;br /&gt;
*** &#039;&#039;&#039;IncludeOthers&#039;&#039;&#039;: &lt;br /&gt;
**** Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
**** If given any string value, that value is used as the value for all the aggregated values.&lt;br /&gt;
**** Default = undefined =&amp;gt; others values will not be included into the results.&lt;br /&gt;
*** &#039;&#039;&#039;ValueIfNull&#039;&#039;&#039;: Value used to indicate null-values.&lt;br /&gt;
**** Default = &amp;quot;(blank)&amp;quot;.&lt;br /&gt;
**** Must be not null.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] object with the following columns:&lt;br /&gt;
&lt;br /&gt;
* Common columns:&lt;br /&gt;
** &#039;&#039;&#039;Type&#039;&#039;&#039;: &lt;br /&gt;
*** Type of the root cause:&lt;br /&gt;
**** &amp;quot;CaseAttributeValue&amp;quot; for case attributes.&lt;br /&gt;
**** &amp;quot;EventAttributeValue&amp;quot; for event attributes.&lt;br /&gt;
** &#039;&#039;&#039;Name&#039;&#039;&#039;: &lt;br /&gt;
*** When type is CaseAttributeValue, case attribute name.&lt;br /&gt;
*** When type is EventAttributeValue, event attribute name.&lt;br /&gt;
** &#039;&#039;&#039;Value&#039;&#039;&#039;: &lt;br /&gt;
*** When type is CaseAttributeValue, case attribute value.&lt;br /&gt;
*** When type is EventAttributeValue, event attribute value and number of occurrences in case.&lt;br /&gt;
** &#039;&#039;&#039;Total&#039;&#039;&#039;: &lt;br /&gt;
*** The total number of cases having the found root cause.&lt;br /&gt;
** &#039;&#039;&#039;Selected&#039;&#039;&#039;: &lt;br /&gt;
*** The number of cases that have the found root cause and belong to the selected cases.&lt;br /&gt;
** &#039;&#039;&#039;Compared&#039;&#039;&#039;:&lt;br /&gt;
*** The number of cases that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
** Columns when WeightingExpression not have value:&lt;br /&gt;
*** &#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
**** The number of cases which contribute to the deviation from the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of cases which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
**** The deviation in percentage between selected cases with the found root cause and the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of selected cases that have the found root cause out of all cases with that root cause.&lt;br /&gt;
** Columns when WeightingExpression have value:&lt;br /&gt;
*** &#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of case weights which contribute to the deviation from the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of case weights which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
**** The deviation in percentage between selected case weights with the found root cause and the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;:&lt;br /&gt;
**** The percent of selected case weights that have the found root cause out of all case weights with that root cause.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;ComparedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;TotalWeight&#039;&#039;&#039;:&lt;br /&gt;
**** The sum of weights of all cases with that root cause.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Calculate root cause analysis for given model using parameters read from the [[Web API: Expression/query|query configuration]].&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;ProcessingMethod&amp;quot;: &amp;quot;DataFrame&amp;quot;,&lt;br /&gt;
  &amp;quot;Root&amp;quot;:&amp;quot;let m = _; _system.RootCauses.FindForDataFrame(m, _query.Configuration.Parameters.FindRootCausesParameters.Clone().Extend(#{\&amp;quot;Filter\&amp;quot;: _query.Configuration.Filter}))&amp;quot;,&lt;br /&gt;
  &amp;quot;Parameters&amp;quot;: {&lt;br /&gt;
    &amp;quot;FindRootCausesParameters&amp;quot;: {&lt;br /&gt;
      &amp;quot;CaseAttributeTypes&amp;quot;: [&amp;quot;Account Manager&amp;quot;,&amp;quot;Customer Group&amp;quot;,&amp;quot;Product Group&amp;quot;,&amp;quot;Region&amp;quot;],&lt;br /&gt;
      &amp;quot;Selection&amp;quot;: {&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]}]}]},&lt;br /&gt;
      &amp;quot;MaxNumUniqueValues&amp;quot;: 2,&lt;br /&gt;
      &amp;quot;MaximumRowCount&amp;quot;: 1000,&lt;br /&gt;
      &amp;quot;MinValueUsage&amp;quot;: 0.20,&lt;br /&gt;
      &amp;quot;WeightingExpression&amp;quot;: &amp;quot;Column(\&amp;quot;Cost\&amp;quot;)&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;Ordering&amp;quot;: [&lt;br /&gt;
    {&amp;quot;Name&amp;quot;: &amp;quot;Contribution&amp;quot;, &amp;quot;Direction&amp;quot;: &amp;quot;Descending&amp;quot;}&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Utils.GetSampledEvents ==&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied. &lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;sourceModel&#039;&#039;&#039;:&lt;br /&gt;
** ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
* &#039;&#039;&#039;sampledCaseCount&#039;&#039;&#039;:&lt;br /&gt;
** The maximum number of cases to return (or null if all cases should be returned).&lt;br /&gt;
* &#039;&#039;&#039;filter&#039;&#039;&#039;:&lt;br /&gt;
** JSON filter to be applied on the event data of the source model prior to performing the sampling.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
[[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Get a sample of all the events of at most 1000 cases having &amp;quot;Hats&amp;quot; as &amp;quot;Product Group&amp;quot; case attribute value from model identified by modelId.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let eventDataSampleSdf = _system.Utils.GetSampledEvents(modelId, 1000, #{&lt;br /&gt;
  &amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
    &amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
	&amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
	  &amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
	  &amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&lt;br /&gt;
	  &amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]&lt;br /&gt;
	}]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Utils.ModifyColumnTypes ==&lt;br /&gt;
In-place modifies the column types of given columns in given data table.&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
* &#039;&#039;&#039;dataTable&#039;&#039;&#039;:&lt;br /&gt;
** ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
* &#039;&#039;&#039;columnTypesToSet&#039;&#039;&#039;:&lt;br /&gt;
** Array of column name/type definitions to set.&lt;br /&gt;
*** Only columns that are to be changed are required to be listed.&lt;br /&gt;
*** Columns that don&#039;t exist in the data table will be skipped.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
Returns the modified data table object.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Modify column &amp;quot;CaseId&amp;quot; to be of type string.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.ModifyColumnTypes(&lt;br /&gt;
  eventsTable, &lt;br /&gt;
  #{#{&amp;quot;Name&amp;quot;: &amp;quot;CaseId&amp;quot;, &amp;quot;DataType&amp;quot;: &amp;quot;String&amp;quot;}}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Utils.RunFunctionWithParallelLogging ==&lt;br /&gt;
Runs given function that generates logging information into given data table in a way that all the logging will be included into the generated script run log as well (if run inside a script).&lt;br /&gt;
&lt;br /&gt;
Internally polls the table every 5 seconds for new rows and adds all the newly added rows to script log.&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;logTable&#039;&#039;&#039;:&lt;br /&gt;
** A DataTable used for logging.&lt;br /&gt;
* &#039;&#039;&#039;callbackFunc&#039;&#039;&#039;:&lt;br /&gt;
** Function that uses given data table for logging it&#039;s current status.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
The result of the callback function.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Run given stored procedure that generates new rows to log table identified by logTableId and log the generated rows into the script run log.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.RunFunctionWithParallelLogging(DataTableById(logTableId), () =&amp;gt; {&lt;br /&gt;
    CreateSnowflakeConnection().CallStoredProcedure(&amp;quot;StoredProcedureTest&amp;quot;, #{})&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
	<entry>
		<id>https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25472</id>
		<title>System Library</title>
		<link rel="alternate" type="text/html" href="https://wiki.onqpr.com/pa/index.php?title=System_Library&amp;diff=25472"/>
		<updated>2024-11-19T13:26:24Z</updated>

		<summary type="html">&lt;p&gt;MarHink: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;System library is a collection of Expression Language functions and properties that provide additional tools for scripting Process Analyzer functionalities. System library is referenced in scripts via &#039;&#039;_system&#039;&#039;-property, which provides additional properties dedicated for different areas of interests for scripting. &lt;br /&gt;
&lt;br /&gt;
The following hierarchy shows the properties and functions available in System Library:&lt;br /&gt;
&lt;br /&gt;
* ML&lt;br /&gt;
** GeneratePredictionModel (documented here: [[Create Predicted Eventlog|GeneratePredictionModel]])&lt;br /&gt;
** ApplyTransformations (documented here: [[Create Simulated Eventlog|ApplyTransformations]])&lt;br /&gt;
* Parallel&lt;br /&gt;
** Run&lt;br /&gt;
* RootCauses&lt;br /&gt;
** FindRootCausesDataFrame&lt;br /&gt;
* Utils&lt;br /&gt;
** GetSampledEvents&lt;br /&gt;
** ModifyColumnTypes&lt;br /&gt;
** RunFunctionWithParallelLogging&lt;br /&gt;
&lt;br /&gt;
== Parallel.Run ==&lt;br /&gt;
Runs given functions in parallel.&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
* &#039;&#039;&#039;functions&#039;&#039;&#039;:&lt;br /&gt;
** An array of functions to run in parallel.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
An array of results returned by the called functions, in the same order as the function generating them in the &#039;&#039;functions&#039;&#039;-parameter.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
The following script uses _system.Parallel.Run to run three functions:&lt;br /&gt;
&lt;br /&gt;
* SAP-extraction from VBAK-table in SAP (connection parameters defined in connectionParametersDict-dictionary).&lt;br /&gt;
* Transform the extracted data by adding a new column.&lt;br /&gt;
* Load the data into data table identified by dataTableId.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
function ExtractTransformAndLoad(extractFunc, transformFunc, loadFunc)&lt;br /&gt;
{&lt;br /&gt;
  let rawDataFlow = extractFunc();&lt;br /&gt;
  let transformedDataFlow = ToDataFlow();&lt;br /&gt;
&lt;br /&gt;
  _system.Parallel.Run([&lt;br /&gt;
    () =&amp;gt; Catch({&lt;br /&gt;
      let df;&lt;br /&gt;
      while (!IsNullTop(df = rawDataFlow.Collect(#{&amp;quot;CollectChunk&amp;quot;: true}))) {&lt;br /&gt;
        transformedDataFlow.Append(transformFunc(df));&lt;br /&gt;
        WriteLog(`A chunk having ${df.NRows} rows has been transformed.`);&lt;br /&gt;
      }&lt;br /&gt;
      if (rawDataFlow.HasError) {&lt;br /&gt;
        transformedDataFlow.Fail(&amp;quot;Error occurred during data extraction.&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        transformedDataFlow.Complete();&lt;br /&gt;
      }&lt;br /&gt;
    }, {&lt;br /&gt;
      transformedDataFlow.Fail(&amp;quot;Error occurred during transformation calculation.&amp;quot;);&lt;br /&gt;
    }),&lt;br /&gt;
    () =&amp;gt; {&lt;br /&gt;
      loadFunc(transformedDataFlow);&lt;br /&gt;
    }&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ExtractTransformAndLoad(&lt;br /&gt;
  () =&amp;gt; ExtractSap(connectionParametersDict.Extend(&lt;br /&gt;
    [&lt;br /&gt;
    &amp;quot;FieldNames&amp;quot;: &amp;quot;VBELN,ERDAT,ERZET,ERNAM,NETWR,WAERK&amp;quot;, &lt;br /&gt;
    &amp;quot;QueryTable&amp;quot;: &amp;quot;VBAK&amp;quot;,&lt;br /&gt;
	&amp;quot;Options&amp;quot;: [&amp;quot;VBELN BETWEEN &#039;0000017448&#039;&amp;quot; ,&amp;quot;AND &#039;0060000042&#039;&amp;quot;],&lt;br /&gt;
    &amp;quot;UseGateway&amp;quot;: true&lt;br /&gt;
    ])&lt;br /&gt;
  ),&lt;br /&gt;
  df =&amp;gt; df.SetColumns([&amp;quot;Test&amp;quot;: () =&amp;gt; `${Column(&amp;quot;NETWR&amp;quot;)} ${Column(&amp;quot;WAERK&amp;quot;)}`]),&lt;br /&gt;
  dataFlow =&amp;gt; {&lt;br /&gt;
    DataTableById(dataTableId).Import(dataFlow, [&amp;quot;Append&amp;quot;: 0]);&lt;br /&gt;
  }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
DataTableById(dataTableId).SqlDataFrame.OrderByColumns([&amp;quot;VBELN&amp;quot;], [true]).Collect()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== RootCauses.FindForDataFrame ==&lt;br /&gt;
Finds root causes for a particular process phenomenon by comparing properties of selected cases against those of all cases in given model.&lt;br /&gt;
&lt;br /&gt;
Based on the similar in-memory function: [[FindRootCauses Function|EventLog.FindRootCauses]].&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;model&#039;&#039;&#039;:&lt;br /&gt;
** Model object of a model whose data tables are used to calculate the root causes.&lt;br /&gt;
* &#039;&#039;&#039;parameters&#039;&#039;&#039;:&lt;br /&gt;
** A parameter convertible to a StringDictionary object with the following supported key-values:&lt;br /&gt;
*** &#039;&#039;&#039;Filter&#039;&#039;&#039;: &lt;br /&gt;
**** Filter json (#30921#) that is applied to the event log before calculating root causes.&lt;br /&gt;
*** &#039;&#039;&#039;Selection&#039;&#039;&#039;: &lt;br /&gt;
**** Selection json (#30927#) that defines selected cases to find root causes for. Is applied on top of the filtered event log (specified by Filter parameter).&lt;br /&gt;
**** If nothing is selected, 100% of cases are counted as selected.&lt;br /&gt;
*** &#039;&#039;&#039;CaseAttributeTypes&#039;&#039;&#039;: &lt;br /&gt;
**** An array of strings with the names of case attributes included into the root causes.&lt;br /&gt;
**** Only case attributes of type string, integer or boolean are included.&lt;br /&gt;
**** If CaseAttributeTypes is null or empty string or not specified, all case attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
**** If the model doesn&#039;t have a case attribute with the specified name, an error message is shown.&lt;br /&gt;
*** &#039;&#039;&#039;EventAttributeTypes&#039;&#039;&#039;:&lt;br /&gt;
**** An array of strings with the names of event attributes included into the root causes.&lt;br /&gt;
**** Only event attributes of type string are included.&lt;br /&gt;
**** If EventAttributeTypes is null, all event attributes having type &amp;quot;String&amp;quot; are included into the root causes.&lt;br /&gt;
**** If EventAttributeTypes is empty array or not specified, event attributes not applied to the root causes.&lt;br /&gt;
**** If the model doesn&#039;t have an event attribute with the specified name, an error message is shown.&lt;br /&gt;
**** Analysis column Name should contain Event Attribute Name.&lt;br /&gt;
**** Analysis column Value should contain Event Attribute Value and number of occurrences in case: &amp;lt;value&amp;gt; (count).&lt;br /&gt;
**** Analysis column Type should have &amp;quot;EventAttributeValue&amp;quot; as its value.&lt;br /&gt;
*** &#039;&#039;&#039;WeightingExpression&#039;&#039;&#039;: &lt;br /&gt;
**** Expression providing weights for each case.&lt;br /&gt;
**** Expression, if defined, must be any of the following types:&lt;br /&gt;
***** A string containing the [[SQL Expressions|SqlExpression]] to evaluate.&lt;br /&gt;
***** A SqlExpression object, created e.g., using ToSqlExpression-function (see also: [[QPR_ProcessAnalyzer_Expressions#In-memory_expression_blocks_in_SQL_expressions|In-memory expression blocks in SQL expressions]]).&lt;br /&gt;
**** A row is filtered out of result if expression result is null.&lt;br /&gt;
*** &#039;&#039;&#039;MaximumRowCount&#039;&#039;&#039;:&lt;br /&gt;
**** The maximum number of the most and least contributing root causes to return. Thus, the actual number of returned rows can be at most two times this value (if specified).&lt;br /&gt;
**** If undefined, 200 is used.&lt;br /&gt;
**** If set to 0, all rows are returned.&lt;br /&gt;
*** &#039;&#039;&#039;MinValueUsage&#039;&#039;&#039;: &lt;br /&gt;
**** The minimum total usage of a value included into the comparison. The number of cases having every returned value should be at least given percentage (a float value between 0.0 and 1.0) of all the compared cases.&lt;br /&gt;
***** If not defined or null, all values are included (=default).&lt;br /&gt;
*** &#039;&#039;&#039;MaxNumUniqueValues&#039;&#039;&#039;: &lt;br /&gt;
**** Maximum number of unique values to include into the comparison for each attribute column. If the amount of unique values for any attribute exceeds this value, only given number of attributes are included that have the highest usage.&lt;br /&gt;
**** If not defined or null, all values are included (=default).&lt;br /&gt;
*** &#039;&#039;&#039;IncludeOthers&#039;&#039;&#039;: &lt;br /&gt;
**** Should the rest of the attribute values not included due to MinValueUsage or MaxNumUniqueValues filtering be included as an aggregated &amp;quot;Others&amp;quot; value?&lt;br /&gt;
**** If given any string value, that value is used as the value for all the aggregated values.&lt;br /&gt;
**** Default = undefined =&amp;gt; others values will not be included into the results.&lt;br /&gt;
*** &#039;&#039;&#039;ValueIfNull&#039;&#039;&#039;: Value used to indicate null-values.&lt;br /&gt;
**** Default = &amp;quot;(blank)&amp;quot;.&lt;br /&gt;
**** Must be not null.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] object with the following columns:&lt;br /&gt;
&lt;br /&gt;
* Common columns:&lt;br /&gt;
** &#039;&#039;&#039;Type&#039;&#039;&#039;: &lt;br /&gt;
*** Type of the root cause:&lt;br /&gt;
**** &amp;quot;CaseAttributeValue&amp;quot; for case attributes.&lt;br /&gt;
**** &amp;quot;EventAttributeValue&amp;quot; for event attributes.&lt;br /&gt;
** &#039;&#039;&#039;Name&#039;&#039;&#039;: &lt;br /&gt;
*** When type is CaseAttributeValue, case attribute name.&lt;br /&gt;
*** When type is EventAttributeValue, event attribute name.&lt;br /&gt;
** &#039;&#039;&#039;Value&#039;&#039;&#039;: &lt;br /&gt;
*** When type is CaseAttributeValue, case attribute value.&lt;br /&gt;
*** When type is EventAttributeValue, event attribute value and number of occurrences in case.&lt;br /&gt;
** &#039;&#039;&#039;Total&#039;&#039;&#039;: &lt;br /&gt;
*** The total number of cases having the found root cause.&lt;br /&gt;
** &#039;&#039;&#039;Selected&#039;&#039;&#039;: &lt;br /&gt;
*** The number of cases that have the found root cause and belong to the selected cases.&lt;br /&gt;
** &#039;&#039;&#039;Compared&#039;&#039;&#039;:&lt;br /&gt;
*** The number of cases that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
** Columns when WeightingExpression not have value:&lt;br /&gt;
*** &#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
**** The number of cases which contribute to the deviation from the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of cases which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
**** The deviation in percentage between selected cases with the found root cause and the average percentage of selected cases among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of selected cases that have the found root cause out of all cases with that root cause.&lt;br /&gt;
** Columns when WeightingExpression have value:&lt;br /&gt;
*** &#039;&#039;&#039;Contribution&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of case weights which contribute to the deviation from the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;ContributionPercentage&#039;&#039;&#039;: &lt;br /&gt;
**** The percent of case weights which contribute to the deviation from the average percentage.&lt;br /&gt;
*** &#039;&#039;&#039;DifferencePercentage&#039;&#039;&#039;:&lt;br /&gt;
**** The deviation in percentage between selected case weights with the found root cause and the average percentage of selected case weights among all analyzed cases.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedPercentage&#039;&#039;&#039;:&lt;br /&gt;
**** The percent of selected case weights that have the found root cause out of all case weights with that root cause.&lt;br /&gt;
*** &#039;&#039;&#039;SelectedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;ComparedWeight&#039;&#039;&#039;: &lt;br /&gt;
**** The sum of weights that have the found root cause and don&#039;t belong to the selected cases.&lt;br /&gt;
*** &#039;&#039;&#039;TotalWeight&#039;&#039;&#039;:&lt;br /&gt;
**** The sum of weights of all cases with that root cause.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Calculate root cause analysis for given model using parameters read from the [[Web API: Expression/query|query configuration]].&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;ProcessingMethod&amp;quot;: &amp;quot;DataFrame&amp;quot;,&lt;br /&gt;
  &amp;quot;Root&amp;quot;:&amp;quot;let m = _; _system.RootCauses.FindForDataFrame(m, _query.Configuration.Parameters.FindRootCausesParameters.Clone().Extend(#{\&amp;quot;Filter\&amp;quot;: _query.Configuration.Filter}))&amp;quot;,&lt;br /&gt;
  &amp;quot;Parameters&amp;quot;: {&lt;br /&gt;
    &amp;quot;FindRootCausesParameters&amp;quot;: {&lt;br /&gt;
      &amp;quot;CaseAttributeTypes&amp;quot;: [&amp;quot;Account Manager&amp;quot;,&amp;quot;Customer Group&amp;quot;,&amp;quot;Product Group&amp;quot;,&amp;quot;Region&amp;quot;],&lt;br /&gt;
      &amp;quot;Selection&amp;quot;: {&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&amp;quot;Items&amp;quot;:[{&amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]}]}]},&lt;br /&gt;
      &amp;quot;MaxNumUniqueValues&amp;quot;: 2,&lt;br /&gt;
      &amp;quot;MaximumRowCount&amp;quot;: 1000,&lt;br /&gt;
      &amp;quot;MinValueUsage&amp;quot;: 0.20,&lt;br /&gt;
      &amp;quot;WeightingExpression&amp;quot;: &amp;quot;Column(\&amp;quot;Cost\&amp;quot;)&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;Ordering&amp;quot;: [&lt;br /&gt;
    {&amp;quot;Name&amp;quot;: &amp;quot;Contribution&amp;quot;, &amp;quot;Direction&amp;quot;: &amp;quot;Descending&amp;quot;}&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Utils.GetSampledEvents ==&lt;br /&gt;
Returns a [[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied. &lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;sourceModel&#039;&#039;&#039;:&lt;br /&gt;
** ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
* &#039;&#039;&#039;sampledCaseCount&#039;&#039;&#039;:&lt;br /&gt;
** The maximum number of cases to return (or null if all cases should be returned).&lt;br /&gt;
* &#039;&#039;&#039;filter&#039;&#039;&#039;:&lt;br /&gt;
** JSON filter to be applied on the event data of the source model prior to performing the sampling.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
[[SqlDataFrame in Expression Language|SqlDataFrame]] containing sampled events of given model where given filter is first applied.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Get a sample of all the events of at most 1000 cases having &amp;quot;Hats&amp;quot; as &amp;quot;Product Group&amp;quot; case attribute value from model identified by modelId.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
let eventDataSampleSdf = _system.Utils.GetSampledEvents(modelId, 1000, #{&lt;br /&gt;
  &amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
    &amp;quot;Type&amp;quot;:&amp;quot;IncludeCases&amp;quot;,&lt;br /&gt;
	&amp;quot;Items&amp;quot;:[#{&lt;br /&gt;
	  &amp;quot;Type&amp;quot;:&amp;quot;CaseAttributeValue&amp;quot;,&lt;br /&gt;
	  &amp;quot;Attribute&amp;quot;:&amp;quot;Product Group&amp;quot;,&lt;br /&gt;
	  &amp;quot;StringifiedValues&amp;quot;:[&amp;quot;0Hats&amp;quot;]&lt;br /&gt;
	}]&lt;br /&gt;
  }]&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Utils.ModifyColumnTypes ==&lt;br /&gt;
In-place modifies the column types of given columns in given data table.&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
* &#039;&#039;&#039;dataTable&#039;&#039;&#039;:&lt;br /&gt;
** ProcessAnalyzer model object of the model whose event data is to be filtered and sampled.&lt;br /&gt;
* &#039;&#039;&#039;columnTypesToSet&#039;&#039;&#039;:&lt;br /&gt;
** Array of column name/type definitions to set.&lt;br /&gt;
*** Only columns that are to be changed are required to be listed.&lt;br /&gt;
*** Columns that don&#039;t exist in the data table will be skipped.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
Returns the modified data table object.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Modify column &amp;quot;CaseId&amp;quot; to be of type string.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.ModifyColumnTypes(&lt;br /&gt;
  eventsTable, &lt;br /&gt;
  #{#{&amp;quot;Name&amp;quot;: &amp;quot;CaseId&amp;quot;, &amp;quot;DataType&amp;quot;: &amp;quot;String&amp;quot;}}&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Utils.RunFunctionWithParallelLogging ==&lt;br /&gt;
Runs given function that generates logging information into given data table in a way that all the logging will be included into the generated script run log as well (if run inside a script).&lt;br /&gt;
&lt;br /&gt;
Internally polls the table every 5 seconds for new rows and adds all the newly added rows to script log.&lt;br /&gt;
&lt;br /&gt;
=== Parameters ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;logTable&#039;&#039;&#039;:&lt;br /&gt;
** A DataTable used for logging.&lt;br /&gt;
* &#039;&#039;&#039;callbackFunc&#039;&#039;&#039;:&lt;br /&gt;
** Function that uses given data table for logging it&#039;s current status.&lt;br /&gt;
&lt;br /&gt;
=== Return value ===&lt;br /&gt;
The result of the callback function.&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
Run given stored procedure that generates new rows to log table identified by logTableId and log the generated rows into the script run log.&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
_system.Utils.RunFunctionWithParallelLogging(DataTableById(logTableId), () =&amp;gt; {&lt;br /&gt;
    CreateSnowflakeConnection().CallStoredProcedure(&amp;quot;StoredProcedureTest&amp;quot;, #{})&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>MarHink</name></author>
	</entry>
</feed>