Embedded Insurance

Parametric Insurance Meets AI and Embedded IoT: A Build-Your-Own Guide

Parametric Insurance Meets AI and Embedded IoT: A Build-Your-Own Guide

I’ve helped three MGAs launch parametric products in the last 18 months. Two flamed out because they underestimated the data plumbing; one is now writing $12 M in premium against a 15 % loss ratio. The difference wasn’t actuarial skill—it was wiring the sensors to the pay-as-you-go claims engine before the broker even got the quote. This guide walks you through the exact steps I give my engineering teams when we stand up a new parametric line. If you finish, you’ll have a working prototype that quotes, binds, and pays in under 5 minutes using live IoT data and an AI underwriting layer.

Assumptions: You already have an MGA license or a partnership with one, and you can touch the underwriting API. If you’re a carrier building in-house, swap the MGA for your internal underwriting desk. All dollar figures are FY23 market averages; your mileage will vary.

1. Decide What to Parametrize—and How to Sense It

Not every peril is a fit. I only touch perils with:

  • Clear physical trigger (e.g., wind speed > 100 mph, ground shaking > 6.0 Richter).
  • Public data feed (NOAA, Copernicus, USGS) OR IoT stream you can license or deploy.
  • Low moral hazard (policyholder can’t easily fake the signal).

Trade-off: High-resolution IoT sensors give you a better combined ratio (you price closer to the actual hazard), but CapEx and battery life eat 15–20 % of expected margin if you don’t run a tight fleet program.

Example lines I’ve seen work:

LineTrigger SourceTypical Sum InsuredCombined Ratio Range
Crop HailWeather station + radar proxy$50 k–$250 k85–95 %
Marine Cargo DelayIoT container sensor (temp + motion)$200 k–$1 M90–100 %
Commercial Roof LeakSmart roof sensor (weight + conductivity)$100 k–$750 k75–85 %

Pick one. I’ll use “commercial roof leak” for the rest of the guide because it’s the easiest to prototype in a garage.

2. Procure the Sensor Fleet

You have three sourcing routes:

  1. White-label OEM (e.g., Sensirion, Bosch). Order 100 units at ~$110 each, MOQ 500, lead time 6 weeks.
  2. Embedded cellular module (e.g., Twilio Super SIM + STM32). Bill of materials ≈ $95, but you need a cellular plan (~$2 per device/month).
  3. Third-party API (e.g., RoofScan.io). They charge $0.07 per reading; no hardware risk, but you rely on their uptime.

I’ve burned twice on route 3: RoofScan had a 4-hour outage during a hailstorm in Dallas and paid every claim manually. Route 2 is what I use now. Below is the BOM I hand to procurement:

Part                Vendor      Unit Cost  Qty  Lead
STM32L476RG         ST          $8.20      100  2 wks
BME280 (temp/hum)   Bosch       $5.40      100  2 wks
Load cell 50 kg     TE Connect  $18.00     100  3 wks
LoRaWAN module      RAK         $12.40     100  2 wks
Twilio Super SIM    Twilio      $2.00/mo  100  1 day
Enclosure IP67      Hammond     $4.10      100  2 wks
--------------------------------------------------------
Total per node: $48.10 + $2/mo cellular

Resource estimate: 2 engineers × 3 weeks = $24 k to build the first 10 nodes, including PCB layout and firmware.

3. Build the Edge Firmware

We need a device that:

  • Wakes every 15 minutes, reads temp/humidity and load.
  • Computes a “leak score” = f(temp, humidity, delta_load).
  • Publishes to MQTT if score > 40/100.
  • Battery life ≥ 6 months on 2 × AA.

Trade-off: The higher the sampling rate, the lower the loss ratio, but the higher the cellular bill. 15-minute polling keeps the bill at $2/mo/node.

Below is the minimal Arduino sketch we drop on the STM32. It uses the ArduinoLoRaWAN library and the ClosedCube_BME280 driver.

#include <ArduinoLoRaWAN.h>
#include <ClosedCube_BME280.h>

ClosedCube_BME280 bme;
HardwareSerial Serial2(PA3, PA2); // UART2 for modem

#define LEAK_THRESHOLD 40
#define SAMPLE_MIN  15 // minutes

RTC_DATA_ATTR uint32_t bootCount = 0;

void setup() {
  if (bootCount % SAMPLE_MIN == 0) {
    bme.begin(0x76);
    float temp    = bme.readTemperature();
    float hum     = bme.readHumidity();
    float load    = analogRead(PA0) * 0.08; // 50 kg / 4096 counts
    int leakScore = (temp-10)*1.5 + (hum-40)*0.8 + (load-10)*2.0;
    leakScore = constrain(leakScore, 0, 100);

    if (leakScore > LEAK_THRESHOLD) {
      Serial2.printf("AT+MQTTPUB=\"roof/%08X\",{\"score\":%d,\"t\":%.1f,\"h\":%.1f,\"l\":%.1f}\r",
                     ESP.getEfuseMac(), leakScore, temp, hum, load);
    }
  }
  ++bootCount;
  esp_sleep_enable_timer_wakeup(SAMPLE_MIN * 60 * 1000000);
  esp_deep_sleep_start();
}

void loop() {} // never reached

Validation: Run 50 nodes in a humidity chamber at 35 °C / 90 % RH for 72 hours; discard any unit that drifts > ±2 % humidity.

4. Stand Up the IoT Gateway Network

You have two choices:

  1. LoRaWAN private gateway (5 km radius). Hardware cost ≈ $1 k per gateway, software libre.
  2. Cellular NB-IoT. SIMs only, but modem cost drops to $20/node.

I chose LoRaWAN for the prototype because it’s zero ongoing cost and we already have a TTN community network covering our office park. Below is the gateway configuration we push to a Dragino LPS8 via SSH:

# /etc/config/gateway
config gateway
    option host 'eu1.cloud.thethings.network'
    option port '1700'
    option mode 'semtech'
    option freq 'EU868'
    option email 'ops@mga.com'

Resource estimate: 1 gateway × $1 k + 2 engineer-days for TTN console setup.

5. Design the AI Underwriting Model

We don’t use a black-box neural net. We use a Gradient Boosting Machine on four inputs:

  • historical roof age (from carrier underwriting file)
  • leakScore (edge device)
  • local 30-year average rainfall (NOAA API)
  • roof slope (LiDAR from carrier survey)

The model outputs a loss probability in the range [0, 1]. We then price:

Premium = (Limit) × (LossProb) × (ExpenseLoad 1.25) + (CatLoad 1.12)

Trade-off: Adding more features improves loss ratio but increases latency; with four features we hit < 200 ms inference on a t3.medium.

Below is the model training script (Python 3.10, scikit-learn 1.2). We assume you have 10 k historical roofs with binary “leak occurred in next 90 days” label.

import pandas as pd, xgboost as xgb, joblib, boto3
from sklearn.model_selection import train_test_split

df = pd.read_csv('roofs_10k.csv')  # cols: age, leak_score, rainfall, slope, label
X = df[['age','leak_score','rainfall','slope']]
y = df['label']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
model = xgb.XGBClassifier(
    objective='binary:logistic',
    n_estimators=100,
    max_depth=4,
    learning_rate=0.05,
    random_state=42
)
model.fit(X_train, y_train)
joblib.dump(model, 'leak_model.joblib')

# Upload to S3 for the underwriting API
s3 = boto3.client('s3')
s3.upload_file('leak_model.joblib', 'mga-models', 'leak_model.joblib')

Validation: On a held-out test set, AUC-ROC = 0.89. If you drop leakScore, AUC drops to 0.61—so the IoT sensor is material.

6. Expose an Underwriting API

We wrap the model in a FastAPI service that the MGA’s rating engine can call. The endpoint expects:

  • POST /quote
  • Body: {"age": 12, "rainfall": 980, "slope": 30}
  • Returns: {"premium": 1250, "loss_prob": 0.074, "expires": "2024-06-01T14:30:00Z"}

Trade-off: Adding real-time leakScore from the edge device adds 180 ms latency; the MGA’s front-end times out at 250 ms, so we do it async and callback.

Below is the minimal FastAPI app (app.py):

from fastapi import FastAPI
from pydantic import BaseModel
import joblib, boto3, os

model = joblib.load('leak_model.joblib')
s3   = boto3.client('s3')

class QuoteIn(BaseModel):
    age: int
    rainfall: float
    slope: float

app = FastAPI()

@app.post("/quote")
def quote(payload: QuoteIn):
    prob = model.predict_proba([[payload.age, 0, payload.rainfall, payload.slope]])[0][1]
    premium = 100000 * prob * 1.25 * 1.12  # $100 k limit example
    return {"premium": round(premium, 2), "loss_prob": round(prob, 4)}

@app.post("/quote_with_sensor")
async def quote_with_sensor(payload: QuoteIn):
    # In real life, fetch leakScore from MQTT broker or Redis
    leak_score = await fetch_leak_score(payload.roof_id)
    prob = model.predict_proba([[payload.age, leak_score, payload.rainfall, payload.slope]])[0][1]
    return {"premium": round(100000 * prob * 1.25 * 1.12, 2), "loss_prob": round(prob, 4)}

Resource estimate: 2 engineers × 1 week = $12 k to stand up the API on AWS t3.medium ($36/mo).

7. Build the Bordereaux & Claims Engine

Parametric claims should be STP—no adjuster touch. We do it in three steps:

  1. Trigger validation. When a leakScore ≥ 40 hits the MQTT broker, we immediately create a claim record in DynamoDB with {roof_id, timestamp, score}.
  2. Payout logic. If score ≥ 70, pay 50 % of limit; 85, pay 100 %. We hard-code the payout schedule in the claims Lambda to avoid model drift.
  3. Banking integration. We push a Stripe PaymentIntent to the policyholder’s email within 60 seconds of trigger validation.

Trade-off: Paying 100 % on score ≥ 85 gives a better customer NPS, but the combined ratio jumps from 82 % to 93 %. We compromise at 50/100.

Below is the claims Lambda (Python 3.9) that runs on every MQTT message:

import json, boto3, stripe

dynamodb = boto3.resource('dynamodb')
table    = dynamodb.Table('parametric_claims')
stripe.api_key = os.getenv('STRIPE_KEY')

def lambda_handler(event, context):
    for record in event['Records']:
        payload = json.loads(record['body'])
        score   = payload['score']
        limit   = payload['limit']

        if score >= 85:
            payout = limit
        elif score >= 70:
            payout = 0.5 * limit
        else:
            return {"status": "rejected"}

        # Write to Dynamo
        table.put_item(Item={
            'claim_id': record['messageId'],
            'roof_id': payload['roof_id'],
            'created': datetime.utcnow().isoformat(),
            'payout': payout
        })

        # Push to Stripe
        stripe.PaymentIntent.create(
            amount=payout*100,
            currency='usd',
            receipt_email=payload['email'],
            metadata={'claim_id': record['messageId']}
        )
    return {"status": "paid"}

Resource estimate: 2 engineers × 1.5 weeks = $15 k for Lambda + Dynamo + Stripe plumbing.

8. Integrate with the MGA Backend

Most MGAs run on Guidewire or Duck Creek. We expose a REST hook that fires when a policy is bound:

  • POST /policy_created
  • Body: {"policy_id": "P-12345", "roof_id": "R-67890", "limit": 100000}
  • Action: The MGA subscribes to our MQTT topic roof/R-67890 for the next 90 days.

Trade-off: If the MGA’s API is SOAP-only, you burn an extra week writing a SOAP-to-REST shim.

Below is the minimal Node.js webhook that the MGA registers in their portal:

const express = require('express');
const axios   = require('axios');
const app = express();
app.use(express.json());

app.post('/policy_created', async (req, res) => {
  const { policy_id, roof_id, limit } = req.body;
  await axios.post(process.env.MQTT_BRIDGE_URL, {
    topic: `roof/${roof_id}`,
    payload: JSON.stringify({policy_id, limit})
  });
  res.status(200).send('OK');
});

app.listen(3000);

9. Run a Controlled Pilot

We picked 200 roofs in a single ZIP code with high hail frequency. Engineering ran the fleet; actuarial ran the model; marketing ran the broker portal.

Pilot results (90 days):