Main Content

Customizing Model Inventory: Risk Tiering

This example shows how to customize the Model Inventory to hold information specific to your organization.

You can customize the model data entry and the model summary table. You can also add new filters to the Inventory Browser app to make it easy to find models with a particular value of a custom attribute.

This example uses a simple tree-based approach to Risk Tiering as an example. The model is from a paper by Mankatonia and Joshi (Measuring model risk: a practitioner’s approach, RMA Journal, 2013). This example along with other examples are also discussed in a paper by Kiritz, Ravitz and Levonian (Model risk tiering: an exploration of industry practices and principles, Journal of Risk Model Validation, 2019).

Add Custom Data to Inventory Browser

The inventory data is most likely stored outside of MATLAB®, for example, in a database. Various features of the Inventory Browser such as the model entry form do not access this external resource directly - rather, they interact with it through a client. Modelscape™ supports both database-backed and (for test use) in-memory clients.

Add new model-specific data to the Inventory Browser as references. To do this:

  • If necessary, create a new type for the reference.

  • Create the reference itself.

  • Associate the reference to a given model.

Create a new reference type called RiskTieringData with the following attributes:

  • RiskPriceValueUse: a string Unset, True or False denoting whether the model is used to measure risk, price, or value.

  • CriticalUse: a string Unset, True or False denoting whether the model is used for critical business decisions, regulatory reporting, or similar.

  • Exposure: a string Unset, High, Medium, or Low denoting the exposure level of the model.

  • Override: a string Unset, High, Medium, or Low denoting a risk tier level override.

  • RiskTier: a string Unset, High, Medium, or Low denoting the final risk tier which is worked out from the information above.

Construct a client with a model and this reference type, and attach a reference to this model. To learn more about how to do this, contact MathWorks Consulting Services.

Open an Inventory session with this client.

app = mrm.inventory.InventoryApp(client);
app.open

Customize Model Data Entry

Customize the Inventory Browser model entry by adding new tabs next to the 'Details' included in the default view, and by changing the layout and contents of the 'Details' tab. Implement these customizations as subclasses of mrm.inventory.model.FormCustomization. Include the subclasses in +mrm/+inventory/+custom/+model/ folder on the MATLAB path. Implement the following methods for each subclass.

  • The constructor must take two inputs: the parent mrm.inventory.model.Form object and the client carried by the Form. If you want to assign these to the Form and Client properties, leave this to the base class constructor and not implement this at all.

  • Use populateCustomContents(this) to set up the additional tab and any controls such as drop-downs.

  • onModelSet(this) must obtain the required references through the client and set these values to the controls. Note that the identifier of the model being displayed on the forms can be obtained from the GUIDEdit property of the parent form.

  • onSubmit(this) must read the values carried by the dropdowns and other controls and use the client to update the relevant references associated to the model.

Note that you can customize the 'Details' tab by modifying the layout grid, stored as the DetailsLayout property, of the parent mrm.inventory.model.Form object. Here are some examples of possible customizations.

  1. Replace controls with new custom controls by hiding the existing control. To do this, use the property Visible and create a new control in the same location in the grid.

  2. Add new controls to the form by resizing the DetailsLayout grid.

  3. Reorganize controls by using Layout.Row properties.

Finally, you can overwrite the labels of the 'Details' tab controls in a customization class. See mrm.inventory.model.Form for the names of properties defining the labels.

The following code snippet illustrates how you can apply these customizations to implement a risk tiering form. For the form layout, populateCustomContents creates dropdowns for RiskPriceValueUse, CriticalUse, Exposure and Override, and a non-editable label to display the resulting RiskTier. There is a button to recalculate the risk tier, as you want this to happen only once all the required inputs have been considered and set. Finally, the example shows a mechanism for displaying whether the risk tier stored in the inventory is in sync with the chosen inputs - if not, the comment '(Stale)' is added to the risk tier. The new tiering data is stored in the Inventory only when the 'Update' button is pressed.

classdef RiskTieringCustomTab < mrm.inventory.model.FormCustomization
%Implements a custom tab for calculating the risk tier

%   Copyright 2021-2023 The MathWorks, Inc.


    properties (Access = private)
        % Base grid
        Grid(1,1) matlab.ui.container.GridLayout

        % Tree model controls
        UsageDD(1,1) matlab.ui.control.DropDown
        CriticalUseDD(1,1) matlab.ui.control.DropDown
        ExposureLevelDD(1,1) matlab.ui.control.DropDown
        OverrideDD(1,1) matlab.ui.control.DropDown

        CalculateButton(1,1) matlab.ui.control.Button
        RiskTierLabel(1,1) matlab.ui.control.Label

        % Convenience variable for setting the stale status of the tiering
        SavedTieringData struct

        % Cache the reference type for risk tiering data
        RiskTieringReferenceType 
    end

    methods
        function this = RiskTieringCustomTab(form, client)
            this@mrm.inventory.model.FormCustomization(form, client);
            this.RiskTieringReferenceType = this.Client.getReferenceTypeByName("RiskTieringData");
        end

        function populateCustomContent(this)
            parentTab = uitab(this.Form.TabGroup, ...
                              'Title', 'Risk tiering', ...
                              'Tag', 'risktiering_tab');

            % Set up grid
            this.Grid = uigridlayout(parentTab, [6 2]);
            this.Grid.RowHeight = repmat(30, 1, 6);
            this.Grid.ColumnWidth = {'1x', '1x'};

            % Row 1
            uilabel(this.Grid, 'Text', 'Does the model measure risk, price or value?');
            this.UsageDD = uidropdown(this.Grid, ...
                                      'Items', ["Unset"; "True"; "False"], ...
                                      "ItemsData", ["Unset"; "True"; "False"], ...
                                      "ValueChangedFcn", @(~,~)this.setStaleStatus);

            % Row 2
            uilabel(this.Grid, 'Text', 'Is the model usage critical?', ...
                    'Tooltip', 'Includes use for critical business decisions, regulatory purposes or financial reporting?');
            this.CriticalUseDD = uidropdown(this.Grid, ...
                                            'Items', ["Unset"; "True"; "False"], ...
                                            "ItemsData", ["Unset"; "True"; "False"], ...
                                            "ValueChangedFcn", @(~,~)this.setStaleStatus);

            % Row 3
            uilabel(this.Grid, 'Text', 'Exposure');
            this.ExposureLevelDD = uidropdown(this.Grid, ...
                                              'Items', ["Unset"; "High"; "Medium"; "Low"], ...
                                              'ItemsData', ["Unset"; "High"; "Medium"; "Low"], ...
                                              'ValueChangedFcn', @(~,~)this.setStaleStatus);

            % Row 4
            % Tier names 5-7 are 'Low (Stale)' etc, so don't include them in the drop-down.
            uilabel(this.Grid, 'Text', 'Override');
            this.OverrideDD = uidropdown(this.Grid, ...
                                         'Items', ["Unset"; "Low"; "Medium"; "High"], ...
                                         "ItemsData", ["Unset"; "Low"; "Medium"; "High"], ...
                                         "ValueChangedFcn", @(~,~)this.setStaleStatus);

            % Row 5
            this.CalculateButton = uibutton(this.Grid, 'Text', 'Calculate');
            this.CalculateButton.ButtonPushedFcn = @this.onCalculateRiskTier;
            this.CalculateButton.Layout.Row = 5;
            this.CalculateButton.Layout.Column = 2;

            % Row 6
            uilabel(this.Grid, 'Text', 'Risk tier');
            this.RiskTierLabel = uilabel(this.Grid, 'Text', '');
        end

        function onModelSet(this)
            guid = this.Form.GUIDEdit.Value;
            tieringDataForThisModel = this.Client.getReferenceByModelAndType( ...
                guid, this.RiskTieringReferenceType.GUID);
            this.SavedTieringData = tieringDataForThisModel.Attributes;

            this.UsageDD.Value = this.SavedTieringData.RiskPriceValueUse;
            this.CriticalUseDD.Value = this.SavedTieringData.CriticalUse;
            this.ExposureLevelDD.Value = this.SavedTieringData.Exposure;
            this.OverrideDD.Value = this.SavedTieringData.Override;
            this.RiskTierLabel.Text = this.SavedTieringData.RiskTier;
        end

        function onSubmit(this)
            guid = this.Form.GUIDEdit.Value;
            data = containers.Map;
            data("RiskPriceValueUse") = this.UsageDD.Value;
            data("CriticalUse") = this.CriticalUseDD.Value;
            data("Exposure") = this.ExposureLevelDD.Value;
            data("Override") = this.OverrideDD.Value;
            data("RiskTier") = this.RiskTierLabel.Text;

            tieringReference = this.Client.getReferenceByModelAndType( ...
                guid, this.RiskTieringReferenceType.GUID);
            this.Client.updateReference(tieringReference.GUID, "Attributes", ...
                data);
        end
    end

    methods (Access = protected)

        function setStaleStatus(this)
            isStale = this.SavedTieringData.RiskPriceValueUse ~= this.UsageDD.Value || ...
                      this.SavedTieringData.CriticalUse ~= this.CriticalUseDD.Value || ...
                      this.SavedTieringData.Exposure ~= this.ExposureLevelDD.Value || ...
                      this.SavedTieringData.Override ~= this.OverrideDD.Value;

            if isStale && ~contains(this.RiskTierLabel.Text, "Stale") && ...
                    this.RiskTierLabel ~= "Unset"
                this.RiskTierLabel.Text = string(this.RiskTierLabel.Text) + " (Stale)";
            elseif ~isStale && contains(this.RiskTierLabel.Text, "Stale")
                this.RiskTierLabel.Text = extractBefore(this.RiskTierLabel.Text, ...
                                                        " (Stale)");
            end
        end

        function onCalculateRiskTier(this, ~, ~)
            tieringInputs.riskpricevalueflag = this.UsageDD.Value;
            tieringInputs.criticalflag = this.CriticalUseDD.Value;
            tieringInputs.exposurelevel = this.ExposureLevelDD.Value;
            tieringInputs.override =  this.OverrideDD.Value;

            riskTier = mrm.inventory.custom.riskTierTreeSimple(tieringInputs);
            this.RiskTierLabel.Text = riskTier;
        end
    end
end

Customize Model Summary Table

Customize the summary table of model data shown in the Inventory Browser by omitting columns, including columns corresponding to the custom data set up in the previous two sections, and reordering any of the columns shown. Implement these customizations as a single subclass of mrm.inventory.model.TableCustomization that must be in a +mrm/+inventory/+custom/+model folder on the MATLAB path. The class must implement the following methods:

  • The constructor must accept a single input consisting of the user-visible headers for the default view of the model table. It must also set properties ColumnVisible, ColumnOrdering and AllHeaders.

  • process(this, uit, modelIds, client) takes as its inputs the uitable being customized and the ids of the model to be displayed, and performs the required customizations. Client is also supplied for looking up custom data.

The following example code illustrates how this can be done. The resulting table shows only the name and the id of each model from the base product model data, and the exposure level, risk tier and any possible override from the tiering data itself. These columns are also reordered to demonstrate this capability.

classdef TableCustomizationExample < mrm.inventory.model.TableCustomization
%Example to illustrate addition, removal and reordering of summary table
%column.

%   Copyright 2021-2023 The MathWorks, Inc.


    methods
        function this = TableCustomizationExample(parentHeaders)
            this.ExtraHeaders = ["Risk Tier", "RiskPrice", "Exposure", "Critical", "Tier override"];
            this.AllHeaders = [parentHeaders, this.ExtraHeaders];

            baseVisible = [true, true, false]; % 1-2 of visible columns
            riskTierVisible = [true, false, true, false, true]; % 3-5 of visible columns

            this.ColumnVisible = [baseVisible, riskTierVisible];
            this.ColumnOrdering = [2 1 3 5 4]; % for visible columns only
        end

        function uit = process(this, uit, modelIds, client)
            arguments
                this
                uit matlab.ui.control.Table 
                modelIds(1,:) string
                client 
            end

 
            % Step 1: Read the risk tiering data for all the modelIds from
            % the client.
            tieringDataType = client.getReferenceTypeByName("RiskTieringData");
            tieringData = arrayfun(@(id)client.getReferenceByModelAndType(id, ...
                tieringDataType.GUID), modelIds);

            % Step 2: Arrange this to extraModelTable table with columns
            % corresponding to this.ExtraHeaders
            [tiers, materialUseFlags, exposures, criticalUseFlags, overrides] =  ...
                arrayfun(@(ref)readRiskTierData(ref), tieringData);
            extraModelData = table(tiers', materialUseFlags', exposures', ...
                criticalUseFlags', overrides', 'VariableNames', ...
                ["riskTier", "riskPriceValue", "exposure", "critical", "override"]);

            % Step 3: Concatenate this with uit.Data:
            uit.Data = [uit.Data, extraModelData];
            uit.ColumnName = this.AllHeaders;

            % Step 4: Use the helper methods from TableCustomization base
            % to reset the visibility and the ordering of the columns.
            this.setVisibility(uit);
            this.setOrdering(uit);
        end
    end
end

function [tier, materialuseflag, exposure, criticaluseflag, override] = readRiskTierData(ref)
    tier = string(ref.Attributes.RiskTier);
    materialuseflag = string(ref.Attributes.RiskPriceValueUse);
    exposure = string(ref.Attributes.Exposure);
    criticaluseflag = string(ref.Attributes.CriticalUse);
    override = string(ref.Attributes.Override);
end

Create Custom Filters

Inventory Browser is equipped with an interactive UI for creating filters to limit the list of models shown in the Models table. These filters make no distinction between data included in the default view and columns added as part of customization process. You can, for example, construct a filter to show only models with a ‘High’ risk tier.

Inventory Browser has two filters in the “Saved Filters” list of the filter editor: “Search by Name”, which allows you to filter by the model name, and “Create Custom Filter”, which shows a more complex filter, intended as a template for creating more complicated queries.

This section shows you how to create new filters and how to add them to the “Saved Filters” list.

To customize your own filters, implement new filters as subclasses of mrm.inventory.model.filter.FilterDefinition with the following properties.

  • Name() must carry a string that is to be displayed in the Filter dropdown - for example "Filter by Risk Tier"

  • Serialization must carry the default initialization of the filters as a JSON string

To understand the format of the serialization JSON, see mrm.inventory.model.filter.FilterByName for simple (“Primitive”) filters that reference just a single column and see the default serialization in mrm.inventory.model.filter.CustomFilterTemplate for more complex (“Composite”) filters.

To make the filters visible, implement a function called modelFilters in a +mrm/+inventory/+custom/+model/ folder on the MATLAB path. This function must take no inputs, and it must return a row array of all the filters you want to include in the Saved Filters list.

The code below illustrates this. Note that the modelFilters output can include a mixture of custom filters and filters shipped with Modelscape itself.

function filters = modelFilters()
%Example filter selection customization

%   Copyright 2022-2023 The MathWorks, Inc.

    filters = [ mrm.inventory.model.filter.FilterByName, ...
        mrm.inventory.custom.model.filter.FilterByRiskTier, ...
        mrm.inventory.model.filter.CustomFilterTemplate];
end

In the filter implementation, the variable name in uit.Data may be different from the user-visible header shows in the Inventory - here riskTier vs user-visible "Risk Tier". The implementation does not need to reside in any package folder, but it makes sense to have all customization code in a single location, so use +mrm/+inventory/+custom/+model/+filter here.

classdef FilterByRiskTier < mrm.inventory.model.filterDefinition
% Example definition for filtering by risk tier in Modelscape Inventory

%   Copyright 2022-2023 The MathWorks, Inc.

    methods
        function this = FilterByRiskTier()
            this.name = "Filter by Risk Tier";
            this.Serialization = ['{"type":"Primitive","header":"riskTier",', ...
            '"operation":"CONTAINS","value":"","parent":[],"id":"1"}']; 
        end
    end
end