Personal data never reaches the analysis.
A medical claims extract is one of the most sensitive files an insurer holds. The honest way to handle that is not to promise we will guard it carefully. It is to make sure most of it never arrives, and to be specific about what happens to the rest.
Last updated 4 September 2026
The whole path, in one picture.
Four stages, and the important one is the second: the columns that identify a person are deleted in your own browser, before the file is sent anywhere.
Identifying columns are deleted before anything reads them.
A member file typically carries names, dates of birth, passport and Emirates ID numbers, addresses, telephone numbers and email addresses. None of it is needed to work out a loss ratio.
So the loader deletes those columns from each record before mapping, not after. The distinction matters. Filtering afterwards means the value existed in memory, and anything that existed can end up in a log line, an error message or a crash dump. Deleting first means an identifying value has nowhere to leak to, because by the time any other code runs it is not there. On the file set we built this against, 33 columns are removed this way.
The database schema reflects that. Where the source files carry 55, 54 and 51 columns, the staging tables that receive them carry 28 and 26. A column the loader needs in order to apply a rule is still read, and still never persists.
The identifiers that remain are hashed, not stored.
Analysis needs to know that a claim belongs to the same person as a premium row. It does not need to know who that person is. Identifiers that serve that joining purpose, such as a member or card number, are put through a keyed HMAC-SHA256 hash, and only the hash is stored.
Two consequences follow. Rows still join correctly, so every figure is exact. And the stored value cannot be turned back into the original, because the reversal depends on a secret that is not held alongside the data.
Age is treated the same way. It is derived from the date of birth at load time, and the date of birth is discarded. The age bands drive the demographic analysis. The birth date drives nothing.
Access is granted one portfolio at a time.
There is no anonymous read path and no self-service sign-up. Every page and every API route sits behind a sign-in, and an account on its own grants nothing: it has to be added to a specific portfolio before it can see anything at all. Someone with no membership signs in successfully and is told they have no portfolio, rather than being shown an empty dashboard that looks like a data problem.
| Role | What it allows |
|---|---|
| viewer | Read every page. Cannot export |
| editor | Read and export, and change the report date and stored assumptions |
| owner | As editor, plus the column mapping, replacing the data, and managing access |
| underwriter | Read and export, and see individual claim lines. Changes nothing |
Two of those are deliberate and worth stating. A viewer cannot export: reading the book on screen and taking a copy of it away are different levels of trust. And an owner does not get claim lines by having authority over the portfolio. That permission is granted to a person explicitly, and the grant is on the record like any other.
A role is a starting point rather than a fixed set. Each permission (export, configure, re-map, load, manage members and view claim lines) is recorded against the individual membership, so one person can be granted or refused any of them without moving them to a different role.
Editor is a larger power than it sounds, which is why it is separated from reading. Changing the report date re-earns the whole book and restates every figure on every page.
The boundary is enforced twice, on purpose.
The application checks first. A request that names a portfolio the signed-in user does not belong to is refused outright. A portfolio identifier arriving in a request is treated as a request, never as a statement of fact.
The database then enforces the same rule independently, through row level security policies that apply to the application's own queries rather than only to outsiders. That second layer exists for the failure the first one cannot cover: a forgotten filter, or a new endpoint that skips the check. Neither can read another client's rows, because the database itself will not return them.
The analytical schemas are also unreachable through the database's public REST interface, so the keys that ship in a browser cannot read this data whatever the policies say.
Nothing loads until you have seen what it will do.
When you upload an extract, the columns are matched against the analytical model and shown back to you with a confidence on each one. Where two readings are genuinely plausible, the portal says so and asks you to settle it rather than guessing quietly, because the difference changes what every breakdown means.
You also see what will go wrong before it does: how many dates and amounts parse, how many rows carry an identifier, and how many claims will fail to find a member and therefore be excluded. Those findings stay attached to the analysis afterwards, so a figure is never read without the caveats that qualify it.
Questions we would rather answer directly.
Data residency, retention periods, contractual terms and your own regulator's requirements are specific to your situation, and a page like this is the wrong place to give a general answer to a particular question. Ask us and we will answer in writing.