Underwriting Plugin
Overview
As discussed in the main feature guide, Underwriting is the process that determines whether some prospective transaction is acceptable to the business, considering risk and any other relevant factors.
Underwriting Flag Levels Refresher
Underwriting flags have levels, used to indicate some risk assessment condition or requirement that needs attention. Flags can be of level:
Approving (
approve
): Supersedes all other flags, the transaction is approvedBlocking (
block
): Prevents policy progression until manually cleared or an approving flag is addedDeclining (
decline
): Prevents policy progression until manually cleared or an approving flag is addedInformational (
info
): Provides information but doesn’t block progressionRejecting (
reject
): Automatically rejects the policy
Flags of level block
, decline
, or reject
will prevent the transaction from progressing until they are cleared or an approve
flag is added.
Underwriting Evaluation Process
The basic underwriting evaluation process works as follows:
The system is requested to transition the quote or transaction to the underwritten state, either directly, or by attempting a downstream action (/accept
or /issue
). In doing so, the system executes two key logic steps:
The underwriting plugin will be called to programmatically assess, add, and/or clear flags
The system will then evaluate the collection of uncleared flags on the quote or transaction with the following algorithm:
"if": "any flag is of level 'approve' ", "then": "underwriting passes, the quote's underwriting status is 'approved' ", "else if": "any flag is of level 'reject' ", "then": "underwriting fails, the quote's underwriting status is 'rejected' ", "else if": "any flag is of level 'decline' ", "then": "underwriting fails, the quote's underwriting status is 'declined' ", "else if": "any flag is of level 'block' ", "then": "underwriting fails, the quote's underwriting status is 'underwritingBlocked' ", "else": "underwriting passes, the quote's underwriting status is 'none' ",
Common Applications
Common applications of this logic might include:
Avoid ever blocking underwriting by not adding any flags, or add only
info
flagsExplicitly add one of an
accept
,reject
, ordecline
flag to essentially by-pass evaluation described above and end directly in that underwriting status [*]Enforce certain checks, (i.e. an inspection or manual review), by adding and subsequently clearing flags of type
block
for each such task
The system is not prescriptive about what combination of flags are used, only the evaluation logic of the final collection of flags. This provides the flexibility to implement whatever business logic is appropriate for your use case.
Blocked Underwriting
There are three failure states that can occur from underwriting that will prevent the quote or transaction from advancing beyond the underwritten state, they have the following implied meanings:
Rejected: The quote is effectively dead. It cannot be processed further, except that it may be discarded.
Declined: The quote cannot proceed as is, but the flags collection can be updated (e.g. clear or add flags). The quote may also be reset.
Blocked: A decision hasn’t been made, but the quote cannot advance, behaving basically the same as if it were declined.
The reason for these two similar but separate states is to cater to different business meanings.
Declined
can be interpreted as meaning a soft reject - perhaps an offer of coverage can be made with alternative terms, or after some changes are made.Blocked
generally means there is more work to be done - such as an inspection or a review.
Plugin Configuration
It is in the underwriting plugin that the logic to interpret, add new, or clear flags is implemented. It is important to note that some flags may have been set via API before the execution of the plugin.
Interface Requirements
Your plugin must implement the UnderwritingPlugin
interface with methods for each product type:
public class CommercialAutoUnderwritingPlugin implements UnderwritingPlugin {
// Implement the underwriting methods for CommercialAuto quotes
public UnderwritingModification underwrite(CommercialAutoQuoteRequest request) {
return underwriteCommercialAuto(request.quote());
}
// Implement the underwriting methods for all other CommercialAuto transaction categories
public UnderwritingModification underwrite(CommercialAutoRequest request) {
if (request.segment().isPresent()) {
return underwriteCommercialAuto(request.segment().get());
}
return UnderwritingModification.builder().build();
}
}
Return Object
The underwriting plugin returns an UnderwritingModification object with these key properties:
UnderwritingModification structure:
.flagsToCreate(newFlags)
- List of UnderwritingFlagCore objects to create new underwriting flagsBuilt using the builder pattern:
UnderwritingModification.builder().flagsToCreate(newFlags).build()
UnderwritingFlagCore structure:
.level(UnderwritingLevel)
- Severity level:none
,info
,block
,decline
,reject
,approve
.note(Optional.of(String))
- Description/message for the flag.tag(Optional.of(String))
- Generally used as unique identifier for the rule.elementLocator(Optional.of(locator))
- ULID pointing to the specific element
Checking for Existing Flags
Note
Once in a declined
or blocked
state, and regardless of what flags are added or cleared, the plugin will be called again when the system is requested to reevaluate underwriting.
In order to make informed decisions about whether to add additional flags, in particular to avoid adding unintended duplicates of already cleared flags (creating an infinite loop scenario), the plugin implementation should always complete an upfront check for existing flags so the relevant context is established.
The example below shows how to fetch existing flags for a CommercialAuto
product, track existing rule IDs (via the flag’s tag
property) to avoid duplicates, and only create a new flag if it doesn’t already exist:
private UnderwritingModification underwriteCommercialAuto(CommercialAuto product) {
// Get existing flags first
DataFetcher dataFetcher = DataFetcher.getInstance();
UnderwritingFlags flags = dataFetcher.getQuoteUnderwritingFlags(product.locator());
// Track existing rule IDs to avoid duplicates
Set<String> existingRuleIds = new HashSet<>();
existingRuleIds.addAll(flags.flags().stream()
.map(flag -> flag.tag().orElse(""))
.filter(tag -> !tag.isEmpty())
.collect(Collectors.toSet()));
existingRuleIds.addAll(flags.clearedFlags().stream()
.map(flag -> flag.tag().orElse(""))
.filter(tag -> !tag.isEmpty())
.collect(Collectors.toSet()));
List<UnderwritingFlagCore> newFlags = new ArrayList<>();
// Only create flag if it doesn't already exist
if (!existingRuleIds.contains("HIGH_RISK_DRIVER")) {
// Create new flag
newFlags.add(createFlag(UnderwritingLevel.block,
"Driver has multiple violations", "HIGH_RISK_DRIVER", driver.locator()));
}
return UnderwritingModification.builder()
.flagsToCreate(newFlags)
.build();
}
Some other important considerations when implementing your plugin:
Conditions where a duplicative flag should be added (e.g. a second high risk driver)
Distinct handling for quotes vs. policy transactions
Scenarios where an already cleared flag should or should not be re-added (e.g. a previously cleared high risk driver who has since had another violation)
Meaningful
tag
values for each flag type to facilitate tracking (e.g."HIGH_RISK_DRIVER_" + driver.locator()
)
Complete Template Example
package com.socotra.deployment.customer.commercialauto;
import com.socotra.coremodel.UnderwritingModification;
import com.socotra.coremodel.UnderwritingFlagCore;
import com.socotra.coremodel.UnderwritingLevel;
import com.socotra.coremodel.UnderwritingFlags;
import com.socotra.deployment.DataFetcher;
import com.socotra.deployment.ResourceSelectorFactory;
import com.socotra.deployment.ResourceSelector;
import com.socotra.deployment.customer.*;
import com.socotra.platform.tools.ULID;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
public class CommercialAutoUnderwritingPlugin implements UnderwritingPlugin {
public UnderwritingModification underwrite(CommercialAutoQuoteRequest request) {
return underwriteCommercialAuto(request.quote());
}
public UnderwritingModification underwrite(CommercialAutoRequest request) {
if (request.segment().isPresent()) {
return underwriteCommercialAuto(request.segment().get());
}
return UnderwritingModification.builder().build();
}
private UnderwritingModification underwriteCommercialAuto(CommercialAuto product) {
// STEP 1: Get existing flags to avoid duplicates
DataFetcher dataFetcher = DataFetcher.getInstance();
UnderwritingFlags flags = dataFetcher.getQuoteUnderwritingFlags(product.locator());
Set<String> existingRuleIds = new HashSet<>();
existingRuleIds.addAll(flags.flags().stream()
.map(flag -> flag.tag().orElse(""))
.filter(tag -> !tag.isEmpty())
.collect(Collectors.toSet()));
existingRuleIds.addAll(flags.clearedFlags().stream()
.map(flag -> flag.tag().orElse(""))
.filter(tag -> !tag.isEmpty())
.collect(Collectors.toSet()));
List<UnderwritingFlagCore> newFlags = new ArrayList<>();
// STEP 2: Implement your underwriting rules
// Example: Driver age validation
for (Driver driver : product.data.drivers) {
if (driver.data.age() < 25 && !existingRuleIds.contains("YOUNG_DRIVER_" + driver.locator())) {
newFlags.add(createFlag(UnderwritingLevel.block,
"Driver under 25 - review experience",
"YOUNG_DRIVER_" + driver.locator(), driver.locator()));
}
}
// Example: Vehicle value validation
for (Vehicle vehicle : product.data.vehicles) {
if (vehicle.data.value != null && vehicle.data.value.compareTo(new BigDecimal("100000")) > 0
&& !existingRuleIds.contains("HIGH_VALUE_VEHICLE")) {
newFlags.add(createFlag(UnderwritingLevel.block,
"Vehicle value exceeds underwriting guidelines",
"HIGH_VALUE_VEHICLE", vehicle.locator()));
}
}
// STEP 3: Return modification with new flags
return UnderwritingModification.builder()
.flagsToCreate(newFlags) // Use flagsToCreate
.build();
}
private UnderwritingFlagCore createFlag(UnderwritingLevel level, String note,
String ruleId, ULID locator) {
return UnderwritingFlagCore.builder()
.level(level) // none, info, block, decline, reject, approve
.note(note) // String description/message for the flag
.tag(Optional.of(ruleId)) // Unique identifier for the rule
.elementLocator(Optional.of(locator)) // Links flag to specific element
.build();
}
}
Best Practices Summary
Always Check Existing Flags First
Use
DataFetcher.getInstance().getQuoteUnderwritingFlags()
Track existing rule IDs to prevent duplicates
Make informed decisions about flag creation/updates
Use Proper Builder Methods
.flagsToCreate()
not.flags()
.note()
not.message()
Handle All Request Types
Implement both quote and policy transaction methods
Check for segment presence in transaction requests
Use Unique Rule Identifiers
Set meaningful
tag
values for each flag typeInclude element-specific identifiers when needed
Provide Clear Flag Messages
Write actionable notes that help underwriters understand the issue
Include relevant context and next steps
Test Thoroughly
Verify flag creation and duplicate prevention
Test both quote and policy transaction scenarios
Validate flag levels and their impact on policy progression