Calculations Module
This module was created by the Project: D1 Formal Sector Support the concept and business logic where develop for that scope but the rules are used in other context such as the Project: 2021.T3 Payment Layer
version
version | date | comment | changes |
---|---|---|---|
1 | 06-2021 |
| |
2 | 08-2021 | remove acceptance criteria form FS cleaning FS specifics move calculation rules in a specific table add filter based on type and sub-type
|
Concepts
The rules for the the calculation of the policy value or payment to HF can be complex and evolving. Therefore, the solution should bring flexibility, to avoid complete redesign and databases change when a parameter to define the contribution value is changed.
To bring that flexibility, the solution described here uses a Calculation Rule (the Calculation Rules are defined in additional openIMIS modules extending the Calculation Module). This module is defining a framework to design calculations and to manage their activation and versioning in openIMIS. This
The value of the parameters will be saved as a JSON string in the json_ext
field.
Later, this module could be used as a framework to create event-based actions
Business process: example of the formal sector
This module will use a "generic contribution" to get the required parameters and the json_ext
database field will be used to save the parameters (generic backend feature). Finally, a signal will trigger the calculation action.
The left part, if there is no reachable technical solution will be hardcoded by having the contribution “knowing“ which are the related class (e.g CPB for PH insuree and contract detail and calculation for Contribution)
Solution
This module will rely heavily on the django signals
In order to have a generic approach that will make the code more readable, one argument will be expected in the signal, this argument will be called obj and should contain the instance of the class sending his signal:
my_signal= dispatch.Signal(providing_args=["instance","user"])
Use cases (specific to formal sector)
UC14-1: add a new Calculation rule (needs to change the openimis.json).
UC14-2: use the contribution_fe to select a calculation rule.
UC14-3: use the contribution_fe to display the parameters required by multiple calculation for an object.
UC14-4: replace a calculation rule.
UC14-5: remove a calculation rule.
Detailed design
Calculations Backend Module
Backend management code
The backend management code will be used to register the calculation sub-module parameters, the sub-module status and the ReadUpdate right management on the database.
This code should have a function to clean the table from the remove calculation
This code should manage the uniqueness of the active version for a given UUID
A Custom signal class be created to add a priority of calculation
from django.dispatch import Signal as BaseSignal
from django.dispatch.dispatcher import _make_id
class Signal(BaseSignal):
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None, priority=50):
if dispatch_uid is None:
dispatch_uid = _make_id(receiver)
inner_uid = '{0}{1}'.format(priority, dispatch_uid)
super(Signal, self).connect(receiver, sender=sender, weak=weak, dispatch_uid=inner_uid)
self.receivers.sort()
Source of the sub Signal class: https://stackoverflow.com/questions/10017567/possible-to-change-order-of-django-signals
absCalculationRule class
The calculation module will be composed of an abstract class "absCalculation" that will have those methods and members.
This class should be imported as a module in the subclass definition (inside the "hat" modules)
Members
static version
The version is used to keep track of the changes in the version of the calculation rule,
status
integer inactive, future, active, archived
static UUID
UUID to follow the version of the calculation
static CalculationRuleName
short name of the calculation rule
static description
description text of the calculation
list of impacted class and parameters
<list>JSONObject objects {class:"className"(to be compare with property name of the FE details object which is pulled from BE), 'parameters':['type':"int",'name':"example",'label':{'en':'DiplayName'},'rights':{'read':"readAuthority", ‘write':"writeAuthority"},relevance:"frontendjsdisplaylogic",condition:"frontendjsvalidationlogic",'optionSet’:[]]} // list of the parameters required on other object,
relevance and condition are Nice to have.
Abstract Methods defined in rules
active_for_object
ready
check_calculation
calculate
get_linked_class
convert
Methods defined in abstract class
get_uuid
return UUID
get_version
return version
get_calculation_rule_name
return calculation rule name
get_rule_name(cls, sender, class_name, **kwargs):
return rule object is classname is part of impacted class
get_description
return description
get_impacted_class_parameter
return impacted_class_parameter
get_rule_details(classname)
For object in listObject
if object.class == classname
return object
get_paramteres(ClassName, instance)
#className is the class name of the object where the calculation param need to be added
#instance is where the link with a calculation need to be found, like the CPB in case of PH insuree or Contract Details
This function has two steps
1- getRuleDetails(className)
2- define if the calculation should be used
for each “eligible“ ruleDetails,
checkCalculation(instance) #check if the calculation is really concerning the class for that context
return a list only with rule details that matches step 1 and 2
run_calculation_rules(instance, context)
this function will be register to the module signal via the ready function if the rule is active
1- getRuleDetails(instance.__class__)
2 if activeForObject(instance)
calculate(instance)
run_convert(instance, from, to, **argv)
execute the conversion for the instance with the first rule that provide the conversion (see get_convert_from_to)
get_convert_from_to()
get the possible conversion, return [calc UUID, from, to]
Module function/Service
NEED TO BE DEFINED ON MODULE LEVEL
get_rule_details(classname)
this function will send a signal and the rules will reply if they have object matching the classname in their list of object
listruledetails = []
for all calculation rules (send signal):
if result_signal != []
merge listruledetails and result_signal
return listruledetails
calculate(instance, context)
for all calculation rules (send signal):
runCalculationRules(instance, context)
no return
get_parameters(ClassName, instance)
#className is the class name of the object where the calculation param need to be added
#instance is where the link with a calculation need to be found, like the CPB in case of PH insuree or Contract Details
listparameters = []
for all calculation rules (send signal):
if result_signal != []
merge listparameters and result_signal
return the ruleDetails that are valid to classname and related to instance
get_linked_class(List[ClassName] = None)
#List[ClassName] is send from FE, by checking the class used in page where the user ave access if None (in case too difficult to do on the FE) all liked class will be retrieve
for all calculation rules (send signal):
returnListClass = []
if listClass == None
sendsignal getLinkedClass(none)
if result_signal != []
merge returnListClass and result_signal
else
for class in listclasss
sendsignal getLinkedClass(class)
if result_signal != []
merge returnListClass and result_signal
return all the returnListClass
Authorities
Calculation (prefix 153)
search → 153 001
update → 153 003 (to activate or not)
Calculations Frontend Module
(NICE TO HAVE, or via Django Admin)Calculation management page
This page will show the calculation page and change the status/activate a given version
This page should group the calculation by UUID
Contributions
Standard contributions should be available for most of the entities (claims, contribution, contribution collection, policies, insuree ....)
PH insuree
Contract details
ContributionPlan
All the other are NICE TO HaVE
Those contributions should get the parameters template from the backend and display the relevant ones. The values should be saved in the JsonExt fields
Ideally the contribution from the main calculation module should manage the contribution form the rules in order to avoid having the same parameters twice. For example , a single insuree can have 2 contributions plan using those two rule different rules based on the income
I see two possible ways (one must be selected)
either by wrapping the fe contribution from the rules in a “overall calculation fe contribution“ (this will require rebuilding the front end when adding a rule)
by managing all the rule fe contributions directly within the calculation fe contributions using the parameters defined in the rules (preferred solution)
only simple type should be supported:
Number (int. float …)
Select ( choice list key(int)-> value([lang-strings]))
checkbox
refresh logic should be based on the link between the entity having the contribution and the entity that hold the calculation
Appearance/logic of the data should be defined based:
on the entity
on the rules related to this entity (directly or not)
(nice to have) format should be based on the rule fields constraint (regex)
(nice to have) constraint/ validation should be based on the rule fileds constraint (js test function)
Overall the logic use in enketo could be reused enketo-core/src/js/input.js at e7159f95c5f459efeb83314fdd0aecac78e47400 · enketo/enketo-core
Nice to have
Formula parser
https://formulas.readthedocs.io/en/stable/
Did you encounter a problem or do you have a suggestion?
Please contact our Service Desk
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. https://creativecommons.org/licenses/by-sa/4.0/