• Home
  • Readings
  • Github
  • MIES
  • TmVal
  • About
Gene Dan's Blog

No. 141: MIES – Premium and Claim Transactions

5 July, 2020 10:28 PM / Leave a Comment / Gene Dan

This entry is part of a series dedicated to MIES – a miniature insurance economic simulator. The source code for the project is available on GitHub.

Current Status

Up until now, I’ve been able to demonstrate basic consumer behavior and certain market phenomena seen in the insurance industry. However, there’s been a major problem with MIES in that no money has actually changed hands in any of the simulations we’ve seen so far. Consumers have simply switched carriers depending on price, which allowed me to demonstrate adverse selection, but not much else. Consumption decisions involving insurance depend on wealth, but since there was no way to calculate wealth in MIES, its ability to model these decisions was limited.

The next view chapters in Varian place a heavy emphasis on wealth and risk tolerance, so this week, I made the decision to work on incorporating transactions before diving deeper into consumer behavior.

At first glance, transactions might seem like a simple thing to implement, after all, why not just keep a running cash balance for each entity, and then add and subtract payments as needed? The problem with this method is the same problem that leads companies to use double-entry accounting. Transactions are more complicated than simply sending money from one place to another. Loans are generated and capital is invested, which creates liabilities that must be considered when trying to calculate the wealth of an entity. Even something as simple as a premium payment is effectively a loan to an insurance company that needs to have a liability recorded (the unearned premium reserve), in addition to an increase in cash to the insurer.

Therefore, I’ve had to draw on my basic knowledge of accounting, which made me uneasy since I’m not an accountant myself. However, in order to get MIES to model the phenomena I want to model, and to answer the questions I have about insurance, I need to implement double-entry accounting, and eventually, statutory accounting rules. I ran across a post on Hacker News titled, ‘It’s OK for your open source library to be a bit shitty,’ which encouraged me to keep moving forward with the project despite the amount of discomfort I have.

I have certainly found many errors in MIES from past versions and there will likely be many more, including in this post. However, there’s not a lot of open source actuarial stuff out there, or in particular, open source actuarial simulations incorporating both economics concepts and double-entry accounting. Or, if there are packages out there, they aren’t easy to find. Thus, I’ve taken the step to put something out there, awaiting any feedback for things that need to fixed, and then making improvements. If this ever proves to be something useful, younger generations will create even better tools in the future.

The Bank Class

Banks facilitate transactions between insurers, brokers, customers, and other banks. While it may eventually be possible to define more than one bank per simulation, all the examples in the near future will have a single bank at which entities can deposit money and send payments to each other.

Schema

As with all the other entities, each bank comes with its own database:

Here, a bank can accept three types of customers. The customer table represents the customer superclass and references the person, insurer, and bank subclasses. Each customer can have an optional number of accounts. Any of these customers can send transactions to any other customer, including themself. Notice that the transaction table has two relationships to the account table, since the debit and credit account fields for each transaction both point to the account table.

The SQLAlchemy mapping is defined below:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, Date, Float, String
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
 
 
Base = declarative_base()
 
 
class Account(Base):
    __tablename__ = 'account'
 
    account_id = Column(
        Integer,
        primary_key=True
    )
 
    customer_id = Column(
        Integer,
        ForeignKey('customer.customer_id')
    )
 
    account_type = Column(String)
 
    transaction_debit = relationship(
        'Transaction',
        primaryjoin='Transaction.debit_account == Account.account_id',
        back_populates='account_debit'
    )
 
    transaction_credit = relationship(
        'Transaction',
        primaryjoin='Transaction.credit_account == Account.account_id',
        back_populates='account_credit'
    )
 
    def __repr__(self):
        return "<Account(" \
               "customer_id='%s', " \
               "account_type='%s', " \
               ")>" % (
                   self.customer_id,
                   self.account_type
               )
 
 
class Transaction(Base):
    __tablename__ = 'transaction'
 
    transaction_id = Column(
        Integer,
        primary_key=True
    )
 
    debit_account = Column(
        Integer,
        ForeignKey('account.account_id')
    )
 
    credit_account = Column(
        Integer,
        ForeignKey('account.account_id')
    )
 
    transaction_date = Column(Date)
 
    transaction_amount = Column(Float)
 
    account_debit = relationship(
        "Account",
        primaryjoin='Transaction.debit_account == Account.account_id',
        back_populates='transaction_debit'
    )
 
    account_credit = relationship(
        "Account",
        primaryjoin='Transaction.credit_account == Account.account_id',
        back_populates='transaction_credit'
    )
 
    def __repr__(self):
        return "<Transaction(" \
               "debit_account='%s', " \
               "credit_account='%s', " \
               "transaction_date='%s', " \
               "transaction_amount='%s'" \
               ")>" % (
                   self.debit_account,
                   self.credit_account,
                   self.transaction_date,
                   self.transaction_amount,
               )
 
 
class Customer(Base):
    __tablename__ = 'customer'
 
    customer_id = Column(
        Integer,
        primary_key=True
    )
    customer_type = Column(String)
 
    person = relationship(
        'Person',
        primaryjoin='Customer.customer_id == Person.customer_id',
        back_populates='customer'
    )
 
    insurer = relationship(
        'Insurer',
        primaryjoin='Customer.customer_id == Insurer.customer_id',
        back_populates='customer'
    )
 
    bank = relationship(
        'Bank',
        primaryjoin='Customer.customer_id == Bank.customer_id',
        back_populates='customer'
    )
 
 
class Person(Base):
    __tablename__ = 'person'
 
    person_id = Column(
        Integer,
        primary_key=True
    )
 
    customer_id = Column(
        Integer,
        ForeignKey('customer.customer_id')
    )
 
    customer = relationship(
        'Customer',
        primaryjoin='Person.customer_id == Customer.customer_id',
        back_populates='person',
        uselist=True
    )
 
 
class Insurer(Base):
    __tablename__ = 'insurer'
 
    insurer_id = Column(
        Integer,
        primary_key=True
    )
 
    customer_id = Column(
        Integer,
        ForeignKey('customer.customer_id')
    )
 
    customer = relationship(
        'Customer',
        primaryjoin='Insurer.customer_id == Customer.customer_id',
        back_populates='insurer',
        uselist=True
    )
 
 
class Bank(Base):
    __tablename__ = 'bank'
 
    bank_id = Column(
        Integer,
        primary_key=True
    )
 
    customer_id = Column(
        Integer,
        ForeignKey('customer.customer_id')
    )
 
    customer = relationship(
        'Customer',
        primaryjoin='Bank.customer_id == Customer.customer_id',
        back_populates='bank',
        uselist=True
    )

Bank Methods

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import datetime as dt
import os
import pandas as pd
import sqlalchemy as sa
from sqlalchemy.orm import sessionmaker
 
import mies.schema.bank as bank
from mies.schema.bank import Account, Customer, Insurer, Person, Transaction
from mies.schema.bank import Bank as BankTable
from mies.utilities.connections import connect_universe
from mies.utilities.queries import query_bank_id
 
 
class Bank:
    def __init__(self, starting_capital, bank_name, path='db/banks/', date_established=dt.datetime(1, 12, 31)):
        if not os.path.exists(path):
            os.makedirs(path)
        self.engine = sa.create_engine(
            'sqlite:///' + path + bank_name + '.db',
            echo=True
        )
        session = sessionmaker(bind=self.engine)
        bank.Base.metadata.create_all(self.engine)
        self.session = session()
        self.connection = self.engine.connect()
        self.name = bank_name
        self.date_established = date_established
        self.id = self.__register()
        self.get_customers(self.id, 'bank')
        self.cash_account = self.assign_account(
            customer_id=self.id,
            account_type='cash'
        )
        self.capital_account = self.assign_account(self.id, 'capital')
        self.liability_account = self.assign_account(self.id, 'liability')
        self.make_transaction(
            self.cash_account,
            self.capital_account,
            self.date_established,
            starting_capital
        )
 
    def __register(self):
        # populate universe company record
        insurer_table = pd.DataFrame([[self.name]], columns=['bank_name'])
        session, connection = connect_universe()
        insurer_table.to_sql(
            'bank',
            connection,
            index=False,
            if_exists='append'
        )
        bank_id = query_bank_id(self.name)
        return bank_id
 
    def get_customers(self, ids, customer_type):
        new_customers = pd.DataFrame()
        new_customers[customer_type + '_id'] = pd.Series(ids)
        new_customers['customer_type'] = customer_type
 
        objects = []
        for index, row in new_customers.iterrows():
            if customer_type == 'person':
                customer_type_table = Person(person_id=row[customer_type + '_id'])
            elif customer_type == 'insurer':
                customer_type_table = Insurer(insurer_id=row[customer_type + '_id'])
            else:
                customer_type_table = BankTable(bank_id=row[customer_type + '_id'])
 
            customer = Customer(
                customer_type=customer_type
            )
            customer_type_table.customer.append(customer)
            objects.append(customer_type_table)
 
        self.session.add_all(objects)
        self.session.commit()
 
    def assign_accounts(self, customer_ids, account_type):
        """
        assign multiple accounts given customer ids
        """
        new_accounts = pd.DataFrame()
        new_accounts['customer_id'] = customer_ids
        new_accounts['account_type'] = account_type
 
        new_accounts.to_sql(
            'account',
            self.connection,
            index=False,
            if_exists='append'
        )
 
    def assign_account(self, customer_id, account_type):
        """
        assign a single account for a customer
        """
        account = Account(customer_id=int(customer_id), account_type=account_type)
        self.session.add(account)
        self.session.commit()
        return account.account_id
 
    def make_transaction(self, debit_account, credit_account, transaction_date, transaction_amount):
        """
        make a single transaction
        """
        transaction = Transaction(
            debit_account=int(debit_account),
            credit_account=int(credit_account),
            transaction_date=transaction_date,
            transaction_amount=transaction_amount
        )
        self.session.add(transaction)
        self.session.commit()
        return transaction.transaction_id
 
    def make_transactions(self, data: pd.DataFrame):
        """
        accepts a DataFrame to make multiple transactions
        need debit, credit, transaction date, transaction amount
        """
        data['debit_account'] = data['debit_account'].astype(int)
        data['credit_account'] = data['credit_account'].astype(int)
        data.to_sql(
            'transaction',
            self.connection,
            index=False,
            if_exists='append'
        )

Upon initialization, a bank will register itself as a customer, this allows it to have its own accounts, as well as to accept deposits from other customers. The reason why it needs to have its own accounts is because each transaction will need to have corresponding debit and credit accounts, and transactions between a bank and its customers will sometimes have one of the bank’s own accounts involved.

The method bank.get_customers() takes a list of IDs, which can be either those of people, insurers, or banks. For each of these entity IDs, the bank will create its own identifier for the customer, which can differ between that customer’s underlying ID. For example, a person with a person ID of 1, and an insurer with an insurer ID of 1, will have separate customer ids.

The method bank.assign_accounts() takes a list of customer IDs, a type of account (such as cash), and then creates that type of account for each customer.

The method bank.make_transactions() will take a list of transactions, each of which have a debit account, credit account, transaction date, and transaction volume defined, and then store them in the transactions table.

Now that we have our bank defined, I’ll walk through the insurance underwriting/claim cycle and show how the transactions that are currently available in MIES work. First, we’ll import the necessary modules, create an environment, make a population of 1000 people, and then create a bank called ‘blargo’ that has 4B in starting capital:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
import datetime as dt
 
from mies.entities.bank import Bank
from mies.entities.broker import Broker
from mies.entities.god import God
from mies.entities.insurer import Insurer
from mies.utilities.queries import query_population
from utilities.queries import query_customers_by_person_id
 
ahura = God()
ahura.make_population(1000)
 
blargo = Bank(4000000, 'blargo')

Starting Wealth

Before we can issue policies to customers, we need to have a starting amount of wealth for each person so that they can actually pay for their policies. Furthermore, we also need each person to have a bank account from which they can issue payments to their insurers. To take care of these two steps, we’ll first send the person IDs to the bank, which will then create corresponding customer IDs and then establish one cash account for each customer:

Python
1
2
3
blargo.get_customers(ids=ids, customer_type='person')
customer_ids = query_customers_by_person_id(ids, 'blargo')
blargo.assign_accounts(customer_ids=customer_ids, account_type='cash')

This action populates two tables. The person table contains a record of all 1000 people, and the customer table has 1001 records, since each person becomes a customer, but the bank itself is already its own customer:

In the next step, we’ll use a new method called grant_wealth() which the environment uses to give each person a starting amount of wealth. Since wealth is not evenly distributed in society, I’ve drawn these values from the Pareto distribution:

1
ahura.grant_wealth(person_ids=ids, bank=blargo, transaction_date=pricing_date)

The method is defined as follows:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class God:
...
    def grant_wealth(
            self,
            person_ids,
            bank: Bank,
            transaction_date
    ):
        """
        assign an initial amount of starting wealth per person
        """
        accounts = query_accounts_by_person_id(
            person_ids,
            bank.name,
            'cash'
        )
        accounts['transaction_amount'] = pareto.rvs(
            b=1,
            scale=pm.person_params['income'],
            size=accounts.shape[0],
        )
        accounts = accounts[[
            'account_id',
            'transaction_amount'
        ]]
        accounts = accounts.rename(columns={
            'account_id': 'debit_account'
        })
        accounts['credit_account'] = bank.liability_account
        accounts['transaction_date'] = transaction_date
        bank.make_transactions(accounts)
...

This action deposits wealth in each person’s cash account. This is marked as the debit side of the transaction, the credit side is the liability that the bank takes on by accepting the deposits, since deposits are loans to the bank:

Policy Inception

Before we issue policies, we need to create two more entities in the simulation. One, a broker called ‘rayon’ and an insurer called ‘company_1.’ Initializing a company now requires two new arguments, a bank with which it associates, and its inception date. These new arguments are used to create bank accounts for the insurer:

Python
1
2
3
4
5
6
7
8
9
rayon = Broker()
 
company_1 = Insurer(4000000, blargo, pricing_date, 'company_1')
 
company_1_formula = 'incurred_loss ~ ' \
                    'age_class + ' \
                    'profession + ' \
                    'health_status + ' \
                    'education_level'

We’ll now use the broker rayon to place the customers with the insurer. This method also has a bank as a new argument, which will be used to facilitate the transactions:

Python
1
2
3
4
5
rayon.place_business(
        pricing_date,
        blargo,
        company_1
    )

This action creates a policy record for each insurer, which I won’t show here since I’ve already done so in a previous post. What has changed however, is that each person needs to pay the premium to the insurer up front. The broker is able to tell the bank to create a transaction for each premium payment. This time, the debit account is the insurer’s cash account, and the credit account is the person’s cash account. In the picture below, we see that there are 1000 additional transactions starting with transaction_id 1003 (1002 was used to seed insurer capital). The debit account on all these transactions is account ID 1004, which is the insurer’s cash account:

In reality, a corresponding liability should also be created in the insurer’s accounting system. This is called the unearned premium reserve which is what the insured is entitled to recieve if their policy gets canceled. This feature is not yet implemented in MIES, but it’s an important liability to consider in insurance.

Loss Occurrence

Now that we have our policies issued, we’re ready to simulate some losses:

Python
1
2
3
event_date = pricing_date + dt.timedelta(days=1)
 
ahura.smite(event_date)

This action produced 57 loss events, which can be found in the events table in the universe database:

Claim Reporting

These losses are not considered claims until they are reported to the insurer. Otherwise, the insurer has no knowledge that they occurred:

Python
1
rayon.report_claims(event_date)

This method has changed from last week. The loss amounts are now reported as case reserves, which are estimates made by an insurer on how much they will need to pay for the claim. This is now distinguished from paid losses, which are the actual payments the insurer makes to the insured:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Broker:
...
    def report_claims(self, report_date):
        # match events to policies in which they are covered
 
        events = query_events_by_report_date(report_date)
 
        policies = query_all_policies()
 
        claims = events.merge(
            policies,
            on='person_id',
            how='left'
        )
        claims = claims.query(
            'event_date >= effective_date '
            'and event_date <= expiration_date'
        )
 
        claims = claims.drop([
            'effective_date',
            'expiration_date',
            'premium',
            'company_id'
        ], axis=1)
 
        companies = get_company_names()
 
        for company in companies:
 
            # register claims by id
 
            reported_claims = claims[claims['company_name'] == company]
 
            reported_claims = reported_claims.rename(columns={
                'event_date': 'occurrence_date'
            })
 
            reported_claims = reported_claims.drop(['company_name'], axis=1)
 
            session, connection = connect_company(company)
 
            objects = []
            for index, row in reported_claims.iterrows():
                claim = Claim(
                    policy_id=row['policy_id'],
                    person_id=row['person_id'],
                    event_id=row['event_id'],
                    occurrence_date=row['occurrence_date'],
                    report_date=row['report_date']
                )
                open_claim = ClaimTransaction(
                    transaction_date=row['report_date'],
                    transaction_type='open claim',
                    transaction_amount=0
                )
                case_reserve = ClaimTransaction(
                    transaction_date=row['report_date'],
                    transaction_type='set case reserve',
                    transaction_amount=row['ground_up_loss']
                )
                claim.claim_transaction.append(open_claim)
                claim.claim_transaction.append(case_reserve)
                objects.append(claim)
 
            session.add_all(objects)
            session.commit()
 
            connection.close()

This action creates two transactions for each claim. One transaction, called ‘open claim’ signals that a claim has been created. Another transaction, called ‘set case reserve,’ sets a case reserve for each claim. Since there are 57 losses, there are 57 claims, and 114 transactions:

Notice that we have some fairly restrictive assumptions for the simulation. The case reserves are equal to the ground up losses, and all 57 losses are reported to and known by the insurer immediately. This is not the case in the real world, where an insurer does not know how much claims will cost until they are settled, and may not know about claims until many years after they have occurred. We’ll need to revisit this problem in the future, since estimating claim amounts, including those on unreported claims, is a core function of actuarial science.

Claim Settlement

Let’s close these claims by issuing payments:

Python
1
company_1.pay_claims(event_date + dt.timedelta(days=1))

Insurer.pay_claims() is a new method used to send checks to the insureds for indemnification:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
    def pay_claims(self, transaction_date):
        # send checks to bank
        case_reserves = query_open_case_reserves(self.company_name)
 
        accounts_to_pay = query_accounts_by_person_id(
            case_reserves['person_id'],
            self.bank.name, 'cash'
        )
 
        case_reserves = case_reserves.merge(
            accounts_to_pay,
            on='person_id',
            how='left'
        )
 
        case_reserves['transaction_date'] = transaction_date
 
        case_reserves = case_reserves.rename(columns={
            'case reserve': 'transaction_amount',
            'account_id': 'debit_account'
        })
 
        case_reserves['credit_account'] = self.cash_account
 
        payments = case_reserves[[
            'debit_account',
            'credit_account',
            'transaction_date',
            'transaction_amount']].copy()
 
        self.bank.make_transactions(payments)
 
        # people then use checks to pay their for their own losses
 
        payments['credit_account'] = payments['debit_account']
        payments['debit_account'] = self.bank.liability_account
 
        self.bank.make_transactions(payments)
 
 
        # reduce case reserves
 
        objects = []
 
        for index, row in case_reserves.iterrows():
            reserve_takedown = ClaimTransaction(
                claim_id = row['claim_id'],
                transaction_date=row['transaction_date'],
                transaction_type='reduce case reserve',
                transaction_amount=row['transaction_amount']
            )
            claim_payment = ClaimTransaction(
                claim_id=row['claim_id'],
                transaction_date=row['transaction_date'],
                transaction_type='claim payment',
                transaction_amount=row['transaction_amount']
            )
            close_claim = ClaimTransaction(
                claim_id=row['claim_id'],
                transaction_date=row['transaction_date'],
                transaction_type='close claim',
                transaction_amount=0
            )
            objects.append(reserve_takedown)
            objects.append(claim_payment)
            objects.append(close_claim)
 
        self.session.add_all(objects)
        self.session.commit()

There’s a lot going on here, so I’ll break it down. The payments to the insureds are handled first. We can see this by going to the transactions table in the bank database:

Notice that there are 114 additional transactions, two for each of the 57 claims. One transaction sends a payment from the insurer to the customer. You can see this since the debit account is the cash account of the customer, and the credit accont (1004) is the cash account of the insurer. The other transaction is a payment from the insured to whomever they owe money to due to the loss. The debit side of the transaction is now a reduction in the bank’s liability account, and the credit amount is a reduction in cash equal to the claim amount for the insured:

Next, three transactions are entered for each claim into the insurer’s database:

  1. Reduce case reserve
  2. Claim payment
  3. Close claim

‘Reduce case reserve’ is a reduction in the claims reserve to zero, signifying that the insurer no longer owes money to the insured. The ‘claim payment’ is a corresponding transaction representing the actual payment, and ‘close claim’ is a transaction that indicates that the claim is now closed. The insurer now has 5 claims transactions for each claim, 57 x 5 = 285 transactions in total. These five transactions are: 1) open claim, 2) set case reserve, 3) reduce case reserve 4) claim payment, and 5) close claim.

One source of confusion I had is that this way of recording transactions does not have the double-entry accounting that you’d see in a general ledger. Indeed, this form is more common for actuarial pricing modelers who do not usually need to get into the finer details of debits and credits. However, I’m leaning towards changing the claims transaction tables to also be double-entry, since doing so also makes things easier to program and avoids questions with negative values. For example, I had to think about whether to record case reserve reductions as a positive or negative value. This confusion is not present if I record them as credit and debit amounts.

Consumer Income

Each person now gets a paycheck during each period. This can be issued by the environment class:

Python
1
ahura.send_paychecks(person_ids=ids, bank=blargo, transaction_date=event_date + dt.timedelta(days=1))

There’s nothing too special about this method, it just debits each person’s cash account by their income amount:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class God:
...
    def send_paychecks(self, person_ids, bank: Bank, transaction_date):
        incomes = query_incomes(person_ids)
        incomes.columns = ['person_id', 'transaction_amount']
 
        accounts = query_accounts_by_person_id(
            person_ids,
            bank.name,
            'cash'
        )
 
        accounts = accounts.merge(incomes, on='person_id', how='left')
 
        accounts = accounts.rename(columns={
            'account_id': 'debit_account'
        })
        accounts['credit_account'] = bank.liability_account
        accounts['transaction_date'] = transaction_date
        accounts = accounts.drop([
            'person_id',
            'customer_id'
        ], axis=1)
        bank.make_transactions(accounts)

An additional 1000 transactions have been recorded, one for each customer. As with wealth, the debit side of the transaction is the person’s cash account, with a corresponding credit to the bank’s liability account:

Policy Pricing

Now that the claims have been reported and settled, the insurer can use this information to recalibrate the premium for each customer. However, unlike last week, claim amounts are not stored as a single column called ‘incurred_loss’, but now must be calculated from the transaction amounts. To handle this, I created a set of queries that can be used to return the case reserves, paid losses, and incurred amounts for each claim:

Python
1
2
3
4
from utilities.queries import query_case_by_claim
from utilities.queries import query_paid_by_claim
from utilities.queries import query_incurred_by_claim
from utilities.queries import query_pricing_model_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
def query_case_by_claim(company_name):
    session, connection = connect_company(company_name)
 
    claim_policy = session.query(
        Claim.claim_id,
        Claim.policy_id
    ).subquery()
 
    case_set = session.query(
        ClaimTransaction.claim_id,
        func.sum(ClaimTransaction.transaction_amount).label('set')
    ).filter(ClaimTransaction.transaction_type == 'set case reserve').group_by(ClaimTransaction.claim_id).subquery()
 
    case_takedown = session.query(
        ClaimTransaction.claim_id,
        func.sum(ClaimTransaction.transaction_amount).label('takedown')
    ).filter(ClaimTransaction.transaction_type == 'reduce case reserve').group_by(ClaimTransaction.claim_id).subquery()
 
    case_query = session.query(
        case_set.c.claim_id,
        (func.ifnull(case_set.c.set, 0) - func.ifnull(case_takedown.c.takedown, 0)).label('case_reserve')
    ).outerjoin(case_takedown, case_set.c.claim_id == case_takedown.c.claim_id).subquery()
 
    claim_case = session.query(
        claim_policy.c.claim_id,
        claim_policy.c.policy_id,
        func.ifnull(case_query.c.case_reserve, 0).label('case_reserve')
    ).outerjoin(case_query, claim_policy.c.claim_id == case_query.c.claim_id).statement
 
    case_reserve = pd.read_sql(claim_case, connection)
 
    connection.close()
 
    return case_reserve
 
 
def query_paid_by_claim(company_name):
    session, connection = connect_company(company_name)
 
    claim_policy = session.query(
        Claim.claim_id,
        Claim.policy_id
    ).subquery()
 
    payment_query = session.query(
        ClaimTransaction.claim_id,
        func.sum(ClaimTransaction.transaction_amount).label('paid_loss')
    ).filter(ClaimTransaction.transaction_type == 'claim payment').group_by(ClaimTransaction.claim_id).subquery()
 
    claim_paid = session.query(
        claim_policy.c.claim_id,
        claim_policy.c.policy_id,
        func.ifnull(payment_query.c.paid_loss, 0).label('paid_loss')
    ).outerjoin(payment_query, claim_policy.c.claim_id == payment_query.c.claim_id).statement
 
    claim_payments = pd.read_sql(claim_paid, connection)
 
    connection.close()
 
    return claim_payments
 
 
def query_incurred_by_claim(company_name):
 
    case = query_case_by_claim(company_name)
    case = case.drop(columns=['policy_id'], axis=1)
 
    paid = query_paid_by_claim(company_name)
 
    incurred = paid.merge(case, on='claim_id', how='left')
 
    incurred['incurred_loss'] = incurred['paid_loss'] + incurred['case_reserve']
 
    return incurred
 
def query_pricing_model_data(company_name):
    session, connection = connect_company(company_name)
 
    policy_query = session.query(
        Policy.policy_id,
        Policy.person_id,
        Customer.age_class,
        Customer.profession,
        Customer.health_status,
        Customer.education_level,
        ).outerjoin(
            Customer,
            Policy.person_id == Customer.person_id
        ).statement
 
    policy = pd.read_sql(policy_query, connection)
 
    claim = query_incurred_by_claim(company_name)
 
    claim = claim.drop(columns=['claim_id', 'paid_loss', 'case_reserve'], axis=1)
 
    claim = claim.groupby(['policy_id'])['incurred_loss'].agg('sum')
 
    model_set = policy.merge(claim, on='policy_id', how='left')
 
    model_set['incurred_loss'] = model_set['incurred_loss'].fillna(0)
 
    return model_set

We mostly need to be concerned about the last one of these, query_pricing_model_data() which uses the first three to combine policy and claim information together, which can be priced with a GLM:

Python
1
query_pricing_model_data('company_1')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Out[15]:
     policy_id  person_id age_class profession health_status education_level  \
0            1          1         E          A             P               H  
1            2          2         E          B             F               P  
2            3          3         Y          C             F               H  
3            4          4         M          C             F               H  
4            5          5         M          A             P               P  
..         ...        ...       ...        ...           ...             ...  
995        996        996         M          B             P               H  
996        997        997         E          A             P               H  
997        998        998         E          B             G               H  
998        999        999         M          A             G               P  
999       1000       1000         M          C             G               H  
     incurred_loss  
0              0.0  
1              0.0  
2              0.0  
3              0.0  
4              0.0  
..             ...  
995            0.0  
996            0.0  
997            0.0  
998            0.0  
999            0.0  
[1000 rows x 7 columns]

Since most people don’t have a claim, most incurred loss amounts are zero. This data set can then be used by the insurer to reprice with a GLM, and the new pricing algorithm is used by the broker to quote and place business:

1
2
3
4
5
6
7
company_1.price_book(company_1_formula)
 
rayon.place_business(
        pricing_date,
        blargo,
        company_1
    )

Further Improvements

Now that I’ve got transactions modeled, I can calculate the wealth for each entity in the simulation. This is key piece required to make further changes to the way consumer preferences work in MIES, which will soon incorporate risk tolerance and wealth.

Posted in: Actuarial, Mathematics, MIES

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Post Navigation

← Previous Post
Next Post →

Archives

  • September 2023
  • February 2023
  • January 2023
  • October 2022
  • March 2022
  • February 2022
  • December 2021
  • July 2020
  • June 2020
  • May 2020
  • May 2019
  • April 2019
  • November 2018
  • September 2018
  • August 2018
  • December 2017
  • July 2017
  • March 2017
  • November 2016
  • December 2014
  • November 2014
  • October 2014
  • August 2014
  • July 2014
  • June 2014
  • February 2014
  • December 2013
  • October 2013
  • August 2013
  • July 2013
  • June 2013
  • March 2013
  • January 2013
  • November 2012
  • October 2012
  • September 2012
  • August 2012
  • July 2012
  • June 2012
  • May 2012
  • April 2012
  • March 2012
  • February 2012
  • January 2012
  • December 2011
  • September 2011
  • August 2011
  • July 2011
  • June 2011
  • January 2011
  • December 2010
  • October 2010
  • September 2010
  • August 2010
  • June 2010
  • May 2010
  • April 2010
  • March 2010
  • September 2009
  • August 2009
  • May 2009
  • December 2008

Categories

  • Actuarial
  • Cycling
  • Logs
  • Mathematics
  • MIES
  • Music
  • Uncategorized

Links

Cyclingnews
Jason Lee
Knitted Together
Megan Turley
Shama Cycles
Shama Cycles Blog
South Central Collegiate Cycling Conference
Texas Bicycle Racing Association
Texbiker.net
Tiffany Chan
USA Cycling
VeloNews

Texas Cycling

Cameron Lindsay
Jacob Dodson
Ken Day
Texas Cycling
Texas Cycling Blog
Whitney Schultz
© Copyright 2025 - Gene Dan's Blog
Infinity Theme by DesignCoral / WordPress