Groovy Examples
This page mentions several Groovy language constructions important when writing Dynamic Logic.
To configure dynamic logic, consider the following recommendations:
-
Check the dynamic logic signature for information on input, throughput, and output restrictions.
-
Check if there are predefined methods applicable to the use case.
-
Use the View Objects page to determine the correct field name for an object.
-
The attribute type from the Groovy examples tables for assigning and accessing values must correlate with the View Objects page in the application. This helps in finding a specific example for a field.
-
Accessing Field Values
There are different ways to access fields of an object. The Oracle Health Insurance data model comprises Native Fields that are native to the system. A user can extend these fields using Dynamic Fields. The way to access both types of fields using Dynamic Logic is the same. This section describes the different methods for accessing each field type.
Attribute Type | Type of Field | Time-valid | Multi-value | Data Type | Example |
---|---|---|---|---|---|
BigDecimal |
Native and Dynamic |
N |
N |
BigDecimal |
person.adjustmentRate >= 0.071 |
Boolean |
Native |
N |
N |
Boolean |
person.bankAccountNumberRelationList[0].preferred == true |
Date |
Native and Dynamic |
N |
N |
Date |
person.dateOfBirth.compareTo(date) |
Datetime |
Native |
N |
N |
Timestamp |
import java.time.LocalDateTime |
DynamicRecord |
Dynamic |
N |
N |
All |
person.reportingCategory.type == 2750 |
FlexCode |
Dynamic |
N |
N |
Character |
person.wearsGlasses.code == "Y" |
Integer |
Native and Dynamic |
N |
N |
Integer |
person.leftEyePower == -5 |
List<BigDecimal> |
Native and Dynamic |
N |
Y |
List of Big Decimals |
person.payments[0].round() == 25000 |
List<Date> |
Native and Dynamic |
N |
Y |
List of Dates |
person.contactDate.compareTo(date) |
List<DynamicFieldPeriodBigDecimal> |
Dynamic |
Y |
Y |
List of Big Decimals |
person.height[0].value >= 6.2 |
List<DynamicFieldPeriodBigDecimal> |
Dynamic |
Y |
N |
List of Big Decimals |
person.height[0].value >= 6.2 |
List<DynamicFieldPeriodFlexCode> |
Dynamic |
Y |
Y |
List of codes |
person.paymentFrequencies[0].value.code == "SP01" |
List<DynamicFieldPeriodFlexCode> |
Dynamic |
Y |
N |
List of codes |
person.paymentFrequencies[0].value.code == "SP01" |
List<DynamicFieldPeriodString> |
Dynamic |
Y |
Y |
List of Strings |
person.dependentNames[0].value == "John" |
List<DynamicFieldPeriodString> |
Dynamic |
Y |
N |
List of Strings |
person.dependentNames[0].value == "John" |
List<DynamicRecord> |
Dynamic |
N |
Y |
List of Dynamic Records |
person.reportingCategories[0].type== 2750 |
List<DynamicRecordPeriod> |
Dynamic |
Y |
Y |
List of Dynamic Records |
person.healthInformation[0].tabaccoStatus == "Y" |
List<DynamicRecordPeriod> |
Dynamic |
Y |
N |
List of Dynamic Records |
person.healthInformation.asOf(date)[0].tabaccoStatus == "Y"
person.healthInformation[0].tabaccoStatus == "Y" |
List<FlexCode> |
Dynamic |
N |
Y |
List of codes |
person.hobbies[0].code == "Cricket" |
List<Object> |
Native |
N |
Y |
List of codes |
person.maritalStatusList.any{ it.maritalStatusType == "Single"} |
List<String> |
Native and Dynamic |
N |
Y |
List of Strings |
person.attendedSchools[0] == "Harvard" |
Long |
Native |
N |
N |
Long |
person.objectLastUpdatedBy != 10 //last updated by user |
OhiMoney |
Native and Dynamic |
N |
N |
Amount |
person.Money == 45.00 |
Reference Object |
Native |
N |
N |
Code |
person.prefix.code == "VON" |
String |
Native and Dynamic |
N |
N |
String |
person.firstName == "Bob" |
Optional Attributes
Optional attributes are the attributes that are left blank and have NULL values. Accessing such an attribute or a method often results in a technical error. You must check your logic before accessing an attribute that can have possible NULL values. Here is a code you can use:
if (claimLine.condition != null) { claimLine.condition.code == "REGULAR" }
The Safe Navigation Operator- or ?
symbol- is an alternative by Groovy to check for NULL.
Here is an example of how to use the operator:
claimLine.condition == null claimLine.condition?.code == null claimLine.condition?.code?.toLowerCase() == null
The operator bypasses a NULL value to return a NULL value. This avoids a technical error. The operator reduces code complexity but can lead to undesirable results. It is important to use the safe navigation operator cautiously and not to use it needlessly.
For example, assume a claim line with a condition attribute. Adding a value to a condition attribute is optional. The condition attribute can have REGULAR, IRREGULAR, or NULL values.
We want to apply a bonus of 10% if a line’s condition is IRREGULAR. The following two logics check the claim line for a condition with a NULL value and later check the type of the condition:
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 receive a bonus. Lines with a REGULAR condition and without a condition do not receive a bonus.
In the second logic, when claimLine.condition?.code
encounters a condition attribute without a value, it returns a NULL.
This is unequal to the string IRREGULAR and thus receives a bonus.
Now, check the following two logic that apply a bonus to any claim line having a condition other than REGULAR:
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 }
The two logic are no longer the same.
The first logic applies a bonus to claim lines that have an IRREGULAR condition.
The second logic applies the bonus to claim lines having an IRREGULAR condition and to claim lines without a condition.
Two results are different.
Accessing Insurable Entity
Suppose the following insurable entity types are configured :
Code | Plural Display Name | Singular Display | Usage Name |
---|---|---|---|
CAR |
Cars |
Car |
servicedCar |
PERSON |
Persons |
Person |
servicedPerson |
HOME |
Homes |
Home |
servicedHome |
In dynamic logic in order to access the attributes of an insurable entity; the sample groovy is:
// Insurable Person if (claimLine.servicedEntity.insurableEntityType.code == "PERSON"){ entityCode = claimLine.servicedEntity.person.code } // Insurable Object if (claimLine.servicedEntity.insurableEntityType.code == "CAR"){ entityCode = claimLine.servicedEntity.code }
Alternately, the code of the person can also be accessed with the configured usage name:
claimLine.servicedPerson.code
The code of a car can be access by:
claimLine.servicedCar.code
Assigning Attributes
Following are the different methods to assign values to an attribute depending on the type:
Attribute Type | Type of Field | Time-valid | Multi-value | Data Type | Examples |
---|---|---|---|---|---|
BigDecimal |
Native and Dynamic |
N |
N |
BigDecimal |
newPerson.adjustmentRate = 3.456 |
claimLine.LegLength = 3.4 |
|||||
Boolean |
Native |
N |
N |
Boolean |
newPerson.bankAccountNumberRelationList[0].preferred = true |
claimLine.emergency = false |
|||||
Date |
Native and Dynamic |
N |
N |
Date |
newPerson.endDate = java.sql.Date.valueOf("2018-07-26") |
claimLine.endDate = java.sql.Date.valueOf("2018-07-26") |
|||||
DynamicRecord |
Dynamic |
N |
N |
All |
Use the Predefined methods |
FlexCode |
Dynamic |
N |
N |
Character |
newPerson.wearsGlasses.code = "Y" |
claimLine.addiction ="1" |
|||||
Integer |
Native and Dynamic |
N |
N |
Integer |
newPerson.leftEyePower = -5 |
claimLine.leftEyePower = -3 |
|||||
List<BigDecimal> |
Native and Dynamic |
N |
Y |
List of Big Decimals |
newPerson.payments =[300.65,234.09] //set |
claimLine.payments =[334.78,12.68] //set |
|||||
List<Date> |
Native and Dynamic |
N |
Y |
List of Dates |
newPerson.contactDate = [java.sql.Date.valueOf("2014-07-26"), java.sql.Date.valueOf("2018-08-26")] //set |
claimLine.contactDate = [java.sql.Date.valueOf("2014-07-26"), java.sql.Date.valueOf("2018-08-26")] //set |
|||||
List<DynamicFieldPeriodBigDecimal> |
Dynamic |
Y |
Y |
List of Big Decimals |
newPerson.height = [new DynamicFieldPeriod(5.11, java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26")) |
Not applicable for Claims. |
|||||
List<DynamicFieldPeriodBigDecimal> |
Dynamic |
Y |
N |
List of Big Decimals |
newPerson.height = [new DynamicFieldPeriod(5.4, java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2015-04-26")) |
Not applicable for Claims. |
|||||
List<DynamicFieldPeriodFlexCode> |
Dynamic |
Y |
Y |
List of codes |
newPerson.paymentFrequencies = [["endDate": java.sql.Date.valueOf("2015-07-20"), "startDate": java.sql.Date.valueOf("2014-07-20"), "value": "MONTHLY"], ["endDate":java.sql.Date.valueOf("2016-07-20"), "startDate": java.sql.Date.valueOf("2015-07-20"), "value": "WEEKLY"]] //set |
Not applicable for Claims. |
|||||
List<DynamicFieldPeriodFlexCode> |
Dynamic |
Y |
N |
List of codes |
newPerson.paymentFrequencies = [["endDate": java.sql.Date.valueOf("2015-07-20"), "startDate": java.sql.Date.valueOf("2014-07-20"), "value": "MONTHLY"], ["endDate":java.sql.Date.valueOf("2016-07-20"), "startDate": java.sql.Date.valueOf("2015-07-21"), "value": "WEEKLY"]] //set |
Not applicable for Claims. |
|||||
List<DynamicFieldPeriodString> |
Dynamic |
Y |
Y |
List of Strings |
newPerson.dependentNames = [ new DynamicFieldPeriod("John", java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2018-08-26")) |
Not applicable for Claims. |
|||||
List<DynamicFieldPeriodString> |
Dynamic |
Y |
N |
List of Strings |
newPerson.dependentNames = [new DynamicFieldPeriod("John", java.sql.Date.valueOf("2014-08-26"), java.sql.Date.valueOf("2015-04-26")) |
Not applicable for Claims. |
|||||
List<DynamicRecord> |
Dynamic |
N |
Y |
List of Dynamic Records |
Use the Predefined methods |
List<DynamicRecordPeriod> |
Dynamic |
Y |
Y |
List of Dynamic Records |
Use the Predefined methods |
List<DynamicRecordPeriod> |
Dynamic |
Y |
N |
List of Dynamic Records |
Use the Predefined methods |
List<FlexCode> |
Dynamic |
N |
Y |
List of codes |
newPerson.hobbies = ["Cricket","Trekking"] //set |
claimLine.hobbies = ["Cricket","Trekking"] //set |
|||||
List<Object> |
Native |
N |
Y |
List of codes |
newPerson.contractAlignmentList[0].code = "PS167" |
claimLine.diagnosis = "125" |
|||||
List<String> |
Native and Dynamic |
N |
Y |
List of Strings |
newPerson.attendedSchools = "Harvard" |
claimLine.attendedSchools = "Harvard" |
|||||
OhiMoney |
Native and Dynamic |
N |
N |
Amount |
newPerson.totalAmount = 45.00 |
claimLine.totalAmount = 65 |
|||||
Reference Object |
Native |
N |
N |
Code |
newPerson.prefix = lookUp(Prefix.class, ['code': 'DE']) |
claimLine.procedure1 = "01.01" |
|||||
String |
Native and Dynamic |
N |
N |
String |
newPerson.suffix = "Mr" |
claimLine.colorCode = "Red" |
Assigning Sets of Attributes
When creating Claim Transactions, A Dynamic Logic is written that transfers values from a claim to a ctrClaim. For example:
ctrClaim.claimDate = claim.claimDate ctrClaim.objectVersionNumber = claim.objectVersionNumber ctrClaim.authorizationCode = claim.authorizationCode
This can be written in different ways. First, using the "with":
ctrClaim.with { claimDate = claim.claimDate objectVersionNumber = claim.objectVersionNumber authorizationCode = claim.authorizationCode }
Knowing that an attribute can be accessed as a map entry in Groovy, this can be rewritten as:
[ "claimDate" , "objectVersionNumber" , "authorizationCode" ].each { fieldname -> ctrClaim[fieldname] = claim[fieldname] }
A variation on the above is:
[ "claimDate" , "objectVersionNumber" , "authorizationCode" ].each { fieldname -> ctrClaim."$fieldname" = claim."$fieldname" }
Checking Starting Characters
Procedure and diagnosis conditions are ways to group codes based on properties of those procedures or diagnoses.
A common way is to group these codes based on the starting characters of their codes.
To clarify the way to write this, consider the dynamic logic to test if a value starts with 123
:
value.startsWith("123")
Alternatively, use the "find" operator (=~)
value =~ /^123/
The characters between the slashes (//) form a regular expression.
Regular expressions provide a powerful way to match strings.
The ^ means the beginning of the string, so in our example, the value needs to have "123" immediately after the beginning of the value.
To check if the value contained 123
at any position, the check would be:
value =~ /123/
Closures
Many of the Groovy methods (such as the aforementioned any
method) use a so-called closure.
As the Groovy documentation on Closures
says, a closure is simply a block of code that can take arguments and return a value.
Such blocks of code generally specify the behaviour of the method to which it is passed, for example the predicate for the any
method, specifying which item to search for.
By default, a closure’s input parameter is called it.
For more elaborate closures, a descriptive name might improve readability:
relation.medicalConditions.any { condition -> condition.code == "123.45" }
Comparing Lists
To compare a list (for example, a set of modifiers), be aware that the Groovy == operator only views the lists as equivalent if they are in the same order. If the order doesn’t matter (logically a set rather than a list) then an alternative is used. See the example below:
list0 = ["a", "b"] list1 = ["a", "b"] list2 = ["b", "a"] list0 == list1 list1 != list2 list1-list2 == [] list2-list1 == [] // Test for list equality ((list1-list2)+(list2-list1)) == [] // Alternative syntax list1.containsAll(list2) && list2.containsAll(list1)
Defining Reusable Constants
There may be occasions where it is desirable to define constants that can be used in the application across many dynamic logics. This can be accomplished by defining a reusable logic with interfaces. Suppose the following is defined in dynamic logic with code CONSTANTS.
interface SCALE { static final String COUPLE = "Couple" static final String ERROR = "Error" static final String FAMILY = "Family" static final String SINGLE = "Single" } interface DATES { static final Date HIGHDATE = java.sql.Date.valueOf("2999-12-31") static final Date LOWDATE = java.sql.Date.valueOf("1900-01-01") } interface INSURABLE_TYPES { static final String ADULT = "ADULT" static final String DEPENDANT = "DEPENDANT" } return
Please note the return statement in the end. This is necessary for the compiler to view the interfaces being separate classes, else it does not compile successfully.
The above reusable constant file can then be used in any logic where needed by importing it. For example:
import ohi.dynamiclogic.CONSTANTS return "abc " + SCALE.FAMILY
Extensions to Dynamic Logic
When dynamic logic is delivered as part of country pack deliverable, the dynamic logic scripts can be extended by customer for customer specific code. This is made possible by creating a dynamic logic with signature Extension and reference to the base dynamic logic that it extends. The methods declared in the dynamic logic must be static in nature and the name of the method and parameters number and type must be matching the method that the extension is extending.
For example: Adding numbers in a list
Base Logic: ADD_NUMBERS
def customerList = getCustomerList(inputList) // inputList is the binding to the logic return list*.value.sum() //Returns the customer list by using the input list or providing a totally new list. //By default returns back the input list def List getCustomerList(List inputList) { return inputList }
Extension: ADD_NUMBERS_EXT: This logic has signature: 'Extension' and has reference to the above logic
//Customer override def static List getCustomerList(List inputList) { def newList = new ArrayList<>(inputList) newList.add(5) return newList }
Logging
From dynamic logic it is possible to write information to the application’s log file. This can be a useful tool for debugging logic and to gain more insight into its execution. It can be used to figure out what the value of a certain variable is at runtime, or to follow the code path that is taken.
For this purpose, each dynamic logic has an input bind variable called log.
That variable refers to a logger instance, which has methods that
are named after the different log levels.
For more information about log levels, see the Logging and Auditing Operations chapter of the System Administration guide.
For example, an INFO level message can be logged using the info
method:
log.info("Hello world!")
Each log method allows further input parameters for message variables, starting from 0, like so:
log.debug("Value for member \{0} is: \{1}", person.code, val)
A log statement from dynamic logic results in a message being written to the application log file, if the configured log level threshold is equal to or lower than the log level of the log statement.
If the configured log level threshold has been set to for example WARN, then only errors and warnings are written to the log file, so a log.info
statement has no effect.
The log level threshold can be configured per dynamic logic, with the logger’s name set to ohi.dynamiclogic.
followed by the logic’s code in lowercase:
<logger name="ohi.dynamiclogic.my_dylo_code_123" level="debug"/>
Be careful not to end the dynamic logic with a log statement, as this might interfere with the default way Groovy implicitly handles return values. |
When using a log statement in a static method, make sure to pass the log statement as parameter. Otherwise, an error is displayed saying that it is not possible to access a non-static variable from static method. See the following example:
static def logInfo(log) { log.info("Hello world!") }
Message Placeholder Values
Validation dynamic logic, and certain conditions, can be associated with a message. These messages appear when the condition or validation is violated, for example by an integration point or operator who is trying to set a dynamic field value. For example, it is possible to create a validation that checks if a numerical value is less than 50, which is tied to the message: "Value must be less than 50". When a claims operator tries to enter a value for that field equal or greater than 50, the entry fails and that message appears.
Dynamic logic that is directly tied to a message can actually set that message’s placeholder values. So, rather than including the "50" as message text, alternatively, create a message with a placeholder instead: "Value must be less than {0}". Using a placeholder allows for greater re-usability of that particular message. The actual value of "{0}" is then determined at run time, during the execution of the dynamic logic to which it is tied.
To make this work, the dynamic logic validation has to look like this:
// setting the max value that is used both to validate and to appear in the message maxValue = 50 // saying that placeholder \{0} must be replaced by the value of maxValue errors[0] = maxValue // the actual validation: the field's value is less than maxValue return value < maxValue
Omitting the Return Statement
Consider the following validation that checks that a numerical values is less than 50:
if (value < 50) { return true } else { return false }
Although the check specified above is perfectly valid, it can be made shorter because groovy allows a logical statement, amounting to a boolean, to be a return value as well:
return value < 50
If a return statement is not specified, Groovy defaults to interpreting the value of the last executed line as the return value. So the validation can be simplified even further:
value < 50
Regular Expressions
Let’s assume a license plate format which looks like this: 99-XXX-99. This can easily be implemented using a regular expression and the match (==~) operator:
value ==~ /\d{2}-[A-Z]{3}-\d{2}/
Breaking it down:
\d{2}
\d means a digit. {2} means exactly 2 occurrences.
[A-Z]{3}
[A-Z] means a character in the range A-Z. [A-Z]{3} means exactly 3 uppercase characters. To check whether a value is a valid email address. This check can also be implemented with a regular expression:
value ==~ /[_A-Za-z0-9-]+(\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[_A-Za-z0-9-]+)/
Suppose a country knows two formats for its bank accounts. One starts with a P followed by 3-7 digits. The other consists of 9-10 digits with a check sum of 11. The dynamic logic that validates a value to be one of these two formats:
// Match for first format if (value ==~ /P\d{3,7}/) { return true } // Match for second format if (value ==~ /\d{9,10}/) { // Perform the checksum calculation j = value.length() total = 0 for( i in 0 .. value.length()-1 ) { total += j * value[i].toInteger(); j-- } return (total % 11) == 0 } else { return false }
Suppose a country knows a bank account format that enforces that either the last two positions of the bank account must be equal to the rest of the division of the earlier positions by 97 OR the last two positions of the bank account must be equal to 97 if the rest of the division of the earlier positions by 97 equals zero.
For example: 884 2337123 65 is a correct bank account because: 8842337123 modulus 97 is 91158114 with rest 65
Integer checksum = value[-2..-1].toInteger() // Last 2 digits Long base = value[0..-3].toLong() Integer mod = base%97 if (mod==0) { return checksum == 97 } else { return checksum == mod }
Consider a validation for International Bank Account Number (IBAN). An example IBAN to be validated is NL69PSTB0001234567.
IBAN consists of a country code (2 positions, alphanumeric), a check number (2 positions, numeric) and an identification (maximum of 30 positions, alphanumeric). The check number must be equal to the number that is arrived at by the following algorithm:
-
take the identification (the input value with the first 4 characters taken off)
-
add the country code to the right
-
replace all characters by their position in the alphabet +9 (so A=10, B=11 etcetera)
-
add two zeros to the right
-
modulus97, determine rest
-
subtract this rest from 98
For example: the identification of a Dutch IBAN from the Postbank is PSTB0001234567
-
PSTB0001234567
-
PSTB0001234567NL
-
2528291100012345672321
-
252829110001234567232100
-
252829110001234567232100 modulus 97 has rest 29
-
98 - 29 = 69
So the complete IBAN must be NL69PSTB0001234567
// length is at least 8 if (value.size() < 8) { return false } def base = value.substring(4) + value[0..1] Integer checkNumber = value[2..3].toInteger() def expand = "" base.each { char c = it if (Character.isDigit(c)) { expand += c } else { expand += (int)c-55 } } expand += "00" rest = expand.toBigInteger()%97 return (checkNumber == 98-rest)
Return Statement in a Closure
Note that there is no returning from a closure. A closure is a predefined code block in Groovy. An example is the "each" method list. Consider the following example:
prefix = triggeringClaimLine.procedure.code.substring(0,3) claimLines.each { claimLine -> // the same first three digits in the procedure code if ( claimLine.procedure.code.startsWith(prefix) ) { return claimLine } } return null
Although the return statement in the closure terminates the current iteration, it does not break from the closure nor actually return a value. The execution simply continues with the next iteration. This example script always reaches the last line and returns null.
If the desired behavior is that the return statement breaks the iteration, an explicit for-loop construct can be used:
prefix = triggeringClaimLine.procedure.code.substring(0,3) for (claimLine in claimLines){ // the same first three digits in the procedure code if ( claimLine.procedure.code.startsWith(prefix) ) { return claimLine } } return null
Alternatively, use a closure with the find method. The behavior of this closure is that it terminates as soon as it finds a list item that meets the scripted condition:
prefix = triggeringClaimLine.procedure.code.substring(0,3) return claimLines.find { claimLine -> // the same first three digits in the procedure code claimLine.procedure.code.startsWith(prefix)
Reusing Dynamic Logic
There may be occasions where it is desirable to reuse a piece of logic across multiple other units of dynamic logic. This can be accomplished by defining the logic that to reuse inside a static method:
static def absoluteValue(value) { if (value < 0) { return value * -1; } else { return value; } }
A static keyword is used to denote that the method is not tied to that particular instance of dynamic logic, but can also be used stand-alone outside the dynamic logic that it is defined in. A static method can be referred to from another dynamic logic. To do that, the method name needs to be prefixed with the package name "ohi.dynamiclogic"(if the package was not already declared in the reusable script) and the code of the owning dynamic logic. That complete reference can be stored at the top of the dynamic logic via an import statement:
import static ohi.dynamiclogic.EXAMPLE_DYLO_CODE_123.absoluteValue; absoluteValue(-10) == 10
If a reusable dynamic logic is changed, and the changes are not reflected in the dynamic logics that have used or imported the reusable dynamic logic. To get it to work, invoke the invalidateall operation before starting other operations. See Invalidate Dynamic Logic
for more info.
|
Using Classes
It is also possible to use the classes in the dynamic logic script. There are few caveats as compared to normal groovy scripting. Only one class can be defined in a script. The name of the class is same as the code of the script(uppercase). For example, script with code ADD_NUMBER.groovy
package ohi.dynamiclogic class ADD_NUMBER { def add(num1, num2) { num1 + num2 } }
This can then be used in another script like following
package ohi.dynamiclogic log.debug(new ADD_NUMBER().add(1,2))
Using Packages
If the dynamic logic does not define a package of its own, then it is by default put in package ohi.dynamiclogic. This package can also be declared directly in the script to make the standalone testing easier. It is also possible to further structure the scripts using nested packages with the condition that the packages must always be nested inside ohi.dynamiclogic. This is done to make that these scripts are Oracle Health Insurance specific and can security checked properly.