En poursuivant votre navigation, vous acceptez l'utilisation de “cookies” destinés à améliorer la performance de ce site, à en adapter les fonctionnalités. Accepter - Refuser - Plus d'infos !
James Whitbeck
Engineer @ alan
15 avrTech & produit

How we built our billing system

Collecting premiums is, along with settling claims, one of the key operations of an insurance company. Having a billing system that's accurate, transparent and easy to understand is essential to maintaining our clients' trust. And in the insurance world, trust is everything!

In 2018, Alan built its billing system from scratch, after relying on a provider for the first two years of the company's life. This post relates how we built a very critical piece of our infrastructure and managed to greatly increase the accuracy and automation of our billing.

mika-baumeister-703680-unsplash

Billing in the health insurance world

As a health insurance company, we bill for the periods for which we provide coverage. The price depends on a few factors: age, number of dependents, level of coverage. This sounds super simple, right?

It turns out it's a bit more complex, in large part because we allow changes to be made both in the future and in the past. Some examples:

  • We have two types of contracts. Individual contracts cover freelancers, while company contracts are signed with a company to cover all its employees at once. They’re not billed the same time! Company contracts are billed at the end of the month (so we know what happened), while individual contracts are billed at the start of the month (to minimize non-payments).
  • An employee on a company contract is covered, by law, from the day they start at the company, but we may not be aware of this employee until several weeks after their start date. So we need to backdate their start of coverage and adjust billing accordingly.
  • Similarly, a company admin may forget to inform us of the departure of an employee. So we need to change the end of coverage retroactively, and adjust billing accordingly.
  • Insured users can add and remove dependents at any time, which impacts billing. In the case of individual contracts that get billed at the beginning of the month, any changes mean adjustments on the following invoice.
  • Given we only charge for the first child, and the following children are free, we can have a situation were a child stops being free when their older sibling is no longer a dependent.

This flexibility on dates is partly a legal requirement, and also something we pride ourselves in offering to our clients. It was unfortunately something our first provider did not handle very well, which led to errors and quite a bit of manual verification. This flexibility, and the need to control proration rules and the timing of billing cycles, also made it difficult for us to use something like Stripe Billing (we do use Stripe for payments, and are very happy with their service).

Basic requirements

Here are the main properties we wanted to get out of our billing system:

  • Very high confidence that it's correct, without any manual intervention, even in the complex scenarios mentioned above.
  • Very transparent about what is being billed and why (including adjustments), so that our clients' accountants wouldn't need to reach out for explanations and so that our support team could answer questions confidently.
  • Precisely auditable, because as an insurance company that is strictly controlled by the regulator (like a bank), we need to be able to clearly track the source of each premium.
  • "Time travelling", or the ability to view the state of the billing system at any point in time.
  • Immutability, in that billed amounts are never overwritten or deleted.

Je suis, tu suis, nous suivons.

Suivez alan sur les réseaux sociaux !

Design

At its core, a billing system is just two things: 1. Computing premiums: they are the amounts due for a single user’s coverage. 2. At regular intervals, grouping these premiums into invoices.

Our Premium model looks roughly like this:

  • user_id
  • month
  • amount
  • invoice_id (nullable; the invoice in which the premium has been included)

The premium table is append-only and all columns are immutable, except for invoice_id which is populated when the premium is included in an invoice. This immutability, associated with creation timestamps, allows us to see exactly the state at any point in time.

Creating invoices is incredibly simple: sum all premiums with no invoice_id for the current and prior months, then populate the invoice_id. This link between premiums and invoices makes it very easy for our support team to explain why our invoice amounts are what they are.

When the amounts for a given month change, we create new rows that adjust the total amount for the month. Instead of just adding the "delta", we create a "cancelling" row with a negative amount equal to the original amount, then a third row with the new amount. This gives us the property that the latest row is the correct premium amount, while the previous rows sum up to 0. This is helpful when debugging because we're all familiar with our pricing, so we can eyeball these amounts more easily than if we had to sum up a lot of deltas. It also makes our data analysts' lives easier.

Computation

Every day, we recompute all the premiums for all our users over all time in a big batch!

chris-ried-534420-unsplash

This may seem wasteful at first, but keep in mind that users usually have just one Premium row per month. It's also a workload that's very easy to parallelize, because one client's contract doesn't interact in any way with another's contract.

For a while, we did explore whether we should make this premium computation “event-based”, and only insert Premium rows in response to certain events. We quickly realized that the complexity of listing all the events that affected billing, and their effects, and ensuring that nothing was missed as our product evolved, was not worth the performance gain. Recomputing everything all the time provides us with a lot of confidence that any coverage change or code fix is reflected correctly in billing.

In order to update the Premiums, we compute a "new set" of Premiums, based on the current state of users and we compare it with an "old set" of Premiums, currently in the database table. Any difference between the sets leads to the "cancellation" of old rows and the creation of new ones (remember, the table is append-only).

If the premiums for a month are updated, it's very easy to re-invoice that particular month, or include the adjustment in the following month's invoice: all you need to do is select the newly created rows and mark them with an invoice_id.

Concurrency control

We do want to avoid situations where multiple threads are inserting Premium rows at the same time, which could lead to the same rows being computed and added twice. We control this very simply, without any locking. All we do is have a "version" column to the Premium table, and a unique constraint on (user_id, month, version). The version is computed by incrementing the previous version for the (user_id, month) pair. Two threads updating the same premiums would compute the same next version and be prevented from both committing.

Je lis, tu lis, nous lisons.

La crème des articles et podcasts alan dans votre boîte mail !

"Self-healing"

The remarkable feature of this simple design is that it is very easy to deal with billing bugs. On a few occasions, when we rolled out product changes, we made mistakes in how those changes affected Premiums.

For example, when we allowed individual clients (ie not on a company plan) to add their spouses as dependents, we computed the price for the spouses based not on their age but on the age of the primary user. This caused a lot of incorrect Premium rows to be created.

However, after quickly fixing the initial bug, all we had to do was wait for the next daily job to run, and all the Premiums were fixed. We didn't have to do any deep dive into the billing data to figure out who was affected. We didn't have to manual edit any billing data. It was all "self-healing". And in the event that some incorrect invoices had already been generated, we knew that the adjustments would automatically be on the next invoices.

This gives us a lot of peace of mind about the accuracy of our billing.

anders-wideskott-57598-unsplash

Extra confidence

At Alan, we try to leverage database constraints to enforce a lot of properties we expect about our data. However, there is only so much you can enforce with database constraints. You certainly can't do table scans on each update, nor join to other tables. So we've supplemented our database constraints with other consistency checks. For example, we verify that the number of days billed for each user is equal to the number of covered days for that user. These checks are run daily and we get alerts in Slack when they detect problems. There is a nifty feature of Metabase (the open-source BI tool we use at Alan) that makes this alerting trivial to set up.

Final words

It took me a while to get around to writing this post, in large part because I felt the system was too simple to be worth writing about. Yet this simplicity is deceptive: it's actually the result of a lot of work and lessons learned by the team. The system has been running in production for the past 6 months, and it has given back to our engineers, ops team and most importantly our users an enormous amount of time: for example, it took us 2-3 days per month to agree with our provider on amounts to debit, and this was with 5x fewer users than we have today! It is a good example of how we are working hard to bring simplicity and transparency to a complex and opaque industry.

If you too want to radically simplify the French healthcare system and improve the health of millions, reach out to us, we're always hiring skilled engineers.

James Whitbeck
Engineer @ alan

Plus d'articles de James Whitbeck

La crème des articles alan

Dans votre boite mail. Garantie sans spam.

Populaires en ce moment

Populaire en ce moment

De la même catégorie