Personal tools
You are here: Home Community Help Center Reference Manuals RiskAnalytics Developer's Guide

RiskAnalytics Developer's Guide

Note: Return to reference manual view.

WORK IN PROGRESS: IT technical issue to develop components and models.

1. Components

Components are the building blocks of models. Most of the business logic is kept in components. Components have typed interfaces to receive and send packets from and to other components and parameters.

1.1. Conventions

Conventions make a developers life easier by reducing the amount of code to be written and increasing the readableness. In RiskAnalytics conventions are used to generate the user interface and database schema.

1.1.1. Naming convention

Beside clean code and higher readableness naming convention, increase coding speed in modern IDE (integrated development environment) as code completion is supported.

A component may have several properties. There exist four prefixes to be used:

  • parm: whenever a variable name starts with parm. It is displayed on the parameterization user interface automatically.
  • in: all in variables of type PacketList receive packets from other components
  • out: all out variables of type PacketList send packets to other components. It is displayed on the result template user interface and optionally on the result page automatically.
  • sub: a component may have sub components. Their name has to start with sub.

Remark: If a component is written in Java, variables starting with any of these prefixes need a public setter and getter method.

1.1.2. Grouping of Components and Parameters in the GUI

The order in which you write the parameters, out channels and sub components in a component determines the order in the GUI.

RiskAnalytics comes with a powerful way of deriving a default user interface for all models, components and its parameters - a model or component developer does not have to write any GUI code. Yet he can group the parameters.

The order in which subcomponents, parameters and out channels in a component are displayed is the order in which they appear in the corresponding code.

Example of a Model and its Visualization in the Parameter View

class PodraModel extends Model {
    DynamicUnderwritingSegments underwriting
    DynamicClaimsGenerators claimsGenerators
    DynamicDependencies correlations
    DynamicMultipleDependencies eventCorrelations
    DynamicConfigurableLobs linesOfBusiness
    MultiLineDynamicReinsuranceProgram reinsuranceProgram
...
}

 Component Order

 

 

1.2. Component

Basics explained using an example.

Advice

Try to keep components simple; a component should do one thing and not many things. This makes it easier to test it and it also increases the chances that such a components can be re-used.

If we think of a model as a directed graph, components are the nodes in such a graph. The edges are wired out and in properties.

Concept

The method doCalculation is executed once only per iteration and period. No loops are possible for a component.

Execution is triggered by the framework: If all wired in channels have received their information doCalculation is executed. Afterwards the packets prepared in the out channels is sent to following components.

Step by step example

We create a component providing a premium value to other components. The premium will be the product of number of policies and the price per policy parameter.

  1. Create a new Groovy or Java class in the corresponding package. (Line 1, 5)
    (Domain classes can be found in src/groovy/org/pillarone/modelling/domain)
  2. The class has to extend org.pillarone.riskanalytics.core.components.Component (Line 2, 5)
  3. The class has two parameters parmNumberOfPolicy and parmPricePerPolicy (Line 6, 7) The order in which the parameters are defined is also the order how they will be displayed in the UI. If parameters are inherited from superclasses, the parameters of the superclass are displayed first.
  4. The class has an output channel outPremium (Line 8) in order to send premium information to other components.
  5. Premium has to be calculated (Line 10), wrapped in a packet and added to the output channel (Line 11)
 1: package org.pillarone.modelling.domain.underwriting

 2: import org.pillarone.modelling.components.Component
 3: import org.pillarone.modelling.packets.PacketList
 4: import org.pillarone.modelling.packets.Premium
 
 5: class PremiumCalculation extends Component {

 6:   double parmNumberOfPolicy
 7:   double parmPricePerPolicy

 8:   PacketList<Premium> outPremium = new PacketList(Premium)

 9:   void doCalculation() {
10:       double premium = parmNumberOfPolicy * parmPricePerPolicy
11:       outPremium << new Premium(value: premium)
12:     }
13: }

Let's say we want another component providing index information which influences the price per policy. In order to cover this use case we write a component with extended functionality:

  1. Add an additional input channel inIndex (Line 5)
  2. Adjust the premium calculation: build the product of all received indices (Line 8 - 10) and adjust the price per policy accordingly (Line 11)

 

 1: package org.pillarone.modelling.domain.underwriting

 2: import org.pillarone.modelling.packets.PacketList
 3: import org.pillarone.modelling.packets.Index
 
 4: class PremiumCalculationWithIndex extends PremiumCalculation {

 5:   PacketList<Index> inIndex = new PacketList(Index)

 6:   void doCalculation() {
 7:       double level = 1.0
 8:       for (Index idx in inIndex) {
 9:           level *= idx.value
10:       }
11:       double pricePerPolicy = parmPricePerPolicy * level
12:       double premium = parmNumberOfPolicy * pricePerPolicy
13:       outPremium << new Premium(value: premium)
14:     }
15: } 

For the sake of completeness the following listings contain the Index and Premium classes:

 1: package org.pillarone.modelling.packets
 
 2: class Premium extends Packet {
 3:   double value
 4: }


 1: package org.pillarone.modelling.packets
 
 2: class Index extends Packet {
 3:   double value
 4: }

1.3. Composed Component

To avoid modelers always starting from scratch, the concept of composed components allows to define building blocks containing several components. Typical building blocks may be lines of business, reinsurance program, ... Somehow a composed component is very similar to a model but can't be executed.

Step by step example

We create a component composed of a frequency and claims size generator.

  1. Create a new Groovy class in the corresponding package (Line 1, 2).
    (Domain classes can be found in src/groovy/org/pillarone/modelling/domain)
    Remark: Composed components have to be defined in a Groovy class as wiring would not work with a Java class in the current implementation.
  2. The class has to extend org.pillarone.riskanalytics.core.components.ComposedComponent (Line 2)
  3. The class has two sub components subFrequencyGenerator and subClaimsGenerator. Make sure all sub components name start with sub! If you forget to add sub, they won't be displayed in any of the user interfaces. (Line 3, 4)
  4. The class has an output channel outClaims (Line 5) in order to send claims to other components such as claims development or reinsurance contracts.
  5. Sub components have to be instantiated in the constructor and not in the declaration! (Line 7, 8)
  6. As this composed component doesn't have any in channels, we have to define which component is executed first in doCalculation() (Line 11).
    Remark: As composed components contain no additional business logic, it is not necessary to implement doCalculation() always as this is the case for components.
  7. Each composed component has to implement the abstract wire() method. Wiring between the sub components is done similar to the wiring in models. (Line 14-16). As sub components are nested we have to make sure that the information required outside the composed component is replicated. This is done with the so called PortReplicatorCategory (Line 17-19).
    Remark: Using the PortReplicatorCategory the properties on the left and right side have to be either in or out properties, whereas for WireCategory the left side property has to be an in and the right side an out property.
 1: package org.pillarone.modelling.domain.generators.claims

    ...

 2: class FrequencyClaimsGenerator extends ComposedComponent {

 3:    FrequencyGenerator subFrequencyGenerator
 4:    SingleClaimsGenerator subClaimsGenerator

 5:    PacketList<Claim> outClaims = new PacketList(Claim)

 6:    FrequencyClaimsGenerator() {
 7:       subFrequencyGenerator = new FrequencyGenerator()
 8:       subClaimsGenerator = new SingleClaimsGenerator()
 9:    }

10:    public void doCalculation() {
11:       subFrequencyGenerator.start()
12:    }

13:    public void wire() {
14:        WiringUtils.use(WireCategory) {
15:            subClaimsGenerator.inClaimCount = subFrequencyGenerator.outFrequency
16:        }
17:        WiringUtils.use(PortReplicatorCategory) {
18:            this.outClaims = subClaimsGenerator.outClaims
19:        }
20:    }
21: }

Calibration of the frequency and claims generator can be done proportional to the number of policies or the premium written. In order to enable this possibility in the composed component too and additional input channel is required.

  1. The class has an input channel inUnderwritingInfo (Line 3)
  2. If an input channel is wired, the default behaviour can be used by omitting doCalculation() at all or calling super.doCalculation() (Line 13). In this example omitting the method doCalculation() is not recommended as it is not possible to assure that inUnderwritingInfo is connected in all use cases.
    Remark: As it is not mandatory to wire all channels, the method isReceiverWired(inUnderwritingInfo) (Line 12) is implement in Component and returns true if any out channel is connected to inUnderwritingInfo.
  3. Finally it is necessary to provide the underwriting information to the frequency and claims generator using the closure PortReplicatorCategory to wire them.
 1: package org.pillarone.modelling.domain.generators.claims

    ...

 2: class FrequencyClaimsGenerator extends ComposedComponent {

 3:    PacketList<UnderwritingInfo> inUnderwritingInfo = new PacketList(UnderwritingInfo)

 4:    FrequencyGenerator subFrequencyGenerator
 5:    SingleClaimsGenerator subClaimsGenerator

 6:    PacketList<Claim> outClaims = new PacketList(Claim)

 7:    FrequencyClaimsGenerator() {
 8:       subFrequencyGenerator = new FrequencyGenerator()
 9:       subClaimsGenerator = new SingleClaimsGenerator()
10:    }

11:    public void doCalculation() {
12:        if (isReceiverWired(inUnderwritingInfo)) {
13:            super.doCalculation()
14:        } else {
15:            subFrequencyGenerator.start()
16:        }
17:    }

18:    public void wire() {
19:        WiringUtils.use(WireCategory) {
20:            subClaimsGenerator.inClaimCount = subFrequencyGenerator.outFrequency
21:        }
22:        WiringUtils.use(PortReplicatorCategory) {
23:            this.outClaims = subClaimsGenerator.outClaims
24:        }
25:        if (isReceiverWired(inUnderwritingInfo)) {
26:            WiringUtils.use(PortReplicatorCategory) {
27:                subFrequencyGenerator.inUnderwritingInfo = this.inUnderwritingInfo
28:                subClaimsGenerator.inUnderwritingInfo = this.inUnderwritingInfo
29:            }
30:        }
31:    }
32: }

1.4. Dynamically Composed Component

Composed components consisting of a flexible number of subcomponents, all of which must be of the same type. The number of subcomponents is defined with the parameterization. According the available records in a database the same number of subcomponents are instantiated.

Concept

  • A dynamic composed component is similar to a composed component in the sense that it contains components and different in the sense that it contains a flexible number of a component of a specific type. The type of these component has to be defined in the function
    abstract Component getDefaultSubComponent()
  • Currently all sub components have the same generic name.
  • The wire() and doCalculation() methods follow exactly the same concepts as for a composed component.
  • The abstract class DynamicComposedComponent handles adding, removing, naming and other checks for all derived class. The components are administered using a list of component called componentList.
  • This concepts enables a data driven modelling. The PODRA modell actually contains six void containers (underwriting, claims generators, correlations, event correlations, lines of business and reinsurance program). As the wiring itself is fix such models typically include several filter components for allocation.
    The drawback of such models are the lower performance and the higher risk of parameterization errors.

Step by step example

We create a dynamically composed component containing reinsurance contracts.

  1. Create a new Groovy class in the corresponding package (Line 1, 2).
    (Domain classes can be found in src/groovy/org/pillarone/modelling/domain)
    Composed components have to be defined in a Groovy class as wiring would not work with a Java class in the current implementation.
  2. The class has to extend org.pillarone.riskanalytics.core.components.DynamicComposedComponent (Line 2)
  3. The DynamicReinsuranceProgram has an arbitrary number of ReinsuranceContract. Their default instantiation is defined in getDefaultSubComponent(). The method is called whenever an application user right clicks on the reinsurance program and adds a new contract.
  4. Finally it is necessary to define the default name (Line 12-14).
 1: package org.pillarone.modelling.domain.reinsurance.programs

    ...

 2: class DynamicReinsuranceProgram extends DynamicComposedComponent {

    ...

 3:   public ReinsuranceContract getDefaultSubComponent() {
 4:       ReinsuranceContract newContract = new ReinsuranceContract(
 5:           parmInuringPriority: 0,
 6:           parmContractStrategy: 
 7:               ReinsuranceContractStrategyFactory.getContractStrategy(
 8:                   ReinsuranceContractType.TRIVIAL, [:]))
 9:       newContract.name = getName(newContract)
10:       return newContract
11:   }

12:   protected String getDynamicComponentName() {
13:       "contract"
14:   }

       ...

The wire() method of the DynamicReinsuranceProgram looks rather complicated. The complexity is related with the fact, that the inuring priority parameter of the contracts allows the application user to define any order of the contracts. Furthermore merger components are required to make sure contracts get the correct net information if several contracts have the same inuring priority. The complexity is not related to the concept of dynamic composed components.

1.5. Multiple Calculation Phases

Whenever a reallocation is needed it is unavoidable to have loops in a model graph. There are specific composed components containing components being executed in different phases.

The concept and implementation is relatively new. The API is not yet stable. The current implementation allows to phases. It is used to reallocate ceded and net claims and underwriting information to lines of business in order to display resulting gross, ceded and net figures in one place.

 

Example implementation is ConfigurableLob.

 

  • org.pillarone.riskanalytics.core.components.MultipleCalculationPhaseComposedComponent
  • org.pillarone.riskanalytics.core.components.MultiPhaseDynamicComposedComponent

1.6. Testing

Verify that the component yields correct results.

In order to verify that a component yields correct results, you need to have reference implementation that does the same calculation. The parameters used as input and the output from the reference calculation are then used in a test case.

 

Step by step example

 

 also include executing the test case

2. Strategy

To avoid a forest of similar components which would be difficult to maintain, the parameterization may be tweaked using the strategy concept.

2.1. Concept

If we think of components as claims generators, claims developers, reinsurance contracts, ... it's clear that each of these groups have common behaviour but different strategies how claims are generated, developed or ceded.

A claims generator produces claims according to a stochastic distribution. In order to avoid a component per distribution, there should be a possibility to trigger its behaviour with the parameterization.

Whereas dynamically composed components allow to specify an arbitrary number of subcomponents, strategies allow to trigger the behaviour of a single component. Furthermore they support the developer to share as much as possible of the implementation without using inheritance.

With the current implementation strategies are defined using a *Type extending form AbstractParameterObjectClassifier and a *StrategyFactory class.

On the user interface strategies are represented with combo boxes allowing the user to select a specific strategy. Switching the strategy will toggle the parameters in the tree.

2.2. Distribution

The RiskAnalytics library contains many distributions. New ones can be added easily.

Please see the Modelling with RiskAnalytics manual to see which distributions are already implemented.

Most distributions are taken from the SSJ library. SSJ is a Java library for stochastic simulation, developed in the Département d'Informatique et de Recherche Opérationnelle (DIRO), at the Université de Montréal.

Step by step example

All classes mentioned below can be found in the org.pillarone.modelling.util package.

We distinguish between random variable and probability distributions. Both types are derived from DistributionType. Before starting to implement a new distribution one should have a look at the following classes in the same package as mentioned above: ContinuousRandomDistributionType, DiscreteRandomDistributionType and ContinuousVariateDistributionType, DiscreteVariateDistributionType. Basically these classes act like an enumeration for the available distribution.

In order to add a new distribution it is necessary to add a new public static final variable, it's string representation and the required parameters in one of the four classes mentioned above. Furthermore the new variable has to be added to the "all" variable.

Adding a triangular distribution requires us to add the following line to the class ContinuousRandomDistributionType:

public static final ContinuousRandomDistributionType TRIANGULAR 
            = new ContinuousRandomDistributionType(
            "Triangular", ["a": 0d, "b": 1d, "m": 0.5])

The parameters of this distribution ('a', 'b', 'm') will be displayed on the UI in the same order as they are defined in this Map.

Furthermore we have to add TRIANGULAR to the "all" property in order to display it in the distribution selection lists:

public static final all = [NORMAL, LOGNORMAL, LOGNORMAL_MU_SIGMA, PARETO, 
            CENSOREDPARETO, TRUNCATEDPARETO, UNIFORM, CONSTANT,
            PIECEWISELINEAREMPIRICAL, PIECEWISELINEAR, TRIANGULAR]

In a next step we need to add the distribution to the RandomNumberGeneratorFactory: First we need a private static getter method in order to instantiate the generator:

private static IRandomNumberGenerator getTriangularGenerator(
    double a, double b, double m) {

    TriangularGen generator = new UniformGen(new F2NL607(), 
                                             new TriangularGen(a, b, m))
    return new RandomNumberGenerator(generator: generator)
}

This function is private as we won't allow direct instantiations. Instead the getGenerator method should be used. Therefore we need to add the new case to this function:

static IRandomNumberGenerator getGenerator(DistributionType type, 
    Map parameters) {

    IRandomNumberGenerator generator
    switch (type) {       
        case ContinuousRandomDistributionType.TRIANGULAR:
            generator = getTriangularGenerator(parameters["a"], 
                                               parameters["b"], 
                                               parameters["c"])
            break

The described steps are sufficient in order to add another distribution that is already available in the SSJ library but not yet wrapped into PillarOne. The newly added distribution is now available in attritional or single claims generators (org.pillarone.modelling.domain.generators.claims).

In order to add a more specific distribution an additional step is required: A new class implementing the umontreal.iro.lecuyer.probdist.Distribution interface has to be written. Our  PiecewiseLinearDistribution is an example of this approach.

 

The implementation is incomplete as we didn't write a test case for our new distribution. It's necessary to complete at least RandomNumberGeneratorFactoryTests.testCreateTypedGenerator() and RandomNumberGeneratorTests.

Caveat: These tests don't test if the generated sample corresponds to the defined distribution.

 

After completing these steps, the new distribution is part of the list of distributions which appears in the user interface and the parameters are displayed if a user selects the distribution. There is no need to program graphical user interface elements.

Further information

We plan to implement a plugin mechanism to make this process much simpler. The plugin mechanism will avoid the modification of core classes and provide a self registration mechanism. This means that the user may specify a new distribution in one file only. The rest will be done using the plugin infrastructure.

3. Model

Models can be considered as directed graphs. The define the nodes (components) and edges (channels). The flow in a model is data driven. They don't define concrete parameterization and result collection.

3.1. Concepts

A model consists of at least one component and needs a parameterization and result template to be run.

Example of a model consisting of a common frequency and two claims size generators:

class MarkModel extends Model {

    FrequencyGenerator frequencyGenerator
    SingleClaimsGenerator claimsGeneratorFire
    SingleClaimsGenerator claimsGeneratorMotor

    void initComponents() {
        frequencyGenerator = new FrequencyGenerator()
        claimsGeneratorFire = new SingleClaimsGenerator()
        claimsGeneratorMotor = new SingleClaimsGenerator()

        addStartComponent frequencyGenerator
    }

    void wireComponents() {
        claimsGeneratorFire.inClaimCount = frequencyGenerator.outFrequency
        claimsGeneratorMotor.inClaimCount = frequencyGenerator.outFrequency
    }
}
  • Each model has to be derived from org.pillarone.riskanalytics.core.model.Model and are defined in a Groovy class as wiring would not work in a Java class. Model files should be placed in src/java/models
  • Components have to be declared as member variables and initialized in the initComponents() method.
  • One or several components are the starting point of the 'net', they have to be added as start components so that they won't wait on other components input.
  • Components are wired in order to send information from one to another component. A component will become active once it has received all input from its proceeding components.

 

3.2. Deterministic Model

A run of a deterministic model contains of a single iteration.

The output of all components in a deterministic model is not random, therefore a single iteration is sufficient.

4. Parameters

Parameters are the inputs for the models.

4.1. Parameter file

Mostly used for default parametrizations.

Although the parameters are usually kept in a relational database, it can be useful to write a default parameterization into a file.

 

 

5. Setting display names

How to set readable names for the GUI in different languages.

5.1. Default convention for display names

This section explains how RiskAnalytics derives display names from the variable names in the program code.

By default, RiskAnalytics derives the display names of parameters and components in a model’s hierarchy as follows.

  1. Remove prefixes: "sub", "parm" or "out"

  2. In the resulting string, replace upper case characters with a space followed by the corresponding lower case character

Example: For the component "subClaimsGenerator"  the default display name is "claims generator". The parameter "parmContractStrategy" yields as "contract strategy".

This naming convention is useful because a model or component developer can focus on the business content and does not have to worry about setting display names.

Yet, there are two problems which such a naming convention cannot resolve:

  1. If you re-use a component which contains other components (i.e. composed component) more than once in one model, then all the contained components have the same display name. Note that this is only the display name. The model knows to distinguish them by their place (i.e. path) in the model hierarchy.
    same SubComponent-Names
    This might be undesirable because the same business component can have different meaning in different places of a model.
  2. Such a naming convention can only provide the display names in one language which is usually English, as most programmers write their code in pseudo-English. What should be done if the users of a model are French, German or Spanish speaking? Or even worse, if a model is intended for a multi-national insurance group and German users would like to work with the German display names, English user with the English ones and so on. In this case, the application has to switch the display names according to the user’s language settings.
    This is a well-known IT problem and is referred to as Internationalization.

5.2. Convention and internationalization for display names

How to use internationalization and labeling for models, components and strategies.

PillarOne RiskAnalytics offers a combination of naming conventions and internationalization methods to offer the utmost flexibility in setting display names. Meaningful display names are very important, in particular, if the model builders are not the same group of people as the users.

To create the display name of a component or its parameters in a model, Risk Analytics performs the following steps:

Rule 1:  Model specific display names

Search the component’s or parameter’s path in the model resource bundle.

The model resource bundle is in the same package (directory) as the model. For the CapitalEagleModel the file names look like this:

  • CapitalEagleModelResources.properties
  • CapitalEagleModelResources_de.properties (for German display names)
  • CapitalEagleModelResources_fr.properties (for French display names)
  • ...
The key is the path in the model. For the Earth Quake Generator in the Capital Eagle Model a key/value pair looks like this:
property.subClaimsGenerator.subEQGenerator=EQ Generator

Note that the default naming schema would yield e q generator. This rule serves the purpose to give components and parameters model specific names.
If no information is found, then apply Rule 2 if it is a parameter else apply Rule 3.

Rule 2: Internationalization of parameters in strategy types

If it is a parameter in a strategy type, then search the parameter name in the corresponding strategy type resource bundle. This resource bundle is located in the same package (i.e. directory) as the strategy type definition. For claims generators it is

org.pillarone.modelling.domain.generators.claims.