Transaction Branching and Invalidation
The transaction stack is more than just a simple stack. It allows for branching in certain conditions.
Branching is needed to handle situations where there are multiple provisional transactions, of which the insured will accept one of them (or one set of them) and not accept the others. For example, the insured may wish to quote different levels of coverage when considering adding a new exposure to a policy.
The branching structure handles these situations cleanly while maintaining the important invariants we hold to with policy data.
Note
Even with complex branching, from the point of view of any given transaction, the transactions below it form a stack without branches.
Example
Consider an auto policy with a Buick covered. Then, Susan (the insured) is considering purchasing a new vehicle for her 16-year-old son: either a 2020 Chevrolet Corvette or a 1983 Toyota Tercel. She can ask for details about both of these:
First, a policy change transaction is created for the Corvette and advanced to
priced
state. By default, itsbasedOn
property will be set to the first transaction’s locator.
Then, a separate policy change transaction is created for the Toyota, only this one has its basedOn
property will be the same as the Corvette transaction’s (pointing at the issuance transaction) rather than the existing top level transaction (the Corvette transaction itself).
The branching in the transaction stack now looks like this:
Note
The branches above are not materialized entities in the system; they are shown based on interpreting the transaction stack starting at the transaction heads, Tx2 and Tx3.
Suppose the initial policy rate was $600 per six months, and the rate increase for the two transactions was calculated at +$1800 for the Corvette and +$450 for the Toyota. Because Susan is prudent, she chooses the Toyota, so transaction #3 is issued. The system will automatically invalidate any transaction on a parallel branch from a branch containing an issued transaction, so the branches in the policy now look like this:
We can assess the state of coverage as usual by starting at the top of the stack for the relevant transaction; here we care about the top-most issued transaction and choose transaction 3, and proceed down to transaction 1. So the interpreted (called local) in-force transaction stack for the policy looks like this:
Note
There is always exactly one answer to the question “what is the top-most issued transaction in the stack?” This will often be the starting point for analyzing data about the policy.
Details
Any transaction can be based on the top of the transaction stack that is in an extendible state (one that’s not discarded
or rejected
or refused
or invalidated
), or alternatively a transaction can be based on a transaction further down the stack, provided that no transaction above that fork point is itself issued (since that would lead to automatic invalidation of the new transaction).
Any transaction must be in the same or earlier state as the transaction it is based on (for example, priced transactions cannot be based on unpriced transactions.)
When branches are formed this way, issuing any transaction in a branch will invalidate all transactions in all parallel branches.
The invalidation discussion above is driven by the need for our stack of issued transactions to be primary. That is, there really can’t be any alternative to an issued transaction, though the issued transaction itself could be updated with a policy change or even reversed. Also, issued transactions are, in a sense, the “permanent record” of the policy. We can’t look at the policy data and ignore the fact that some issued transaction existed, even if has been reversed.
Conversely, non-issued “provisional” transactions aren’t primary in the same way as issued transactions, so those are handled differently than issued transactions:
Provisional non-accepted transactions can be taken out of consideration at any time by the client either invalidating them directly or discarding them outright. All transactions above the invalidated or discarded transaction will automatically have their state set to invalidated or discarded, respectively, as well.
Likewise, resetting such transactions will reset those that are above; they would all then be in draft
state.
accepted
transactions may have been invoiced, and it such cases it is not OK to discard them. Because of this, these transactions may be accepted but cannot be discarded. They can be invalidated, which would reverse the effects of any billing for them.
invalidated
, rejected
and discarded
transactions can usually be completely ignored for most purposes. When the API is queried about transactions, discarded
transactions will never be included unless that specific transaction is requested by its locator.
Note
Invalidated and rejected transactions may be filtered out, or perhaps filtered by default unless the user specifically wants them to be included; this is behavior may change.
As mentioned above, all of these provisional transactions will be automatically invalidated when a transaction on a parallel branch is issued.
Elements
New elements can be added to a policy by a transaction. There can be more than one “creating” transaction because transactions can add a new element with either this for an element that already exists:
{
"addElements": [
{
"locator": "ABC"
}
]
}
or with this:
{
"addElements": [
{
"type": "vehicle",
"data": {
"make": "Toyota",
"model": "Camry",
"vin": "123abc"
}
}
]
}
The first approach can be useful when creating different branching alternatives (say, by creating the same element in each branch but varying its initial data.) In such a case, attempting to add an element with an invalid locator or that references a discarded element would cause an error.
Auto-Rebasing
Auto rebasing is available when a transaction is to be based on some transaction other then the latest created transaction, and transactions that would be invalidated are still desired.
For example, suppose a policy is issued on January 1, and a renewal, effective July 1 is created on June 15, but is only accepted
but not issued
.
Then, a policy change effective June 20 needed. You now have three options for how to handle this:
Base the policy change on the renewal. It will be a standard out-of-sequence transaction, but to issue it would require issuing the renewal transaction as well.
Base the policy change on the issuance transaction. The renewal will be invalidated when the change is issued because it is in a conflicting branch.
Base the policy change on the issuance, but also set
autoRebase=true
when issuing it.
For the auto-rebase case, the system will do the following when the policy change transaction is issued:
It will change the
baseTransactionLocator
of the renewal to be the locator of the new policy change transaction.And, it will reset the renewal to
draft
(because it will need to be reprocessed based on possible changes to the policy contents.)
By default, autoRebase
is set to true
for these cases. Select false
if you would prefer conflicting transactions to be invalidated.
All conflicting branches that can be rebased will be rebased, in the case there is more than one branch.
Note
Only branches that start with an effectiveTime
on or after the effective time of the new transaction can be automatically rebased. All others will be invalidated because they would create out-of-sequence situations, and for those a different base transaction should probably be used.