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 approved

  • Blocking (block): Prevents policy progression until manually cleared or an approving flag is added

  • Declining (decline): Prevents policy progression until manually cleared or an approving flag is added

  • Informational (info): Provides information but doesn’t block progression

  • Rejecting (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:

  1. The underwriting plugin will be called to programmatically assess, add, and/or clear flags

  2. 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 flags

  • Explicitly add one of an accept, reject, or decline 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 flags

  • Built 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

  1. Always Check Existing Flags First

    • Use DataFetcher.getInstance().getQuoteUnderwritingFlags()

    • Track existing rule IDs to prevent duplicates

    • Make informed decisions about flag creation/updates

  2. Use Proper Builder Methods

    • .flagsToCreate() not .flags()

    • .note() not .message()

  3. Handle All Request Types

    • Implement both quote and policy transaction methods

    • Check for segment presence in transaction requests

  4. Use Unique Rule Identifiers

    • Set meaningful tag values for each flag type

    • Include element-specific identifiers when needed

  5. Provide Clear Flag Messages

    • Write actionable notes that help underwriters understand the issue

    • Include relevant context and next steps

  6. Test Thoroughly

    • Verify flag creation and duplicate prevention

    • Test both quote and policy transaction scenarios

    • Validate flag levels and their impact on policy progression