Skip to content

Instantly share code, notes, and snippets.

@daniel-barlow
Created July 17, 2017 08:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daniel-barlow/0a16e39763acfee6cab57c4352756de9 to your computer and use it in GitHub Desktop.
Save daniel-barlow/0a16e39763acfee6cab57c4352756de9 to your computer and use it in GitHub Desktop.
import qualified Data.Map as Map
import qualified Data.List as List
-- Getting started
-- download this file as e.g. hello.hs
-- brew install ghc
-- ghci
-- :r hello.hs
-- -- --
-- Some background to the problem:
-- An insurance cover is a contractual obligation to insure some thing
-- or person or service ("the risk", or "the insured") against some
-- event
-- there is a cost ("cover premium") associated with providing this cover
-- the cost depends on material facts of the risk & event
-- material facts are also known as "risk variables"
-- Cover premium is calculated based on some or all of the risk
-- variables, and rating criteria set by the insurer. Classic rating
-- model design is in two steps:
-- First we choose the "exposure base". This is the "basic unit of
-- risk" to which the insurance rate applies: it should be some very
-- good proxy for expected losses (e.g. turnover, or number of
-- employees, or value of property). Note that this is often
-- constrained by historical precedent so that we can meaningfully
-- compare expected losses of an entire book of business with other
-- insurers in the same market
-- So, to get the base premium we choose a rate from a table according
-- to the number of units of the exposure base, then multiply by the
-- said number of units. The table may be flat/regressive/progressive
-- depending on the relationship between the "size" of the insured &
-- expected loss
-- THe second step, once we have the base premium, is to add "loads"
-- and discounts, by applying other rates using other tables based on
-- other risk variables. For example, postcode loadings or discounts
-- for years without claim.
-- A note on rate calculation:
-- With any model that has {pro,re}gressive rates it is worth
-- checking that the premium is a continuous function of the number of
-- units. Otherwise there are weird discontinuities in the premium
-- payable as a business size increases. One approach which
-- guarantees this, is to use "marginal" rates: if there is an
-- inflection at, say, £40000 turnover, then charge x% on the first
-- £40k and x-y% on (remaining turnover - 40000). HMRC do this for
-- income tax. This may be combined with a flat fee for the first
-- £x000 before unit rates kick in
-- The result of this two-step calculation is known as the "technical
-- premium". There are a bunch of other things we will probably want
-- to do subsequently, like add commissions, or enforce a minimum
-- premium, or add tax. We may also want to offer multiple covers in
-- the same transaction, but I'm deferring the decision of how to do
-- that for the moment
-- -- -- -- --
-- OK, here goes
-- -- -- -- --
-- It would be good to be able to use money. We introduce a Money
-- type which wraps a Double (yes, I know that floating point money is
-- a bad idea, this is temporary)
data Money = Money Double deriving (Show)
instance Eq Money where (Money m1) == (Money m2) = m1 == m2
instance Ord Money where (Money m1) `compare` (Money m2) = m1 `compare` m2
-- We can't define (*) for Money unless we make it a member of the
-- Num, typeclass, but that would mean defining all the numeric
-- operations for it. Which I think would be wrong because you can't
-- multiply it by itself (or divide anything by it). But it is nice
-- to be able to multiply a sum of money by a scalar.
multiply rate (Money m) = Money (m * rate)
-- A Cover contains some attributes of some kind (don't yet know what,
-- String is used here as a placeholder) and a premium.
data Cover = Cover (String, Money) deriving (Show)
-- But this is a quoting service, not an insurance purchase service,
-- so we can't actually form contracts - only offers. Our output is
-- going to be an offer of a cover, which the purchaser chooses to
-- accept (or not)
data Decision = Offer Cover | Rejection String deriving (Show)
-- Rate is basically an alias for Double
type Rate = Double
-- Right now I think there should be a unified interface to applying a
-- rate from any kind of rating table, but may change my mind.
data RateTable a = Bands [(a, Rate)]
| MarginalBands [(a, Rate)]
| Match [(a, Rate)]
deriving (Show)
applyRate (Match table) index val =
let (Just rate) = lookup index table in
multiply rate val
applyRate (Bands table) index val =
let f (upperLimit, _) = (upperLimit > index)
(threshold, rate) = head (filter f table) in
multiply rate val
exampleTurnoverTable = Bands [(Money 10000.0, 0.1),
(Money 20000.0, 0.05),
(Money 50000.0, 0.04),
(Money 100000.0, 0.02)]
exampleEmployeesTable = Bands [(4, 0.1),
(10, 0.05),
(25, 0.04),
(100, 0.02)]
data Postcode = Postcode String
instance Eq Postcode where (Postcode p1) == (Postcode p2) = p1 == p2
-- presently applyRate requires that the index argument implements
-- Ord, because its definition on Bands uses (<). This is not
-- the way I'd like things to work, but I don't know enough Haskell
-- to be able to fix it
instance Ord Postcode where (Postcode p1) `compare` (Postcode p2) =
p1 `compare` p2
exampleFloodTable = Match [(Postcode "SE1", 1),
(Postcode "NN1", 1.2)]
exampleTheftTable = Match [(Postcode "SE1", 1),
(Postcode "NN1", 1.2)]
-- this is syntactic sugar so we can write the rating stages in
-- "natural" order
pipeline functions = foldl (.) id (reverse functions)
-- here is our made-up premium calculator
costOfEL turnover postcode =
(pipeline [(applyRate exampleTurnoverTable turnover),
(applyRate exampleFloodTable postcode),
(applyRate exampleTheftTable postcode)])
turnover
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment