Levelbrook Labs

Building an Insurance Program Data Configurator: Notes on a Hard Insurtech Problem

Patrick Donahue · Levelbrook Consulting

Insurtech presents a class of engineering problems that are far from the typical CRUD application. They operate in a domain where correctness is a non-negotiable business requirement and complexity is the default state. One of the most interesting challenges is building tooling for Managing General Agents (MGAs). MGAs underwrite risk on behalf of insurance carriers, which means they must precisely define, configure, and manage "insurance programs"—the specific bundle of rules, coverages, limits, and pricing for a product like commercial property insurance in a given region.

The core task is to translate a carrier's dense, 200-page PDF underwriting guide into a dynamic, interactive, and foolproof digital system. This system must allow an MGA's internal experts to configure these programs without writing code, ensure every policy written against that program is valid, and adapt to constant changes in rates and regulations. This isn't just data entry; it's about encoding deep, conditional domain logic into a durable software artifact.

Try the interactive demo

Architecting for Deep Configurability

Let's consider how to approach this with a modern, pragmatic stack: a Ruby on Rails backend API, an Angular/TypeScript frontend, and infrastructure managed with Terraform. The choice of stack is less important than the architectural principles, but these tools provide a solid foundation.

The Data Model: JSONB is Your Friend

The first temptation is to model insurance programs with a rigid SQL schema: tables for coverages, limits, deductibles, etc., with dozens of columns and foreign keys. This path leads to pain. Every new carrier or product variation requires a database migration. The logic becomes scattered across a sprawling object graph.

A more resilient approach is to embrace semi-structured data. PostgreSQL's JSONB type, managed through Rails/ActiveRecord, is ideal. We can define a core model, perhaps ProgramTemplate, that stores the *shape* of a program, and a corresponding ProgramConfiguration that stores the *values* for a specific MGA's instance of that program.

The ProgramTemplate could hold a structure based on JSON Schema to define the available fields, their types, and basic validation rules. It would also contain a uiSchema to give the frontend hints on how to render the form—grouping fields into sections, defining control types (slider, dropdown), and providing help text.

# A simplified ProgramTemplate's schema field (in JSONB)
{
  "title": "Commercial Property Program",
  "type": "object",
  "properties": {
    "location": {
      "type": "object",
      "properties": {
        "state": { "type": "string", "enum": ["CA", "NY", "TX"] }
      }
    },
    "building": {
      "type": "object",
      "properties": {
        "construction_type": { "type": "string", "enum": ["Frame", "Masonry"] },
        "coverage_limit": { "type": "integer", "minimum": 50000 }
      }
    },
    "deductibles": {
      "type": "object",
      "properties": {
        "standard": { "type": "integer", "default": 2500 }
      }
    }
  },
  "required": ["location", "building"]
}

A ProgramConfiguration would then store a JSONB document that conforms to this schema, representing a live, configured program. This approach gives us versioning, flexibility, and the ability to introduce new program types without touching the database schema.

The Frontend: A State Machine with a UI

The user experience for the configurator must be real-time. If an underwriter selects "CA" as the state, the available endorsements and deductible options must update instantly. This is where a robust frontend framework like Angular, combined with TypeScript's static typing, shines. TypeScript interfaces can be generated directly from the JSON Schemas, ensuring type safety between the Rails backend and the Angular frontend.

The configurator isn't a single form; it's a stateful application. The state of the entire configuration object is the central source of truth. As the user makes changes, the frontend can:

While WebSockets or Turbo Streams could provide server-pushed updates, a well-designed async request/response model on field changes is often simpler and sufficient for this use case. The key is to keep the frontend responsive by offloading complex rule evaluation to the backend.

Where Things Break: Cross-Field Validation and Scale

JSON Schema handles basic validation well, but insurance logic is rarely basic. The real complexity lies in cross-field validation: "If construction_type is 'Frame' AND state is 'CA', then the minimum standard_deductible must be $5,000."

This is business logic, and it belongs on the server. In our Rails backend, we'd implement a set of validation service objects. These plain Ruby objects would take a configuration hash as input, traverse it, and apply a series of rules, returning a structured list of errors or warnings. This keeps the logic out of the models and makes it highly testable.

# app/services/program_validators/california_frame_deductible_validator.rb
class ProgramValidators::CaliforniaFrameDeductibleValidator
  def self.validate(config)
    errors = []
    is_ca = config.dig('location', 'state') == 'CA'
    is_frame = config.dig('building', 'construction_type') == 'Frame'
    deductible = config.dig('deductibles', 'standard').to_i

    if is_ca && is_frame && deductible < 5000
      errors << { path: '#/deductibles/standard', message: 'Minimum deductible for Frame construction in CA is $5,000' }
    end
    errors
  end
end

At scale, with thousands of potential fields and hundreds of cross-cutting rules, running these validations can become a performance bottleneck. The solution involves memoization, optimizing rule execution order, and potentially designing a more sophisticated rule engine if the complexity warrants it. However, starting with simple, explicit Ruby code is the most pragmatic path.

Pragmatism, Correctness, and the Human in the Loop

In a domain with such high stakes, engineering tradeoffs lean heavily towards correctness and auditability. An error in configuration could lead to writing millions in uninsurable risk.

This means:

A Reflection on the Problem

Building an insurance program configurator is a compelling technical problem because it forces a synthesis of data modeling, user experience, and complex business logic. It's not about replacing the expert underwriter. It's about building a tool that amplifies their expertise—a system that handles the tedious, error-prone task of checking hundreds of rules, freeing them to focus on the truly difficult part of risk assessment.

The solution isn't a magical AI but a well-architected system that makes complexity manageable. It encodes domain knowledge not as rigid, brittle code, but as flexible, versionable, and auditable data. That, to me, is the essence of good engineering in a complex domain.