Obeject Model
The object model describes the structure of all known entities that are available in dynamic logic. Each entity is modeled using a Groovy class, which consists of methods and fields (sometimes also referred to as attributes or properties). All entities contain fixed fields, which are predefined by OHI, and certain entities also support dynamic field, which can be configured by the user. An entity’s fixed fields are documented in the corresponding implementation guide. A complete overview of all fields (i.e. including the dynamic ones) is automatically generated on-the-fly by the system, and can be found in the system’s View Object page. This chapter will describe how to access both kind of attributes from dynamic logic.
Fixed fields
Attributes that are predefined as part of the system’s data model are fixed. In dynamic logic, they can always be read, but cannot always be written. Whether or not a particular attribute can be written, depends on the purpose of the dynamic logic. Dynamic logic validations and conditions are solely meant to return either true or false, whereas certain dynamic logic functions are indented for data modifications, in which case the function’s signature description will contain more details.
Reading
The Dynamic Logic guide assumes that the reader is already familiar with the general aspects of the Groovy programming language, so the following syntax example should come as no surprise:
assert claimLine.sequence == 1
Writing
Similarly, writing a fixed field can be done by using the assignment operator (<tt>=</tt>):
claimLine.benefitsInputDate = claimLine.startDate
Dynamic fields
Accessing an object’s dynamic field is done using the configured dynamic field usage name. The dynamic attribute’s type not only depends on the configured data type, but also on the configured time-valid and multi-value indicators. Whenever one or both indicators are checked, a dynamic field can have multiple different values. Those values are stored inside a List object. This is the case regardless of the actual number of values, so even if a field only has one value, it will still be represented as a list.
Reading
The below table shows examples of accessing dynamic fields with different configuration.
Configuration | Dynamic logic | |||
---|---|---|---|---|
Time-valid |
Multi-value |
Data type |
Attribute type |
Example |
N |
N |
Character |
String |
assert claimLine.dependentName == 'John' |
N |
N |
Number |
BigDecimal |
assert claimLine.patientWeight > 100 |
N |
N |
Date |
Date |
assert claimLine.accidentDate.after(claimLine.startDate) |
N |
N |
Code |
FlexCode |
// Assuming the flex code field usages "name" and "severity" are configured assert claimLine.accidentType.name == 'CAR' assert claimLine.accidentType.severity == 17 |
N |
N |
Provider |
Provider |
assert claimLine.preferredProvider.code == '1970000011' |
N |
N |
Procedure |
Procedure |
assert claimLine.extraProcedure.code == '08.14' |
N |
N |
Diagnosis |
Diagnosis |
assert claimLine.secondaryDiagnosis.code == '690.02' |
N |
Y |
All |
List<Object> // Explained further below |
assert claimLine.dependentNames.size() == 1 |
Y |
N |
All |
List<DynamicFieldPeriod> |
assert claimLine.familyTiers.size() == 2 |
Y |
Y |
All |
List<DynamicFieldPeriod> |
assert claimLine.insuranceCategories.size() == 3 |
The above table illustrates that for single-value non-time-valid dynamic fields the resulting attribute type is directly related to the configured data type. If a dynamic field is either multi-value or time-valid, then the attribute’s type becomes a List. The type of the elements in that list further depend on the time-valid indicator:
-
For non-time-valid dynamic fields, the element type simply depends on the configured data type, equivalent to the single-value case. For example, a 'Character' dynamic field becomes List<String>, and a 'Code' dynamic field becomes List<FlexCode>.
-
For time-valid dynamic fields, each value coincides with a time validity: a start and end date. This information is captured within an DynamicFieldPeriod object, so the dynamic field’s type is always List<DynamicFieldPeriod>. This class is structured as follows:
class DynamicFieldPeriod { Date startDate Date endDate Object value }
To illustrate the aforementioned list mechanics further, the following table contains some more detailed examples related to accessing such dynamic fields:
Configuration | Example | ||
---|---|---|---|
Time-valid |
Multi-value |
Data type |
|
N |
N |
Character |
assert claimLine.dependentName == 'John' |
N |
Y |
Character |
assert claimLine.dependentNames[0] == 'John' |
Y |
N |
Character |
assert claimLine.dependentNames[0].value == 'John' assert claimLine.dependentNames[0].startDate >= claimLine.startDate assert claimLine.dependentNames.asOf(date) == 'John' |
Y |
Y |
Character |
assert claimLine.dependentNames[0].value == 'John' assert claimLine.dependentNames.asOf(date)[0] == 'John' |
N |
N |
Code |
// Assuming the flex code field usages "name" and "severity" are configured assert claimLine.accidentType.name == 'CAR' |
N |
Y |
Code |
assert claimLine.accidentTypes[0].name == 'CAR' |
Y |
N |
Code |
assert claimLine.accidentTypes[0].value.name == 'CAR' assert claimLine.accidentTypes[0].value.severity == 17 assert claimLine.accidentTypes[0].startDate >= claimLine.startDate assert claimLine.accidentTypes.asOf(date).name == 'CAR' assert claimLine.accidentTypes.asOf(date).severity == 17 |
Y |
Y |
Code |
assert claimLine.accidentTypes[0].value.name == 'CAR' assert claimLine.accidentTypes.asOf(date)[0].name == 'CAR' |
As the examples illustrate, the value of a time-valid dynamic field cannot be accessed directly, but has to be accessed through the value attribute of the DynamicFieldPeriod class. The example also shows that finding the value that is valid on a particular date, can be done with the asOf convenience method (see the Predefined Methods section for details). Note that for single-value dynamic fields, the asOf method directly returns the value, not a DynamicFieldPeriod object. For multi-value dynamic fields, the asOf method returns a list of values, not a List<DynamicFieldPeriod>.
Writing
Similar to fixed fields, the assignment operator can also be used to write dynamic fields. For single-value non-time-valid dynamic fields, it operates the same as for fixed fields. For multi-value and/or time-valid dynamic fields, however, the operator is used for setting the initial value and for adding subsequent values, so it is not idempotent. For time-valid dynamic fields, the value has to be specified alongside the start and end date. This is done using the DynamicFieldPeriod class described above. The below table shows some dynamic logic examples of manipulating a 'Character' dynamic field.
Configuration | Dynamic logic | ||
---|---|---|---|
Time-valid |
Multi-value |
Operation |
Example |
N |
N |
Set |
claimLine.dependentName = 'John' |
Update |
claimLine.dependentName = 'Bob' |
||
Remove |
claimLine.dependentName = null |
||
N |
Y |
Set |
claimLine.dependentNames = ['John', 'Bob'] |
Add |
claimLine.dependentNames = 'Jim' |
||
Update |
claimLine.dependentNames[0] = 'Peter' |
||
Remove |
claimLine.dependentNames[0] = null |
||
Y |
Y/N |
Set |
claimLine.dependentNames = [ new DynamicFieldPeriod('John', startDate1, endDate1) , new DynamicFieldPeriod('Bob', startDate2, endDate2) ] |
Add |
claimLine.dependentNames = new DynamicFieldPeriod('Jim', startDate, endDate) |
||
Update |
claimLine.dependentNames[0].value = 'Peter' claimLine.dependentNames[1].endDate = newEndDate |
||
Remove |
// Updating a field’s value to null results in its removal claimLine.dependentNames[0].value = null |
The syntax for time-valid dynamic fields is the same for both the single-value and the multi-value case. The difference between the two is in the fact that values are allowed to overlap in time whenever the time-valid indicator is checked. A business rule violation exception will be raised when they overlap and the indicator is not checked.
Dynamic records
Records can be accessed through the configured usage name, just like dynamic fields. Similarly, the data type also depends on the configured multi-value and time-valid indicators. The only difference being that a dynamic record consists of multiple fields, so unlike dynamic fields, they do not have a single value attribute. Instead, the records configured fields can be accessed individually.
Reading
The below table again shows the different examples, assuming the accidentState record has a type field.
Time-valid | |||
---|---|---|---|
N |
Y |
||
Multivalue |
N |
assert claimLine.accidentState.type == 'CAR' |
assert claimLine.accidentStates[0].type == 'CAR' assert claimLine.accidentStates.asOf(date)[0].type == 'CAR' |
Y |
assert claimLine.accidentStates[0].type == 'CAR' |
assert claimLine.accidentStates[0].type == 'CAR' assert claimLine.accidentStates.asOf(date)[0].type == 'CAR' |
Writing
Dynamic records can be written using the predefined methods addDynamicRecord, updateDynamicRecord, and deleteDynamicRecord. Refer to the Predefined Methods chapter for more details and examples.
Optional attributes
Attributes that are optional might not have a value. In Groovy, the value null is used to indicate that fact. It is a special kind of value that deserves a mention in this chapter. Accessing an attribute or a method on a null value will result in a technical error, and should therefore be avoided. This can generally be done by simply checking whether or not an attribute is equal to null, before further accessing it:
if (claimLine.condition != null) { assert claimLine.condition.code == 'REGULAR' }
The Groovy language support a shorter way of writing such checks, using the ?-symbol, which they call the Safe navigation operator:
assert claimLine.condition == null assert claimLine.condition?.code == null assert claimLine.condition?.code?.toLowerCase() == null
As you can see, the ?-operator short-circuits further navigation as soon as a null value is encountered, and immediately returns null, thus avoiding a technical error. The operator can be used to reduce code complexity, but if used profusely, might also reduce readability or even lead to undesirable results.
Example
Assume some dynamic logic is applying a bonus percentage to a claim line, based on the line’s condition, which is optional. If a line’s condition is irregular, a bonus of 10% should be applied. Since the condition is optional, before checking the condition’s type, it should first be checked that the claim line actually has a condition. The following two pieces of dynamic logic code are equivalent:
if (claimLine.condition != null && claimLine.condition.type == 'IRREGULAR') { claimLine.bonusPercentage = 10 } else { claimLine.bonusPercentage = 0 }
if (claimLine.condition?.type == 'IRREGULAR') { claimLine.bonusPercentage = 10 } else { claimLine.bonusPercentage = 0 }
In both cases, only claim lines with an irregular condition will receive a bonus. Lines with a regular condition, and lines without a condition at all, will not receive a bonus. The latter code works, because claimLine.condition?.code yields null, and it so happens to be that null is not equal to the string 'IRREGULAR'.
Now, instead of doing an exact match on the 'IRREGULAR' condition, the above pieces of logic are extended to match all conditions other than 'REGULAR'. These two are no longer equivalent:
if (claimLine.condition != null && claimLine.condition.type != 'REGULAR') { claimLine.bonusPercentage = 10 } else { claimLine.bonusPercentage = 0 }
if (claimLine.condition?.type != 'REGULAR') { claimLine.bonusPercentage = 10 } else { claimLine.bonusPercentage = 0 }
As before, the first piece of logic will only apply the bonus to claim lines that have a condition, specifically an irregular condition. However, the second logic will now also apply the bonus to lines without any condition. It illustrates that carelessly applying the ?-operator to each navigation, is not always the correct solution to deal with possible null values.